コストを味方につける『ロンドン・ブレイクアウトEA』—初心者でも実装できるMT4完全自動売買の作り方

FX

Cost‑Aware London Breakout EA / MT4 (MQL4)

コストを味方につける『ロンドン・ブレイクアウトEA』—初心者でも実装できるMT4完全自動売買の作り方

本稿は、東京〜ロンドンの重複時間に起きやすい初動トレンドを、コスト(手数料・スプレッド・スリッページ)を明示的に織り込んで収益機会に変えるシステムをゼロから構築する実践解説です。戦略のロジック、検証ワークフロー、資金管理、そしてMT4用MQL4の完全EAコードまでを一つにまとめ、初心者でも再現できるよう細部まで具体化しました。

1. なぜロンドン・ブレイクアウトなのか

ロンドン市場は為替の中心地です。東京時間で形成されたレンジ(狭い値動き)を、ロンドン勢の参加で一気に抜ける「初動」が比較的よく出現します。初心者に適する理由は次の通りです。

  • 構造がシンプル:特定時間帯のレンジ上抜け・下抜けに限定。
  • 再現性:日々同じ時間帯を観測でき、検証可能性が高い。
  • 指標の少なさ:高度な予測モデルなしでも運用可能。

ただし、この戦略はコスト管理がすべてです。スプレッドや手数料、約定滑りは、勝率や損益比の見かけを大きく悪化させます。ここを明示的にモデル化することで、初心者でも実運用に耐えうる水準へ引き上げられます。

2. エッジの源泉とコストの扱い

2.1 エッジの源泉

エッジは「秩序ある時間割」から生まれます。東京時間に機関投資家のフローが小さく、ロンドン参入時の流動性拡大とオーダー消化がブレイクを誘発します。方向は日々異なるため、片方向に賭けない仕組み(上下どちらでも付いていく)を採用します。

2.2 コストの三点セット

  • スプレッド:エントリー直後に負の距離。
  • 手数料:往復固定の費用。
  • スリッページ:実値と約定値の差。ボラ急拡大時に増える。

本稿のEAは、CostPipsというパラメータで、期待利得に対して最低限上乗せすべきコスト相当のpips(例:1.5〜2.5pips)を事前に差し引いて意思決定します。これにより、バックテストと実運用の乖離を抑制します。

3. 取引ルール(初心者向けに厳格化)

3.1 対象時間帯

ブローカーのサーバー時間でロンドン前後(例:15:00〜20:00)を取引可とします。時間帯はパラメータで変更可能です。

3.2 レンジ定義

直前の観測窓(例:08:00〜14:00)での高値・安値をレンジと定義。

3.3 エントリー

  • 買い:Askレンジ高値 + BufferPips + ATRFilterPips を上回ったら成行。
  • 売り:Bidレンジ安値 − BufferPips − ATRFilterPips を下回ったら成行。
  • スプレッドが MaxSpreadPips を超える場合は新規建て禁止。

3.4 退出(損切り・利確)

  • 損切り:基本は レンジ幅(最小でもMinSLPips)。
  • 利確:損益比 = RR(例:1.2〜1.8)。
  • オプション:トレーリングまたはBE(建値ストップ)を時間経過で適用。

3.5 フィルター

  • ATRが小さすぎる日は見送り(MinATRPips)。
  • 指定の指標ウィンドウ(NoTradeHour)は売買停止。
  • 同一バー内の連続建玉は禁止(OncePerBar)。

4. 資金管理とドローダウン許容

口座残高に対して1回あたり0.3〜0.8%のリスクから開始します。想定ドローダウンは「1−(1−リスク%)^連敗数」で概算できます。例:0.6%リスクで10連敗 → 約5.8%の資産低下。

勝率55%、損益比1.3の系なら、期待値 = 0.55×1.3 − 0.45×1.0 = 0.265(コスト控除前)。CostPipsを見積もっても正の期待値が残る領域でのみ運用します。

5. 検証フロー(過去検証→フォワード)

  1. ヒストリカル準備:MT4で対象通貨(例:USDJPY、GBPUSD)のM1データを最新まで取得。
  2. バックテスト:モデル“Every tick based on real ticks”推奨。手数料・スリッページをEA内パラメータで加味。
  3. パラメータ探索:過度な組合せ爆発を避け、時間帯・レンジ窓・RR・CostPipsの4軸に限定。
  4. フォワード:最適化外期間で1〜3か月の実地検証。
  5. 継続監視:週次でコスト・AT R水準・勝率のドリフトを監視。

6. MQL4:完全EAコード

以下は、説明で用いたロジックをそのまま実装したシンプルなEAです。初心者でも読めるようにコメントを多めに入れています。ブローカーによりポイント定義やサーバー時間が異なるため、最初はデモ口座で検証してください。


//+------------------------------------------------------------------+
//|  Cost-Aware London Breakout EA (Beginner Friendly)               |
//|  Symbol: Any FX (default USDJPY), Timeframe: M15/M5              |
//|  Notes: Simple, one-position model with session & cost filters   |
//+------------------------------------------------------------------+
#property strict

input bool   UseSessionFilter   = true;   // 取引時間帯フィルター
input int    SessionStartHour   = 15;     // 例: 15 (サーバー時間)
input int    SessionEndHour     = 20;     // 例: 20

input int    RangeStartHour     = 8;      // レンジ観測開始
input int    RangeEndHour       = 14;     // レンジ観測終了
input double BufferPips         = 0.5;    // ブレイクバッファ
input int    ATRPeriod          = 14;     // ATR期間
input double ATRFilterPips      = 0.3;    // ATRによる微フィルター
input double MinATRPips         = 0.2;    // ATRが小さすぎる日は見送り

input double RR                 = 1.4;    // 損益比 (TP = SL*RR)
input double MinSLPips          = 5.0;    // 最小SL(狭すぎ回避)

input double RiskPercent        = 0.6;    // 1トレード当たり%(例: 0.6%)
input double MaxSpreadPips      = 2.0;    // 新規禁止スプレッド閾値
input double CostPips           = 1.8;    // コスト見積り(手数料/スリッページ含む)

input bool   UseTrailing        = false;  // トレーリング有無
input double TrailStartPips     = 6.0;
input double TrailStepPips      = 1.0;

input bool   OncePerBar         = true;   // 同一バー内は1回のみ
input int    Magic              = 20250904;

datetime lastBarTime = 0;

// ユーティリティ: 1pipのPoint換算
double PipsToPoints(double pips) {
   double p = pips * (Point * (Digits == 3 || Digits == 5 ? 10 : 1));
   return p;
}
// スプレッド(pips)
double CurrentSpreadPips() {
   double spr = (Ask - Bid) / Point;
   if (Digits == 3 || Digits == 5) spr /= 10.0;
   return spr;
}
// ATR(pips)
double GetATRPips(int period) {
   double atr = iATR(Symbol(), PERIOD_M15, period, 0);
   double pips = atr / Point;
   if (Digits == 3 || Digits == 5) pips /= 10.0;
   return pips;
}
// 時間帯か
bool InHourRange(int h1, int h2) {
   int h = TimeHour(TimeCurrent());
   if (h1 <= h2) return (h >= h1 && h < h2);
   // 跨ぎ(例: 22-2時)
   return (h >= h1 || h < h2);
}
// レンジ高値・安値を取得
bool GetRange(double &hi, double &lo) {
   hi = -DBL_MAX; lo = DBL_MAX;
   datetime now = TimeCurrent();
   datetime dayStart = StringToTime(TimeToString(now, TIME_DATE));
   for (int i=0; i<Bars(Symbol(), PERIOD_M5); i++) {
      datetime t = iTime(Symbol(), PERIOD_M5, i);
      if (t < dayStart) break;
      int h = TimeHour(t);
      if (h >= RangeStartHour && h < RangeEndHour) {
         double h5 = iHigh(Symbol(), PERIOD_M5, i);
         double l5 = iLow(Symbol(), PERIOD_M5, i);
         if (h5 > hi) hi = h5;
         if (l5 < lo) lo = l5;
      }
   }
   return (hi > -DBL_MAX/2 && lo < DBL_MAX/2 && hi > lo);
}
// ロット計算(リスク%とSL pipsから)
double CalcLots(double sl_pips) {
   if (sl_pips <= 0) return 0.01;
   double equity = AccountEquity();
   double risk = MathMax(0.0, RiskPercent) / 100.0 * equity;
   double tickvalue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double pip_value_per_lot = tickvalue * (Digits == 3 || Digits == 5 ? 10.0 : 1.0);
   double lots = risk / (sl_pips * pip_value_per_lot);
   // ブローカー最小/刻みに丸め
   double minlot = MarketInfo(Symbol(), MODE_MINLOT);
   double lotstep = MarketInfo(Symbol(), MODE_LOTSTEP);
   lots = MathFloor(lots / lotstep) * lotstep;
   if (lots < minlot) lots = minlot;
   return NormalizeDouble(lots, 2);
}
// 既存ポジション件数
int CountPositions() {
   int cnt = 0;
   for (int i=0; i<OrdersTotal(); i++) {
      if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if (OrderSymbol()==Symbol() && OrderMagicNumber()==Magic) cnt++;
      }
   }
   return cnt;
}

// トレーリング
void DoTrailing() {
   for (int i=0; i<OrdersTotal(); i++) {
      if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if (OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
      double pipsProfit = 0.0;
      if (OrderType()==OP_BUY) {
         pipsProfit = (Bid - OrderOpenPrice())/Point; if (Digits==3 || Digits==5) pipsProfit/=10.0;
         if (pipsProfit >= TrailStartPips) {
            double newSL = Bid - PipsToPoints(TrailStepPips);
            if (newSL > OrderStopLoss()+Point) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);
         }
      }
      if (OrderType()==OP_SELL) {
         pipsProfit = (OrderOpenPrice() - Ask)/Point; if (Digits==3 || Digits==5) pipsProfit/=10.0;
         if (pipsProfit >= TrailStartPips) {
            double newSL = Ask + PipsToPoints(TrailStepPips);
            if (newSL < OrderStopLoss()-Point) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);
         }
      }
   }
}

int OnInit() { lastBarTime = 0; return(INIT_SUCCEEDED); }
int OnDeinit() { return(0); }

void OnTick() {
   // バー更新検出
   datetime bt = iTime(Symbol(), PERIOD_M5, 0);
   bool isNewBar = (bt != lastBarTime);
   if (isNewBar) lastBarTime = bt;
   if (OncePerBar && !isNewBar) { DoTrailing(); return; }

   // セッションチェック
   if (UseSessionFilter && !InHourRange(SessionStartHour, SessionEndHour)) { DoTrailing(); return; }

   // コスト・スプレッドチェック
   if (CurrentSpreadPips() > MaxSpreadPips) { DoTrailing(); return; }

   // 既存ポジションがある場合は何もしない
   if (CountPositions() > 0) { DoTrailing(); return; }

   // レンジ/ATR
   double hi, lo;
   if (!GetRange(hi, lo)) { DoTrailing(); return; }
   double range_pips = (hi - lo)/Point; if (Digits==3 || Digits==5) range_pips/=10.0;
   double atr_pips = GetATRPips(ATRPeriod);
   if (atr_pips < MinATRPips) { DoTrailing(); return; }

   // SL/TP設計
   double sl_pips = MathMax(range_pips, MinSLPips);
   double tp_pips = sl_pips * RR;

   // ブレイク条件(CostPipsを事前控除の意思決定に使うなら、バッファに加算して保守化)
   double buyTrig = hi + PipsToPoints(BufferPips + ATRFilterPips + CostPips/2.0);
   double sellTrig = lo - PipsToPoints(BufferPips + ATRFilterPips + CostPips/2.0);

   // 発注量
   double lots = CalcLots(sl_pips);
   int slippage = 5;

   // ブレイク判定と発注
   if (Ask >= buyTrig) {
      double sl = Ask - PipsToPoints(sl_pips);
      double tp = Ask + PipsToPoints(tp_pips);
      int tk = OrderSend(Symbol(), OP_BUY, lots, Ask, slippage, sl, tp, "LB-buy", Magic, 0, clrBlue);
   } else if (Bid <= sellTrig) {
      double sl = Bid + PipsToPoints(sl_pips);
      double tp = Bid - PipsToPoints(tp_pips);
      int tk = OrderSend(Symbol(), OP_SELL, lots, Bid, slippage, sl, tp, "LB-sell", Magic, 0, clrRed);
   }

   DoTrailing();
}
    

7. 推奨パラメータと調整方針

  • 通貨:USDJPYまたはGBPUSDから開始。
  • 時間足:M5〜M15。M1はノイズ増。
  • Session:15:00〜20:00(サーバー時間)。
  • Range:08:00〜14:00。
  • RR:1.3〜1.6。勝率と総合で最適化。
  • CostPips:ECN口座で1.5〜2.5を試験。
  • MaxSpread:USDJPYで2.0pips前後から。
  • Risk%:0.3〜0.8%で開始。

最適化では「一度に調整するのは2パラメータまで」とし、外部期間での再現性を重視します。

8. 運用オペレーション(VPS・監視・更新)

  1. VPS常時稼働:MT4を立ち上げEA稼働。再起動時の自動ログイン設定。
  2. 週次レビュー:コスト(平均スプレッド・約定滑り)と勝率変化を記録。
  3. 月次リセット:ブローカーのサーバー時間変更・夏時間切替を確認。
  4. ログ監査:Experts/Logsでエラーやリクオートを把握。

9. 失敗パターンと回避策

  • コスト過小見積もり:CostPipsを0に近づけるとバックテストが“盛られ”ます。最低でも1.5pipsは確保。
  • 時間帯の誤設定:サーバー時間とJSTを混同しない。決まった「絶対時刻」で運用。
  • リスク過大:1%超の固定リスクは初心者には過大になりがち。
  • 最適化のやりすぎ:過去の一部相場にだけ過適合。外部期間で連続陽線を確認。

10. 用語ミニ解説

スリッページ
発注時の理論価格と約定価格の差。ボラ増で広がりやすい。
ATR
平均的な日内変動幅。小さすぎる日はブレイクが伸びにくい。
RR(リワードリスク比)
利益目標と損切り幅の比。
ECN口座
手数料別建て・スプレッド狭めの口座タイプ。

11. 一般的な口座開設〜MT4準備の手順

  1. ブローカー選定:スプレッド・手数料・約定品質・規約の確認。
  2. 口座開設:本人確認、入出金手段の準備。
  3. MT4インストール:対象通貨のヒストリカルを取得。
  4. EA導入:MQL4/Expertsへファイル配置→再起動→チャートに適用。
  5. デモ検証:最低2週間。ログでエラー有無を確認。

12. まとめ

ロンドン・ブレイクアウトは「時間割」に基づくシンプルな優位性です。本稿のEAは、CostPipsで実コストを明示的に織り込み、時間帯・レンジ・RR・ATRのみの簡素な構造に収めました。初心者でも運用可能な再現手順を提示したので、まずはデモ口座でのフォワード検証から始めてください。

コメント

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