レジーム・スイッチングFXデイトレEA:ADX×ATRで相場の“トレンド/レンジ”を自動判定して手法を切り替える(USD/JPY)

FX

相場は常に同じ顔をしていません。トレンドが強く一方向に伸びる時間帯もあれば、短いレンジに閉じ込められて往復ビンタになりやすい時間帯もあります。多くの初心者が躓くのは、この「相場の顔」が変わった瞬間に手法を切り替えられず、トレンド相場で逆張りをして踏み上げられたり、レンジ相場で順張りブレイクに追随してダマシをくらうことです。

本記事では、ADX(トレンドの強さ)ATR(ボラティリティの大きさ)の二軸で相場のレジーム(状態)を簡潔に判定し、「トレンド用の順張りブレイク」「レンジ用の逆張りリバート」を自動で切り替えるレジーム・スイッチングEAを、具体的なMQL4コードとともに解説します。対象はUSD/JPYの5分足〜15分足です。記事の方針は「シンプルで壊れにくい」、そして「初心者でも運用できる現実的な作り」です。

この手法の狙いと設計思想

裁量でもシステムでも、短期FXで重要なのは「相場認識のミスを減らす」ことです。レジームを数行で判定し、トレンドならブレイク順張り、レンジなら逆張りという反射行動をEAにさせます。面倒なニュースフィルタや特殊指標は使いません。MT4標準インジケータだけで、壊れにくい小さなルールを積み上げます。

レジーム判定のコア

  • ADX(14)…トレンド強度の把握。閾値は22を初期値とします。
  • ATR(14)…ボラティリティの把握。ATRをpips換算して閾値と比較します(たとえば4〜8pipsが一つの目安)。

この2つが一定以上なら「トレンド・モード」。どちらか、または両方が弱ければ「レンジ・モード」。判定は新しいバー確定ごとに行い、EA内部でロジックを切り替えます。

2つの売買ロジック(モード)

  1. トレンド・モードDonchian的ブレイクの順張り。
    条件:ADX≥22かつATR≥閾値。
    売買:直近バー高値の少し上にBuy Stop、直近バー安値の少し下にSell Stop(バッファ=0.5〜1.0pips)。
    方向:+DI > -DIなら買い優先、-DI > +DIなら売り優先。
    退出:ATR倍率のストップ(1.5〜2.0倍)と、2R程度の利確。ポジション保有中はATRトレーリングを実行。
  2. レンジ・モードRSI(2)の極値を使った逆張りリバート。
    条件:ADX<22 または ATR<閾値。
    売買:RSI(2)≤10 かつ価格がMA(20)−k×ATRより下 → 成行買い。RSI(2)≥90 かつ価格がMA(20)+k×ATRより上 → 成行売り(k=0.5〜1.0)。
    退出:MA(20)付近で手仕舞い(または1×ATR固定TP)。ストップはATR×1.5程度。

タイムゾーンと稼働時間

USD/JPYは東京時間の実需フロー、ロンドン〜NYのマクロフローで性格が変わります。初心者でも管理しやすいよう、時間フィルタで稼働帯を2つに分けます(ブローカー時間に合わせて調整)。

  • 帯1:東京後場終盤〜欧州初動(例:サーバー時間で 06:00–10:59)
  • 帯2:ロンドン後半〜NY前半(例:サーバー時間で 13:00–20:59)

仲値(JST 9:55)や米指標(雇用統計、CPIなど)は流動性が偏るため、稼働停止の手動スイッチをEAに用意します(後述パラメータ)。

初期パラメータ(推奨の目安)

項目 推奨初期値 補足
Timeframe M5〜M15 M5はシグナル多め、M15はノイズ低下
ADXPeriod 14 短すぎるとダマシ増加
ADXTrendThreshold 22 20〜25で最適化
ATRPeriod 14 標準的
ATRTrendThreshold (pips) 6.0 4.0〜8.0で調整
BufferPips(ブレイク) 0.8 スプレッド×1〜1.5目安
RSI2閾値 買い≤10 / 売り≥90 8/92など微調整可
MA期間 20 SMAでOK
MA偏差係数 k 0.7 0.5〜1.0
SL(ATR倍率) 1.6 1.4〜2.0
TP(トレンド) 2R 1.5〜2.5Rで最適化
TP(レンジ) MA(20)または1×ATR 小さく素早く
RiskPerTrade 0.5%/trade 初心者は0.25%でも可

MQL4 EA(完全自動売買・初心者向けのシンプル構造)

以下は最小限で動作するサンプルEAです。1ポジション制御、時間フィルタ、レジーム判定、ブレイクのストップ注文とレンジの成行、ATRトレーリングまでを搭載しています。


//+------------------------------------------------------------------+
//| RegimeSwitch_EA_USDJPY.mq4                                      |
//| ADX×ATRでトレンド/レンジを自動判定しロジック切替                |
//+------------------------------------------------------------------+
#property strict
input string   InpSymbol            = "USDJPY";
input int      TimeframeMinutes     = 5;         // 5か15を想定
input double   RiskPerTradePct      = 0.5;       // 口座に対するリスク%
input int      ADXPeriod            = 14;
input double   ADXTrendThreshold    = 22.0;
input int      ATRPeriod            = 14;
input double   ATRTrendPipsThresh   = 6.0;       // トレンド判定に使うATR閾値(pips)
input double   BreakBufferPips      = 0.8;       // ドンチャン的ブレイクのバッファ
input int      RSIPeriod            = 2;
input int      RSIbuy               = 10;
input int      RSIsell              = 90;
input int      MAPeriod             = 20;
input double   MADevATRK            = 0.7;       // MA±k*ATRの偏差
input double   SL_ATR_Mult          = 1.6;
input double   TP_R_Mult_Trend      = 2.0;       // トレンド時のR倍
input bool     UseTrailingATR       = true;
input double   TrailATRMult         = 1.0;
input int      StartHour1           = 6;  // サーバー時間
input int      EndHour1             = 10;
input int      StartHour2           = 13;
input int      EndHour2             = 20;
input bool     TradingSwitch        = true;      // 手動停止スイッチ
input int      Magic                = 24090401;
input int      Slippage             = 3;

datetime lastBarTime = 0;

double Pip() {
   return (Digits==3 || Digits==5) ? 10*Point : Point;
}

bool InSession() {
   int h = TimeHour(TimeCurrent());
   bool s1 = (h>=StartHour1 && h<=EndHour1);
   bool s2 = (h>=StartHour2 && h<=EndHour2);
   return (s1 || s2);
}

bool HasPosition() {
   for(int i=OrdersTotal()-1;i>=0;i--) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if(OrderSymbol()==InpSymbol && OrderMagicNumber()==Magic) return true;
      }
   }
   return false;
}

bool HasPending() {
   for(int i=OrdersTotal()-1;i>=0;i--) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         int type=OrderType();
         if((type==OP_BUYSTOP || type==OP_SELLSTOP) && OrderSymbol()==InpSymbol && OrderMagicNumber()==Magic) return true;
      }
   }
   return false;
}

void CancelPendings() {
   for(int i=OrdersTotal()-1;i>=0;i--) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         int type=OrderType();
         if((type==OP_BUYSTOP || type==OP_SELLSTOP) && OrderSymbol()==InpSymbol && OrderMagicNumber()==Magic) {
            OrderDelete(OrderTicket());
         }
      }
   }
}

double AccountRiskLot(double stopPips) {
   if(stopPips<=0) return 0.01;
   double riskMoney = AccountBalance() * RiskPerTradePct/100.0;
   double tickValue = MarketInfo(InpSymbol, MODE_TICKVALUE);
   double lot = riskMoney / (stopPips * Pip() / Point * tickValue);
   double minLot = MarketInfo(InpSymbol, MODE_MINLOT);
   double lotStep = MarketInfo(InpSymbol, MODE_LOTSTEP);
   lot = MathFloor(lot/lotStep) * lotStep;
   if(lot<minLot) lot = minLot;
   return lot;
}

void TrailPositions() {
   if(!UseTrailingATR) return;
   double atr = iATR(InpSymbol, Period(), ATRPeriod, 1);
   double trail = TrailATRMult * atr;
   for(int i=OrdersTotal()-1;i>=0;i--) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if(OrderSymbol()!=InpSymbol || OrderMagicNumber()!=Magic) continue;
         if(OrderType()==OP_BUY) {
            double newSL = Bid - trail;
            if(OrderStopLoss() < newSL) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
         } else if(OrderType()==OP_SELL) {
            double newSL = Ask + trail;
            if(OrderStopLoss()==0 || OrderStopLoss() > newSL) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
         }
      }
   }
}

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

void OnDeinit(const int reason) {
   // クリーンナップ等
}

void OnTick() {
   if(Symbol()!=InpSymbol) return;
   if(!TradingSwitch) return;
   if(!InSession()) return;

   int tf = TimeframeMinutes;
   if(Period() != tf*60) return; // チャートの時間軸を合わせる

   // 新バー判定
   datetime curBar = iTime(InpSymbol, Period(), 0);
   if(curBar == lastBarTime) { TrailPositions(); return; }
   lastBarTime = curBar;

   // 既存注文の管理(モードが変わったらキャンセル)
   // レジーム判定
   double adx  = iADX(InpSymbol, Period(), ADXPeriod, PRICE_CLOSE, MODE_MAIN, 1);
   double plus = iADX(InpSymbol, Period(), ADXPeriod, PRICE_CLOSE, MODE_PLUSDI, 1);
   double minus= iADX(InpSymbol, Period(), ADXPeriod, PRICE_CLOSE, MODE_MINUSDI, 1);
   double atr  = iATR(InpSymbol, Period(), ATRPeriod, 1);
   double atrPips = atr / Pip();

   bool trendMode = (adx >= ADXTrendThreshold && atrPips >= ATRTrendPipsThresh);

   // MAとRSI
   double ma = iMA(InpSymbol, Period(), MAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
   double rsi= iRSI(InpSymbol, Period(), RSIPeriod, PRICE_CLOSE, 1);

   // 既存のポジション/指値確認
   bool hasPos = HasPosition();
   bool hasPend = HasPending();

   // モード変更時に未約定の指値はキャンセル
   if(!trendMode && hasPend) CancelPendings();

   // 1ポジション制御
   if(hasPos) { TrailPositions(); return; }

   double stopPips = SL_ATR_Mult * atrPips;
   double lots = AccountRiskLot(stopPips);
   double buffer = BreakBufferPips * Pip();

   // トレンド・モード:ドンチャン風ブレイクの指値
   if(trendMode) {
      double prevHigh = iHigh(InpSymbol, Period(), 1);
      double prevLow  = iLow(InpSymbol, Period(), 1);
      if(!hasPend) {
         if(plus > minus) {
            double price = prevHigh + buffer;
            double sl = price - stopPips*Pip();
            double tp = price + (TP_R_Mult_Trend * stopPips * Pip());
            OrderSend(InpSymbol, OP_BUYSTOP, lots, NormalizeDouble(price, Digits), Slippage, sl, tp, "TrendBuy", Magic, 0, clrBlue);
         } else if(minus > plus) {
            double price = prevLow - buffer;
            double sl = price + stopPips*Pip();
            double tp = price - (TP_R_Mult_Trend * stopPips * Pip());
            OrderSend(InpSymbol, OP_SELLSTOP, lots, NormalizeDouble(price, Digits), Slippage, sl, tp, "TrendSell", Magic, 0, clrRed);
         }
      }
      return;
   }

   // レンジ・モード:RSI2の極値で成行エントリ→MA目標
   if(rsi <= RSIbuy) {
      double sl = Bid - stopPips*Pip();
      double tp = ma; // MA回帰
      OrderSend(InpSymbol, OP_BUY, lots, Bid, Slippage, sl, tp, "RangeBuy", Magic, 0, clrGreen);
   } else if(rsi >= RSIsell) {
      double sl = Ask + stopPips*Pip();
      double tp = ma; // MA回帰
      OrderSend(InpSymbol, OP_SELL, lots, Ask, Slippage, sl, tp, "RangeSell", Magic, 0, clrMagenta);
   }
}

バックテスト手順(MT4)

  1. USDJPYのヒストリカルデータを最新まで同期します(M1→M5/M15へ変換)。
  2. ストラテジーテスターでモデルはEvery tick based on real ticksが理想。時間足はM5またはM15。
  3. 初期証拠金100万円相当、レバレッジはブローカー既定、手数料・スプレッドは現状の平均に近い値を設定します。
  4. 期間は最低でも直近2〜3年。相場環境が大きく異なる時期(低ボラ期/高ボラ期)を含めて評価します。
  5. パラメータはADX閾値(20〜25)ATR閾値(4〜8pips)SL倍率(1.4〜2.0)TP倍率(1.5〜2.5R)を中心に部分最適化します。過学習を避けるため、同時最適化の自由度を上げすぎないこと。

検証で見るべき6つのKPI

  1. PF(Profit Factor):1.2以上を最低ライン、1.4以上で実運用候補。
  2. 平均損益R(Expectancy):+0.05R以上を目標。Rは1トレードのリスク。
  3. 最大ドローダウン:資金の10%以内が目安。20%を超える設定はサイズを落とす。
  4. 連敗長:レンジ・モード主体の設定で伸びやすい。資金管理に反映。
  5. 相場局面別の内訳:東京帯/ロンドン帯、低ボラ/高ボラでの寄与。
  6. スリッページ耐性:ブレイクのStop注文は滑りやすい。スプレッド×1〜1.5のバッファで吸収。

ロット設計と資金管理

本EAはATRベースのストップ距離からリスク一定(%)のロット算出を行います。初心者は0.25%〜0.5%/トレード、上限でも1.0%を推奨します。連敗時は自動的にロットが縮むため、破綻確率が抑制されます。

よくある失敗と対策

  • 最適化でKPIが急に良くなる:自由度の上げ過ぎです。変数は4〜5個までに抑え、OOS(アウト・オブ・サンプル)で再検証。
  • 指標で損切り連発:重要指標前後はTradingSwitch=falseで手動停止。指標カレンダーを習慣化。
  • サーバー時間とJSTの齟齬:ブローカー時間で稼働帯を合わせてください。夏時間も要確認。
  • スプレッド拡大時の暴発:Start/Endを避け、流動性の厚い帯で走らせる。BreakBufferPipsをスプレッド連動で見直す。

発展案:2つの軽微な改良

  1. ADX平滑化:直近3バーのADX平均で判定を頑強に。
  2. RSIのダイバージェンス検出:逆張りの質を上げたい場合、過去高安の非一致でフィルタ。

導入と運用チェックリスト

  • MT4へEAを配置、パラメータを初期値で設定。
  • デモ/センチ口座で2週間稼働、連敗長・DD・スプレッド挙動を確認。
  • 本番口座は0.25%リスクの極小ロットから開始。
  • 週次でKPIをダッシュボード化(PF、勝率、R期待値、連敗)。
  • 月次でパラメータを調整(過学習は回避)。

Q&A(初心者向け)

Q1:このEAは他通貨でも動きますか?
A:設計はUSD/JPY向けですが、EUR/USDやGBP/USDでも動作します。ただしATR閾値や稼働時間は通貨特性に合わせて再調整してください。

Q2:勝率はどれくらいを想定すべき?
A:トレンド・モードは勝率50%前後でもRマルチプルで利益を狙います。レンジ・モードは勝率がやや高めになりやすい反面、連敗リスクがあります。全体で45〜55%でも十分成立します。

Q3:VPSは必要ですか?
A:安定稼働にはVPS推奨です。スリッページと接続切断のリスクを抑えます。

Q4:複利運用は?
A:リスク%一定のため擬似的に複利になります。月次で最大ドローダウンに応じてリスク%を微調整してください。

まとめ

相場の顔に合わせてロジックを切り替えるだけで、初心者の典型的なミスが大きく減ります。ADX×ATRでシンプルに状態判定し、トレンドは順張り・レンジは逆張り。EAとして機械的に実行することで、感情のノイズを最小化できます。まずは小さく検証し、壊れにくい設定で長く使える武器に育ててください。

コメント

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