ダイナミックマルチペアEAの形成(第3回):平均回帰とモメンタム戦略
はじめに
本記事では、市場のダイナミクスに適応し、長期トレンド中の重複シグナルを避け、統計的閾値を活用して精度の高いエントリーをおこないながら、複数の通貨ペアにリアルタイムでスケールさせるロジックの構築方法を示します。トレーダーが直面しやすい一般的な課題の一つはシグナル疲労です。強いトレンドやレンジ相場でシステムが同じ方向に継続してエントリーシグナルを出し、過剰取引や不要な損失を招く現象です。特に値動きが速い市場や方向感のない相場では、コンテキストやモメンタムを考慮しない戦略は、誤シグナル、遅いエントリー、相関通貨ペア間でのリスク配分の不適切さに直面しやすくなります。
これに対処するため、本記事では平均回帰とモメンタム戦略を組み合わせたダイナミックマルチペア取引フレームワークを紹介します。従来のインジケーターに頼るだけでなく、Zスコアを計算して統計的に有意な価格乖離を検出し、モメンタムの範囲閾値を用いてエントリーの可否や再エントリーのタイミングを判断します。EAは取引方向、エントリー進捗、通貨ペアごとの条件をリアルタイムで追跡することで、現在の価格変動の進行状況に応じてエントリーを一時停止したり継続したりできます。
本記事では、これらのアイデアを技術的に実装する方法を詳しく解説し、複数ペアにわたるロジックを効率的に管理し、条件が最適な場合のみ取引をおこない、ノイズによる意思決定を減らす方法に焦点を当てます。堅牢なEAの構築や裁量取引システムの改善において、このアプローチはより賢い取引選択、適切なタイミング、そして現代の市場環境に適応したリスク管理を促進します。
EAのロジック
平均回帰は、価格が時間とともにその平均値(中央値)に戻る傾向があるという考えに基づく取引手法です。この理論では、価格の極端な変動(上昇でも下降でも)は一時的なものであり、最終的には過去の標準的な水準に修正されると仮定します。金融市場では、こうした極端な動きは、短期的な需給バランスの崩れ、ニュースへの過剰反応、または流動性ショックによって引き起こされることが多いです。平均回帰戦略は、資産が統計的に過買いまたは過売りの状態にあると判断されたときに逆方向の取引をおこない、価格が平均値に戻ることを狙います。
技術的には、平均回帰はZスコアなどの統計ツールを使って実装されます。Zスコアは、現在の価格が移動平均からどれだけ離れているかを標準偏差で表します。Zスコアが高く正の値であれば価格は平均よりかなり上(買われ過ぎの可能性)、低く負の値であれば平均よりかなり下(売られ過ぎの可能性)を示します。トレーダーは閾値(例:±2.0)を設定し、価格乖離が取引に値するかどうかを判断します。閾値を超え、さらにモメンタムの弱化や反転シグナルなどの条件を満たすと、平均値への回帰を見込んで取引を開始します。この戦略はレンジ相場や平均回帰が期待できる市場で最も効果的ですが、継続的なトレンドがある場合には信号が無効になる可能性があるため、慎重なリスク管理が必要です。

モメンタム取引は、既存のトレンドの継続から利益を得ることを目的とした戦略です。基本的な考え方は、ある方向に強い価格変動を示した資産は、反転よりもその方向への動きを続ける可能性が高い、というものです。この動きは、群衆心理、機関投資家のフロー、ニュースによるセンチメント、またはテクニカルブレイクアウトによって引き起こされることがあります。モメンタム戦略を用いるトレーダーは、強い方向性のある動きの初期または中期フェーズでエントリーし、勢いが衰えたり反転の兆候が現れるまでトレンドに沿ってポジションを維持します。
技術的には、モメンタム戦略は通常、一定期間の価格変化率を利用して実装されます。単純な価格差(例:現在の価格 − N本前の価格)、モメンタムオシレーター、あるいは価格の速度や加速度を捉えるカスタム指標などで測定されます。マルチペア取引システムでは、通貨ペアごとに個別にモメンタムを算出し、しきい値と比較してエントリーの可否を判断します。モメンタムが確認されると、トレンド方向に沿って取引を行い、勢いが衰えない限りポジションを追加していくことも可能です。

導入手順
//+------------------------------------------------------------------+ //| Dyna Mean&Mom.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh> #include <Math/Stat/Math.mqh> CTrade trade;
いつものように、EAが適切に機能するために必要なクラスを追加することから始めます。今回特に異なるのは、<Math/Stat/Math.mqh>ライブラリも追加している点です。このライブラリにより、平均値、標準偏差、Zスコア計算などの強力な統計ツールを利用でき、平均回帰戦略およびモメンタム戦略の実装に不可欠となります。
//+------------------------------------------------------------------+ //| Enhanced Mean-Reversion + Momentum EA | //+------------------------------------------------------------------+ //--- Input settings input string Symbols = "XAUUSD,GBPUSD,USDCAD,USDJPY"; input int TakeProfit = 150; // TP in points input int StopLoss = 100; // SL in points input int MAPeriod = 20; input int MomentumPeriod = 5; input double Z_Threshold = 2.0; input double Mom_Threshold = 1.5; // Price change in standard deviations input double RiskPercent_High = 1.5, RiskPercent_Mod = 1.0, RiskPercent_Low = 0.5;
このコードのセクションでは、EAの入力設定を定義します。これにより、ユーザーは取引対象の銘柄(例:XAUUSD、GBPUSD)のリストを指定したり、テイクプロフィット、ストップロス、リスク割合などの主要なリスク管理パラメータを設定したりできます。また、統計的な入力値を使って戦略の挙動を微調整することも可能です。これには、移動平均の計算期間、モメンタムを測定する期間、Zスコアやモメンタム強度の閾値などが含まれます。これらの入力設定により、EAは複数通貨ペアにまたがるリスクを管理しつつ、さまざまな市場環境に適応できるようになります。
//--- Global parameters string symb_List[]; int Num_symbs = 0; // Indicator handles arrays int MA_hndl[]; int STDev_hndl[]; int ATR_hndl[];
ここでは、EA全体で使用されるグローバル変数を宣言します。symb_List[]は取引対象のシンボルを格納する配列で、Num_symbsは処理対象となるシンボルの総数を保持します。さらに、インジケーターのハンドル用配列も定義します。MA_hndl[]は移動平均、STDev_hndl[]は標準偏差、ATR_hndl[]はATRのハンドルを管理するためのもので、各銘柄に対して実行時に効率的にインジケーターデータへアクセスや操作するために使用されます。
//+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- Split symbol list ushort separator = StringGetCharacter(",", 0); StringSplit(Symbols, separator, symb_List); Num_symbs = ArraySize(symb_List); //--- Resize arrays ArrayResize(MA_hndl, Num_symbs); ArrayResize(STDev_hndl, Num_symbs); ArrayResize(ATR_hndl, Num_symbs); //--- Prepare each symbol for (int i = 0; i < Num_symbs; i++) { string symbol = symb_List[i]; StringTrimLeft(symbol); StringTrimRight(symbol); //--- Create indicator handles MA_hndl[i] = iMA(symbol, PERIOD_H1, MAPeriod, 0, MODE_SMA, PRICE_CLOSE); STDev_hndl[i] = iStdDev(symbol, PERIOD_H1, MAPeriod, 0, MODE_SMA, PRICE_CLOSE); ATR_hndl[i] = iATR(symbol, PERIOD_H1, 14); if (MA_hndl[i] == INVALID_HANDLE || STDev_hndl[i] == INVALID_HANDLE || ATR_hndl[i] == INVALID_HANDLE) { Print("Failed to create indicator handles for ", symbol); return INIT_FAILED; } } //--- Set magic number for trade identification trade.SetExpertMagicNumber(54321); return INIT_SUCCEEDED; }
このセクションでは、チャートにEAが読み込まれた際の初期化処理をおこないます。まず、ユーザーが指定したSymbols文字列を配列に分割し、処理対象の銘柄数を数えます。次に、インジケーターハンドル配列のサイズを銘柄数に合わせて調整します。各銘柄について、余分な空白を除去し、H1時間軸で計算される移動平均(MA)、標準偏差(STDev)、ATRのインジケーターハンドルを作成します。ハンドルの初期化に失敗した場合、EAはエラーメッセージを表示して処理を停止します。最後に、trade.SetExpertMagicNumber(54321)を使用してこのEAによるエントリーを識別するためのユニークなマジックナンバーを設定します。
//+------------------------------------------------------------------+ //| Mean and Momentum Signal Generator | //+------------------------------------------------------------------+ void MeanAndMomentum(string symbol, int idx) { //--- Get current price data MqlRates current[]; if(CopyRates(symbol, PERIOD_H1, 0, 1, current) < 1) return; double close = current[0].close; //--- Get historical price for momentum calculation MqlRates historical[]; if(CopyRates(symbol, PERIOD_H1, MomentumPeriod, 1, historical) < 1) return; double histClose = historical[0].close; //--- Get indicator values double ma[1], stddev[1], atr[1]; if(CopyBuffer(MA_hndl[idx], 0, 0, 1, ma) < 1) return; if(CopyBuffer(STDev_hndl[idx], 0, 0, 1, stddev) < 1) return; if(CopyBuffer(ATR_hndl[idx], 0, 0, 1, atr) < 1) return; //--- Calculate metrics double momentum = close - histClose; double zscore = (stddev[0] > 0) ? (close - ma[0]) / stddev[0] : 0; double momThreshold = Mom_Threshold * stddev[0]; // Dynamic momentum threshold //--- Determine signal type int signal = 0; double riskPercent = 0; bool meanReversionLong = (zscore < -Z_Threshold); bool meanReversionShort = (zscore > Z_Threshold); bool momentumLong = (momentum > momThreshold); bool momentumShort = (momentum < -momThreshold); //--- Signal priority: Momentum > Mean Reversion if(momentumLong && meanReversionLong) { signal = 1; riskPercent = RiskPercent_High; // Strong signal } else if(momentumShort && meanReversionShort) { signal = -1; riskPercent = RiskPercent_High; } else if(momentumLong) { signal = 1; riskPercent = RiskPercent_Mod; } else if(momentumShort) { signal = -1; riskPercent = RiskPercent_Mod; } else if(meanReversionLong) { signal = 1; riskPercent = RiskPercent_Low; } else if(meanReversionShort) { signal = -1; riskPercent = RiskPercent_Low; } //--- Exit if no signal if(signal == 0) return; //--- Check existing positions if(PositionSelect(symbol)) { long positionType = PositionGetInteger(POSITION_TYPE); if((positionType == POSITION_TYPE_BUY && signal == 1) || (positionType == POSITION_TYPE_SELL && signal == -1)) { return; // Already in position in same direction } else { // Close opposite position before opening new one trade.PositionClose(symbol); Sleep(100); // Allow time for order execution } } //--- Calculate position size double lotSize = CalculatePositionSize(symbol, riskPercent, atr[0]); if(lotSize <= 0) return; //--- Execute trade ExecuteTrade(signal == 1 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, symbol, lotSize); }
MeanAndMomentum()関数は、指定された銘柄に対して平均回帰とモメンタムを組み合わせて取引シグナルを生成します。まず、直近の終値と、MomentumPeriod本前の過去価格を取得し、これを使ってモメンタムを計算します。また、あらかじめ初期化されたインジケーターハンドルを用いて、移動平均(MA)、標準偏差(STDev)、ATRの値も取得します。このデータを基に、価格が平均からどれだけ離れているかを標準偏差で示すZスコアや、単純に価格変化を示すモメンタムを計算します。さらに、標準偏差をスケーリングして動的モメンタム閾値を算出し、市場の変動性の変化に応じてシステムが柔軟に反応できるようにします。
これらの指標を算出した後、関数は取引シグナル条件が満たされているかを評価します。Zスコアが平均回帰のチャンスを示しているか(価格が平均から大きく乖離しているか)、あるいはモメンタムが十分に強くトレンド方向を示唆しているかをチェックします。シグナルの優先順位は層別化されており、平均回帰とモメンタムの両方が同じ方向で揃った場合は最大リスク割合(RiskPercentage_High)を割り当てます。一方、片方の条件しか満たされない場合は低めのリスク割合が設定されます。この層構造により、EAはより質の高いセットアップを選別し、シグナルに応じてリスクエクスポージャーを調整できます。
最後に、同じ銘柄で既存のポジションを確認します。同方向のポジションが既に存在する場合は重複を避け、反対方向のポジションがある場合は新規エントリー前に決済します。その後、ATRベースのリスクモデルを用いて適切なロットサイズを計算し、決定したシグナル方向に従って取引を実行します。この構造により、取引は統計的優位性に沿ったもので、ボラティリティに応じた適切なサイズで行われ、既存の市場ポジションと競合しないようになります。
//+------------------------------------------------------------------+ //| Calculate position size based on risk and volatility | //+------------------------------------------------------------------+ double CalculatePositionSize(string symbol, double riskPercent, double atrValue) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = balance * (riskPercent / 100.0); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE_LOSS); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); if(point <= 0 || tickValue <= 0 || tickSize <= 0) { Print("Invalid symbol parameters for ", symbol); return 0; } // Use ATR-based stop loss double slDistance = atrValue * 1.5; double lossPerLot = slDistance * (tickValue / tickSize); if(lossPerLot <= 0) { Print("Invalid loss calculation for ", symbol); return 0; } double lots = riskAmount / lossPerLot; lots = NormalizeLots(symbol, lots); return lots; }
CalculatePositionSize()関数は、トレーダーの口座残高、設定されたリスク割合、そしてATRで測定される市場のボラティリティに基づいて、取引の最適ロットサイズを算出します。まず、口座残高の何パーセントをリスクにさらすかを計算し、riskAmountとして算出します。次に、指定されたシンボルに関する重要な取引パラメータ(ポイントサイズ、ティックバリュー、ティックサイズ)を取得し、価格変動を金額ベースのリスクに換算するために使用します。
これらのパラメータをもとに、ATRの値に1.5を掛けることでボラティリティに応じたバッファを加えたストップロス距離を推定します。このストップ距離とシンボル固有のティックバリューから、標準ロットあたりの潜在的損失を算出します。次に、リスク金額を1ロットあたりの損失で割ることで、希望するロット数を算出し、設定されたリスク許容範囲内に収まるように調整します。最後に、NormalizeLots()を呼び出してシンボルの取引制約に従ってロット数を丸め、結果を返します。この方法により、各取引は口座残高と市場環境の両方に適したサイズで実行されます。
//+------------------------------------------------------------------+ //| Normalize lot size to broker requirements | //+------------------------------------------------------------------+ double NormalizeLots(string symbol, double lots) { double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(lotStep > 0) { lots = MathRound(lots / lotStep) * lotStep; } lots = MathMax(minLot, MathMin(maxLot, lots)); return lots; }
ここで、関数はブローカーの要件に応じてロットサイズを単純に正規化します。
//+------------------------------------------------------------------+ //| Execute trade with proper risk management | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol, double lotSize) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); // Get current ATR for dynamic stop levels double atr[1]; int idx = ArrayPosition(symbol); if(idx >= 0 && CopyBuffer(ATR_hndl[idx], 0, 0, 1, atr) > 0) { double slDistance = atr[0] * 1.5; double tpDistance = atr[0] * 2.5; double sl = (tradeType == ORDER_TYPE_BUY) ? price - slDistance : price + slDistance; double tp = (tradeType == ORDER_TYPE_BUY) ? price + tpDistance : price - tpDistance; trade.PositionOpen(symbol, tradeType, lotSize, price, sl, tp, "MR-Mom System"); } else { // Fallback to fixed stops if ATR fails double sl = (tradeType == ORDER_TYPE_BUY) ? price - (StopLoss * point) : price + (StopLoss * point); double tp = (tradeType == ORDER_TYPE_BUY) ? price + (TakeProfit * point) : price - (TakeProfit * point); trade.PositionOpen(symbol, tradeType, lotSize, price, sl, tp, "MR-Mom System"); } }
ExecuteTrade()関数は、取引方向、銘柄、算出されたロットサイズに基づき、適切なリスク管理を行いながら取引を実行する役割を担います。まず、買い注文の場合はAsk価格、売り注文の場合はBid価格を使用して現在の市場価格を取得します。その後、指定銘柄の最新のATR値を取得しようとします。ATRデータが利用可能な場合、ストップロスとテイクプロフィットの水準を動的に計算します。具体的には、ストップロスはATRの1.5倍、テイクプロフィットはATRの2.5倍として設定し、現在の市場ボラティリティに応じてリスクとリワードを調整します。最後に、trade.PositionOpen()メソッドを用いて、計算したレベルと識別用ラベルを付けて取引を実行します。
//+------------------------------------------------------------------+ //| Find symbol position in array | //+------------------------------------------------------------------+ int ArrayPosition(string symbol) { for(int i = 0; i < Num_symbs; i++) { if(symb_List[i] == symbol) return i; } return -1; }この関数は、指定された銘柄をsymb_List配列内で検索し、そのインデックスを返します。もしシンボルが見つからなければ-1を返します。これにより、関連するインジケーターハンドルにアクセスする際に、銘柄の正しい位置を特定するのに役立ちます。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ if(isNewBar()){ for(int i = 0; i < Num_symbs; i++) { MeanAndMomentum(symb_List[i], i); } } }最後に、OnTick()関数はEAの心臓部であり、新しいティックを受信するたびにロジックを実行する役割を持ちます。すべてのティックで処理を行わないよう、まずisNewBar()を使って新しいバーが形成されたかを確認し、シグナルは1本のローソク足につき1回だけ評価されるようにします。新しいバーが確認されると、関数は指定されたすべての銘柄をループ処理し、各銘柄に対してMeanAndMomentum()を呼び出します。これにより、EAは最新の市場データに基づき、複数通貨ペアの取引機会を同期して評価することができます。
バックテスト結果
バックテストは1時間足(H1)で、2025年5月1日から2025年6月20日までの2か月間の期間を対象に実施され、以下の入力設定で評価されました。
- テイクプロフィット(TP) = 972ポイント
- ストップロス(SL) = 846ポイント
- 移動平均(MA)期間 = 80
- モメンタム期間 = 43
- Zスコア閾値(Z_Threshold) = 3.0
- 標準偏差に対する価格変動 = 4.05
- 高リスク割合 = 9.75%
- 中リスク割合 = 10.0%
- 低リスク割合 = 4.65%


結論
まとめると、本EAでは平均回帰とモメンタム戦略を組み合わせた、ダイナミックな多銘柄対応のEAを設計し、実装しました。移動平均、標準偏差(Zスコア)、ATRに基づくボラティリティなどの統計ツールを活用し、複数通貨ペアを同時に分析して計算された取引判断をおこなえるシステムを構築しています。各銘柄は独立して監視され、価格動向、ボラティリティ、トレンド強度に基づいたカスタムシグナル生成が可能です。戦略は、モメンタムと平均回帰のシグナルが同方向で揃った場合にエントリーを優先し、リスク調整済みのポジションサイズにより適切にエクスポージャーを管理します。
結論として、このEAは、複数通貨ペアに対応した統計的根拠に基づく適応型取引システムを構築するための堅固な基盤を提供します。モジュール化された設計と階層的な意思決定フレームワークにより、スキャルピングからスイングトレードまで幅広い戦略に対応可能で、拡張性とカスタマイズ性も高くなっています。リアルタイムのボラティリティとモメンタム追跡を活用することで、エントリーは統計的に根拠があるだけでなく、市場の状況にも敏感に反応します。適切な最適化とモニタリングを行えば、このシステムはさまざまな市場環境において、リスク管理された安定したパフォーマンスを提供する可能性があります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18037
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
グラフ理論:ダイクストラ法を取引に適用する
MQL5入門(第18回):ウォルフ波動パターンの基本
知っておくべきMQL5ウィザードのテクニック(第74回): 教師あり学習で一目均衡表とADX Wilderのパターンを利用する
初心者からエキスパートへ:MQL5を使用したアニメーションニュースヘッドライン(IV) - ローカルホストAIモデル市場インサイト
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索