
MQL5でのカスタム市場レジーム検出システムの構築(第2回):エキスパートアドバイザー
はじめに
第1回では、常に変化する市場のダイナミクスに対処するための基礎を築きました。堅牢な統計的基盤を構築し、市場の動きを客観的に分類できるCMarketRegimeDetectorクラスを作成。さらに、チャート上でレジームを直接視覚化できるカスタムインジケーター(MarketRegimeIndicator)も開発しました。私たちは、静的な戦略が動的な市場でパフォーマンスを低下させるという問題を認識するところから出発し、現在の市場状態を識別できるシステムの構築へと進みました。このシステムでは、「トレンド(上昇/下降)」「レンジ」「高ボラティリティ」の3つの市場状態を検出可能です。
しかし、現在のレジームを識別することは、あくまで戦いの半分にすぎません。真の力は、この情報を活用し、取引手法を柔軟に適応させることにあります。どれほど高度な検出器であっても、それだけでは単なる分析ツールに過ぎません。重要なのは、その洞察を実際の取引判断に活かすことです。もし私たちの取引システムが、市場の状況に応じて自動的にギアを切り替えられたとしたらどうでしょうか。たとえば、トレンドが強いときにはトレンドフォロー戦略を採用し、横ばい相場では平均回帰戦略に切り替え、ボラティリティが高いときにはリスクパラメータを調整する――そのような動的対応が可能になれば、戦略の柔軟性と安定性は飛躍的に高まります。
それこそが、第2回で解決すべき課題です。前回構築した基盤を活かし、今回はその実用的な応用と改善に焦点を当てていきます。本記事で取り扱う内容は以下の通りです。
- 適応型EAの構築:CMarketRegimeDetectorを統合し、検出されたレジームに応じて異なる取引戦略(トレンドフォロー、平均回帰、ブレイクアウト)を自動で選択・実行するMarketRegimeEAを構築します。
- レジーム別リスク管理の実装:現在の市場状態に基づいて、ロットサイズ、ストップロス、テイクプロフィットなどのリスクパラメータを動的に調整する方法を紹介します。
- 実運用における考慮事項:異なる通貨ペアや時間足ごとのパラメータ最適化など、現実的な運用に必要な要素を詳しく解説します。
- レジーム移行の対応:市場があるレジームから別のレジームへと移行する際に、スムーズに戦略を切り替えるための対応策を紹介します。
- 統合テクニック:既存の取引フレームワークに市場レジーム検出システム(Market Regime Detection System)を統合し、柔軟性と適応性を高める方法についても議論します。
この第2回を読み終える頃には、市場レジームを検出する方法だけでなく、多様な市場環境において一貫したパフォーマンスを目指し、自律的に挙動を適応させる自動売買システムの構築方法も理解できるようになるでしょう。それでは、私たちの検出システムを真の適応型取引ソリューションへと進化させましょう。
適応型EAの構築
このセクションでは、市場レジーム検出器を活用し、現在の市場状況に応じて取引戦略を適応可能なEAを作成します。これは、レジーム検出をどのようにして完全な取引システムに統合できるかを示す実例となります。
MarketRegimeEA(市場レジームEA)
このEAは、検出された市場レジームごとに異なる取引手法を用います。- トレンド相場では、トレンドフォロー戦略を使用
- レンジ相場では、平均回帰戦略を使用
- 高ボラティリティ相場では、ポジションサイズを抑えたブレイクアウト戦略を使用
実装は次のとおりです。
// Include the Market Regime Detector #include <MarketRegimeEnum.mqh> #include <MarketRegimeDetector.mqh> // EA input parameters input int LookbackPeriod = 100; // Lookback period for calculations input int SmoothingPeriod = 10; // Smoothing period for regime transitions input double TrendThreshold = 0.2; // Threshold for trend detection (0.1-0.5) input double VolatilityThreshold = 1.5; // Threshold for volatility detection (1.0-3.0) // Trading parameters input double TrendingLotSize = 0.1; // Lot size for trending regimes input double RangingLotSize = 0.05; // Lot size for ranging regimes input double VolatileLotSize = 0.02; // Lot size for volatile regimes input int TrendingStopLoss = 100; // Stop loss in points for trending regimes input int RangingStopLoss = 50; // Stop loss in points for ranging regimes input int VolatileStopLoss = 150; // Stop loss in points for volatile regimes input int TrendingTakeProfit = 200; // Take profit in points for trending regimes input int RangingTakeProfit = 80; // Take profit in points for ranging regimes input int VolatileTakeProfit = 300; // Take profit in points for volatile regimes // Global variables CMarketRegimeDetector *Detector = NULL; int OnBarCount = 0; datetime LastBarTime = 0;
このEAには、市場レジームの検出設定および取引戦略の構成に関するパラメータが含まれています。注目すべき点は、市場レジームごとにロットサイズ、ストップロス、テイクプロフィットを個別に設定していることです。これにより、EAは現在の市場状況に応じてリスク管理のアプローチを柔軟に適応させることができます。
EAの初期化
OnInit関数は、市場レジーム検出器を作成して構成します。
int OnInit() { // Create and initialize the Market Regime Detector Detector = new CMarketRegimeDetector(LookbackPeriod, SmoothingPeriod); if(Detector == NULL) { Print("Failed to create Market Regime Detector"); return INIT_FAILED; } // Configure the detector Detector.SetTrendThreshold(TrendThreshold); Detector.SetVolatilityThreshold(VolatilityThreshold); Detector.Initialize(); // Initialize variables OnBarCount = 0; LastBarTime = 0; return INIT_SUCCEEDED; }
この関数は、ユーザーが指定したパラメータを使用して市場レジーム検出器を作成および構成します。また、新しいバーを追跡するために使用するバーカウント変数も初期化します。
EAティック処理
OnTick関数は新しい価格データを処理し、レジームベースの取引戦略を実行します。
void OnTick() { // Check for new bar datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, 0); if(currentBarTime == LastBarTime) return; // No new bar LastBarTime = currentBarTime; OnBarCount++; // Wait for enough bars to accumulate if(OnBarCount < LookbackPeriod) { Comment("Accumulating data: ", OnBarCount, " of ", LookbackPeriod, " bars"); return; } // Get price data double close[]; ArraySetAsSeries(close, true); int copied = CopyClose(Symbol(), PERIOD_CURRENT, 0, LookbackPeriod, close); if(copied != LookbackPeriod) { Print("Failed to copy price data: copied = ", copied, " of ", LookbackPeriod); return; } // Process data with the detector if(!Detector.ProcessData(close, LookbackPeriod)) { Print("Failed to process data with Market Regime Detector"); return; } // Get current market regime ENUM_MARKET_REGIME currentRegime = Detector.GetCurrentRegime(); // Display current regime information string regimeText = "Current Market Regime: " + Detector.GetRegimeDescription(); string trendText = "Trend Strength: " + DoubleToString(Detector.GetTrendStrength(), 4); string volatilityText = "Volatility: " + DoubleToString(Detector.GetVolatility(), 4); Comment(regimeText + "\n" + trendText + "\n" + volatilityText); // Execute trading strategy based on market regime ExecuteRegimeBasedStrategy(currentRegime); }この関数は以下の処理をおこないます。
- 新しいバーが形成されたどうかを確認し、不要な計算を避ける
- 十分な本数のバーが蓄積されるまで待機し、信頼性のあるレジーム検出をおこなう
- 最新の価格データを取得する
- 市場レジーム検出器でデータを処理する
- 現在の市場レジームを取得する
- レジーム情報を表示する
- 現在のレジームに基づいた取引戦略を実行する
ArraySetAsSeries(close, true)の使用は重要です。これにより、価格配列が逆順(インデックス0に最新の価格)でインデックス付けされることが保証されます。これは、MQL5で時系列データを扱う際の標準的な配列のインデックス方式です。
レジームベースの取引戦略
ExecuteRegimeBasedStrategy関数は、さまざまな市場レジームに対して異なる取引アプローチを実装します。
void ExecuteRegimeBasedStrategy(ENUM_MARKET_REGIME regime) { // Check if we already have open positions if(PositionsTotal() > 0) return; // Don't open new positions if we already have one // Get current market information double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT); // Determine trading parameters based on regime double lotSize = 0.0; int stopLoss = 0; int takeProfit = 0; ENUM_ORDER_TYPE orderType = ORDER_TYPE_BUY; switch(regime) { case REGIME_TRENDING_UP: { lotSize = TrendingLotSize; stopLoss = TrendingStopLoss; takeProfit = TrendingTakeProfit; orderType = ORDER_TYPE_BUY; // Buy in uptrend break; } case REGIME_TRENDING_DOWN: { lotSize = TrendingLotSize; stopLoss = TrendingStopLoss; takeProfit = TrendingTakeProfit; orderType = ORDER_TYPE_SELL; // Sell in downtrend break; } case REGIME_RANGING: { // In ranging markets, we can use mean-reversion strategies // For simplicity, we'll use RSI to determine overbought/oversold double rsi[]; ArraySetAsSeries(rsi, true); int rsiCopied = CopyBuffer(iRSI(Symbol(), PERIOD_CURRENT, 14, PRICE_CLOSE), 0, 0, 2, rsi); if(rsiCopied != 2) return; lotSize = RangingLotSize; stopLoss = RangingStopLoss; takeProfit = RangingTakeProfit; if(rsi[0] < 30) // Oversold orderType = ORDER_TYPE_BUY; else if(rsi[0] > 70) // Overbought orderType = ORDER_TYPE_SELL; else return; // No signal break; } case REGIME_VOLATILE: { // In volatile markets, we can use breakout strategies // For simplicity, we'll use Bollinger Bands double upper[], lower[]; ArraySetAsSeries(upper, true); ArraySetAsSeries(lower, true); int bbCopied1 = CopyBuffer(iBands(Symbol(), PERIOD_CURRENT, 20, 2, 0, PRICE_CLOSE), 1, 0, 2, upper); int bbCopied2 = CopyBuffer(iBands(Symbol(), PERIOD_CURRENT, 20, 2, 0, PRICE_CLOSE), 2, 0, 2, lower); if(bbCopied1 != 2 || bbCopied2 != 2) return; lotSize = VolatileLotSize; stopLoss = VolatileStopLoss; takeProfit = VolatileTakeProfit; double close[]; ArraySetAsSeries(close, true); int copied = CopyClose(Symbol(), PERIOD_CURRENT, 0, 2, close); if(copied != 2) return; if(close[1] < upper[1] && close[0] > upper[0]) // Breakout above upper band orderType = ORDER_TYPE_BUY; else if(close[1] > lower[1] && close[0] < lower[0]) // Breakout below lower band orderType = ORDER_TYPE_SELL; else return; // No signal break; } default: return; // No trading in undefined regime } // Calculate stop loss and take profit levels double slLevel = 0.0; double tpLevel = 0.0; if(orderType == ORDER_TYPE_BUY) { slLevel = ask - stopLoss * point; tpLevel = ask + takeProfit * point; } else if(orderType == ORDER_TYPE_SELL) { slLevel = bid + stopLoss * point; tpLevel = bid - takeProfit * point; } // Execute trade MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = Symbol(); request.volume = lotSize; request.type = orderType; request.price = (orderType == ORDER_TYPE_BUY) ? ask : bid; request.sl = slLevel; request.tp = tpLevel; request.deviation = 10; request.magic = 123456; // Magic number for this EA request.comment = "Market Regime: " + Detector.GetRegimeDescription(); request.type_filling = ORDER_FILLING_FOK; bool success = OrderSend(request, result); if(success) { Print("Trade executed successfully: ", result.retcode, " ", result.comment); } else { Print("Trade execution failed: ", result.retcode, " ", result.comment); } }この関数は、包括的なレジームベースの取引戦略を実装します。
- トレンドレジーム:トレンドフォロー戦略を使用し、上昇トレンドで買い、下降トレンドで売ります。
- レンジレジーム:RSI に基づいた平均回帰戦略を使用し、売られ過ぎのときに買い、買われ過ぎのときに売ります。
- 高ボラティリティレジーム:ボリンジャー バンドに基づくブレイクアウト戦略を使用し、ポジションサイズを縮小してリスクを管理します。
異なる市場レジームに対して異なる戦略を使い分けることこそ、このEAの核心的な革新点です。市場環境に合わせて手法を柔軟に適応させることで、多様な相場状況においてより安定したパフォーマンスを目指すことが可能になります。
EAクリーンアップ
OnDeinit関数は、EAが削除されたときに適切なクリーンアップを保証します。
void OnDeinit(const int reason) { // Clean up if(Detector != NULL) { delete Detector; Detector = NULL; } // Clear the comment Comment(""); }
この関数は、メモリリークを防ぐために市場レジーム検出器オブジェクトを削除し、チャート上のコメントを消去します。
レジームベース取引の利点
このEAは、レジームベースの取引が持ついくつかの重要な利点を示しています。- 適応性:現在の市場状況に自動で適応し、各レジームに応じて異なる取引手法を使用します。
- リスク管理:市場のボラティリティに応じてポジションサイズを調整し、特にボラティリティが高いレジームでは小さいロットサイズを使ってリスクを抑えます。
- 戦略の選択:トレンド相場ではトレンドフォロー戦略、レンジ相場では平均回帰戦略など、それぞれの市場レジームに最適な戦略を選択します。
- 透明性:現在の市場レジームとその主要な特徴を明確に表示できるため、トレーダーが状況を把握しやすくなり、判断材料としての価値を提供します。
市場レジーム検出を取引システムに組み込むことで、これらと同様のメリットを得ることができ、より堅牢で柔軟性のある戦略を構築し、幅広い市場状況で安定したパフォーマンスを目指せるようになります。
次のセクションでは、実際の取引環境において市場レジーム検出システムを導入・最適化するための実践的なポイントについて解説します。
実運用における考慮事項と最適化
この最終セクションでは、市場レジーム検出システムを実際の取引環境で導入・最適化する際の実践的なポイントについて解説します。ここで取り上げる内容は、パラメータの最適化、レジーム移行時の対応、既存の取引システムとの統合方法です。
パラメータの最適化
市場レジーム検出システムの有効性は、使用するパラメータの選定によって大きく左右されます。以下に、トレーダーが自分の取引銘柄や時間足に合わせて最適化すべき主要パラメータを示します。
- ルックバック期間:この期間は、レジーム検出に使用される過去のデータ量を決定します。長めの参照期間は、より安定したレジーム判定を提供しますが、最新の市場変化への反応が遅くなる可能性があります。一方、短めの参照期間は、最新の動きに素早く反応できますが、誤検出(ノイズ)によるシグナル増加のリスクがあります。
// Example of testing different lookback periods for(int lookback = 50; lookback <= 200; lookback += 25) { CMarketRegimeDetector detector(lookback, SmoothingPeriod); detector.SetTrendThreshold(TrendThreshold); detector.SetVolatilityThreshold(VolatilityThreshold); // Process historical data and evaluate performance // ... }
通常、50〜200本のバーをルックバック期間として設定すると、ほとんどの銘柄や時間足で良好な結果が得られます。最適な値は、その銘柄における市場レジームの持続期間の傾向によって異なります。 - トレンド閾値 :この閾値は、市場が「トレンド状態」と見なされるために必要なトレンドの強さを決定します。高めの閾値を設定すると、トレンドと判定される場面は少なくなりますが、より確信度の高い(ノイズの少ない)トレンド検出が可能になります。一方、低めの閾値では、より多くのトレンドを検出できますが、その中には強さに欠ける不明瞭なトレンドが含まれる可能性もあります。
// Example of testing different trend thresholds for(double threshold = 0.1; threshold <= 0.5; threshold += 0.05) { CMarketRegimeDetector detector(LookbackPeriod, SmoothingPeriod); detector.SetTrendThreshold(threshold); detector.SetVolatilityThreshold(VolatilityThreshold); // Process historical data and evaluate performance // ... }
一般的に、トレンド閾値は0.1〜0.3の範囲から始めるのが一般的です。最適な値は、対象となる銘柄のトレンドの出やすさや持続性といった特性に依存します。 - ボラティリティ閾値 :この閾値は、市場を「高ボラティリティ状態」と分類するために必要な価格変動の大きさを決定します。高めの閾値を設定すると、高ボラティリティと判定される状況は減少しますが、低めの閾値では、より多くの期間がボラティリティが高いと分類されます。
// Example of testing different volatility thresholds for(double threshold = 1.0; threshold <= 3.0; threshold += 0.25) { CMarketRegimeDetector detector(LookbackPeriod, SmoothingPeriod); detector.SetTrendThreshold(TrendThreshold); detector.SetVolatilityThreshold(threshold); // Process historical data and evaluate performance // ... }
一般的に、ボラティリティ閾値は 1.5〜2.5 の範囲から設定することが多いです。最適な値は、対象となる銘柄の典型的なボラティリティ特性によって異なります。
レジーム移行の対応:
メモ:以下の5つのコードブロックは、アイデアの実装例ではなく、あくまで実装のイメージを示した擬似コードです。
レジームの移行は非常に重要な局面であり、特別な注意が必要です。移行期における取引戦略の急激な切り替えは、不適切な注文執行やスリッページの増加を招く可能性があります。そこで、移行をより効果的に処理するための戦略を以下に示します。
- 移行の平滑化:平滑化期間パラメータは、レジーム判定のノイズを抑えるために用いられます。具体的には、あるレジームが一定期間以上(最低バー数)継続した場合にのみ、そのレジームとして認識する仕組みです。
// Example of implementing smoothed regime transitions ENUM_MARKET_REGIME SmoothRegimeTransition(ENUM_MARKET_REGIME newRegime) { static ENUM_MARKET_REGIME regimeHistory[20]; static int historyCount = 0; // Add new regime to history for(int i = 19; i > 0; i--) regimeHistory[i] = regimeHistory[i-1]; regimeHistory[0] = newRegime; if(historyCount < 20) historyCount++; // Count occurrences of each regime int regimeCounts[5] = {0}; for(int i = 0; i < historyCount; i++) regimeCounts[regimeHistory[i]]++; // Find most common regime int maxCount = 0; ENUM_MARKET_REGIME dominantRegime = REGIME_UNDEFINED; for(int i = 0; i < 5; i++) { if(regimeCounts[i] > maxCount) { maxCount = regimeCounts[i]; dominantRegime = (ENUM_MARKET_REGIME)i; } } return dominantRegime; }
この関数は、直近のレジーム判定の履歴を保持し、最も頻繁に出現するレジームを返すことで、一時的な変動の影響を軽減します。 - 段階的なポジションサイジング:レジーム移行の際は、ポジションサイズを急激に変更するのではなく、多くの場合、段階的に調整していくことが賢明です。
// Example of gradual position sizing during transitions double CalculateTransitionLotSize(ENUM_MARKET_REGIME previousRegime, ENUM_MARKET_REGIME currentRegime, int transitionBars, int maxTransitionBars) { // Base lot sizes for each regime double regimeLotSizes[5] = { TrendingLotSize, // REGIME_TRENDING_UP TrendingLotSize, // REGIME_TRENDING_DOWN RangingLotSize, // REGIME_RANGING VolatileLotSize, // REGIME_VOLATILE 0.0 // REGIME_UNDEFINED }; // If not in transition, use current regime's lot size if(previousRegime == currentRegime || transitionBars >= maxTransitionBars) return regimeLotSizes[currentRegime]; // Calculate weighted average during transition double previousWeight = (double)(maxTransitionBars - transitionBars) / maxTransitionBars; double currentWeight = (double)transitionBars / maxTransitionBars; return regimeLotSizes[previousRegime] * previousWeight + regimeLotSizes[currentRegime] * currentWeight; }
この関数は、レジーム移行期におけるポジションサイズの加重平均を計算し、市場変化に対してより滑らかな調整を実現します。
既存の取引システムとの統合
市場レジーム検出システムは、既存の取引システムに組み込むことで、そのパフォーマンスを向上させることができます。効果的な統合のための戦略を以下に示します。
- 戦略の選択:検出されたレジームを活用して、最も適した取引戦略を選択します。
// Example of strategy selection based on market regime bool ExecuteTradeSignal(ENUM_MARKET_REGIME regime, int strategySignal) { // Strategy signal: 1 = buy, -1 = sell, 0 = no signal switch(regime) { case REGIME_TRENDING_UP: case REGIME_TRENDING_DOWN: // In trending regimes, only take signals in the direction of the trend if((regime == REGIME_TRENDING_UP && strategySignal == 1) || (regime == REGIME_TRENDING_DOWN && strategySignal == -1)) return true; break; case REGIME_RANGING: // In ranging regimes, take all signals if(strategySignal != 0) return true; break; case REGIME_VOLATILE: // In volatile regimes, be more selective // Only take strong signals (implementation depends on strategy) if(IsStrongSignal(strategySignal)) return true; break; default: // In undefined regimes, don't trade break; } return false; }
この関数は、現在の市場レジームに基づいて取引シグナルをフィルタリングし、レジームの特性に合致する取引のみを実行します。 - パラメータの適応:検出されたレジームに応じて、戦略のパラメータを動的に調整します。
// Example of parameter adaptation based on market regime void AdaptStrategyParameters(ENUM_MARKET_REGIME regime) { switch(regime) { case REGIME_TRENDING_UP: case REGIME_TRENDING_DOWN: // In trending regimes, use longer moving averages FastPeriod = 20; SlowPeriod = 50; // Use wider stop losses StopLoss = TrendingStopLoss; // Use larger take profits TakeProfit = TrendingTakeProfit; break; case REGIME_RANGING: // In ranging regimes, use shorter moving averages FastPeriod = 10; SlowPeriod = 25; // Use tighter stop losses StopLoss = RangingStopLoss; // Use smaller take profits TakeProfit = RangingTakeProfit; break; case REGIME_VOLATILE: // In volatile regimes, use very short moving averages FastPeriod = 5; SlowPeriod = 15; // Use wider stop losses StopLoss = VolatileStopLoss; // Use larger take profits TakeProfit = VolatileTakeProfit; break; default: // In undefined regimes, use default parameters FastPeriod = 14; SlowPeriod = 28; StopLoss = 100; TakeProfit = 200; break; } }
この関数は、現在の市場レジームに基づいて戦略のパラメータを調整し、特定の市場環境に最適化します。
パフォーマンス監視
市場レジーム検出システムが市場レジームを正確に分類しているかどうかを確認するために、定期的にパフォーマンスを監視します。
// Example of performance monitoring for regime detection void MonitorRegimeDetectionPerformance() { static int regimeTransitions = 0; static int correctPredictions = 0; static ENUM_MARKET_REGIME lastRegime = REGIME_UNDEFINED; // Get current regime ENUM_MARKET_REGIME currentRegime = Detector.GetCurrentRegime(); // If regime has changed, evaluate the previous regime's prediction if(currentRegime != lastRegime && lastRegime != REGIME_UNDEFINED) { regimeTransitions++; // Evaluate if the previous regime's prediction was correct // Implementation depends on your specific evaluation criteria if(EvaluateRegimePrediction(lastRegime)) correctPredictions++; // Log performance metrics double accuracy = (double)correctPredictions / regimeTransitions * 100.0; Print("Regime Detection Accuracy: ", DoubleToString(accuracy, 2), "% (", correctPredictions, "/", regimeTransitions, ")"); } lastRegime = currentRegime; }
この関数はレジーム移行を追跡し、レジーム予測の精度を評価します。これにより、システムの最適化に役立つ貴重なフィードバックを提供します。
インジケーター:MultiTimeframeRegimes(多時間枠レジーム)
完全なコード
#property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 // Include the Market Regime Detector #include <MarketRegimeEnum.mqh> #include <MarketRegimeDetector.mqh> // Input parameters input int LookbackPeriod = 100; // Lookback period for calculations input double TrendThreshold = 0.2; // Threshold for trend detection (0.1-0.5) input double VolatilityThreshold = 1.5; // Threshold for volatility detection (1.0-3.0) // Timeframes to analyze input bool UseM1 = false; // Use 1-minute timeframe input bool UseM5 = false; // Use 5-minute timeframe input bool UseM15 = true; // Use 15-minute timeframe input bool UseM30 = true; // Use 30-minute timeframe input bool UseH1 = true; // Use 1-hour timeframe input bool UseH4 = true; // Use 4-hour timeframe input bool UseD1 = true; // Use Daily timeframe input bool UseW1 = false; // Use Weekly timeframe input bool UseMN1 = false; // Use Monthly timeframe // Global variables CMarketRegimeDetector *Detectors[]; ENUM_TIMEFRAMES Timeframes[]; int TimeframeCount = 0; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize timeframes array InitializeTimeframes(); // Create detectors for each timeframe ArrayResize(Detectors, TimeframeCount); for(int i = 0; i < TimeframeCount; i++) { Detectors[i] = new CMarketRegimeDetector(LookbackPeriod); if(Detectors[i] == NULL) { Print("Failed to create Market Regime Detector for timeframe ", EnumToString(Timeframes[i])); return INIT_FAILED; } // Configure the detector Detectors[i].SetTrendThreshold(TrendThreshold); Detectors[i].SetVolatilityThreshold(VolatilityThreshold); Detectors[i].Initialize(); } // Set indicator name IndicatorSetString(INDICATOR_SHORTNAME, "Multi-Timeframe Regime Analysis"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Initialize timeframes array based on user inputs | //+------------------------------------------------------------------+ void InitializeTimeframes() { // Count selected timeframes TimeframeCount = 0; if(UseM1) TimeframeCount++; if(UseM5) TimeframeCount++; if(UseM15) TimeframeCount++; if(UseM30) TimeframeCount++; if(UseH1) TimeframeCount++; if(UseH4) TimeframeCount++; if(UseD1) TimeframeCount++; if(UseW1) TimeframeCount++; if(UseMN1) TimeframeCount++; // Resize and fill timeframes array ArrayResize(Timeframes, TimeframeCount); int index = 0; if(UseM1) Timeframes[index++] = PERIOD_M1; if(UseM5) Timeframes[index++] = PERIOD_M5; if(UseM15) Timeframes[index++] = PERIOD_M15; if(UseM30) Timeframes[index++] = PERIOD_M30; if(UseH1) Timeframes[index++] = PERIOD_H1; if(UseH4) Timeframes[index++] = PERIOD_H4; if(UseD1) Timeframes[index++] = PERIOD_D1; if(UseW1) Timeframes[index++] = PERIOD_W1; if(UseMN1) Timeframes[index++] = PERIOD_MN1; } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { // Check if there's enough data if(rates_total < LookbackPeriod) return 0; // Process data for each timeframe string commentText = "Multi-Timeframe Regime Analysis\n\n"; for(int i = 0; i < TimeframeCount; i++) { // Get price data for this timeframe double tfClose[]; ArraySetAsSeries(tfClose, true); int copied = CopyClose(Symbol(), Timeframes[i], 0, LookbackPeriod, tfClose); if(copied != LookbackPeriod) { Print("Failed to copy price data for timeframe ", EnumToString(Timeframes[i])); continue; } // Process data with the detector if(!Detectors[i].ProcessData(tfClose, LookbackPeriod)) { Print("Failed to process data for timeframe ", EnumToString(Timeframes[i])); continue; } // Add timeframe information to comment commentText += TimeframeToString(Timeframes[i]) + ": "; commentText += Detectors[i].GetRegimeDescription(); commentText += " (Trend: " + DoubleToString(Detectors[i].GetTrendStrength(), 2); commentText += ", Vol: " + DoubleToString(Detectors[i].GetVolatility(), 2) + ")"; commentText += "\n"; } // Display the multi-timeframe analysis Comment(commentText); // Return the number of calculated bars return rates_total; } //+------------------------------------------------------------------+ //| Convert timeframe enum to readable string | //+------------------------------------------------------------------+ string TimeframeToString(ENUM_TIMEFRAMES timeframe) { switch(timeframe) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return "Unknown"; } } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up for(int i = 0; i < TimeframeCount; i++) { if(Detectors[i] != NULL) { delete Detectors[i]; Detectors[i] = NULL; } } // Clear the comment Comment(""); }
このインジケーターはチャート上にラインを描画しません。代わりに、ユーザーが選択した複数の時間枠にわたって市場レジーム(上昇・下降トレンド、レンジ、高ボラティリティ)を分析し、その結果をチャート左上にテキストで表示します。解析には、第1回に作成したCMarketRegimeDetectorクラスを使用します。
コードの説明
-
プロパティとインクルード
- #property indicator_chart_window:インジケーターをメインチャートウィンドウで実行します。
- #property indicator_buffers 0、#property indicator_plots 0:このインジケーター自体がデータバッファやプロットライン/ヒストグラムを使用しないことを指定します。
- #include <MarketRegimeEnum.mqh>、#include <MarketRegimeDetector.mqh>:必要な定義と、以前の作業からのコア検出器クラスが含まれています。
-
入力パラメータ
- LookbackPeriod、TrendThreshold、VolatilityThreshold:これらは、分析されたすべての時間枠に均一に適用されます。
- UseM1~UseMN1:それぞれの標準時間枠(1分足~月足)を解析対象に含めるかどうかを真偽値で指定します。
-
グローバル変数
- Detectors[]:選択された各時間枠に対応する、個別のCMarketRegimeDetectorインスタンスを保持するための配列。各時間枠ごとに専用の検出器オブジェクトがこの配列内に格納されます。
- Timeframes[]:ユーザーが入力を通じて選択した時間枠に対応するMQL5 時間枠識別子(PERIOD_H1、PERIOD_D1など)を格納する配列
- TimeframeCount:実際に選択された時間枠の数を追跡するための整数
-
OnInit(初期化関数)
- インジケーターの起動時に一度だけ実行されます。
- InitializeTimeframesを呼び出して、ユーザーが選択した時間枠を特定し、Timeframes配列を構成します。
- Detectors配列のサイズをTimeframeCountに合わせて変更します。
- TimeframeCountの回数だけループを実行します。
- 選択された各時間枠に対して、共通のLookbackPeriodを使って新しいCMarketRegimeDetectorオブジェクトを生成します。
- その検出器インスタンスに、共通のTrendThresholdおよびVolatilityThresholdを設定します。
- 重要な点として、各検出器インスタンスは、それぞれに割り当てられた時間枠のデータに基づいて独自の内部状態を保持します。
- インジケータの表示名を設定します。
-
InitializeTimeframes(ヘルパー関数)
- Use...で始まる入力のうち、trueに設定されている数をカウントします。
- それに応じて、グローバルなTimeframes配列のサイズを変更します。
- 選択された時間枠に対応する PERIOD_...定数をTimeframes配列に格納します。
-
OnCalculate(メイン計算関数)
- 新しいティックまたはバーが発生するたびに実行されます。
- チャートの現在の時間枠において、LookbackPeriodの要件を満たすだけの履歴バー数(rates_total)があるかどうかを確認します。
- 空の文字列commentTextを初期化します。
- 選択された各時間枠(インデックスiで追跡)に対してループ処理をおこないます。
- CopyCloseを使用して、Symbol()とTimeframes[i]に対する直近のLookbackPeriod分の終値データを取得します(例:インジケーターがM15チャート上にある場合でも、H4の終値を取得)。
- データの取得が成功した場合、対応する検出器オブジェクト(Detectors[i])のProcessDataメソッドを呼び出し、取得した価格データ(tfClose)を渡します。
- Detectors[i]から取得した結果(時間枠名、レジームの説明、トレンドの強さ、ボラティリティ)をcommentTextに追記します。
- 最後に、Comment(commentText)を使って、すべての選択時間枠に関する分析結果をチャート左上に表示します。
-
TimeframeToString(ヘルパー関数)
- MQL5 PERIOD_...定数を「M1」、「H4」、「D1」などの読み取り可能な文字列に変換するシンプルなユーティリティ
- MQL5 PERIOD_...定数を「M1」、「H4」、「D1」などの読み取り可能な文字列に変換するシンプルなユーティリティ
-
OnDeinit(初期化関数)
- インジケーターがチャートから削除される、または端末が閉じられるときに一度だけ実行されます。
- Detectors配列をループし、OnInitで作成された各CMarketRegimeDetectorオブジェクトのメモリをdeleteを使って解放し、メモリリークを防ぎます。
- チャートの隅に表示されているテキストコメントを消去します。
本質的に、このコードは複数の時間枠に対して独立したレジーム検出器を効率的に設定し、それぞれに必要なデータを取得し、分析を実行し、統合されたマルチ時間枠 レジームの概要をユーザーのチャートに直接表示します。
上記の多時間枠インジケーターを使用すると、取引をおこなう前に複数の時間枠を分析する別のEAを作成できます。ただし、簡潔さを保つために、新たなEAは作成せず、まずは現在のEAをテストすることにします。
適応型エキスパートアドバイザーの評価:バックテスト結果
MarketRegimeEAを構築した後は、そのパフォーマンスをバックテストによって評価するのが論理的な次のステップです。これにより、レジーム適応ロジックが過去のデータにおいてどのように機能するかを確認し、そのパラメータ設定の影響を評価することができます。
初期テスト構成
このデモンストレーションでは、テスト対象としてゴールド(XAUUSD)を選択し、M1時間枠のデータを使用しました。EAに適用された初期パラメータは、以下の通り特に根拠なく設定されています。
- LookbackPeriod:100
- SmoothingPeriod:10
- TrendThreshold:0.2
- VolatilityThreshold:1.5
- Lot Sizes:Trending=0.1, Ranging=0.1, Volatile=0.1
- SL: Trending=1000, Ranging=1600, Volatile=2000
- TP:Trending=1400, Ranging=1000, Volatile=1800
これらのデフォルトパラメータを使用してEAを実行したところ、次の結果が得られました。
資産曲線およびパフォーマンス指標から見て取れるように、これらの設定による初回バックテストでは最適とは言えない結果が得られました。一時的には戦略が利益を上げ、最大で約20%の資産成長を記録した場面もありましたが、全体的には一貫した収益性に欠け、大きなドローダウンも見られました。この結果は、取引対象の銘柄や時間枠に応じたパラメーターの調整が極めて重要であることを示しています。今回使用したパラメータはあくまで出発点に過ぎず、最適な構成を表すものではありません。
より良いパフォーマンスの可能性を探るために、MetaTrader 5のストラテジーテスターに搭載されているパラメータ最適化機能を使用し、遺伝的アルゴリズムによる探索をおこないました。この最適化の目的は、ゴールドの過去の価格動向とレジーム検出および取引ロジックがより適切に一致するようなパラメータセット(指定された範囲内)を特定することです。最適化の対象となったパラメータには、レジーム別のストップロスおよびテイクプロフィット値が含まれ、それ以外のパラメータはデフォルトのままとしました。
最適化プロセスの結果、ストラテジーテスターは以下のパラメータセットが、過去のパフォーマンスを大幅に改善することを示しました。
これらの最適化されたパラメータを使用して再度バックテストを実行したところ、パフォーマンスプロファイルは顕著に異なる結果となりました。
最適化されたパラメータを用いたバックテストでは、明確な改善が見られ、初回のテストと比較して、純利益がプラスとなり、より安定した資産曲線が示されました。
ただし、これらの結果を解釈する際には注意が必要です。パラメータ最適化、特に遺伝的アルゴリズムを用いて過去のデータ上でおこない、その同じ期間でバックテストを実施する場合、過学習のリスクが本質的に存在します。これは、最適化されたパラメータが特定の過去データに非常によく適合していても、将来の未知の市場データには適用できない可能性があることを意味します。
そのため、最適化された結果は、パラメータを調整することで適応型戦略フレームワークには多くの可能性があることを示してはいるものの、将来の利益を保証する決定的な証拠とはなりません。この作業の主な目的は、次の点を示すことにあります。
- レジーム適応型EAの機能性
- パラメータがパフォーマンスに与える大きな影響
- 戦略を市場レジームに適応させるという基本的なコンセプトが、適切に構成すれば過去データにおいて有効であること
また、最適化されていない状態でも一時的な利益が確認されたという事実は、EAのコアロジックに根本的な欠陥がないことを示唆しています。しかし、これを堅牢で実運用可能な戦略へと発展させるためには、単なる最適化を超えたさらなるステップが必要です。これには以下が含まれます。
- パラメータの堅牢性を検証するための厳密なアウト・オブ・サンプルテスト
- より洗練されたリスク管理の導入
- 市場レジームの変化に柔軟に対応するための「スムーズな移行」や「段階的なポジションサイズ調整」など、先述したテクニックの実装
結論
この2部構成のシリーズを通じて、アルゴリズム取引における本質的な課題、つまり変化する市場環境が静的な戦略に及ぼす悪影響を明らかにし、それに対応するための完全な適応型ソリューションの設計と実装に至るまでの道のりを辿ってきました。第1部では、市場の状態を理解するためのエンジンとして、統計的根拠に基づいた市場レジーム検出システムを構築し、その出力を視覚的に表示する方法について紹介しました。
そして第2部では、「検出」から「行動」への転換に焦点を当てました。CMarketRegimeDetectorの機能を活かし、検出された市場レジームに応じてトレンドフォロー、平均回帰、ブレイクアウト戦略を自動で切り替えるEA「MarketRegimeEA」を構築することで、理論を実践に落とし込みました。さらに、エントリーのロジックだけでなく、ロットサイズやストップレベルなどのリスクパラメータをレジームに応じて動的に変更することで、より堅牢な取引手法の構築が可能であることを示しました。
加えて、このようなシステムを実際の取引環境に展開する際に必要となる実務的な検討事項についても触れました。具体的には、ルックバック期間やトレンド・ボラティリティの閾値など、パラメータの最適化が持つ重要性を取り上げ、さらに、レジーム移行時におけるスムーズな戦略の切り替え方や、過剰反応を抑えるための工夫についても解説しました。最後に、この市場レジーム検出フレームワークを既存の取引システムに統合し、インテリジェントなフィルターや動的パラメータ調整機能として活用する可能性も提示しました。
本プロジェクトの目標は、市場が本質的に非定常であるという事実を正面から捉え、それに適応できる戦略を構築することにありました。第1部で開発した検出能力と、第2部で紹介した適応型の実行フレームワークおよび運用面での工夫を組み合わせることで、市場のダイナミックな変化に強く対応できる戦略構築の設計図が完成しました。静的なアプローチからレジーム適応型戦略への進化は、ここに完了しました。これにより、複雑な市場環境をより高い知性と柔軟性をもって航行するための力を、あなたは手にしたのです。
ファイルの概要
この記事で作成されたすべてのファイルの概要は次のとおりです。ファイル名 | 説明 |
---|---|
MarketRegimeEnum.mqh | システム全体で使用される市場レジームの列挙型の定義 |
CStatistics.mqh | 市場レジーム検出のための統計計算クラス |
MarketRegimeDetector.mqh | 市場レジーム検出器のコア実装 |
MarketRegimeEA.mq5 | さまざまな市場環境に適応するEA |
MultiTimeframeRegimes.mq5 | 複数の時間枠にわたるレジームの分析例 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17781
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。






- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索