preview
Automating Trading Strategies in MQL5 (Part 43): Adaptive Linear Regression Channel Strategy

Automating Trading Strategies in MQL5 (Part 43): Adaptive Linear Regression Channel Strategy

MetaTrader 5Trading systems |
10 421 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 42), we developed a session-based Opening Range Breakout (ORB) system in MetaQuotes Language 5 (MQL5) that allowed custom session start times and opening range durations in minutes, automatically determined the true high and low on a selected timeframe, and executed trades only in the breakout direction. In Part 43, we develop an adaptive linear regression channel strategy.

This system calculates a linear regression line with standard deviation bands over a user-defined period. It only activates when the absolute slope exceeds a minimum threshold to ensure a trending market. Additionally, it automatically recreates the channel when the price deviates beyond a configurable percentage of the channel width and opens positions on clean breakouts from inside the channel. We will cover the following topics:

  1. Understanding the Adaptive Linear Regression Channel Framework
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have a functional MQL5 program that maintains a dynamic regression channel with filled deviation zones, breakout detection, middle-line cross exits, and normal/inverse trading modes — let’s dive in!


Understanding the Adaptive Linear Regression Channel Framework

The Linear Regression Channel strategy applies a least-squares linear regression line over a set number of bars to identify the underlying trend direction and strength, then adds parallel bands at a defined number of standard deviations above and below the regression line to form upper and lower boundaries. This creates a dynamic price channel that represents the expected price range in a trending market: price oscillating within the channel indicates continuation, touches or slight penetrations of the boundaries offer pullback entries, while significant deviations signal potential exhaustion or the need to recalculate the regression on newer data. We usually buy when the price closes below the lower channel and sell when the price closes above the upper channel.

In our implementation, we will calculate the regression slope, intercept, and standard deviation over a configurable period, only creating the channel when the absolute slope exceeds the minimum threshold to confirm directional movement. The channel will be anchored from the oldest bar in the period to a future point extended by a percentage of its duration, filled in two colored zones (pink upper half, light green lower half) with solid trendlines for upper, middle, and lower boundaries. On each new bar, we will either extend the channel one bar to the right if the price remains contained, or fully recreate it if the price deviates beyond a defined percentage of channel width.

Trades will open on clean breakouts from inside the channel with fixed pip stop-loss/take-profit, maximum concurrent positions per direction, and an inverse mode option; all positions in one direction close immediately upon crossing the middle line. Labels for upper, middle, and lower channels move with the right edge, and arrows mark every entry. Inverse mode simply swaps buy and sell logic, allowing the same program to trade mean-reversion breakouts instead of continuation pullbacks. It is something we figured would be helpful in case we want to do the opposite. In a nutshell, here is a visual representation of our objectives.

LINEAR REGRESSION CHANNEL FRAMEWORK


Implementation in MQL5

To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Experts 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 input parameters and global variables that we will use throughout the program.

//+------------------------------------------------------------------+
//|                                 Linear Regression Channel EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum TradeMode {                                                  // Define trade mode enum
   Normal,                                                        // Normal
   Inverse                                                        // Inverse
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input int      RegressionPeriod       = 100;                      // Period for regression calculation
input double   Deviations             = 2.0;                      // Standard deviation multiplier for channel
input double   MinSlopeThreshold      = 0.00001;                  // Min absolute slope to identify clear trend (low for detection)
input int      UpdateThresholdPercent = 30;                       // Update threshold in percent of channel width (e.g., 30 for 30%)
input double   ExtensionPercent       = 50.0;                     // Initial extension percent of channel length to the right
input TradeMode TradeDirection        = Normal;                   // Trade Mode
input double   Lots                   = 0.01;                     // Lot size
input int      StopLossPips           = 100;                      // Stop loss in pips
input int      TakeProfitPips         = 100;                      // Take profit in pips
input int      MaxBuys                = 2;                        // Maximum open buy positions
input int      MaxSells               = 2;                        // Maximum open sell positions
input int      MagicNumber            = 123456;                   // Magic number for positions
input int      Slippage               = 3;                        // Slippage

We begin the implementation by including the trade library with "#include <Trade\Trade.mqh>", which provides the CTrade class for handling order execution and position management. We declare the "obj_Trade" object globally from the "CTrade" class to use throughout the program for sending orders and modifying positions.

We define the "TradeMode" enumeration with two options: "Normal" for standard pullback-to-channel trading (buy on dips below the lower band in uptrends, sell on rallies above the upper band in downtrends) and "Inverse" to reverse the logic for mean-reversion breakout trading. We then set up the input parameters that we can adjust directly in the program properties. These include "RegressionPeriod" to specify how many bars are used for the linear regression calculation, "Deviations" as the standard deviation multiplier for the channel width (commonly 2.0 for approximately 95% containment), "MinSlopeThreshold" as the minimum absolute slope value required to consider the market trending and create a channel, and the rest which we added comments to make them easy to understand. These inputs give us full control over channel behavior, risk management, and trading style without modifying the program. The values we used are default and can be changed at any time for adaptation. When you compile, you should get the following window.

INPUT PARAMETERS WINDOW

With the inputs in place, we can proceed to define some global variables for use throughout the program.

//--- Global variables
datetime lastBarTime    = 0;                                      //--- Last bar time
double   channelUpper, channelLower, channelMiddle;               //--- Channel levels
string   channelName    = "LRC_Channel";                          //--- Channel name
string   upperLabelName = "LRC_Upper_Label";                      //--- Upper label name
string   middleLabelName = "LRC_Middle_Label";                    //--- Middle label name
string   lowerLabelName = "LRC_Lower_Label";                      //--- Lower label name
bool     hasValidChannel = false;                                 //--- Valid channel flag
datetime fixedTimeOld   = 0;                                      //--- Fixed old time
double   slope_global   = 0, intercept_global = 0, stdDev_global = 0; //--- Global slope, intercept, stdDev
long     period_sec     = PeriodSeconds(_Period);                 //--- Period seconds
int      arrowCounter   = 0;                                      //--- Arrow counter
double   current_right_x = 0;                                     //--- Current right x

We continue by declaring additional global variables to support the adaptive channel logic and visualization. We use "lastBarTime" to track the timestamp of the most recently processed bar. Current projected channel levels are stored in "channelUpper", "channelLower", and "channelMiddle" for quick reference during breakout checks. Constant strings define object names: "channelName" as the base "LRC_Channel" for the multi-part channel object, with "upperLabelName", "middleLabelName", and "lowerLabelName" for the moving text labels that identify each boundary.

The boolean "hasValidChannel" flag indicates whether a trending regression channel is currently active, while "fixedTimeOld" holds the datetime anchor of the oldest bar in the regression period for consistent x-coordinate calculations. We store the calculated regression parameters globally as "slope_global", "intercept_global", and "stdDev_global" so they can be reused for projections without recalculation on every tick. "period_sec" captures the duration of one bar in seconds via "PeriodSeconds(_Period)" for accurate time-to-x conversions. An "arrowCounter" integer provides unique naming for entry arrows, and "current_right_x" tracks the x-coordinate of the channel's rightmost point as it extends bar-by-bar, allowing precise one-bar extensions without recreating the entire channel object. This is important so we don't have flickering of the channel. With that complete, we'll begin the implementation by defining helper functions for rendering the channel.

//+-----------------------------------------------------------------------------------+
//| Create Linear Regression Channel using channels for fill and trendlines for lines |
//+-----------------------------------------------------------------------------------+
bool ChannelCreate(const long chart_ID, const string name, const int sub_window, datetime time1, datetime time2) {
   double price1_middle = intercept_global;                       //--- Price1 middle
   double price2_middle = intercept_global + slope_global * current_right_x; //--- Price2 middle
   double price1_upper = price1_middle + Deviations * stdDev_global; //--- Price1 upper
   double price2_upper = price2_middle + Deviations * stdDev_global; //--- Price2 upper
   double price1_lower = price1_middle - Deviations * stdDev_global; //--- Price1 lower
   double price2_lower = price2_middle - Deviations * stdDev_global; //--- Price2 lower
   // Upper-middle fill channel
   string um_name = name + "_um";                                 //--- UM name
   if (!ObjectCreate(chart_ID, um_name, OBJ_CHANNEL, sub_window, time1, price1_upper, time2, price2_upper, time1, price1_middle)) { //--- Create UM channel
      Print(__FUNCTION__, ": failed to create upper-middle channel! Error code = ", GetLastError()); //--- Log error
      return(false);                                              //--- Return failure
   }
   ObjectSetInteger(chart_ID, um_name, OBJPROP_COLOR, clrPink);   //--- Set color
   ObjectSetInteger(chart_ID, um_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(chart_ID, um_name, OBJPROP_WIDTH, 1);         //--- Set width
   ObjectSetInteger(chart_ID, um_name, OBJPROP_FILL, true);       //--- Set fill
   ObjectSetInteger(chart_ID, um_name, OBJPROP_BACK, true);       //--- Set back
   ObjectSetInteger(chart_ID, um_name, OBJPROP_SELECTABLE, true); //--- Set selectable
   ObjectSetInteger(chart_ID, um_name, OBJPROP_SELECTED, false);  //--- Set not selected
   ObjectSetInteger(chart_ID, um_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray
   ObjectSetInteger(chart_ID, um_name, OBJPROP_HIDDEN, false);    //--- Set not hidden
   ObjectSetInteger(chart_ID, um_name, OBJPROP_ZORDER, 0);        //--- Set zorder
   // Middle-lower fill channel
   string ml_name = name + "_ml";                                 //--- ML name
   if (!ObjectCreate(chart_ID, ml_name, OBJ_CHANNEL, sub_window, time1, price1_middle, time2, price2_middle, time1, price1_lower)) { //--- Create ML channel
      Print(__FUNCTION__, ": failed to create middle-lower channel! Error code = ", GetLastError()); //--- Log error
      return(false);                                              //--- Return failure
   }
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_COLOR, clrLightGreen); //--- Set color
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_WIDTH, 1);         //--- Set width
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_FILL, true);       //--- Set fill
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_BACK, true);       //--- Set back
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_SELECTABLE, true); //--- Set selectable
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_SELECTED, false);  //--- Set not selected
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_HIDDEN, false);    //--- Set not hidden
   ObjectSetInteger(chart_ID, ml_name, OBJPROP_ZORDER, 0);        //--- Set zorder
   // Upper trendline
   string upper_name = name + "_upper";                           //--- Upper name
   if (!ObjectCreate(chart_ID, upper_name, OBJ_TREND, sub_window, time1, price1_upper, time2, price2_upper)) { //--- Create upper trend
      Print(__FUNCTION__, ": failed to create upper trendline! Error code = ", GetLastError()); //--- Log error
      return(false);                                              //--- Return failure
   }
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_COLOR, clrRed); //--- Set color
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_WIDTH, 1);      //--- Set width
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_BACK, false);   //--- Set foreground
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_SELECTABLE, true); //--- Set selectable
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_SELECTED, false); //--- Set not selected
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_HIDDEN, false); //--- Set not hidden
   ObjectSetInteger(chart_ID, upper_name, OBJPROP_ZORDER, 1);     //--- Set zorder
   // Middle trendline
   string middle_name = name + "_middle";                         //--- Middle name
   if (!ObjectCreate(chart_ID, middle_name, OBJ_TREND, sub_window, time1, price1_middle, time2, price2_middle)) { //--- Create middle trend
      Print(__FUNCTION__, ": failed to create middle trendline! Error code = ", GetLastError()); //--- Log error
      return(false);                                              //--- Return failure
   }
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_COLOR, clrBlue); //--- Set color
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_WIDTH, 1);     //--- Set width
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_BACK, false);  //--- Set foreground
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_SELECTABLE, true); //--- Set selectable
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_SELECTED, false); //--- Set not selected
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_HIDDEN, false); //--- Set not hidden
   ObjectSetInteger(chart_ID, middle_name, OBJPROP_ZORDER, 1);    //--- Set zorder
   // Lower trendline
   string lower_name = name + "_lower";                           //--- Lower name
   if (!ObjectCreate(chart_ID, lower_name, OBJ_TREND, sub_window, time1, price1_lower, time2, price2_lower)) { //--- Create lower trend
      Print(__FUNCTION__, ": failed to create lower trendline! Error code = ", GetLastError()); //--- Log error
      return(false);                                              //--- Return failure
   }
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_COLOR, clrGreen); //--- Set color
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_WIDTH, 1);      //--- Set width
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_BACK, false);   //--- Set foreground
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_SELECTABLE, true); //--- Set selectable
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_SELECTED, false); //--- Set not selected
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_HIDDEN, false); //--- Set not hidden
   ObjectSetInteger(chart_ID, lower_name, OBJPROP_ZORDER, 1);     //--- Set zorder
   return(true);                                                  //--- Return success
}

//+------------------------------------------------------------------+
//| Delete the channel                                               |
//+------------------------------------------------------------------+
bool ChannelDelete(const long chart_ID, const string name) {
   bool success = true;                                           //--- Init success
   if (!ObjectDelete(chart_ID, name + "_um")) {                   //--- Delete um
      Print(__FUNCTION__, ": failed to delete um! Error code = ", GetLastError()); //--- Log error
      success = false;                                            //--- Set failure
   }
   if (!ObjectDelete(chart_ID, name + "_ml")) {                   //--- Delete ml
      Print(__FUNCTION__, ": failed to delete ml! Error code = ", GetLastError()); //--- Log error
      success = false;                                            //--- Set failure
   }
   if (!ObjectDelete(chart_ID, name + "_upper")) {                //--- Delete upper
      Print(__FUNCTION__, ": failed to delete upper! Error code = ", GetLastError()); //--- Log error
      success = false;                                            //--- Set failure
   }
   if (!ObjectDelete(chart_ID, name + "_middle")) {               //--- Delete middle
      Print(__FUNCTION__, ": failed to delete middle! Error code = ", GetLastError()); //--- Log error
      success = false;                                            //--- Set failure
   }
   if (!ObjectDelete(chart_ID, name + "_lower")) {                //--- Delete lower
      Print(__FUNCTION__, ": failed to delete lower! Error code = ", GetLastError()); //--- Log error
      success = false;                                            //--- Set failure
   }
   return(success);                                               //--- Return success
}

Here, we implement the "ChannelCreate" function to build the visual Linear Regression Channel using a combination of filled channel objects and trendlines, giving us colored zones and clear boundary lines without relying on a single built-in channel object. By now, you might have noticed we equally give deep attention to visualization so as to simulate everything clearly. We begin by calculating the exact price coordinates for both ends of the channel using the stored global regression values: we set the left middle price to "intercept_global", the right middle to "intercept_global + slope_global * current_right_x", and then derive the upper and lower prices at both ends by adding or subtracting "Deviations * stdDev_global".

To create the filled zones, we first construct the upper-middle section: we form a unique name by appending "_um" to the base name, then use ObjectCreate with OBJ_CHANNEL to draw a three-point channel from left upper to right upper to left middle. We configure this channel with pink color, solid style, width 1, filling enabled, background placement, selectable but not selected, no right ray, not hidden, and z-order 0. We repeat the process for the middle-lower section with "_ml" suffix: we create another OBJ_CHANNEL from left middle to right middle to left lower, this time using light green color with identical styling to achieve the two-tone filled effect we want.

We then draw the three visible boundary lines as separate trendlines for sharper appearance: we create the upper trendline with "_upper" suffix using OBJ_TREND from left upper to right upper, setting it red, solid, width 1, foreground (not back), selectable, no ray, z-order 1; we do the same for the middle trendline in blue with "_middle" suffix, and the lower in green with "_lower" suffix. By separating the fills and lines, we get both colored zones and crisp boundaries that remain clear even when the price moves inside. If any "ObjectCreate" call fails, we log the error with GetLastError and return false; otherwise, we return true on full success.

We also define the companion "ChannelDelete" function to cleanly remove all five components when we need to recreate or reset the channel: we attempt to delete each object by its full name ("_um", "_ml", "_upper", "_middle", "_lower"), logging any failures but continuing, and return true if everything deleted or false if any error occurred. We use the ObjectDelete function to achieve this. These paired functions will allow us to fully rebuild the visual channel whenever price breaks out significantly or a new trend forms. We will also need to display the signals via arrows and update the channel labels.

//+------------------------------------------------------------------+
//| Draw arrow on chart for signal                                   |
//+------------------------------------------------------------------+
void DrawArrow(bool isBuy, datetime time, double price) {
   string name = "SignalArrow_" + IntegerToString(arrowCounter++);  //--- Arrow name
   ObjectCreate(0, name, OBJ_ARROW, 0, time, price);                //--- Create arrow
   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, isBuy ? 233 : 234); //--- Set code
   ObjectSetInteger(0, name, OBJPROP_COLOR, isBuy ? clrGreen : clrRed); //--- Set color
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);                     //--- Set width
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, isBuy ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);            //--- Set not selectable
   ChartRedraw(0);                                                  //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Update channel labels                                            |
//+------------------------------------------------------------------+
void UpdateLabels(datetime labelTime) {
   double label_x = (double)(labelTime - fixedTimeOld) / period_sec; //--- Calc label x
   double middlePrice = intercept_global + slope_global * label_x;   //--- Calc middle
   double upperPrice = middlePrice + Deviations * stdDev_global;     //--- Calc upper
   double lowerPrice = middlePrice - Deviations * stdDev_global;     //--- Calc lower
   // Upper label
   if (ObjectFind(0, upperLabelName) < 0) {                          //--- Check no upper label
      ObjectCreate(0, upperLabelName, OBJ_TEXT, 0, labelTime, upperPrice); //--- Create upper label
   } else {                                                          //--- Exists
      ObjectMove(0, upperLabelName, 0, labelTime, upperPrice);       //--- Move upper label
   }
   ObjectSetString(0, upperLabelName, OBJPROP_TEXT, "Upper Channel"); //--- Set text
   ObjectSetInteger(0, upperLabelName, OBJPROP_COLOR, clrRed);       //--- Set color
   ObjectSetInteger(0, upperLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Set anchor
   ObjectSetInteger(0, upperLabelName, OBJPROP_SELECTABLE, false);   //--- Set not selectable
   // Middle label
   if (ObjectFind(0, middleLabelName) < 0) {                         //--- Check no middle label
      ObjectCreate(0, middleLabelName, OBJ_TEXT, 0, labelTime, middlePrice); //--- Create middle label
   } else {                                                          //--- Exists
      ObjectMove(0, middleLabelName, 0, labelTime, middlePrice);     //--- Move middle label
   }
   ObjectSetString(0, middleLabelName, OBJPROP_TEXT, "Middle Channel"); //--- Set text
   ObjectSetInteger(0, middleLabelName, OBJPROP_COLOR, clrBlue);     //--- Set color
   ObjectSetInteger(0, middleLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   ObjectSetInteger(0, middleLabelName, OBJPROP_SELECTABLE, false);  //--- Set not selectable
   // Lower label
   if (ObjectFind(0, lowerLabelName) < 0) {                          //--- Check no lower label
      ObjectCreate(0, lowerLabelName, OBJ_TEXT, 0, labelTime, lowerPrice); //--- Create lower label
   } else {                                                          //--- Exists
      ObjectMove(0, lowerLabelName, 0, labelTime, lowerPrice);       //--- Move lower label
   }
   ObjectSetString(0, lowerLabelName, OBJPROP_TEXT, "Lower Channel"); //--- Set text
   ObjectSetInteger(0, lowerLabelName, OBJPROP_COLOR, clrGreen);     //--- Set color
   ObjectSetInteger(0, lowerLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Set anchor
   ObjectSetInteger(0, lowerLabelName, OBJPROP_SELECTABLE, false);   //--- Set not selectable
   ChartRedraw(0);                                                   //--- Redraw chart
}

We proceed to implement the "DrawArrow" function to place a clear visual marker on the chart whenever a trade signal occurs. We generate a unique object name by combining "SignalArrow_" with an incrementing "arrowCounter", then create an OBJ_ARROW at the specified time and price. We choose wingdings symbol 233 for buy signals (upward arrow) or 234 for sell signals (downward arrow), apply green color for buys and red for sells, set width to 2 for visibility, anchor the arrow at the top for buys or bottom for sells so it points correctly from the candle, make it non-selectable to avoid accidental moves, and redraw the chart immediately. MQL5 provides code. You can see below to switch to your desired ones.

MQL5 WINGDINGS

Moving on with the implementation, we create the "UpdateLabels" function to keep descriptive text labels positioned exactly at the current right edge of the channel, moving them smoothly as the channel extends or recreates. We first calculate the x-coordinate equivalent of the provided "labelTime" by subtracting "fixedTimeOld" and dividing by "period_sec", then use this x-value with the global regression parameters to compute precise middle, upper, and lower prices at that exact point.

For each of the three labels (upper, middle, lower), we first check with ObjectFind whether it already exists; if not, we create a new OBJ_TEXT object at the label time and calculate the price, otherwise we simply move the existing one with ObjectMove to the new position. We set the appropriate text ("Upper Channel", "Middle Channel", "Lower Channel"), apply matching colors (red, blue, green), position the anchor as left-upper, left, or left-lower, respectively, so the text sits neatly beside the lines without overlapping, make them non-selectable, and finally redraw the chart. We can now use these functions to identify and draw the channel. We will have the logic as a function as well to modularize the code as below.

//+------------------------------------------------------------------+
//| Create channel if clear trend                                    |
//+------------------------------------------------------------------+
void CreateChannelIfTrend() {
   if (Bars(_Symbol, _Period) < RegressionPeriod + 1) return;     //--- Return if insufficient bars
   double closeArray[];                                           //--- Close array
   ArraySetAsSeries(closeArray, true);                            //--- Set as series
   if (CopyClose(_Symbol, _Period, 1, RegressionPeriod, closeArray) != RegressionPeriod) return; //--- Copy close or return
   double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;               //--- Init sums
   int n = RegressionPeriod;                                      //--- Set n
   for (int i = 0; i < n; i++) {                                  //--- Iterate
      double x = (double)(n - 1 - i);                             //--- Calc x
      double y = closeArray[i];                                   //--- Get y
      sumX += x;                                                  //--- Add x
      sumY += y;                                                  //--- Add y
      sumXY += x * y;                                             //--- Add xy
      sumX2 += x * x;                                             //--- Add x2
   }
   double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); //--- Calc slope
   // Check for clear trend
   if (MathAbs(slope) < MinSlopeThreshold) {                      //--- Check no trend
      hasValidChannel = false;                                    //--- Reset channel
      ChannelDelete(0, channelName);                              //--- Delete channel
      ObjectDelete(0, upperLabelName);                            //--- Delete upper label
      ObjectDelete(0, middleLabelName);                           //--- Delete middle label
      ObjectDelete(0, lowerLabelName);                            //--- Delete lower label
      return;                                                     //--- Return
   }
   double intercept = (sumY - slope * sumX) / n;                  //--- Calc intercept
   // Calculate stdDev
   double sumRes2 = 0;                                            //--- Init res2 sum
   for (int i = 0; i < n; i++) {                                  //--- Iterate
      double x = (double)(n - 1 - i);                             //--- Calc x
      double predicted = intercept + slope * x;                   //--- Calc predicted
      double res = closeArray[i] - predicted;                     //--- Calc res
      sumRes2 += res * res;                                       //--- Add res2
   }
   double variance = sumRes2 / (n - 2);                           //--- Calc variance
   double stdDev = MathSqrt(variance);                            //--- Calc stdDev
   // Store for projection
   slope_global = slope;                                          //--- Set global slope
   intercept_global = intercept;                                  //--- Set global intercept
   stdDev_global = stdDev;                                        //--- Set global stdDev
   // Fixed anchors with initial extension (future time2)
   fixedTimeOld = iTime(_Symbol, _Period, RegressionPeriod);      //--- Set old time
   datetime fixedTimeNew = iTime(_Symbol, _Period, 1);            //--- Set new time
   long channel_sec = fixedTimeNew - fixedTimeOld;                //--- Calc channel sec
   long extension_sec = (long)(channel_sec * (ExtensionPercent / 100.0)); //--- Calc extension
   datetime time_extended = fixedTimeNew + (datetime)extension_sec; //--- Calc extended time
   current_right_x = (double)(time_extended - fixedTimeOld) / period_sec; //--- Calc right x
   // Delete old and create new
   ChannelDelete(0, channelName);                                 //--- Delete channel
   if (!ChannelCreate(0, channelName, 0, fixedTimeOld, time_extended)) return; //--- Create channel or return
   hasValidChannel = true;                                        //--- Set valid channel
   double channelWidth = 2 * Deviations * stdDev_global;          //--- Calc width
   double channelWidthPoints = channelWidth / _Point;             //--- Calc width points
   Print("Channel created: slope=" + DoubleToString(slope, 8) + ", range=" + DoubleToString(channelWidth, _Digits) + " (" + DoubleToString(channelWidthPoints, 0) + " points), times: " + TimeToString(fixedTimeOld) + " to " + TimeToString(time_extended)); //--- Log channel
   UpdateLabels(time_extended);                                   //--- Update labels
   ChartRedraw(0);                                                //--- Redraw chart
}

Here, we implement the "CreateChannelIfTrend" function to perform the full linear regression calculation and decide whether to build or update the channel, ensuring we only display it during clear trending periods. We first verify that sufficient historical bars exist (at least "RegressionPeriod + 1") — if not, we return immediately to avoid errors. We then declare a dynamic array for close prices, set it as a series with ArraySetAsSeries so index 0 is the most recent bar, and copy exactly "RegressionPeriod" closes starting from shift 1 via CopyClose — returning early if the copy fails.

We initialize summation variables for the least-squares formula and loop through the period: for each bar i, we assign x as (n-1-i) so the oldest bar gets x = n-1 and the newest gets x = 0, y as the close price, and accumulate sumX, sumY, sumXY, and sumX2 accordingly. We strongly need this for the trend calculation formula. We calculate the slope using the standard formula. If the absolute slope is below "MinSlopeThreshold", we consider the market flat, set "hasValidChannel" to false, delete any existing channel and labels with "ChannelDelete" and ObjectDelete, and return — this prevents drawing meaningless horizontal channels in ranging conditions. You can skip this check if you want to trade ranging markets, though.

When the slope is sufficient, we proceed to compute the intercept as (sumY - slope × sumX) / n. We then calculate the standard deviation: in the second loop, we find the predicted price for each x, compute the squared residual from the actual close, sum them, divide by (n-2) to get the variance, and take the square root for the standard deviation. We store slope, intercept, and deviation in the global variables for later projections. We anchor the channel's left side at the oldest bar's time via iTime at shift "RegressionPeriod" into "fixedTimeOld", set the base right anchor at the most recent completed bar (shift 1), calculate the channel duration in seconds, extend it rightward by "ExtensionPercent" of that duration, and compute "current_right_x" as the exact x-coordinate of the extended endpoint.

We delete any previous channel with "ChannelDelete", then call "ChannelCreate" to build the new multi-part channel from "fixedTimeOld" to the extended time. On success, we set "hasValidChannel" to true, log the new channel details (slope, width in price and points, time range), update the moving labels to the extended right edge with "UpdateLabels", and redraw the chart. We can now call this function in the OnInit event handler to draw the first channel if we have enough data.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(MagicNumber);                   //--- Set magic number
   CreateChannelIfTrend();                                        //--- Initial try
   return(INIT_SUCCEEDED);                                        //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ChannelDelete(0, channelName);                                 //--- Delete channel
   ObjectDelete(0, upperLabelName);                               //--- Delete upper label
   ObjectDelete(0, middleLabelName);                              //--- Delete middle label
   ObjectDelete(0, lowerLabelName);                               //--- Delete lower label
}

In the OnInit event handler, we start by assigning the "MagicNumber" to the "obj_Trade" object with "SetExpertMagicNumber", ensuring every trade we open carries this identifier for proper management. We then call "CreateChannelIfTrend" to attempt building the regression channel using the most recent data available at startup, giving us an initial channel if the market already shows sufficient trend strength. We finish by returning INIT_SUCCEEDED to confirm successful initialization.

Also, in the OnDeinit function, we perform a complete cleanup: we call "ChannelDelete" to remove all five components of the regression channel (the two filled zones and three trendlines), then individually delete the three moving text labels with ObjectDelete using "upperLabelName", "middleLabelName", and "lowerLabelName". This ensures no orphaned objects remain on the chart after the program stops running. Upon compilation, we get the following outcome.

INITIAL LINEAR REGRESSION CHANNEL

From the image, we can see that we initialize the channel if we have a trend and enough bars to calculate it. We can now move on to managing it and updating it as we have more bars in the tick function. We'll need to do that on new bars only to avoid overloading the program unnecessarily. We defined the following function to handle that.

//+------------------------------------------------------------------+
//| Check if new bar has opened                                      |
//+------------------------------------------------------------------+
bool IsNewBar() {
   datetime currentBarTime = iTime(_Symbol, _Period, 0);          //--- Get current time
   if (currentBarTime != lastBarTime) {                           //--- Check new
      lastBarTime = currentBarTime;                               //--- Update last
      return true;                                                //--- Return true
   }
   return false;                                                  //--- Return false
}

Here, we define the "IsNewBar" function to detect when a new bar has fully formed on the chart's timeframe, allowing us to run the main calculation and trading logic only once per bar instead of on every tick. We obtain the open time of the current bar (shift 0) using iTime with the current symbol and timeframe, storing it in "currentBarTime". We then compare this to the stored "lastBarTime" global variable: if they differ, we have a new bar, so we update "lastBarTime" to the current value and return true to signal that processing should continue. If the times match, we simply return false, skipping the heavy logic for the rest of the tick. We can now use this in the OnTick event handler for when new bars are born.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   // Check for new bar
   if (!IsNewBar()) return;                                       //--- Return if not new bar
   // Scan every bar if no channel
   if (!hasValidChannel) {                                        //--- Check no channel
      CreateChannelIfTrend();                                     //--- Create if trend
      if (!hasValidChannel) return;                               //--- Return if no channel
   }
   // Project values manually for completed bar (shift 1)
   datetime previousTime = iTime(_Symbol, _Period, 1);            //--- Get previous time
   int x = Bars(_Symbol, _Period, fixedTimeOld, previousTime) - 1; //--- Calc x
   channelMiddle = intercept_global + slope_global * x;           //--- Calc middle
   channelUpper = channelMiddle + Deviations * stdDev_global;     //--- Calc upper
   channelLower = channelMiddle - Deviations * stdDev_global;     //--- Calc lower
   // Project for shift 2
   datetime time2 = iTime(_Symbol, _Period, 2);                   //--- Get time 2
   if (time2 <= fixedTimeOld) return;                             //--- Return if invalid
   int x2 = Bars(_Symbol, _Period, fixedTimeOld, time2) - 1;      //--- Calc x2
   double middle2 = intercept_global + slope_global * x2;         //--- Calc middle2
   double upper2 = middle2 + Deviations * stdDev_global;          //--- Calc upper2
   double lower2 = middle2 - Deviations * stdDev_global;          //--- Calc lower2
   // Get closes
   double closePrevious = iClose(_Symbol, _Period, 1);            //--- Get close previous
   double close2 = iClose(_Symbol, _Period, 2);                   //--- Get close 2
   if (closePrevious == 0 || close2 == 0) return;                 //--- Return if invalid
   // Check if beyond end
   datetime current_time2 = (datetime)ObjectGetInteger(0, channelName + "_middle", OBJPROP_TIME, 1); //--- Get current time2
   if (previousTime > current_time2) {                            //--- Check beyond end
      Print("Bars beyond channel end: previousTime=" + TimeToString(previousTime) + ", current_time2=" + TimeToString(current_time2) + " - recreating channel"); //--- Log recreate
      CreateChannelIfTrend();                                     //--- Recreate channel
      return;                                                     //--- Return
   }
}

In the OnTick function, we begin by calling "IsNewBar" to detect whether a new bar has fully formed on the current timeframe. If no new bar is present, we immediately return to avoid running the logic multiple times within the same candle, keeping everything synchronized to completed bars only. If "hasValidChannel" is false — meaning we currently have no active regression channel due to insufficient trend or a previous deletion — we immediately invoke "CreateChannelIfTrend" to scan the latest data and build a fresh channel if the slope now meets the minimum threshold. Should the function still leave "hasValidChannel" false (flat market), we return early and wait for the next bar.

Once we confirm a valid channel exists, we manually project the regression levels specifically for the just-completed bar (shift 1). We retrieve its timestamp with iTime at shift 1 into "previousTime", calculate its exact x-coordinate relative to our fixed left anchor using Bars between "fixedTimeOld" and "previousTime" minus 1, then compute the middle, upper, and lower channels. This gives us precise channel positions exactly at the close of the previous bar. We repeat the projection for the bar before that (shift 2). These values let us determine whether the previous bar's close broke out from inside the channel on the prior bar. We then fetch the actual close prices: "closePrevious" from shift 1 and "close2" from shift 2, returning early if either is invalid (zero).

Finally, we perform a safety check: we read the current right endpoint time of the middle trendline object ("channelName + "_middle"") via the ObjectGetInteger function with OBJPROP_TIME index 1 into "current_time2". If the previous bar's time already exceeds this endpoint — meaning price has moved beyond where our channel currently reaches — we log the situation and force an immediate recreation with "CreateChannelIfTrend", ensuring the channel never lags behind developing price action. Upon compilation, we get the following.

LINEAR REGRESSION CHANNEL TICK UPDATES

So far, so good. We can see that we update the channel on new bars when the trend is confirmed. We can now proceed to detect the breakouts, so we either extend the channel or redraw it. Here is the code snippet we use to achieve that.

// Check if breakout (deviation > threshold * width) for recreation
double channelWidth = channelUpper - channelLower;             //--- Calc width
double channelWidthPoints = channelWidth / _Point;             //--- Calc width points
double updateThreshold = UpdateThresholdPercent / 100.0;       //--- Calc threshold
double deviation = MathMax(closePrevious - channelUpper, channelLower - closePrevious); //--- Calc deviation
double deviationPercent = (deviation / channelWidth) * 100;    //--- Calc percent
if (deviation > updateThreshold * channelWidth) {              //--- Check breakout
   Print("Breakout detected - deviation: " + DoubleToString(deviation, _Digits) + " (" + DoubleToString(deviationPercent, 2) + "%), threshold: " + DoubleToString(updateThreshold * channelWidth, _Digits) + " (" + IntegerToString(UpdateThresholdPercent) + "%) - recreating channel"); //--- Log breakout
   CreateChannelIfTrend();                                     //--- Recreate channel
   return;                                                     //--- Return
} else {                                                       //--- No breakout
   // Extend right by one bar if within
   datetime new_time2 = current_time2 + (datetime)period_sec;  //--- Calc new time2
   current_right_x += 1.0;                                     //--- Increment x
   double new_price_middle = intercept_global + slope_global * current_right_x; //--- Calc new middle
   double new_price_upper = new_price_middle + Deviations * stdDev_global; //--- Calc new upper
   double new_price_lower = new_price_middle - Deviations * stdDev_global; //--- Calc new lower
   // Move channels
   ObjectMove(0, channelName + "_um", 1, new_time2, new_price_upper); //--- Move um
   ObjectMove(0, channelName + "_ml", 1, new_time2, new_price_middle); //--- Move ml
   // Move trendlines
   ObjectMove(0, channelName + "_upper", 1, new_time2, new_price_upper); //--- Move upper
   ObjectMove(0, channelName + "_middle", 1, new_time2, new_price_middle); //--- Move middle
   ObjectMove(0, channelName + "_lower", 1, new_time2, new_price_lower); //--- Move lower
   UpdateLabels(new_time2);                                    //--- Update labels
   ChartRedraw(0);                                             //--- Redraw chart
}

We now handle the adaptive behavior of the channel: deciding whether to extend it bar-by-bar or fully recreate it when price shows a significant breakout from the current regression. We first calculate the full channel width as the distance between "channelUpper" and "channelLower", convert it to points for reference, and compute the update threshold as "UpdateThresholdPercent" divided by 100. We then determine the deviation of the previous close from the nearer boundary using MathMax, and express this deviation as a percentage of the total channel width. If this deviation exceeds the threshold (for example, 30% of width by default), we consider it a meaningful breakout or exhaustion of the current regression, log the details including deviation in price and percentage along with the threshold, and immediately call "CreateChannelIfTrend" to recalculate the regression on the newest data and build a fresh channel that better fits the updated trend — then return early since the channel has been rebuilt. The logic for recreation can be altered as per your choice; this is just an arbitrary logic we figured when building the system.

Then, when the price remains reasonably contained (deviation <= threshold), we instead extend the existing channel one bar to the right for continuity. We advance the right endpoint time by one full bar period with "current_time2 + period_sec" into "new_time2", increment "current_right_x" by 1.0, and project the new middle, upper, and lower prices using the stored globals. We then update every component by moving its right anchor point (index 1) to the new time and corresponding price: the upper-middle fill with "_um", middle-lower with "_ml", and the three trendlines "_upper", "_middle", and "_lower". Finally, we call "UpdateLabels" with the new right time to reposition the text labels and redraw the chart. This extension keeps the channel smoothly following the price in trending conditions without unnecessary recalculation, while ensuring it snaps to a new regression when the price moves too far outside the expected range. When we have a breakout, now we will know via the print-out as below.

BREAKOUT DETECTION LOGGING

Since we can manage the channel fully now, we can proceed to generate signals when the price breaks from the channel itself, though we also want to close existing positions when we have middle line crosses. Another arbitrary management logic that you can skip if you want positions to fully close only on stop-loss or take-profit hits.

// Count existing positions
int buyCount = 0, sellCount = 0;                               //--- Init counts
int total = PositionsTotal();                                  //--- Get total positions
for (int i = total - 1; i >= 0; i--) {                         //--- Iterate reverse
   ulong ticket = PositionGetTicket(i);                        //--- Get ticket
   if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check symbol magic
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
         buyCount++;                                              //--- Increment buy
      } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
         sellCount++;                                             //--- Increment sell
      }
   }
}
// Close logic: Close all buys if crossed above middle, all sells if below
if (closePrevious > channelMiddle) {                           //--- Check close above middle
   for (int i = PositionsTotal() - 1; i >= 0; i--) {           //--- Iterate reverse
      ulong ticket = PositionGetTicket(i);                     //--- Get ticket
      if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
         obj_Trade.PositionClose(ticket, Slippage);                 //--- Close position
         Print("Closing Buy: Price " + DoubleToString(closePrevious, _Digits) + " crossed above middle channel " + DoubleToString(channelMiddle, _Digits)); //--- Log close
      }
   }
}
if (closePrevious < channelMiddle) {                           //--- Check close below middle
   for (int i = PositionsTotal() - 1; i >= 0; i--) {           //--- Iterate reverse
      ulong ticket = PositionGetTicket(i);                     //--- Get ticket
      if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
         obj_Trade.PositionClose(ticket, Slippage);                 //--- Close position
         Print("Closing Sell: Price " + DoubleToString(closePrevious, _Digits) + " crossed below middle channel " + DoubleToString(channelMiddle, _Digits)); //--- Log close
      }
   }
}
// Open on clear breakout if room (with inverse option)
bool buySignal = (close2 >= lower2) && (closePrevious < channelLower); //--- Buy signal
bool sellSignal = (close2 <= upper2) && (closePrevious > channelUpper); //--- Sell signal
if (TradeDirection == Inverse) {                               //--- Check inverse
   bool temp = buySignal;                                      //--- Temp buy
   buySignal = sellSignal;                                     //--- Swap buy
   sellSignal = temp;                                          //--- Swap sell
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);            //--- Get ask
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);            //--- Get bid
if (buySignal && buyCount < MaxBuys) {                         //--- Check buy signal
   // Buy
   double sl = (StopLossPips == 0) ? 0 : NormalizeDouble(ask - StopLossPips * _Point, _Digits); //--- Calc SL
   double tp = (TakeProfitPips == 0) ? 0 : NormalizeDouble(ask + TakeProfitPips * _Point, _Digits); //--- Calc TP
   if (obj_Trade.Buy(Lots, _Symbol, 0, sl, tp, "LRC Buy")) {      //--- Open buy
      Print("Buy signal: Price " + DoubleToString(closePrevious, _Digits) + " broke below lower channel from inside"); //--- Log signal
      DrawArrow(true, previousTime, closePrevious);            //--- Draw arrow
   } else {                                                    //--- Failed
      Print("Buy order failed: " + obj_Trade.ResultRetcodeDescription()); //--- Log failure
   }
}
if (sellSignal && sellCount < MaxSells) {                      //--- Check sell signal
   // Sell
   double sl = (StopLossPips == 0) ? 0 : NormalizeDouble(bid + StopLossPips * _Point, _Digits); //--- Calc SL
   double tp = (TakeProfitPips == 0) ? 0 : NormalizeDouble(bid - TakeProfitPips * _Point, _Digits); //--- Calc TP
   if (obj_Trade.Sell(Lots, _Symbol, 0, sl, tp, "LRC Sell")) {    //--- Open sell
      Print("Sell signal: Price " + DoubleToString(closePrevious, _Digits) + " broke above upper channel from inside"); //--- Log signal
      DrawArrow(false, previousTime, closePrevious);           //--- Draw arrow
   } else {                                                    //--- Failed
      Print("Sell order failed: " + obj_Trade.ResultRetcodeDescription()); //--- Log failure
   }
}

We now turn to position management and trade execution on each new bar. We first count how many buy and sell positions are currently open that belong to this program: we loop backward through PositionsTotal, retrieve each ticket with "PositionGetTicket", and check for matching symbol and "MagicNumber". We increment "buyCount" for POSITION_TYPE_BUY or "sellCount" for "POSITION_TYPE_SELL", giving us accurate limits before opening new trades. We then implement the middle-line cross exit logic: if the previous bar's close ("closePrevious") is above "channelMiddle", we immediately close every open buy position by iterating again, checking type, and calling "obj_Trade.PositionClose" with the ticket and allowed "Slippage", while logging the reason. Similarly, if the previous close is below "channelMiddle" triggers the closure of all sell positions with the same process and logging. This aggressively protects profits or cuts losses the moment the price crosses the regression line itself, regardless of how many deviations away it is.

Then, we define entry signals based on clean breakouts from inside the channel: a buy signal occurs when the bar-2's close ("close2") was at or above the lower band at that time ("lower2") but the previous bar's close is below the current lower band ("channelLower"), meaning price has decisively broken downward out of the channel. A sell signal triggers with inverted conditions. If "TradeDirection" is "Inverse", we simply swap the two signals, instantly converting the program from trend-continuation pullbacks to mean-reversion fade trading.

We retrieve current ask and bid prices, then check for a buy signal and room under "MaxBuys": if both conditions are met, we calculate stop-loss, take-profit, and send a market buy order via "obj_Trade.Buy" with fixed "Lots", no predefined price (market execution), the calculated SL/TP, and comment "LRC Buy". On success, we log the signal details and call "DrawArrow" with true for a green upward arrow at the previous bar's time and close; on failure, we log the retcode description. We mirror the process for sell signals. Upon compilation, we get the following outcome.

LINEAR REGRESSION CHANNEL TEST GIF

From the visualization, we can see that we define the linear regression channel, update it when needed, and open positions on breakouts, hence achieving our objectives. The thing that remains is backtesting the program, and that is handled in the next section.


Backtesting

After thorough backtesting, we have the following results.

Backtest graph:

GRAPH

Backtest report:

REPORT


Conclusion

In conclusion, we’ve developed an adaptive Linear Regression Channel system in MQL5. It calculates the regression line and standard deviation bands over a user-defined period. The system only activates when the absolute slope exceeds a minimum threshold. The channel extends automatically bar-by-bar while the price stays inside. It fully recreates the channel when the price moves beyond a configurable percentage of the channel width. The system supports both normal (pullback) and inverse (fade) trading modes. It opens positions on clean breakouts from inside the channel. Visually, it displays filled dual-color zones, solid trendlines for upper, middle, and lower boundaries, as well as moving labels and entry arrows.

Disclaimer: This article is for educational purposes only. Trading carries significant financial risks, and market volatility may result in losses. Thorough backtesting and careful risk management are crucial before deploying this program in live markets.

This adaptive Linear Regression Channel strategy offers dynamic updates, normal or inverse modes, and middle-line cross exits. You’re equipped to trade trending markets with channel-based signals. The strategy is ready for further optimization in your trading journey. Happy trading!

The View component for tables in the MQL5 MVC paradigm: Base graphical element The View component for tables in the MQL5 MVC paradigm: Base graphical element
The article covers the process of developing a base graphical element for the View component as part of the implementation of tables in the MVC (Model-View-Controller) paradigm in MQL5. This is the first article on the View component and the third one in a series of articles on creating tables for the MetaTrader 5 client terminal.
Price Action Analysis Toolkit Development (Part 53): Pattern Density Heatmap for Support and Resistance Zone Discovery Price Action Analysis Toolkit Development (Part 53): Pattern Density Heatmap for Support and Resistance Zone Discovery
This article introduces the Pattern Density Heatmap, a price‑action mapping tool that transforms repeated candlestick pattern detections into statistically significant support and resistance zones. Rather than treating each signal in isolation, the EA aggregates detections into fixed price bins, scores their density with optional recency weighting, and confirms levels against higher‑timeframe data. The resulting heatmap reveals where the market has historically reacted—levels that can be used proactively for trade timing, risk management, and strategy confidence across any trading style.
The MQL5 Standard Library Explorer (Part 5): Multiple Signal Expert The MQL5 Standard Library Explorer (Part 5): Multiple Signal Expert
In this session, we will build a sophisticated, multi-signal Expert Advisor using the MQL5 Standard Library. This approach allows us to seamlessly blend built-in signals with our own custom logic, demonstrating how to construct a powerful and flexible trading algorithm. For more, click to read further.
Introduction to MQL5 (Part 29): Mastering API and WebRequest Function in MQL5 (III) Introduction to MQL5 (Part 29): Mastering API and WebRequest Function in MQL5 (III)
In this article, we continue mastering API and WebRequest in MQL5 by retrieving candlestick data from an external source. We focus on splitting the server response, cleaning the data, and extracting essential elements such as opening time and OHLC values for multiple daily candles, preparing the data for further analysis.