ローソク足のヒゲ比率 Wick Exhaustion Ratio(WER) で定量化し、EAで自動執行します。
1. 戦略のコンセプト
多くの初心者が最初に学ぶのは移動平均やRSIですが、値動きの根源は「注文フロー」です。
勢いよく一方向に走った後、上髭や下髭が極端に伸びたローソクは、
しばしば「最後の追い込み(exhaustion)」を示唆します。つまり、流動性を吸って一旦反転しやすい。
本稿では、この「行き過ぎ→小反転」を機械的に捉えるため、直近確定足のヒゲと実体の比率を使った独自の
指標 WER(Wick Exhaustion Ratio) を定義し、ルールに落とし込みます。
2. WER(Wick Exhaustion Ratio)の定義
確定足(シフト=1)の価格要素を Open[1], High[1], Low[1], Close[1]
とします。
実体の上端 top = max(Open[1], Close[1])
、下端 bottom = min(Open[1], Close[1])
。
- 上髭:
upper = High[1] - top
- 下髭:
lower = bottom - Low[1]
- 実体:
body = top - bottom
WERは「髭/実体」。安定化のために微小値eps
を加えます。
WER_upper = upper / (body + eps)
WER_lower = lower / (body + eps)
発想は単純ですが、ボラティリティ正規化とスプレッド/時間帯フィルタを併用することで
無駄打ちを抑えられます。
3. 売買ルール(初期版)
3.1 エントリー
- 買い(反転狙い):直近確定足が長い下髭(
WER_lower ≥ θ
)かつ下落方向に拡張後、
新バー開始時に成行で買い。 - 売り(反転狙い):直近確定足が長い上髭(
WER_upper ≥ θ
)かつ上昇方向に拡張後、
新バー開始時に成行で売り。
推奨の併用フィルタ:
- ATRフィルタ:確定足のレンジがATR(14)の0.6〜1.5倍に入っていること
(極端すぎる髭や極小値動きを除外)。 - ボディ閾値:
BodyPips ≥ 2
など最小実体を保証。 - スプレッド:
SpreadPips ≤ MaxSpread
。 - 時間帯:ロンドン/NY時間中心(例:
08:00–23:00
サーバ時刻)。
3.2 エグジット
- 固定SL/TP:例)
SL=8–15 pips, TP=8–20 pips
。 - 時間切れ:ポジション寿命(例:
bars_alive ≥ 8
で成行決済)。 - トレーリング(任意):
TrailingStart
到達後、TrailingStep
で更新。
4. リスク管理とロット計算
EAは口座残高に対するリスク%からロットを自動算定します。例えば、リスク1%・SL=10pipsなら、
損失が1%を超えないロットに調整します。ボラが上がればロットは自然に下がり、下がれば増えます。
推奨:1トレードのリスクは口座の0.25〜1.0%。連敗時はEAの自動停止やロットの段階縮小を
組み込み、破綻確率を抑えます。
5. どの通貨・足で使うか
初心者は EURUSD・GBPUSDのM5 から開始が無難です。流動性が高く、スプレッドが安定しているためです。
スキャルピングなので超低スプレッド口座が前提です。最初はスプレッド上限を2.0pipsに厳しめ設定し、
慣れたら3.0pipsまで拡張すると良いでしょう。
6. バックテスト手順(MT4)
- MT4の「ストラテジーテスター」を開く。
- モデルは「毎ティック」。通貨はEURUSD、期間は1〜2年、時間足はM5。
- スプレッドはブローカー実勢に近い値(例:
Spread=12
= 1.2pips)。 - 可変スプレッド環境なら、可変のまま実行して現実に寄せる。
- レポートでは、PF、ドローダウン、連敗数、平均勝ち負けpipsを確認。
7. 改良の方向性
- セッション識別:ロンドン/NYで閾値を変える。
- ラウンドナンバー:00/50付近の反転は強い傾向。距離フィルタを追加。
- 多時間足:上位足のトレンド方向と逆張り閾値を連動。
- クールダウン:同方向の連続シグナルを制限(例:同一方向2連続まで)。
- ヒゲの比率の平滑化:直近数本のWERをEMAでならし、スパイクを除外。
8. フルコード(MQL4 EA)
そのままMT4に貼り付けて動かせる最小実装です。ロジックを読みやすく整理し、パラメータで調整できるようにしています。
//+------------------------------------------------------------------+
//| Wick Exhaustion Scalper EA (WER-based Reversal) |
//| Timeframe: M5 (recommend) |
//| Symbols : EURUSD, GBPUSD (low spread) |
//+------------------------------------------------------------------+
#property strict
input double RiskPercent = 0.5; // % risk per trade
input int SL_Pips = 10;
input int TP_Pips = 12;
input double WER_Thresh = 2.0; // wick/body threshold
input double MinBodyPips = 2.0;
input double AtrMulMin = 0.6; // range >= ATR(14)*AtrMulMin
input double AtrMulMax = 1.5; // range <= ATR(14)*AtrMulMax
input int MaxSpread = 20; // 2.0 pips if Digits=5
input int Magic = 20250905;
input bool UseTimeFilter = true;
input int StartHour = 8; // server time
input int EndHour = 23;
input bool UseTrailing = true;
input int TrailStart = 8; // pips
input int TrailStep = 4; // pips
input int MaxBarsAlive = 8;
datetime last_bar_time = 0;
double Pip()
{
if(Digits==3 || Digits==5) return(0.1*Point);
return(Point);
}
int DigitsPip()
{
return (Digits==3 || Digits==5) ? 1 : 0;
}
double ToPips(double price_diff)
{
return price_diff / Pip();
}
double FromPips(double pips)
{
return pips * Pip();
}
bool NewBar()
{
datetime t = Time[0];
if(t != last_bar_time) { last_bar_time = t; return true; }
return false;
}
bool TimeAllowed()
{
if(!UseTimeFilter) return true;
int h = TimeHour(TimeCurrent());
if(StartHour <= EndHour) return (h >= StartHour && h < EndHour);
// overnight wrap
return (h >= StartHour || h < EndHour);
}
double LotsForRisk(double stop_pips)
{
if(stop_pips <= 0.0) return 0.0;
double risk_money = AccountBalance() * (RiskPercent/100.0);
double tickval = MarketInfo(Symbol(), MODE_TICKVALUE);
double per_lot_per_pip = (tickval / Pip());
if(per_lot_per_pip <= 0) return 0.01;
double lots = risk_money / (stop_pips * per_lot_per_pip);
double lotstep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minlot = MarketInfo(Symbol(), MODE_MINLOT);
double maxlot = MarketInfo(Symbol(), MODE_MAXLOT);
lots = MathFloor(lots/lotstep) * lotstep;
lots = MathMax(minlot, MathMin(lots, maxlot));
return NormalizeDouble(lots, 2);
}
double iATRpips(int period=14, int shift=1)
{
double atr = iATR(Symbol(), Period(), period, shift);
return ToPips(atr);
}
void TrailPositions()
{
if(!UseTrailing) return;
for(int i=OrdersTotal()-1; i>=0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
double profit_pips = 0;
if(OrderType()==OP_BUY) profit_pips = ToPips(Bid - OrderOpenPrice());
if(OrderType()==OP_SELL) profit_pips = ToPips(OrderOpenPrice() - Ask);
if(profit_pips >= TrailStart)
{
double new_sl = OrderStopLoss();
if(OrderType()==OP_BUY)
{
double candidate = Bid - FromPips(TrailStep);
if(candidate > new_sl)
OrderModify(OrderTicket(), OrderOpenPrice(), candidate, OrderTakeProfit(), 0);
}
else if(OrderType()==OP_SELL)
{
double candidate = Ask + FromPips(TrailStep);
if(candidate < new_sl || new_sl==0)
OrderModify(OrderTicket(), OrderOpenPrice(), candidate, OrderTakeProfit(), 0);
}
}
}
}
int BarsAliveOf(int ticket)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET)) return 0;
datetime open_time = OrderOpenTime();
int bars = 0;
for(int i=0; i<Bars; i++)
{
if(Time[i] <= open_time) break;
bars++;
}
return bars;
}
void CloseOldPositions()
{
for(int i=OrdersTotal()-1; i>=0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
int barsAlive = BarsAliveOf(OrderTicket());
if(barsAlive >= MaxBarsAlive)
{
if(OrderType()==OP_BUY) OrderClose(OrderTicket(), OrderLots(), Bid, 10);
if(OrderType()==OP_SELL) OrderClose(OrderTicket(), OrderLots(), Ask, 10);
}
}
}
int ActivePositions()
{
int n=0;
for(int i=0; i<OrdersTotal(); i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol()==Symbol() && OrderMagicNumber()==Magic) n++;
}
return n;
}
int OnInit(){ last_bar_time = 0; return(INIT_SUCCEEDED); }
int OnDeinit(){ return(0); }
int start()
{
if(NewBar())
{
CloseOldPositions();
if(!TimeAllowed()) return(0);
if(ActivePositions()>=1) return(0);
// Spread filter
int spread_points = MarketInfo(Symbol(), MODE_SPREAD);
if(spread_points > MaxSpread) return(0);
// candle[1]
double o = Open[1], h = High[1], l = Low[1], c = Close[1];
double top = MathMax(o,c);
double bottom = MathMin(o,c);
double upper = h - top;
double lower = bottom - l;
double body = top - bottom;
double eps = FromPips(0.1);
double upper_p = ToPips(upper);
double lower_p = ToPips(lower);
double body_p = ToPips(body);
// body filter
if(body_p < MinBodyPips) return(0);
// ATR filter using candle range
double range_p = ToPips(h - l);
double atr14_p = iATRpips(14, 1);
if(range_p < atr14_p*AtrMulMin || range_p > atr14_p*AtrMulMax) return(0);
double WER_up = upper_p / (body_p + 0.1);
double WER_lo = lower_p / (body_p + 0.1);
double sl_p = SL_Pips;
double tp_p = TP_Pips;
double lots = LotsForRisk(sl_p);
if(lots <= 0.0) return(0);
int ticket = -1;
// BUY signal: long lower wick exhaustion
if(WER_lo >= WER_Thresh && Close[1] < Open[1])
{
double sl = Bid - FromPips(sl_p);
double tp = Bid + FromPips(tp_p);
ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 10, sl, tp, "WER BUY", Magic, 0, clrBlue);
}
// SELL signal: long upper wick exhaustion
else if(WER_up >= WER_Thresh && Close[1] > Open[1])
{
double sl = Ask + FromPips(sl_p);
double tp = Ask - FromPips(tp_p);
ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, 10, sl, tp, "WER SELL", Magic, 0, clrRed);
}
}
TrailPositions();
return(0);
}
//+------------------------------------------------------------------+
9. 補助インジケータ(MQL4)
チャートにWERを可視化するインジケータです。閾値以上の足にマークを表示します。
//+------------------------------------------------------------------+
//| Wick Exhaustion Ratio (WER) Indicator |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 Blue
#property indicator_color2 Red
input double WER_Thresh = 2.0;
double upBuf[];
double loBuf[];
int OnInit()
{
SetIndexBuffer(0, upBuf);
SetIndexStyle(0, DRAW_HISTOGRAM, STYLE_SOLID, 2);
SetIndexLabel(0, "WER_upper");
SetIndexBuffer(1, loBuf);
SetIndexStyle(1, DRAW_HISTOGRAM, STYLE_SOLID, 2);
SetIndexLabel(1, "WER_lower");
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
int start = (prev_calculated==0) ? 1 : prev_calculated-1;
for(int i=start; i<rates_total; i++)
{
double top = MathMax(open[i], close[i]);
double bottom = MathMin(open[i], close[i]);
double body = top - bottom + Point*0.1;
double upper = high[i] - top;
double lower = bottom - low[i];
double WU = upper / body;
double WL = lower / body;
upBuf[i] = WU;
loBuf[i] = WL;
}
return(rates_total);
}
//+------------------------------------------------------------------+
10. ビジュアル検証用(補足)Pine Script v5
TradingViewでWERを重ねて挙動を直感的に確認したい場合の補助スクリプトです(売買や自動執行はEA側で行ってください)。
//@version=5
indicator("Wick Exhaustion Ratio (WER)", overlay=false)
eps = syminfo.mintick * 0.1
body = math.abs(close - open) + eps
upper = high - math.max(open, close)
lower = math.min(open, close) - low
WER_up = upper / body
WER_lo = lower / body
plot(WER_up, title="WER_upper", style=plot.style_histogram)
plot(WER_lo, title="WER_lower", style=plot.style_histogram, color=color.new(color.red, 0))
hline(2.0, "th=2.0")
11. 期待値の源泉と限界
WERの根拠は「流動性狩り後の短期的な反転バイアス」。ただし万能ではありません。
強トレンド押し戻り中の髭は反転せず続伸・続落することも多い。よってATRフィルタや時間帯、
ラウンドナンバーの距離などの文脈フィルタが効きます。
12. パラメータの初期ガイド
項目 | 推奨初期値 | 意図 |
---|---|---|
WER_Thresh | 2.0 | 「極端な髭」に限定 |
SL/TP | 10/12 pips | スプレッド耐性と期待値の妥協点 |
AtrMulMin/Max | 0.6/1.5 | 極端レンジを除外 |
RiskPercent | 0.5% | 破綻確率の抑制 |
MaxBarsAlive | 8 | 時間切れ決済 |
13. 実運用の注意点
スリッページ・約定拒否・可変スプレッドはスキャルに致命的です。VPSと低レイテンシ、
低スプレッド口座、原則固定or狭スプレッド環境を前提に。重要なのは「約定品質」です。
必ずデモ→小額→段階的に。
14. まとめ
WERは、初心者でも理解しやすく、機械化しやすい値動きの素朴な偏りを狙います。
本稿のEAは最小構成なので、時間帯やラウンドナンバー、上位足判定などを足しながら、
自分のブローカー特性に合う「現実解」を見つけてください。
コメント