
Building a Custom Market Regime Detection System in MQL5 (Part 2): Expert Advisor
- Introduction
- Building an Adaptive Expert Advisor
- Practical Considerations and Optimization
- Indicator: Multi Timeframe Regimes
- Evaluating the Adaptive Expert Advisor: Backtesting Results
- Conclusion
Introduction
In Part 1 of this series, we laid the essential groundwork for tackling the challenge of ever-changing market dynamics. We established a robust statistical foundation, constructed a CMarketRegimeDetector class capable of objectively classifying market behavior, and developed a custom indicator ( MarketRegimeIndicator ) to visualize these regimes directly on our charts. We moved from recognizing the problem – the performance degradation of static strategies in dynamic markets – to developing a system that can identify the prevailing market state: Trending (Up/Down), Ranging, or Volatile (Link Part 1 for Reference).
However, merely identifying the current regime is only half the battle. The true power lies in adapting our trading approach based on this knowledge. A detector, no matter how sophisticated, remains an analytical tool until its insights are translated into actionable trading decisions. What if our trading system could automatically switch gears, deploying trend-following logic when trends are strong, shifting to mean-reversion tactics in sideways markets, and adjusting risk parameters when volatility spikes?
This is precisely the gap we will bridge in Part 2. Building upon the foundation laid previously, we will now focus on practical application and refinement. In this article, we will delve into:
- Building an Adaptive Expert Advisor (EA): We'll construct a complete MarketRegimeEA that integrates our CMarketRegimeDetector to automatically select and execute different trading strategies (trend-following, mean-reversion, breakout) tailored to the detected regime.
- Implementing Regime-Specific Risk Management: The EA will demonstrate how to adjust parameters like lot size, stop loss, and take profit based on the current market state.
- Practical Considerations: We'll explore crucial aspects of real-world implementation, including parameter optimization for different instruments and timeframes.
- Handling Regime Transitions: Strategies to manage the critical moments when the market shifts from one regime to another, ensuring smoother strategy adjustments.
- Integration Techniques: Discussing how to incorporate the Market Regime Detection System into your existing trading frameworks to enhance their adaptability.
By the end of this second part, you will not only understand how to detect market regimes but also how to build an automated trading system that intelligently adapts its behavior, aiming for more consistent performance across the diverse conditions financial markets present. Let's transform our detection system into a truly adaptive trading solution.
Building an Adaptive Expert Advisor
In this section, we'll create an Expert Advisor that uses our Market Regime Detector to adapt its trading strategy based on the current market conditions. This demonstrates how regime detection can be integrated into a complete trading system.
The MarketRegimeEA
Our Expert Advisor will use different trading approaches for different market regimes:- In trending markets, it will use trend-following strategies
- In ranging markets, it will use mean-reversion strategies
- In volatile markets, it will use breakout strategies with reduced position sizes
Here's the implementation:
// 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;
The EA includes parameters for configuring both the regime detection and the trading strategy. Note how we use different lot sizes, stop losses, and take profits for different market regimes. This allows the EA to adapt its risk management approach to the current market conditions.
EA Initialization
The OnInit() function creates and configures the Market Regime Detector:
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; }
This function creates and configures the Market Regime Detector with the user-specified parameters. It also initializes the bar counting variables that we'll use to track new bars.
EA Tick Processing
The OnTick() function processes new price data and executes the regime-based trading strategy:
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); }This function:
- Checks for a new bar to avoid redundant calculations
- Waits until enough bars have accumulated for reliable regime detection
- Retrieves the latest price data
- Processes the data with the Market Regime Detector
- Gets the current market regime
- Displays the regime information
- Executes the regime-based trading strategy
The use of ArraySetAsSeries(close, true) is important as it ensures that the price array is indexed in reverse order, with the most recent price at index 0. This is the standard indexing convention in MQL5 for working with time series data.
Regime-Based Trading Strategy
The ExecuteRegimeBasedStrategy() function implements different trading approaches for different market regimes:
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); } }This function implements a comprehensive regime-based trading strategy:
- Trending Regimes: Uses trend-following strategies, buying in uptrends and selling in downtrends.
- Ranging Regimes: Uses mean-reversion strategies based on RSI, buying when oversold and selling when overbought.
- Volatile Regimes: Uses breakout strategies based on Bollinger Bands, with reduced position sizes to manage risk.
The use of different strategies for different regimes is the key innovation of this EA. By adapting its approach to the current market conditions, the EA can achieve more consistent performance across a wide range of market environments.
EA Cleanup
The OnDeinit() function ensures proper cleanup when the EA is removed:
void OnDeinit(const int reason) { // Clean up if(Detector != NULL) { delete Detector; Detector = NULL; } // Clear the comment Comment(""); }
This function deletes the Market Regime Detector object to prevent memory leaks and clears any comments from the chart.
Advantages of Regime-Based Trading
This Expert Advisor demonstrates several key advantages of regime-based trading:- Adaptability: The EA automatically adapts its trading strategy to the current market conditions, using different approaches for different regimes.
- Risk Management: The EA adjusts position sizes based on market volatility, using smaller positions in more volatile regimes to manage risk.
- Strategy Selection: The EA selects the most appropriate strategy for each regime, using trend-following in trending markets and mean-reversion in ranging markets.
- Transparency: The EA clearly displays the current market regime and its key characteristics, providing traders with valuable context for their trading decisions.
By incorporating market regime detection into your trading systems, you can achieve similar benefits, creating more robust and adaptive strategies that perform well across a wide range of market conditions.
In the next section, we'll discuss practical considerations for implementing and optimizing the Market Regime Detection System in real trading environments.
Practical Considerations and Optimization
In this final section, we'll discuss practical considerations for implementing and optimizing the Market Regime Detection System in real trading environments. We'll cover parameter optimization, regime transition handling, and integration with existing trading systems.
Parameter Optimization
The effectiveness of our Market Regime Detection System depends significantly on the choice of parameters. Here are key parameters that traders should optimize for their specific trading instruments and timeframes:
- Lookback Period:The lookback period determines how much historical data is used for regime detection. Longer periods provide more stable regime classifications but may be less responsive to recent market changes. Shorter periods are more responsive but may generate more false signals.
// 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 // ... }
Typically, lookback periods between 50 and 200 bars work well for most instruments and timeframes. The optimal value depends on the typical duration of market regimes for the specific instrument being traded. - Trend Threshold: The trend threshold determines how strong a trend must be to classify the market as trending. Higher thresholds result in fewer trending classifications but with higher confidence. Lower thresholds identify more trends but may include weaker ones.
// 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 // ... }
Trend thresholds between 0.1 and 0.3 are common starting points. The optimal value depends on the instrument's typical trending behavior. - Volatility Threshold: The volatility threshold determines how much volatility is required to classify the market as volatile. Higher thresholds result in fewer volatile classifications, while lower thresholds identify more volatile periods.
// 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 // ... }
Volatility thresholds between 1.5 and 2.5 are common starting points. The optimal value depends on the instrument's typical volatility characteristics.
Handling Regime Transitions
Note: The following 5 code blocks are not the implementation of the idea rather just the ideas and pseudo code of how teh implementation might look like.
Regime transitions are critical moments that require special attention. Abrupt changes in trading strategy during transitions can lead to poor execution and increased slippage. Here are strategies for handling transitions more effectively
- Smoothing Transitions: The smoothing period parameter helps reduce regime classification noise by requiring a regime to persist for a minimum number of bars before being recognized:
// 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; }
This function maintains a history of recent regime classifications and returns the most common regime, reducing the impact of temporary fluctuations. - Gradual Position Sizing: During regime transitions, it's often prudent to adjust position sizes gradually rather than making abrupt changes:
// 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; }
This function calculates a weighted average of position sizes during regime transitions, providing a smoother adjustment as the market changes.
Integration with Existing Trading Systems
The Market Regime Detection System can be integrated with existing trading systems to enhance their performance. Here are strategies for effective integration:
- Strategy Selection: Use the detected regime to select the most appropriate trading strategy:
// 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; }
This function filters trading signals based on the current market regime, only executing trades that align with the regime's characteristics. - Parameter Adaptation: Adapt strategy parameters based on the detected regime:
// 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; } }
This function adjusts strategy parameters based on the current market regime, optimizing the strategy for the specific market conditions.
Performance Monitoring
Regularly monitor the performance of your Market Regime Detection System to ensure it's accurately classifying market regimes:
// 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; }
This function tracks regime transitions and evaluates the accuracy of regime predictions, providing valuable feedback for system optimization.
Indicator: Multi Timeframe Regimes
Full Code:
#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(""); }
This indicator doesn't plot lines on the chart. Instead, it analyzes market regimes (Trending Up/Down, Ranging, Volatile) across multiple user-selected timeframes using the CMarketRegimeDetector class (developed in Part 1) and displays the results as text in the top-left corner of the chart.
Code Explanation:
-
Properties & Includes:
- #property indicator_chart_window : Makes the indicator run in the main chart window.
- #property indicator_buffers 0 , #property indicator_plots 0 : Specifies that this indicator doesn't use any data buffers or plot lines/histograms itself.
- #include <MarketRegimeEnum.mqh> , #include <MarketRegimeDetector.mqh> : Includes the necessary definitions and the core detector class from our previous work.
-
Input Parameters:
- LookbackPeriod , TrendThreshold , VolatilityThreshold : These are the core settings for the regime detection logic, applied uniformly to all analyzed timeframes.
- UseM1 to UseMN1 : A series of boolean inputs allowing the user to toggle ( true / false ) which standard timeframes (1-minute to Monthly) should be included in the analysis.
-
Global Variables:
- Detectors[] : An array designed to hold separate instances of the CMarketRegimeDetector . Each selected timeframe will have its own dedicated detector object stored here.
- Timeframes[] : An array to store the MQL5 timeframe identifiers (like PERIOD_H1 , PERIOD_D1 ) corresponding to the timeframes chosen by the user via the inputs.
- TimeframeCount : An integer to keep track of how many timeframes were actually selected.
-
OnInit() (Initialization Function):
- Runs once when the indicator starts.
- Calls InitializeTimeframes() to figure out which timeframes the user selected and populate the Timeframes array.
- Resizes the Detectors array to match the TimeframeCount .
- Loops TimeframeCount times:
- For each selected timeframe, it creates a new CMarketRegimeDetector object using the shared LookbackPeriod .
- It configures this specific detector instance with the shared TrendThreshold and VolatilityThreshold .
- Crucially, each detector instance maintains its own internal state based on the data from its assigned timeframe.
- Sets the indicator's display name.
-
InitializeTimeframes() (Helper Function):
- Counts how many Use... inputs are set to true .
- Resizes the global Timeframes array accordingly.
- Populates the Timeframes array with the actual PERIOD_... constants for the selected timeframes.
-
OnCalculate() (Main Calculation Function):
- Runs on every new tick or bar.
- Checks if enough historical bars ( rates_total ) are available on the chart's current timeframe to meet the LookbackPeriod requirement.
- Initializes an empty string commentText .
- Loops through each selected timeframe (tracked by i ):
- Uses CopyClose() to fetch the last LookbackPeriod close prices specifically for the Symbol() and Timeframes[i] (e.g., fetches H4 close prices even if the indicator is on an M15 chart).
- If data fetching is successful, it calls the ProcessData() method of the corresponding detector object ( Detectors[i] ) with the fetched price data ( tfClose ).
- Appends the results (Timeframe name, Regime Description, Trend Strength, Volatility) from Detectors[i] to the commentText string.
- Finally, uses Comment(commentText) to display the aggregated analysis for all selected timeframes in the chart's corner.
-
TimeframeToString() (Helper Function):
- A simple utility to convert MQL5 PERIOD_... constants into readable strings like "M1", "H4", "D1".
- A simple utility to convert MQL5 PERIOD_... constants into readable strings like "M1", "H4", "D1".
-
OnDeinit() (Deinitialization Function):
- Runs once when the indicator is removed from the chart or the terminal closes.
- Loops through the Detectors array and uses delete to free the memory allocated for each CMarketRegimeDetector object created in OnInit() , preventing memory leaks.
- Clears the text comment from the chart corner.
In essence, this code efficiently sets up independent regime detectors for multiple timeframes, fetches the necessary data for each, performs the analysis, and presents a consolidated multi-timeframe regime overview directly on the user's chart.
Using this above multi-time frame indicator, another EA can be created that analyses multiple time frame before placing the trade. Although for the sake of brevity, we will not create another Expert Advisor rather we should test this one first.
Evaluating the Adaptive Expert Advisor: Backtesting Results
Having constructed the MarketRegimeEA , the next logical step is to evaluate its performance through backtesting. This allows us to observe how the regime-adaptive logic functions on historical data and assess the impact of its parameter settings.
Initial Test Configuration
For this demonstration, we selected Gold (XAUUSD) as the testing instrument, utilizing data on the M1 timeframe. The initial parameters applied to the EA were arbitrarily chosen as:
- 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
Running the EA with these default parameters yielded the following results:
As we observe from the equity curve and performance metrics, the initial backtest with these settings produced suboptimal results. While there were periods where the strategy generated profit (at one point reaching approximately 20% equity growth), the overall performance indicates a lack of consistent profitability and significant drawdowns. This outcome underscores the importance of parameter tuning for the specific instrument and timeframe being traded. These parameters serve as a starting point, but does not represent the optimal configuration.
To explore the potential for improved performance, we employed the parameter optimization capabilities within the MetaTrader 5 Strategy Tester, utilizing its genetic algorithm. The goal was to identify a set of parameters (within the tested range) that better align the regime detection and trading logic with the historical price behavior of Gold during the backtest period. The parameters targeted for optimization included the regime-specific Stop Loss and Take Profit values with other params as default.
Following the optimization process, the Strategy Tester identified the following parameter set as yielding significantly improved historical performance:
Executing the backtest again using these optimized parameters resulted in a notably different performance profile:
The backtest utilizing the optimized parameters demonstrates a marked improvement, showing a positive net profit and a more favorable equity curve compared to the initial run.
However, It is crucial to interpret these results with caution. Parameter optimization, especially using genetic algorithms on historical data and running the backtest on the same period, inherently introduces a risk of overfitting. This means the parameters may be exceptionally well-suited to the specific historical data used but may not generalize well to future, unseen market data.
Therefore, while the optimized results showcase the potential of the adaptive strategy framework when parameters are tuned, they should not be taken as a definitive proof of future profitability. The primary purpose of this exercise is to demonstrate:
- The functionality of the regime-adaptive EA.
- The significant impact parameters have on performance.
- That the underlying concept (adapting strategy to regime) can yield positive results on historical data when appropriately configured.
The fact that even the unoptimized version showed periods of profitability suggests the core logic isn't fundamentally flawed. However, developing this into a robust, production-ready strategy would require further steps beyond simple optimization, including:
- Rigorous out-of-sample testing to validate parameter robustness.
- Incorporating more sophisticated risk management.
- Potentially implementing techniques discussed earlier, such as Smoothing Transitions and Gradual Position Sizing , to handle regime changes more effectively.
Conclusion
Over the course of this two-part series, we have journeyed from identifying a fundamental challenge in algorithmic trading – the detrimental effect of changing market conditions on static strategies – to architecting and implementing a complete, adaptive solution. In Part 1, we built the engine for understanding the market's state by developing a statistically grounded Market Regime Detection System and visualizing its output.
In this second and concluding part, we took the crucial step from detection to action. We demonstrated how to harness the power of our CMarketRegimeDetector by building the MarketRegimeEA, an Expert Advisor capable of automatically switching between trend-following, mean-reversion, and breakout strategies based on the identified market regime. We saw how adapting not just the entry logic but also risk parameters like lot size and stop levels can create a more resilient trading approach.
Furthermore, we addressed the practical realities of deploying such a system. We explored the importance of parameter optimization (Lookback Period, Trend Threshold, Volatility Threshold) to tune the detector to specific market characteristics and discussed techniques for handling regime transitions smoothly, minimizing potential whipsaws or abrupt strategy shifts. Finally, we touched upon how this regime detection framework can be integrated into existing trading systems, acting as an intelligent filter or parameter adjuster.
The goal was ambitious: to create trading systems that acknowledge and adapt to the market's inherent non-stationarity. By combining the detection capabilities developed in Part 1 with the adaptive execution framework and practical considerations discussed in Part 2, you now possess the blueprint for building strategies that are significantly more robust and attuned to the dynamic nature of financial markets. The journey from a static approach to a regime-adaptive one is complete, empowering you to navigate the market's complexities with greater intelligence and flexibility.
File Overview
Here's a summary of all the files created in this article: File Name | Description |
---|---|
MarketRegimeEnum.mqh | Defines the market regime enumeration types used throughout the system |
CStatistics.mqh | Statistical calculations class for market regime detection |
MarketRegimeDetector.mqh | Core market regime detection implementation |
MarketRegimeEA.mq5 | Expert Advisor that adapts to different market regimes |
MultiTimeframeRegimes.mq5 | Example for analyzing regimes across multiple timeframes |





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use