
Automating Trading Strategies in MQL5 (Part 10): Developing the Trend Flat Momentum Strategy
Introduction
In the previous article (Part 9), we developed an Expert Advisor to automate the Asian Breakout Strategy using key session levels and dynamic risk management in MetaQuotes Language 5 (MQL5). Now, in Part 10, we shift our focus to the Trend Flat Momentum Strategy—a method that combines a two moving averages crossover with momentum filters like Relative Strength Index (RSI) and Commodity Channel Index (CCI) to capture trending movements with precision. We will structure the article via the following topics:
By the end, we’ll have a fully functional Expert Advisor that automates the Trend Flat Momentum Strategy. Let’s dive in!
Strategy Blueprint
The Trend Flat Momentum Strategy is designed to capture market trends by blending a simple moving average crossover system with robust momentum filtering. The core idea is to generate buy signals when a fast-moving average crosses above a slower-moving average—suggesting a bullish trend—while confirming the signal with momentum indicators, which are an RSI and two different CCI values. Conversely, a short trade is signaled when the fast-moving average exceeds the slow-moving average and the momentum indicators confirm bearish conditions. The indicator settings are:
- Commodity Channel Index (CCI) (36 Periods, Close)
- Commodity Channel Index (CCI) (55 Periods, Close)
- Slow Relative Strength Index (RSI) (27 Periods, Close)
- Moving Average Fast (11 Periods, Smoothed, Median Price/2)
- Moving Average Fast (25 Periods, Smoothed, Median Price/2)
As for the exit strategy, we will place the stop loss at the previous swing low for a long trade and the previous swing high for a short trade. Take profit will be at a predetermined level, 300 points from the entry price. This multi-faceted approach will help filter out false signals and aims to improve the quality of trade entries by ensuring that trend direction and momentum are aligned. In a nutshell, the visualization below depicts the simplified strategic plan.
Implementation in MQL5
To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Indicators folder, click on the "New" tab, and follow the prompts to create the file. Once it is made, in the coding environment, we will need to declare some global variables that we will use throughout the program.
//+------------------------------------------------------------------+ //| Copyright 2025, Forex Algo-Trader, Allan. | //| "https://t.me/Forex_Algo_Trader" | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "This EA trades based on Trend Flat Momentum Strategy" #property strict #include <Trade\Trade.mqh> //--- Include the Trade library for order management. CTrade obj_Trade; //--- Create an instance of the CTrade class to handle trading operations. // Input parameters input int InpCCI36Period = 36; //--- CCI period 1 input int InpCCI55Period = 55; //--- CCI period 2 input int InpRSIPeriod = 27; //--- RSI period input int InpMAFastPeriod = 11; //--- Fast MA period input int InpMASlowPeriod = 25; //--- Slow MA period input double InpRSIThreshold = 58.0; //--- RSI threshold for Buy signal (Sell uses 100 - Threshold) input int InpTakeProfitPoints = 300; //--- Take profit in points input double InpLotSize = 0.1; //--- Trade lot size // Pivot parameters for detecting swing highs/lows input int PivotLeft = 2; //--- Number of bars to the left for pivot detection input int PivotRight = 2; //--- Number of bars to the right for pivot detection // Global indicator handles int handleCCI36; //--- Handle for the CCI indicator with period InpCCI36Period int handleCCI55; //--- Handle for the CCI indicator with period InpCCI55Period int handleRSI; //--- Handle for the RSI indicator with period InpRSIPeriod int handleMA11; //--- Handle for the fast moving average (MA) with period InpMAFastPeriod int handleMA25; //--- Handle for the slow moving average (MA) with period InpMASlowPeriod // Global dynamic storage buffers double ma11_buffer[]; //--- Dynamic array to store fast MA values double ma25_buffer[]; //--- Dynamic array to store slow MA values double rsi_buffer[]; //--- Dynamic array to store RSI values double cci36_buffer[]; //--- Dynamic array to store CCI values (period 36) double cci55_buffer[]; //--- Dynamic array to store CCI values (period 55) // To detect a new bar datetime lastBarTime = 0; //--- Variable to store the time of the last processed bar
We start by including the file "Trade\Trade.mqh" using #include to access built-in trading functions and create "obj_Trade", an instance of the "CTrade" class, for executing buy and sell orders. We define key input parameters for strategy configuration, including "InpCCI36Period" and "InpCCI55Period" for "CCI" indicators, "InpRSIPeriod" for "RSI", and "InpMAFastPeriod" and "InpMASlowPeriod" for two moving averages. "InpRSIThreshold" sets a condition for trade filtering, while "InpTakeProfitPoints" determines the fixed take-profit level, and "InpLotSize" controls the position size.
To improve trade execution, we introduce "PivotLeft" and "PivotRight", which define the number of bars used to detect swing highs and lows for stop-loss placement. Global indicator handles, such as "handleCCI36", "handleCCI55", "handleRSI", "handleMA11", and "handleMA25", allow us to retrieve indicator values efficiently. Dynamic arrays store these values in "ma11_buffer", "ma25_buffer", "rsi_buffer", "cci36_buffer", and "cci55_buffer", ensuring smooth data processing. Finally, "lastBarTime" tracks the last processed bar to prevent multiple trades on the same candle, ensuring accurate trade execution. We can then initialize the indicators in the OnInit event handler.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create CCI handle for period InpCCI36Period using the close price. handleCCI36 = iCCI(_Symbol, _Period, InpCCI36Period, PRICE_CLOSE); //--- Create the CCI36 indicator handle. if (handleCCI36 == INVALID_HANDLE) { //--- Check if the CCI36 handle is valid. Print("Error creating CCI36 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create CCI handle for period InpCCI55Period using the close price. handleCCI55 = iCCI(_Symbol, _Period, InpCCI55Period, PRICE_CLOSE); //--- Create the CCI55 indicator handle. if (handleCCI55 == INVALID_HANDLE) { //--- Check if the CCI55 handle is valid. Print("Error creating CCI55 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create RSI handle for period InpRSIPeriod using the close price. handleRSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); //--- Create the RSI indicator handle. if (handleRSI == INVALID_HANDLE) { //--- Check if the RSI handle is valid. Print("Error creating RSI handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create fast MA handle using MODE_SMMA on the median price with period InpMAFastPeriod. handleMA11 = iMA(_Symbol, _Period, InpMAFastPeriod, 0, MODE_SMMA, PRICE_MEDIAN); //--- Create the fast MA handle. if (handleMA11 == INVALID_HANDLE) { //--- Check if the fast MA handle is valid. Print("Error creating MA11 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create slow MA handle using MODE_SMMA on the median price with period InpMASlowPeriod. handleMA25 = iMA(_Symbol, _Period, InpMASlowPeriod, 0, MODE_SMMA, PRICE_MEDIAN); //--- Create the slow MA handle. if (handleMA25 == INVALID_HANDLE) { //--- Check if the slow MA handle is valid. Print("Error creating MA25 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Set the dynamic arrays as time series (index 0 = most recent closed bar). ArraySetAsSeries(ma11_buffer, true); //--- Set ma11_buffer as a time series. ArraySetAsSeries(ma25_buffer, true); //--- Set ma25_buffer as a time series. ArraySetAsSeries(rsi_buffer, true); //--- Set rsi_buffer as a time series. ArraySetAsSeries(cci36_buffer, true); //--- Set cci36_buffer as a time series. ArraySetAsSeries(cci55_buffer, true); //--- Set cci55_buffer as a time series. return (INIT_SUCCEEDED); //--- Return success after initialization. }
On the OnInit event handler, we initialize the Expert Advisor by creating and validating indicator handles. We use the iCCI function to create CCI handles with periods "InpCCI36Period" and "InpCCI55Period," the iRSI function for the RSI handle, and the iMA function for fast and slow SMMA handles with periods "InpMAFastPeriod" and "InpMASlowPeriod." If any handle is invalid (INVALID_HANDLE), we print an error and return failure (INIT_FAILED). Finally, we use the ArraySetAsSeries function to format buffers as time series and return success upon successful initialization. To save resources, we need to release the created handles when the program is removed as follows.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (handleCCI36 != INVALID_HANDLE) { //--- If the CCI36 handle is valid, IndicatorRelease(handleCCI36); //--- release the CCI36 indicator. } if (handleCCI55 != INVALID_HANDLE) { //--- If the CCI55 handle is valid, IndicatorRelease(handleCCI55); //--- release the CCI55 indicator. } if (handleRSI != INVALID_HANDLE) { //--- If the RSI handle is valid, IndicatorRelease(handleRSI); //--- release the RSI indicator. } if (handleMA11 != INVALID_HANDLE) { //--- If the fast MA handle is valid, IndicatorRelease(handleMA11); //--- release the fast MA indicator. } if (handleMA25 != INVALID_HANDLE) { //--- If the slow MA handle is valid, IndicatorRelease(handleMA25); //--- release the slow MA indicator. } }
Here, we handle the deinitialization of the Expert Advisor by releasing indicator resources in the OnDeinit. We check if each indicator handles—"CCI36", "CCI55", "RSI", fast MA, and slow MA—is valid. If so, we use the IndicatorRelease function to free the allocated resources, ensuring efficient memory management. We can now proceed to the OnTick event handler where all the data procession and decision-making will take place.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get the time of the current bar. datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Retrieve the current bar's time. if (currentBarTime != lastBarTime) { //--- If a new bar has formed, lastBarTime = currentBarTime; //--- update lastBarTime with the current bar's time. OnNewBar(); //--- Process the new bar. } }
Here, we use the OnTick event handler to monitor price updates and detect new bars. We retrieve the time of the current bar using the iTime function and compare it with the stored "lastBarTime" value. If a new bar is detected, we update "lastBarTime" ensuring we trade only once per bar, and call the "OnNewBar" function to process the new bar's data. This is the function where we handle all signal generation and it is as follows.
//+------------------------------------------------------------------+ //| Function called on every new bar (bar close) | //+------------------------------------------------------------------+ void OnNewBar() { //--- Resize the dynamic arrays to hold 2 values (last closed bar and the one before). ArrayResize(ma11_buffer, 2); //--- Resize ma11_buffer to 2 elements. ArrayResize(ma25_buffer, 2); //--- Resize ma25_buffer to 2 elements. ArrayResize(rsi_buffer, 2); //--- Resize rsi_buffer to 2 elements. ArrayResize(cci36_buffer, 2); //--- Resize cci36_buffer to 2 elements. ArrayResize(cci55_buffer, 2); //--- Resize cci55_buffer to 2 elements. //--- Copy indicator values into the dynamic arrays. if (CopyBuffer(handleMA11, 0, 1, 2, ma11_buffer) != 2) { //--- Copy 2 values from the fast MA indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleMA25, 0, 1, 2, ma25_buffer) != 2) { //--- Copy 2 values from the slow MA indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleRSI, 0, 1, 2, rsi_buffer) != 2) { //--- Copy 2 values from the RSI indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleCCI36, 0, 1, 2, cci36_buffer) != 2) { //--- Copy 2 values from the CCI36 indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleCCI55, 0, 1, 2, cci55_buffer) != 2) { //--- Copy 2 values from the CCI55 indicator. return; //--- Exit the function if copying fails. } //--- For clarity, assign the values from the arrays. //--- Index 0: last closed bar, Index 1: bar before. double ma11_current = ma11_buffer[0]; //--- Fast MA value for the last closed bar. double ma11_previous = ma11_buffer[1]; //--- Fast MA value for the previous bar. double ma25_current = ma25_buffer[0]; //--- Slow MA value for the last closed bar. double ma25_previous = ma25_buffer[1]; //--- Slow MA value for the previous bar. double rsi_current = rsi_buffer[0]; //--- RSI value for the last closed bar. double cci36_current = cci36_buffer[0]; //--- CCI36 value for the last closed bar. double cci55_current = cci55_buffer[0]; //--- CCI55 value for the last closed bar. }
In the void function "OnNewBar" that we create, we first ensure that the dynamic arrays holding indicator values are resized using the ArrayResize function to store the last two closed bars. We then retrieve indicator values using the CopyBuffer function for the fast-moving average, slow-moving average, RSI, and two CCI indicators. If any of these operations fail, the function exits to prevent errors. Once the values are successfully copied, we assign them to variables for easier reference, distinguishing between the last closed bar and the bar before it. This setup ensures that we always have the most recent market data available for making trading decisions. If we retrieve the data successfully, we can proceed to make trading decisions. We start with the buy logic.
//--- Check for Buy Conditions: bool maCrossoverBuy = (ma11_previous < ma25_previous) && (ma11_current > ma25_current); //--- True if fast MA crosses above slow MA. bool rsiConditionBuy = (rsi_current > InpRSIThreshold); //--- True if RSI is above the Buy threshold. bool cci36ConditionBuy = (cci36_current > 0); //--- True if CCI36 is positive. bool cci55ConditionBuy = (cci55_current > 0); //--- True if CCI55 is positive. if (maCrossoverBuy) { //--- If crossover for MA Buy is true... bool conditionsOk = true; //--- Initialize a flag to track if all conditions are met. //--- Check RSI condition for Buy. if (!rsiConditionBuy) { //--- If the RSI condition is not met... Print("Buy signal rejected: RSI condition not met. RSI=", rsi_current, " Threshold=", InpRSIThreshold); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI36 condition for Buy. if (!cci36ConditionBuy) { //--- If the CCI36 condition is not met... Print("Buy signal rejected: CCI36 condition not met. CCI36=", cci36_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI55 condition for Buy. if (!cci55ConditionBuy) { //--- If the CCI55 condition is not met... Print("Buy signal rejected: CCI55 condition not met. CCI55=", cci55_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. }
Here, we evaluate the conditions for entering a buy trade. The "maCrossoverBuy" variable checks if the fast-moving average ("ma11") has crossed above the slow-moving average ("ma25"), indicating a potential buy signal. The "rsiConditionBuy" ensures that the RSI value is above the defined "InpRSIThreshold," confirming strong bullish momentum. The "cci36ConditionBuy" and "cci55ConditionBuy" check if both CCI indicators are positive, suggesting that the market is in a favorable trend. If the "maCrossoverBuy" condition is true, we proceed to validate the remaining conditions. If any condition fails, we print a message indicating why the buy signal is rejected and set the "conditionsOk" flag to false to prevent further trade execution. This comprehensive check ensures that only trades with strong bullish confirmation are taken. If we then detect one, we can proceed to open the position.
if (conditionsOk) { //--- If all Buy conditions are met... //--- Get stop loss from previous swing low. double stopLoss = GetPivotLow(PivotLeft, PivotRight); //--- Use pivot low as the stop loss. if (stopLoss <= 0) { //--- If no valid pivot low is found... stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Fallback to current bid price. } double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Determine entry price as current ask price. double tp = entryPrice + InpTakeProfitPoints * _Point; //--- Calculate take profit based on fixed points. //--- Print the swing point (pivot low) used as stop loss. Print("Buy signal: Swing Low used as Stop Loss = ", stopLoss); //--- Notify the user of the pivot low used. if (obj_Trade.Buy(InpLotSize, NULL, entryPrice, stopLoss, tp, "Buy Order")) { //--- Attempt to open a Buy order. Print("Buy order opened at ", entryPrice); //--- Notify the user if the order is opened successfully. } else { Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Notify the user if the order fails. } return; //--- Exit after processing a valid Buy signal. } else { Print("Buy signal not executed due to failed condition(s)."); //--- Notify the user if Buy conditions failed. }
After confirming that all buy conditions are met, we proceed to determine the stop loss for the trade. We use the "GetPivotLow" function to find the previous swing low, which is set as the stop loss. If no valid pivot low is found (i.e., stop loss is less than or equal to 0), the current bid price is used as a fallback. The entry price is taken from the current ask price using the SymbolInfoDouble function with the SYMBOL_ASK parameter. We calculate the take profit ("tp") by adding the specified "InpTakeProfitPoints" to the entry price, adjusted by the market's point value (_Point).
Once the entry price, stop loss, and take profit are determined, a buy order is attempted using the "obj_Trade.Buy" method. If the buy order is successfully opened, we Print a confirmation message. If the order fails, we provide the failure message and error description using "obj_Trade.ResultRetcodeDescription". If the buy conditions are not met, a message indicating that the buy signal was rejected is printed, and no trade is opened. We used a "GetPivotLow" custom function and its implementation is as below.
//+------------------------------------------------------------------+ //| Function to find the most recent swing low (pivot low) | //+------------------------------------------------------------------+ double GetPivotLow(int left, int right) { MqlRates rates[]; //--- Declare an array to store rate data. int copied = CopyRates(_Symbol, _Period, 0, 100, rates); //--- Copy the last 100 bars into the rates array. if (copied <= (left + right)) { //--- Check if sufficient data was copied. return (0); //--- Return 0 if there are not enough bars. } //--- Loop through the bars to find a pivot low. for (int i = left; i <= copied - right - 1; i++) { bool isPivot = true; //--- Assume the current bar is a pivot low. double currentLow = rates[i].low; //--- Get the low value of the current bar. for (int j = i - left; j <= i + right; j++) { //--- Loop through neighboring bars. if (j == i) { //--- Skip the current bar. continue; } if (rates[j].low <= currentLow) { //--- If any neighbor's low is lower or equal, isPivot = false; //--- then the current bar is not a pivot low. break; } } if (isPivot) { //--- If a pivot low is confirmed, return (currentLow); //--- return the low value of the pivot. } } return (0); //--- Return 0 if no pivot low is found. }
In the function, we aim to identify the most recent swing low (pivot low) by scanning the last 100 bars of market data using the CopyRates function. The array "rates" of the MqlRates structure, holds the price data, and we ensure there are enough bars to perform the calculation by checking if the data copied is greater than or equal to the sum of the "left" and "right" parameters. The function then loops through the bars, checking for a pivot low by comparing the current bar's low value with the neighboring bars within the specified "left" and "right" range. If any neighboring bar's low is lower or equal to the current bar's low, the current bar is not a pivot low. If a pivot low is found, its low value is returned. If no pivot low is found after checking all the bars, the function returns 0.
To get the pivot high, we use a similar approach, only with inversed logic.
//+------------------------------------------------------------------+ //| Function to find the most recent swing high (pivot high) | //+------------------------------------------------------------------+ double GetPivotHigh(int left, int right) { MqlRates rates[]; //--- Declare an array to store rate data. int copied = CopyRates(_Symbol, _Period, 0, 100, rates); //--- Copy the last 100 bars into the rates array. if (copied <= (left + right)) { //--- Check if sufficient data was copied. return (0); //--- Return 0 if there are not enough bars. } //--- Loop through the bars to find a pivot high. for (int i = left; i <= copied - right - 1; i++) { bool isPivot = true; //--- Assume the current bar is a pivot high. double currentHigh = rates[i].high; //--- Get the high value of the current bar. for (int j = i - left; j <= i + right; j++) { //--- Loop through neighboring bars. if (j == i) { //--- Skip the current bar. continue; } if (rates[j].high >= currentHigh) { //--- If any neighbor's high is higher or equal, isPivot = false; //--- then the current bar is not a pivot high. break; } } if (isPivot) { //--- If a pivot high is confirmed, return (currentHigh); //--- return the high value of the pivot. } } return (0); //--- Return 0 if no pivot high is found. }
Armed with the functions, we can process the sell trade signals using a similar inversed approach as we did for the buy positions.
//--- Check for Sell Conditions: bool maCrossoverSell = (ma11_previous > ma25_previous) && (ma11_current < ma25_current); //--- True if fast MA crosses below slow MA. bool rsiConditionSell = (rsi_current < (100.0 - InpRSIThreshold)); //--- True if RSI is below the Sell threshold. bool cci36ConditionSell = (cci36_current < 0); //--- True if CCI36 is negative. bool cci55ConditionSell = (cci55_current < 0); //--- True if CCI55 is negative. if (maCrossoverSell) { //--- If crossover for MA Sell is true... bool conditionsOk = true; //--- Initialize a flag to track if all conditions are met. //--- Check RSI condition for Sell. if (!rsiConditionSell) { //--- If the RSI condition is not met... Print("Sell signal rejected: RSI condition not met. RSI=", rsi_current, " Required below=", (100.0 - InpRSIThreshold)); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI36 condition for Sell. if (!cci36ConditionSell) { //--- If the CCI36 condition is not met... Print("Sell signal rejected: CCI36 condition not met. CCI36=", cci36_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI55 condition for Sell. if (!cci55ConditionSell) { //--- If the CCI55 condition is not met... Print("Sell signal rejected: CCI55 condition not met. CCI55=", cci55_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } if (conditionsOk) { //--- If all Sell conditions are met... //--- Get stop loss from previous swing high. double stopLoss = GetPivotHigh(PivotLeft, PivotRight); //--- Use pivot high as the stop loss. if (stopLoss <= 0) { //--- If no valid pivot high is found... stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Fallback to current ask price. } double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Determine entry price as current bid price. double tp = entryPrice - InpTakeProfitPoints * _Point; //--- Calculate take profit based on fixed points. //--- Print the swing point (pivot high) used as stop loss. Print("Sell signal: Swing High used as Stop Loss = ", stopLoss); //--- Notify the user of the pivot high used. if (obj_Trade.Sell(InpLotSize, NULL, entryPrice, stopLoss, tp, "Sell Order")) { //--- Attempt to open a Sell order. Print("Sell order opened at ", entryPrice); //--- Notify the user if the order is opened successfully. } else { Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Notify the user if the order fails. } return; //--- Exit after processing a valid Sell signal. } else { Print("Sell signal not executed due to failed condition(s)."); //--- Notify the user if Sell conditions failed. } }
Here, we check the conditions for a Sell signal by reversing the logic used for Buy signals. We start by checking if the fast-moving average crosses below the slow-moving average, which is captured by the "maCrossoverSell" condition. Next, we verify if the RSI is below the Sell threshold, and check that both CCI36 and CCI55 values are negative.
If all conditions are met, we calculate the stop loss using the "GetPivotHigh" function to find the most recent swing high and determine the take profit based on a fixed point distance. We then attempt to open a Sell order using the "obj_Trade.Sell" method. If the order is successful, we print a confirmation message; if not, we display an error message. If any condition fails, we notify the user that the Sell signal has been rejected. Upon compilation and running the program, we get the following outcome.
From the image, we can see that the program identifies and verifies all the entry conditions and if validated, opens the respective position with the respective entry parameters, hence achieving our objective. The thing that remains is backtesting the program, and that is handled in the next section.
Backtesting
On backtesting the program intensively, we did notice that when finding swing points, we used the oldest data first for comparison, which led to invalid swing points sometimes though rare, and hence resulting in invalid stops for the stop loss.
To mitigate the issue, we adopted an approach where we set the search data as a time series using the ArraySetAsSeries function, so we have the latest data at the first position in the storage arrays, and hence we use the latest data first for analysis as follows.
//+------------------------------------------------------------------+ //| Function to find the most recent swing low (pivot low) | //+------------------------------------------------------------------+ double GetPivotLow(int left, int right) { MqlRates rates[]; //--- Declare an array to store rate data. ArraySetAsSeries(rates, true); //--- }
Upon further testing for confirmation, we have the following outcome.
From the image, we can see that we correctly get the actual recent swing points, hence getting rid of the "invalid stops" error. Thus, we will not get locked out of trades and after thorough testing, from 2023 to 2024, we have the following results.
Backtest graph:
Backtest report:
Conclusion
In conclusion, we have successfully developed an MQL5 Expert Advisor designed to automate a comprehensive Trend Flat Momentum trading strategy that combines multiple trend and momentum indicators for both Buy and Sell signals. By incorporating key conditions such as indicator crossovers and threshold checks, we have created a dynamic system that reacts to market trends with precise entry and exit points.
Disclaimer: This article is for educational purposes only. Trading involves significant financial risk, and market conditions can change rapidly. While the strategy provided offers a structured approach to trading, it does not guarantee profitability. Thorough backtesting and sound risk management are crucial before applying this system in live environment.
By implementing these concepts, you can enhance your algorithmic trading skills and refine your approach to technical analysis. Best of luck as you continue developing and improving your trading strategies!





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