東京レンジ逆張り×ロンドン・ブレイクアウトEA完全ガイド(MQL4フルコード付)

FX

本稿では、東京時間(アジア時間)の「レンジ逆張り」と、ロンドン時間(欧州前後)の「ブレイクアウト」をひとつのアルゴリズムに統合した、初心者でも扱いやすいデイトレード型EA(Expert Advisor)を解説します。日中はボラティリティが低く均衡しやすい特性を利用して小さく抜き、流動性が一気に膨らむロンドン始動ではトレンドの初動に乗る──という市場構造に沿った二刀流です。記事末尾にフル機能のMQL4コードを掲載します。

1. なぜ「東京=逆張り」「ロンドン=順張り」なのか

為替市場の一日は、流動性と参加者の入れ替わりで明確なリズムがあります。アジア時間は実需と裁定が中心で方向感が出にくく、レンジ回帰(ミーンリバージョン)の傾向が強い。一方、ロンドン時間は機関投資家のフローが立ち上がり、方向性(トレンド)が出やすい。この非対称性は長期にわたり観測されており、戦略を時間帯で切り替える合理性があります。

  • 東京セッション:ボラティリティ低め、値動きは往復・押し戻しが多い → 逆張りが機能しやすい
  • ロンドン立ち上がり:流動性拡大、ブレイク後の走りが出やすい → 順張り(ブレイクアウト)が機能しやすい

本EAはこの性質を活かし、時間帯フィルターで売買ロジックを自動切替します。

2. 使う通貨ペアと時間足

対象はスプレッドが狭く、ロンドン時間に実需と投機が厚いメジャー通貨を推奨します。

  • USDJPY・EURUSD:実運用の第一候補。取扱いブローカーも多く、約定品質の検証が容易です。
  • 時間足はM5もしくはM15。レンジ回帰の反応速度とブレイク検出の鮮度のバランスが取りやすいです。

3. 売買ルール(東京:レンジ逆張り)

逆張りは「平均回帰」の定量化が肝です。本稿ではボリンジャーバンド(期間20、偏差2.0)+RSIの組み合わせを用います。

  1. 時間帯フィルター:ブローカー時間でTokyoStart~TokyoEnd。初期値は例として 01:00~09:00。
  2. 買い条件:Bidが下バンドをブレイクし、かつRSI(14)<30。バッファ(例:0.5×ATR(14))分だけオーバーシュートを要求し、浅いタッチを回避。
  3. 売り条件:Askが上バンドをブレイクし、かつRSI(14)>70。同様にATRバッファを要求。
  4. 損切り:シグナル確定足の高安からATR倍数(例:1.5×ATR(14))で置く。
  5. 利確:ミドルバンド(平均) or 固定TP(例:SLの0.8~1.2倍)。
  6. 保有制御:東京時間外に跨ぐポジションは原則クローズ(ロンドンのブレイクとは別ロジックのため)。

エントリー頻度は日数回に制御し、連敗時はサイズを自動縮小します(後述のリスク制御)。

4. 売買ルール(ロンドン:ブレイクアウト)

アジア時間で形成された「当日のボックス」を利用します。ボックス高値・安値を基準に、ロンドン前後でStop注文を置きます。

  1. アジアレンジ:AsiaStart~AsiaEnd(例:00:00~08:00)。この区間の確定高値・安値を算出。
  2. 買いStop:AsiaHigh + BufferPips(例:3~7pips)。
  3. 売りStop:AsiaLow − BufferPips。
  4. 発注タイミング:LondonStart直前にOCOで同時置き。どちらか約定で他方は自動取消
  5. 損切り:ボックスの反対側 −/+ バッファ(あるいは固定SL)。
  6. 利確:1R~2Rの固定、またはトレーリング(ブレイク後の押し戻りで利益を守る)。
  7. 時間切れ:LondonEndまでに未約定 or 含み益が消えたらクローズ、Pendingは取消。

ダマシ回避として、Asiaレンジ幅が極端に狭い(例:<15pips)場合は見送りのフィルターを入れます。

5. リスク管理とロット計算

ロットは口座残高×許容リスク% ÷(ストップ距離×1pipの価値)で決めます。本EAは自動計算します。

  • 許容リスク:0.5%~1.0%(初心者は0.5%)。
  • 連敗コントロール:直近Nトレードの損失合計が閾値を超えたら一時休止
  • トレーリング:含み益がX pips超えたら、高値・安値−ATR×係数で追随。

6. コスト最適化(スプレッド・スリッページ・約定率)

スキャル寄りの逆張りは総取引コストに極端に敏感です。EAは以下を実装しています。

  • MaxSpreadフィルター:スプレッドが閾値超過ならエントリー回避。
  • 最小価格改善(Price Improve):逆張りで有利方向に数ポイント戻った瞬間だけ成行。
  • スリッページ許容:ブレイク時は許容を広げ、東京逆張り時はタイトに。

7. バックテストの再現性を高める手順

  1. テスター:MT4の「全ティック」を使用。スプレッドは固定ではなく変動近似(ブローカー記録に合わせる)。
  2. 期間:最低3年(例:2022–2024)。アジア・欧州・米国のレジームを跨ぐ。
  3. パラメータの探索は段階固定:まず時間帯→次にATR係数→最後にR倍率。多変量同時最適化は過剰適合の温床
  4. 検証:ウォークフォワード(WFA)+モンテカルロ。ロット・順序シャッフル耐性を確認。

8. EAの使い方(主要パラメータ)

  • RiskPerTrade:1トレードの口座リスク%(初期0.5–1.0)。
  • TokyoStartHour / TokyoEndHour:東京ロジックの稼働時間。
  • AsiaStartHour / AsiaEndHour / LondonStartHour / LondonEndHour:ブレイク用ボックスと稼働時間。
  • BBPeriod / BBDev / RSIPeriod / ATRMult:逆張りの感度と損切り距離。
  • BoxBufferPips / MinBoxPips:ブレイクの発注距離と最小レンジ幅。
  • MaxSpreadPips / SlippageTokyo / SlippageLondon:コスト制御。
  • UseTrailing / TrailStartPips / TrailATRMult:トレール条件。

注意:ブローカーのサーバー時間とJSTは異なる場合があります。必ず実際のチャートの時間表示に合わせて入力してください。

9. MQL4 フルコード(初心者向け・シンプル版)


//+------------------------------------------------------------------+
//|  TokyoRange_Contrarian_LondonBreakout.mq4                       |
//|  Simple educational EA for beginners                            |
//+------------------------------------------------------------------+
#property strict

input double   RiskPerTrade      = 0.7;   // % of balance per trade
input int      Magic             = 20250905;
input int      SlippageTokyo     = 2;
input int      SlippageLondon    = 5;
input int      MaxSpreadPips     = 20;

// --- Sessions (broker server time) ---
input int TokyoStartHour   = 1;
input int TokyoEndHour     = 9;
input int AsiaStartHour    = 0;
input int AsiaEndHour      = 8;
input int LondonStartHour  = 10;
input int LondonEndHour    = 18;

// --- Contrarian (Tokyo) ---
input int    BBPeriod      = 20;
input double BBDev         = 2.0;
input int    RSIPeriod     = 14;
input double ATRMultSL     = 1.5;
input double ATRBufferK    = 0.5;

// --- Breakout (London) ---
input int    BoxBufferPips = 5;
input int    MinBoxPips    = 15;
input double RMultipleTP   = 1.5;

// --- Trailing ---
input bool   UseTrailing   = true;
input int    TrailStartPips= 12;
input double TrailATRMult  = 1.0;

// Utilities
double Pip() {
   if (Digits == 3 || Digits == 5) return 10 * Point;
   return Point;
}

bool InHourRange(int startH, int endH, datetime t) {
   int h = TimeHour(t);
   if (startH <= endH) return (h >= startH && h < endH);
   // wrap midnight
   return (h >= startH || h < endH);
}

int OnInit() { return(INIT_SUCCEEDED); }
void OnDeinit(const int reason) {}

void OnTick() {
   // Spread filter
   double spreadPips = (MarketInfo(Symbol(), MODE_SPREAD) * Point) / Pip();
   if (spreadPips > MaxSpreadPips) return;

   // One position per symbol
   int total = 0;
   for (int i=OrdersTotal()-1; i>=0; i--) {
      if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if (OrderSymbol()==Symbol() && OrderMagicNumber()==Magic) total++;
      }
   }

   datetime now = TimeCurrent();
   bool tokyo  = InHourRange(TokyoStartHour, TokyoEndHour, now);
   bool london = InHourRange(LondonStartHour, LondonEndHour, now);

   if (tokyo) TokyoContrarian(total);
   if (london) LondonBreakout(total);

   if (UseTrailing) DoTrailing();
   // Housekeeping: cancel pending if out of london hours
   if (!london) CancelLondonPendings();
}

// --- Position sizing (risk %) ---
double CalcLotsByRisk(double stopPips) {
   if (stopPips <= 0.0) stopPips = 10.0;
   double riskMoney = AccountBalance() * (RiskPerTrade/100.0);
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double lotStep   = MarketInfo(Symbol(), MODE_LOTSTEP);
   double minLot    = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot    = MarketInfo(Symbol(), MODE_MAXLOT);

   double onePipValuePerLot = (tickValue / Point) * Pip();
   double lots = riskMoney / (stopPips * onePipValuePerLot);
   // normalize
   lots = MathFloor(lots / lotStep) * lotStep;
   if (lots < minLot) lots = minLot;
   if (lots > maxLot) lots = maxLot;
   return NormalizeDouble(lots, 2);
}

// --- Indicators helper ---
double iATRp(int p=14) { return iATR(Symbol(), Period(), p, 0); }

// ========== Tokyo Contrarian ==========
void TokyoContrarian(int total) {
   // indicators
   double upper = iBands(Symbol(), Period(), BBPeriod, 2, 0, PRICE_CLOSE, MODE_UPPER, 1);
   double lower = iBands(Symbol(), Period(), BBPeriod, 2, 0, PRICE_CLOSE, MODE_LOWER, 1);
   double mid   = iBands(Symbol(), Period(), BBPeriod, 2, 0, PRICE_CLOSE, MODE_MAIN , 1);
   double rsi   = iRSI(Symbol(), Period(), RSIPeriod, PRICE_CLOSE, 1);
   double atr   = iATRp(14);

   double buffer = ATRBufferK * atr;
   double slPips = (ATRMultSL * atr)/Pip();
   double tpPips = slPips; // 1:1 デフォルト
   double ask = NormalizeDouble(Ask, Digits);
   double bid = NormalizeDouble(Bid, Digits);

   if (total == 0) {
      // Long contrarian
      if (bid <= (lower - buffer) && rsi < 30) {
         double sl = bid - slPips*Pip();
         double tp = mid; // ミドルで利確
         double lots = CalcLotsByRisk(slPips);
         int ticket = OrderSend(Symbol(), OP_BUY, lots, ask, SlippageTokyo, sl, tp, "TokyoContrarian", Magic, 0, clrBlue);
      }
      // Short contrarian
      if (ask >= (upper + buffer) && rsi > 70) {
         double sl = ask + slPips*Pip();
         double tp = mid;
         double lots = CalcLotsByRisk(slPips);
         int ticket = OrderSend(Symbol(), OP_SELL, lots, bid, SlippageTokyo, sl, tp, "TokyoContrarian", Magic, 0, clrRed);
      }
   } else {
      // optional: exit if session ends (handled elsewhere if desired)
   }
}

// ========== London Breakout ==========
datetime asiaBoxDate = 0;
double asiaHigh=0, asiaLow=0;
bool boxReady=false;
bool pendingsPlaced=false;

void UpdateAsiaBox() {
   datetime now = TimeCurrent();
   if (TimeDay(asiaBoxDate) != TimeDay(now)) {
      // reset daily
      asiaHigh=0; asiaLow=0; boxReady=false; pendingsPlaced=false;
      asiaBoxDate = now;
   }
   // Build box only within Asia window (yesterday midnight to AsiaEndHour today)
   datetime startT = StrToTime(TimeToStr(now, TIME_DATE) + " " + DoubleToStr(AsiaStartHour,0) + ":00");
   datetime endT   = StrToTime(TimeToStr(now, TIME_DATE) + " " + DoubleToStr(AsiaEndHour,0) + ":00");
   int bars = iBarShift(Symbol(), Period(), startT, false) - iBarShift(Symbol(), Period(), endT, true);
   if (bars < 1) return;
   int iStart = iBarShift(Symbol(), Period(), startT, true);
   int iEnd   = iBarShift(Symbol(), Period(), endT, true);
   double hi = -1e10, lo = 1e10;
   for (int i=iEnd; i<=iStart; i++) {
      double h = iHigh(Symbol(), Period(), i);
      double l = iLow(Symbol(), Period(), i);
      if (h > hi) hi = h;
      if (l < lo) lo = l;
   }
   asiaHigh = hi; asiaLow = lo;
   double boxPips = (asiaHigh - asiaLow)/Pip();
   if (boxPips >= MinBoxPips) boxReady = true;
}

void PlaceLondonPendings() {
   if (pendingsPlaced || !boxReady) return;
   double buyPrice = asiaHigh + BoxBufferPips*Pip();
   double sellPrice= asiaLow  - BoxBufferPips*Pip();
   double slBuy = asiaLow - BoxBufferPips*Pip();
   double slSell= asiaHigh + BoxBufferPips*Pip();
   double stopPips = (buyPrice - slBuy)/Pip();
   double lots = CalcLotsByRisk(stopPips);
   double tpBuy = buyPrice + RMultipleTP*stopPips*Pip();
   double tpSell= sellPrice - RMultipleTP*stopPips*Pip();

   int b = OrderSend(Symbol(), OP_BUYSTOP, lots, buyPrice, SlippageLondon, slBuy, tpBuy, "LondonBO", Magic, 0, clrGreen);
   int s = OrderSend(Symbol(), OP_SELLSTOP, lots, sellPrice, SlippageLondon, slSell, tpSell, "LondonBO", Magic, 0, clrMaroon);
   if (b>0 || s>0) pendingsPlaced=true;
}

void CancelLondonPendings() {
   for (int i=OrdersTotal()-1; i>=0; i--) {
      if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if (OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
      int type = OrderType();
      if (type==OP_BUYSTOP || type==OP_SELLSTOP) OrderDelete(OrderTicket());
   }
   pendingsPlaced=false;
}

void OCO_Manager() {
   // cancel the other pending when one triggers
   bool buyActive=false, sellActive=false;
   for (int i=OrdersTotal()-1; i>=0; i--) {
      if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if (OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
      int type = OrderType();
      if (type==OP_BUY || type==OP_BUYSTOP) buyActive=true;
      if (type==OP_SELL || type==OP_SELLSTOP) sellActive=true;
   }
   if (buyActive && sellActive) {
      // both pending exist; if one became market order, delete the other pending
      for (int i2=OrdersTotal()-1; i2>=0; i2--) {
         if (!OrderSelect(i2, SELECT_BY_POS, MODE_TRADES)) continue;
         if (OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
         int type2 = OrderType();
         if (type2==OP_SELLSTOP) OrderDelete(OrderTicket());
      }
   }
}

void LondonBreakout(int total) {
   UpdateAsiaBox();
   datetime now = TimeCurrent();
   bool beforeLondon = InHourRange(AsiaEndHour, LondonStartHour, now); // pre-london
   bool london = InHourRange(LondonStartHour, LondonEndHour, now);

   if (beforeLondon) {
      CancelLondonPendings(); // keep clean
      if (boxReady) PlaceLondonPendings();
   }
   if (london) {
      OCO_Manager();
   }
}

// --- Trailing Stop ---
void DoTrailing() {
   double atr = iATRp(14);
   for (int i=OrdersTotal()-1; i>=0; i--) {
      if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if (OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
      if (OrderType()==OP_BUY) {
         double profitPips = (Bid - OrderOpenPrice())/Pip();
         if (profitPips > TrailStartPips) {
            double newSL = Bid - TrailATRMult*atr;
            if (newSL > OrderStopLoss())
               OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(newSL, Digits), OrderTakeProfit(), 0, clrBlue);
         }
      }
      if (OrderType()==OP_SELL) {
         double profitPips = (OrderOpenPrice() - Ask)/Pip();
         if (profitPips > TrailStartPips) {
            double newSL = Ask + TrailATRMult*atr;
            if (newSL < OrderStopLoss() || OrderStopLoss()==0)
               OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(newSL, Digits), OrderTakeProfit(), 0, clrRed);
         }
      }
   }
}
    

上記は教育用のシンプル版です。実運用ではVPSの遅延、ブローカー仕様(最小距離、約定拒否)に合わせてパラメータを調整してください。

10. TradingView(任意)での可視化用スクリプト

MT4が使えない場面でも、レンジとブレイクの見える化は有効です。以下は学習用の参考コードです(発注機能はありません)。


//@version=5
indicator("Asia Box & London Breakout (Study)", overlay=true)
asiaStart = input.session("0000-0800", "Asia Session (HHMM-HHMM)")
london    = input.session("1000-1800", "London Session (HHMM-HHMM)")
inAsia = time(timeframe.period, asiaStart)
var float hi = na
var float lo = na
if inAsia
    hi := math.max(na(hi) ? low : hi, high)
    lo := math.min(na(lo) ? high : lo, low)
else
    hi := na
    lo := na
plot(hi, color=color.new(color.green, 0), style=plot.style_linebr, linewidth=1)
plot(lo, color=color.new(color.red, 0), style=plot.style_linebr, linewidth=1)
    

11. 具体的な運用手順(初心者向け)

  1. MT4インストール→デモ口座:まずはデモで2週間。実売買の前に稼働安定性をチェックします。
  2. 通貨ペア選定:USDJPYとEURUSDから開始。
  3. 時間帯調整:ブローカーのサーバー時間を確認し、東京・ロンドン時間に一致するようパラメータを合わせる。
  4. スプレッド閾値:MaxSpreadPipsを、通常時スプレッドの1.5倍程度に設定。
  5. バックテスト→フォワード:過去3年検証→1〜2週間のデモ実弾(小ロット)→本稼働。
  6. 損失上限:日次ドローダウン3%到達で停止。翌営業日に再開。

12. よくある失敗と対策

  • 時間帯のズレ:夏時間/冬時間やサーバー時刻の違いでロジックがずれる → 週初に必ず確認。
  • 過剰最適化:過去に合いすぎると将来崩れる → パラメータは荒いグリッドで固定。
  • スプレッド拡大中の約定:指標前後や週明け直後は広がる → MaxSpreadと時間外停止で回避。
  • VPS遅延:ロンドンの初動で不利 → 約定遅延が大きいならBufferPipsを広げる。

13. 初心者向け:FX口座の選び方(一般論)

特定の事業者名は挙げません。判断軸は以下の通りです。

  • 約定品質:スリッページ統計が提示されているか、約定拒否が少ないか。
  • コスト:平均スプレッドと手数料の合計が小さいか。
  • 取引インフラ:VPS連携、サーバーの安定稼働、サポートの迅速性。
  • リスク管理機能:ロスカット水準、追証有無、証拠金の管理体制。

口座開設の一般的な流れは、本人確認→審査→ログイン情報受領→取引ツール設定→入金→デモ/小ロット検証です。

14. まとめ

アジア時間の平均回帰と、ロンドン時間のトレンド初動を一台で捉えるのが本EAの狙いです。再現性は、時間帯整合・コスト管理・連敗制御の3点で大きく変わります。必ずデモで挙動を確認してください。

コメント

タイトルとURLをコピーしました