
Automating Trading Strategies in MQL5 (Part 34): Trendline Breakout System with R-Squared Goodness of Fit
Introduction
In our previous article (Part 33), we developed a Shark Pattern system in MetaQuotes Language 5 (MQL5) that detected bullish and bearish Shark harmonic patterns using Fibonacci ratios, automating trades with customizable take-profit and stop-loss levels, visualized through chart objects like triangles and trendlines. In Part 34, we create a Trendline Breakout System that identifies support and resistance trendlines using swing points, validated by R-squared goodness of fit and angle constraints, to execute trades on breakouts with dynamic chart visualizations. We will cover the following topics:
- Understanding the Trendline Breakout Strategy Framework
- Implementation in MQL5
- Backtesting
- Conclusion
By the end, you’ll have a robust MQL5 strategy for trendline breakout trading, ready for customization—let’s dive in!
Understanding the Trendline Breakout Strategy Framework
The trendline breakout strategy involves drawing diagonal lines on price charts to connect swing highs (resistance) or swing lows (support), identifying key price levels where the market is likely to reverse or continue. When the price breaks through these trendlines—either closing above a resistance line or below a support line—it signals a potential shift in market momentum, prompting traders to enter trades in the direction of the breakout with defined risk and reward parameters. This approach capitalizes on strong price movements following the break, aiming to capture significant trends while managing risk through stop-loss and take-profit levels. Here is an illustration of a downward trendline breakout.
Our plan is to detect swing highs and lows within a specified lookback period, construct trendlines with a minimum number of touch points, and validate them using R-squared metrics and angle constraints to ensure reliability. In case you need to know, R-squared, also called the coefficient of determination, is a statistical measure that indicates how well a regression model explains the variability of the dependent variable using the independent variables. It represents the proportion of the total variation in the outcome that is accounted for by the model, with values ranging from 0 to 1. Here is a quick visualization of the model.
We will implement trade execution logic for breakouts, triggered by candle closes or entire candles crossing the trendline, with visual feedback through trendlines, arrows, and labels, and manage the trendline lifecycle by removing expired or broken ones, creating a breakout trading system. Have a look at the result we aim for, and then we can proceed to the implementation.
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 start by declaring some inputs and structures that will make the program more dynamic.
//+------------------------------------------------------------------+ //| Trendline Breakout Trader 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" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations CTrade obj_Trade; //--- Instantiate trade object //+------------------------------------------------------------------+ //| Breakout definition enumeration | //+------------------------------------------------------------------+ enum ENUM_BREAKOUT_TYPE { BREAKOUT_CLOSE = 0, // Breakout on close above/below line BREAKOUT_CANDLE = 1 // Breakout on entire candle above/below line }; //+------------------------------------------------------------------+ //| Swing point structure | //+------------------------------------------------------------------+ struct Swing { //--- Define swing point structure datetime time; //--- Store swing time double price; //--- Store swing price }; //+------------------------------------------------------------------+ //| Starting point structure | //+------------------------------------------------------------------+ struct StartingPoint { //--- Define starting point structure datetime time; //--- Store starting point time double price; //--- Store starting point price bool is_support; //--- Indicate support/resistance flag }; //+------------------------------------------------------------------+ //| Trendline storage structure | //+------------------------------------------------------------------+ struct TrendlineInfo { //--- Define trendline info structure string name; //--- Store trendline name datetime start_time; //--- Store start time datetime end_time; //--- Store end time double start_price; //--- Store start price double end_price; //--- Store end price double slope; //--- Store slope bool is_support; //--- Indicate support/resistance flag int touch_count; //--- Store number of touches datetime creation_time; //--- Store creation time int touch_indices[]; //--- Store touch indices array bool is_signaled; //--- Indicate signal flag }; //+------------------------------------------------------------------+ //| Forward declarations | //+------------------------------------------------------------------+ void DetectSwings(); //--- Declare swing detection function void SortSwings(Swing &swings[], int count); //--- Declare swing sorting function double CalculateAngle(datetime time1, double price1, datetime time2, double price2); //--- Declare angle calculation function bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen); //--- Declare trendline validation function void FindAndDrawTrendlines(bool isSupport); //--- Declare trendline finding/drawing function void UpdateTrendlines(); //--- Declare trendline update function void RemoveTrendlineFromStorage(int index); //--- Declare trendline removal function bool IsStartingPointUsed(datetime time, double price, bool is_support); //--- Declare starting point usage check function double CalculateRSquared(const datetime ×[], const double &prices[], int n, double slope, double intercept); //--- Declare R-squared calculation function //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input ENUM_BREAKOUT_TYPE BreakoutType = BREAKOUT_CLOSE; // Breakout Definition input int LookbackBars = 200; // Set bars for swing detection lookback input double TouchTolerance = 10.0; // Set tolerance for touch points (points) input int MinTouches = 3; // Set minimum touch points for valid trendline input double PenetrationTolerance = 5.0; // Set allowance for bar penetration (points) input int ExtensionBars = 100; // Set bars to extend trendline right input int MinBarSpacing = 10; // Set minimum bar spacing between touches input double inpLot = 0.01; // Set lot size input double inpSLPoints = 100.0; // Set stop loss (points) input double inpRRRatio = 1.1; // Set risk:reward ratio input double MinAngle = 1.0; // Set minimum inclination angle (degrees) input double MaxAngle = 89.0; // Set maximum inclination angle (degrees) input double MinRSquared = 0.8; // Minimum R-squared for trendline acceptance input bool DeleteExpiredObjects = false; // Enable deletion of expired/broken objects input bool EnableTradingSignals = true; // Enable buy/sell signals and trades input bool DrawTouchArrows = true; // Enable drawing arrows at touch points input bool DrawLabels = true; // Enable drawing trendline/point labels input color SupportLineColor = clrGreen; // Set color for support trendlines input color ResistanceLineColor = clrRed; // Set color for resistance trendlines //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ Swing swingLows[]; //--- Store swing lows int numLows = 0; //--- Track number of swing lows Swing swingHighs[]; //--- Store swing highs int numHighs = 0; //--- Track number of swing highs TrendlineInfo trendlines[]; //--- Store trendlines int numTrendlines = 0; //--- Track number of trendlines StartingPoint startingPoints[]; //--- Store used starting points int numStartingPoints = 0; //--- Track number of starting points
We start the implementation of our trendline breakout system by setting up the foundational components for detecting and trading trendline breakouts. First, we include the "Trade.mqh" library and instantiate a CTrade object named "obj_Trade" for trade operations. Then, we define the "ENUM_BREAKOUT_TYPE" enumeration with options "BREAKOUT_CLOSE" (breakout on candle close) and "BREAKOUT_CANDLE" (breakout on entire candle), allowing flexible breakout detection. Next, we create the "Swing" structure to store swing point time and price, the "StartingPoint" structure for tracking used trendline starting points with a support/resistance flag, and the "TrendlineInfo" structure to store trendline details like name, start/end times and prices, slope, touch count, creation time, touch indices, and signal status.
We declare forward functions like "DetectSwings", "SortSwings", and "CalculateAngle" for core logic. Then, we set input parameters: "BreakoutType" as "BREAKOUT_CLOSE", "LookbackBars" at 200, and the rest, which are self-explanatory. Finally, we initialize global arrays "swingLows", "swingHighs", "trendlines", and "startingPoints" with counters "numLows", "numHighs", "numTrendlines", and "numStartingPoints" to manage swing points and trendlines, forming the backbone for detecting and validating trendlines for breakout trading. Since we are all set, we can initialize the storage arrays in the initialization.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ArrayResize(trendlines, 0); //--- Resize trendlines array numTrendlines = 0; //--- Reset trendlines count ArrayResize(startingPoints, 0); //--- Resize starting points array numStartingPoints = 0; //--- Reset starting points count return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ArrayResize(trendlines, 0); //--- Resize trendlines array numTrendlines = 0; //--- Reset trendlines count ArrayResize(startingPoints, 0); //--- Resize starting points array numStartingPoints = 0; //--- Reset starting points count }
To ensure proper setup and cleanup of resources, we implement the OnInit function by calling ArrayResize to set the "trendlines" array to zero, resetting "numTrendlines" to 0, resizing the "startingPoints" array to zero, and resetting "numStartingPoints" to 0, then returning INIT_SUCCEEDED to confirm successful initialization. Then, in the OnDeinit function, we perform identical cleanup, ensuring no memory leaks when the program terminates. With initialization complete, we can now proceed to defining the strategy logic. To help modularize the logic, we will use functions, and the first logic we will define is swing points detection, so we can have base trendline points.
//+------------------------------------------------------------------+ //| Check for new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime lastTime = 0; //--- Store last bar time datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time if (lastTime != currentTime) { //--- Check for new bar lastTime = currentTime; //--- Update last time return true; //--- Indicate new bar } return false; //--- Indicate no new bar } //+------------------------------------------------------------------+ //| Sort swings by time (ascending, oldest first) | //+------------------------------------------------------------------+ void SortSwings(Swing &swings[], int count) { for (int i = 0; i < count - 1; i++) { //--- Iterate through swings for (int j = 0; j < count - i - 1; j++) { //--- Compare adjacent swings if (swings[j].time > swings[j + 1].time) { //--- Check time order Swing temp = swings[j]; //--- Store temporary swing swings[j] = swings[j + 1]; //--- Swap swings swings[j + 1] = temp; //--- Complete swap } } } } //+------------------------------------------------------------------+ //| Detect swing highs and lows | //+------------------------------------------------------------------+ void DetectSwings() { numLows = 0; //--- Reset lows count ArrayResize(swingLows, 0); //--- Resize lows array numHighs = 0; //--- Reset highs count ArrayResize(swingHighs, 0); //--- Resize highs array int totalBars = iBars(_Symbol, _Period); //--- Get total bars int effectiveLookback = MathMin(LookbackBars, totalBars); //--- Calculate effective lookback if (effectiveLookback < 5) { //--- Check sufficient bars Print("Not enough bars for swing detection."); //--- Log insufficient bars return; //--- Exit function } for (int i = 2; i < effectiveLookback - 2; i++) { //--- Iterate through bars double low_i = iLow(_Symbol, _Period, i); //--- Get current low double low_im1 = iLow(_Symbol, _Period, i - 1); //--- Get previous low double low_im2 = iLow(_Symbol, _Period, i - 2); //--- Get two bars prior low double low_ip1 = iLow(_Symbol, _Period, i + 1); //--- Get next low double low_ip2 = iLow(_Symbol, _Period, i + 2); //--- Get two bars next low if (low_i < low_im1 && low_i < low_im2 && low_i < low_ip1 && low_i < low_ip2) { //--- Check for swing low Swing s; //--- Create swing struct s.time = iTime(_Symbol, _Period, i); //--- Set swing time s.price = low_i; //--- Set swing price ArrayResize(swingLows, numLows + 1); //--- Resize lows array swingLows[numLows] = s; //--- Add swing low numLows++; //--- Increment lows count } double high_i = iHigh(_Symbol, _Period, i); //--- Get current high double high_im1 = iHigh(_Symbol, _Period, i - 1); //--- Get previous high double high_im2 = iHigh(_Symbol, _Period, i - 2); //--- Get two bars prior high double high_ip1 = iHigh(_Symbol, _Period, i + 1); //--- Get next high double high_ip2 = iHigh(_Symbol, _Period, i + 2); //--- Get two bars next high if (high_i > high_im1 && high_i > high_im2 && high_i > high_ip1 && high_i > high_ip2) { //--- Check for swing high Swing s; //--- Create swing struct s.time = iTime(_Symbol, _Period, i); //--- Set swing time s.price = high_i; //--- Set swing price ArrayResize(swingHighs, numHighs + 1); //--- Resize highs array swingHighs[numHighs] = s; //--- Add swing high numHighs++; //--- Increment highs count } } if (numLows > 0) SortSwings(swingLows, numLows); //--- Sort swing lows if (numHighs > 0) SortSwings(swingHighs, numHighs); //--- Sort swing highs }
With the foundational setup complete, we now implement the core logic for detecting swing points and managing bar updates. First, we develop the "IsNewBar" function, which uses a static "lastTime" variable to store the previous bar’s time, compares it with the current bar’s time obtained via iTime for the symbol and period at shift 0, updates "lastTime" if different, and returns true to indicate a new bar, or false otherwise. Then, we implement the "SortSwings" function, which sorts a "Swing" array by time in ascending order using a bubble sort algorithm, iterating through the array with nested loops, comparing adjacent elements’ "time" fields, and swapping them using a temporary "Swing" struct if out of order.
Next, we create the "DetectSwings" function, resetting "numLows" and "numHighs" to 0 and resizing "swingLows" and "swingHighs" arrays to zero, calculating an effective lookback with MathMin of "LookbackBars" and total bars from iBars, and exiting with a Print error if fewer than 5 bars are available. We then iterate through bars from index 2 to "effectiveLookback - 2", checking for swing lows by comparing the current bar’s low ("iLow") against two prior and two subsequent bars, and for swing highs similarly using iHigh; if a swing is detected, we create a "Swing" struct, set its "time" with "iTime" and "price" with the low or high, append it to "swingLows" or "swingHighs" using ArrayResize, and increment the respective counter. Finally, we call "SortSwings" on "swingLows" and "swingHighs" if they contain elements, ensuring chronological order for trendline construction. Let us now define functions to calculate the trendline inclination for restriction based on inclination and its validation.
//+------------------------------------------------------------------+ //| Calculate visual inclination angle | //+------------------------------------------------------------------+ double CalculateAngle(datetime time1, double price1, datetime time2, double price2) { int x1, y1, x2, y2; //--- Declare coordinate variables if (!ChartTimePriceToXY(0, 0, time1, price1, x1, y1)) return 0.0; //--- Convert time1/price1 to XY if (!ChartTimePriceToXY(0, 0, time2, price2, x2, y2)) return 0.0; //--- Convert time2/price2 to XY double dx = (double)(x2 - x1); //--- Calculate x difference double dy = (double)(y2 - y1); //--- Calculate y difference if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0); //--- Handle vertical line case double angle = MathArctan(-dy / dx) * 180.0 / M_PI; //--- Calculate angle in degrees return angle; //--- Return angle } //+------------------------------------------------------------------+ //| Validate trendline | //+------------------------------------------------------------------+ bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen) { int bar_start = iBarShift(_Symbol, _Period, start_time); //--- Get start bar index if (bar_start < 0) return false; //--- Check invalid bar index for (int bar = bar_start; bar >= 0; bar--) { //--- Iterate through bars datetime bar_time = iTime(_Symbol, _Period, bar); //--- Get bar time double dk = (double)(bar_time - ref_time); //--- Calculate time difference double line_price = ref_price + slope * dk; //--- Calculate line price if (isSupport) { //--- Check support case double low = iLow(_Symbol, _Period, bar); //--- Get bar low if (low < line_price - tolerance_pen) return false;//--- Check if broken } else { //--- Handle resistance case double high = iHigh(_Symbol, _Period, bar); //--- Get bar high if (high > line_price + tolerance_pen) return false;//--- Check if broken } } return true; //--- Return valid }
Here, we implement functions to calculate trendline angles and validate their integrity. First, we develop the "CalculateAngle" function, which converts two time-price points ("time1", "price1" and "time2", "price2") to chart coordinates ("x1", "y1" and "x2", "y2") using ChartTimePriceToXY, returning 0.0 if either conversion fails; we calculate the differences "dx" and "dy", handle vertical lines by returning -90.0 or 90.0 if "dx" is zero based on "dy", and compute the angle in degrees using MathArctan of "-dy / dx" multiplied by 180/M_PI for visual inclination.
Then, we implement the "ValidateTrendline" function, which validates a trendline by obtaining the bar index of "start_time" with iBarShift, returning false if invalid; we iterate from this index to the present, calculating the trendline price at each bar’s time ("iTime") using the formula "ref_price + slope * (bar_time - ref_time)"; for support trendlines ("isSupport" true), we check if the bar’s low (iLow) falls below "line_price - tolerance_pen", returning false if broken; for resistance, we check if the bar’s high (iHigh) exceeds "line_price + tolerance_pen", returning false if broken, otherwise returning true, ensuring trendlines meet angle constraints and remain unbreached for reliable breakout detection. We can now define the function for the R-squared goodness-of-fit model.
//+------------------------------------------------------------------+ //| Calculate R-squared for goodness of fit | //+------------------------------------------------------------------+ double CalculateRSquared(const datetime ×[], const double &prices[], int n, double slope, double intercept) { double sum_y = 0.0; //--- Initialize sum of y for (int k = 0; k < n; k++) { //--- Iterate through points sum_y += prices[k]; //--- Accumulate y } double mean_y = sum_y / n; //--- Calculate mean y double ss_tot = 0.0, ss_res = 0.0; //--- Initialize sums of squares for (int k = 0; k < n; k++) { //--- Iterate through points double x = (double)times[k]; //--- Get x (time) double y_pred = intercept + slope * x; //--- Calculate predicted y double y = prices[k]; //--- Get actual y ss_res += (y - y_pred) * (y - y_pred); //--- Accumulate residual sum ss_tot += (y - mean_y) * (y - mean_y); //--- Accumulate total sum } if (ss_tot == 0.0) return 1.0; //--- Handle constant y case return 1.0 - ss_res / ss_tot; //--- Calculate and return R-squared }
We develop the "CalculateRSquared" function, which takes arrays of times and prices, the number of points "n", and the trendline’s "slope" and "intercept" as inputs; we initialize "sum_y" to 0 and iterate through "prices" to compute the sum, then calculate the mean "mean_y" by dividing "sum_y" by "n". Then, we initialize "ss_tot" and "ss_res" for total and residual sums of squares, iterate again to compute predicted prices ("y_pred") using the formula "intercept + slope * time", accumulate residuals ("y - y_pred" squared) in "ss_res" and deviations from the mean ("y - mean_y" squared) in "ss_tot", and return 1.0 if "ss_tot" is zero (constant prices) or calculate R-squared as "1.0 - ss_res / ss_tot". We just use the R-squared formula for the calculation of the trendlines' validity. Let us now define a function to manage the trendlines.
//+------------------------------------------------------------------+ //| Check if starting point is already used | //+------------------------------------------------------------------+ bool IsStartingPointUsed(datetime time, double price, bool is_support) { for (int i = 0; i < numStartingPoints; i++) { //--- Iterate through starting points if (startingPoints[i].time == time && MathAbs(startingPoints[i].price - price) < TouchTolerance * _Point && startingPoints[i].is_support == is_support) { //--- Check match return true; //--- Return used } } return false; //--- Return not used } //+------------------------------------------------------------------+ //| Remove trendline from storage and optionally chart objects | //+------------------------------------------------------------------+ void RemoveTrendlineFromStorage(int index) { if (index < 0 || index >= numTrendlines) return; //--- Check valid index Print("Removing trendline from storage: ", trendlines[index].name); //--- Log removal if (DeleteExpiredObjects) { //--- Check deletion flag ObjectDelete(0, trendlines[index].name); //--- Delete trendline object for (int m = 0; m < trendlines[index].touch_count; m++) { //--- Iterate touches string arrow_name = trendlines[index].name + "_touch" + IntegerToString(m); //--- Generate arrow name ObjectDelete(0, arrow_name); //--- Delete touch arrow string text_name = trendlines[index].name + "_point_label" + IntegerToString(m); //--- Generate text name ObjectDelete(0, text_name); //--- Delete point label } string label_name = trendlines[index].name + "_label"; //--- Generate label name ObjectDelete(0, label_name); //--- Delete trendline label string signal_arrow = trendlines[index].name + "_signal_arrow"; //--- Generate signal arrow name ObjectDelete(0, signal_arrow); //--- Delete signal arrow string signal_text = trendlines[index].name + "_signal_text"; //--- Generate signal text name ObjectDelete(0, signal_text); //--- Delete signal text } for (int i = index; i < numTrendlines - 1; i++) { //--- Shift array trendlines[i] = trendlines[i + 1]; //--- Copy next trendline } ArrayResize(trendlines, numTrendlines - 1); //--- Resize trendlines array numTrendlines--; //--- Decrement trendlines count }
Here, we implement functions to manage trendline starting points and their cleanup. First, we develop the "IsStartingPointUsed" function, which iterates through the "startingPoints" array, checking if a given "time", "price", and "is_support" match an existing starting point within "TouchTolerance * _Point" using MathAbs, returning true if found, or false otherwise. This will help to ensure no more than 1 trendline comes from one point.
Then, we create the "RemoveTrendlineFromStorage" function, which validates the input "index" against "numTrendlines", logs the removal of the trendline’s "name" with "Print", and, if "DeleteExpiredObjects" is true, deletes chart objects using ObjectDelete for the trendline ("name"), touch arrows ("name + '_touch' + index"), point labels ("name + '_point_label' + index"), trendline label ("name + '_label'"), signal arrow ("name + '_signal_arrow'"), and signal text ("name + '_signal_text'"). Next, we shift elements in the "trendlines" array left from "index" using a loop, resize the array with ArrayResize to reduce its size by one, and decrement "numTrendlines", ensuring unique trendline starting points and proper cleanup of invalid trendlines and their chart visuals. Let us now define a function to find and draw the trendlines using the helper functions we have defined.
//+------------------------------------------------------------------+ //| Find and draw trendlines if no active one exists | //+------------------------------------------------------------------+ void FindAndDrawTrendlines(bool isSupport) { bool has_active = false; //--- Initialize active flag for (int i = 0; i < numTrendlines; i++) { //--- Iterate through trendlines if (trendlines[i].is_support == isSupport) { //--- Check type match has_active = true; //--- Set active flag break; //--- Exit loop } } if (has_active) return; //--- Exit if active trendline exists Swing swings[]; //--- Initialize swings array int numSwings; //--- Initialize swings count color lineColor; //--- Initialize line color string prefix; //--- Initialize prefix if (isSupport) { //--- Handle support case numSwings = numLows; //--- Set number of lows ArrayResize(swings, numSwings); //--- Resize swings array for (int i = 0; i < numSwings; i++) { //--- Iterate through lows swings[i].time = swingLows[i].time; //--- Copy low time swings[i].price = swingLows[i].price; //--- Copy low price } lineColor = SupportLineColor; //--- Set support line color prefix = "Trendline_Support_"; //--- Set support prefix } else { //--- Handle resistance case numSwings = numHighs; //--- Set number of highs ArrayResize(swings, numSwings); //--- Resize swings array for (int i = 0; i < numSwings; i++) { //--- Iterate through highs swings[i].time = swingHighs[i].time; //--- Copy high time swings[i].price = swingHighs[i].price; //--- Copy high price } lineColor = ResistanceLineColor; //--- Set resistance line color prefix = "Trendline_Resistance_"; //--- Set resistance prefix } if (numSwings < 2) return; //--- Exit if insufficient swings double pointValue = _Point; //--- Get point value double touch_tolerance = TouchTolerance * pointValue; //--- Calculate touch tolerance double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance int best_j = -1; //--- Initialize best j index int max_touches = 0; //--- Initialize max touches double best_rsquared = -1.0; //--- Initialize best R-squared int best_touch_indices[]; //--- Initialize best touch indices double best_slope = 0.0; //--- Initialize best slope double best_intercept = 0.0; //--- Initialize best intercept datetime best_min_time = 0; //--- Initialize best min time for (int i = 0; i < numSwings - 1; i++) { //--- Iterate through first points for (int j = i + 1; j < numSwings; j++) { //--- Iterate through second points datetime time1 = swings[i].time; //--- Get first time double price1 = swings[i].price; //--- Get first price datetime time2 = swings[j].time; //--- Get second time double price2 = swings[j].price; //--- Get second price double dt = (double)(time2 - time1); //--- Calculate time difference if (dt <= 0) continue; //--- Skip invalid time difference double initial_slope = (price2 - price1) / dt; //--- Calculate initial slope int touch_indices[]; //--- Initialize touch indices ArrayResize(touch_indices, 0); //--- Resize touch indices int touches = 0; //--- Initialize touches count ArrayResize(touch_indices, touches + 1); //--- Add first index touch_indices[touches] = i; //--- Set first index touches++; //--- Increment touches ArrayResize(touch_indices, touches + 1); //--- Add second index touch_indices[touches] = j; //--- Set second index touches++; //--- Increment touches for (int k = 0; k < numSwings; k++) { //--- Iterate through swings if (k == i || k == j) continue; //--- Skip used indices datetime tk = swings[k].time; //--- Get swing time double dk = (double)(tk - time1); //--- Calculate time difference double expected = price1 + initial_slope * dk; //--- Calculate expected price double actual = swings[k].price; //--- Get actual price if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch within tolerance ArrayResize(touch_indices, touches + 1); //--- Add index touch_indices[touches] = k; //--- Set index touches++; //--- Increment touches } } if (touches >= MinTouches) { //--- Check minimum touches ArraySort(touch_indices); //--- Sort touch indices bool valid_spacing = true; //--- Initialize spacing flag for (int m = 0; m < touches - 1; m++) { //--- Iterate through touches int idx1 = touch_indices[m]; //--- Get first index int idx2 = touch_indices[m + 1]; //--- Get second index int bar1 = iBarShift(_Symbol, _Period, swings[idx1].time); //--- Get first bar int bar2 = iBarShift(_Symbol, _Period, swings[idx2].time); //--- Get second bar int diff = MathAbs(bar1 - bar2); //--- Calculate bar difference if (diff < MinBarSpacing) { //--- Check minimum spacing valid_spacing = false; //--- Mark invalid spacing break; //--- Exit loop } } if (valid_spacing) { //--- Check valid spacing datetime touch_times[]; //--- Initialize touch times double touch_prices[]; //--- Initialize touch prices ArrayResize(touch_times, touches); //--- Resize times array ArrayResize(touch_prices, touches); //--- Resize prices array for (int m = 0; m < touches; m++) { //--- Iterate through touches int idx = touch_indices[m]; //--- Get index touch_times[m] = swings[idx].time; //--- Set time touch_prices[m] = swings[idx].price; //--- Set price } double slope = initial_slope; //--- Use initial slope from two points double intercept = price1 - slope * (double)time1; //--- Calculate intercept double rsquared = CalculateRSquared(touch_times, touch_prices, touches, slope, intercept); //--- Calculate R-squared if (rsquared >= MinRSquared) { //--- Check minimum R-squared int adjusted_touch_indices[]; //--- Initialize adjusted indices ArrayResize(adjusted_touch_indices, touches); //--- Resize to current touches ArrayCopy(adjusted_touch_indices, touch_indices); //--- Copy indices int adjusted_touches = touches; //--- Set adjusted touches if (adjusted_touches >= MinTouches) { //--- Check minimum adjusted touches datetime temp_min_time = swings[adjusted_touch_indices[0]].time; //--- Get min time double temp_ref_price = intercept + slope * (double)temp_min_time; //--- Calculate ref price if (ValidateTrendline(isSupport, temp_min_time, temp_min_time, temp_ref_price, slope, pen_tolerance)) { //--- Validate trendline datetime temp_max_time = swings[adjusted_touch_indices[adjusted_touches - 1]].time; //--- Get max time double temp_max_price = intercept + slope * (double)temp_max_time; //--- Calculate max price double angle = CalculateAngle(temp_min_time, temp_ref_price, temp_max_time, temp_max_price); //--- Calculate angle double abs_angle = MathAbs(angle); //--- Get absolute angle if (abs_angle >= MinAngle && abs_angle <= MaxAngle) { //--- Check angle range if (adjusted_touches > max_touches || (adjusted_touches == max_touches && rsquared > best_rsquared)) { //--- Check better trendline max_touches = adjusted_touches; //--- Update max touches best_rsquared = rsquared; //--- Update best R-squared best_j = j; //--- Update best j best_slope = slope; //--- Update best slope best_intercept = intercept; //--- Update best intercept best_min_time = temp_min_time; //--- Update best min time ArrayResize(best_touch_indices, adjusted_touches); //--- Resize best indices ArrayCopy(best_touch_indices, adjusted_touch_indices); //--- Copy indices } } } } } } } } } if (max_touches < MinTouches) { //--- Check insufficient touches string type = isSupport ? "Support" : "Resistance"; //--- Set type string return; //--- Exit function } int touch_indices[]; //--- Initialize touch indices ArrayResize(touch_indices, max_touches); //--- Resize touch indices ArrayCopy(touch_indices, best_touch_indices); //--- Copy best indices int touches = max_touches; //--- Set touches count datetime min_time = best_min_time; //--- Set min time double price_min = best_intercept + best_slope * (double)min_time; //--- Calculate min price datetime max_time = swings[touch_indices[touches - 1]].time; //--- Set max time double price_max = best_intercept + best_slope * (double)max_time; //--- Calculate max price datetime start_time_check = min_time; //--- Set start time check double start_price_check = price_min; //--- Set start price check (approximate if not exact) if (IsStartingPointUsed(start_time_check, start_price_check, isSupport)) { //--- Check used starting point return; //--- Skip if used } datetime time_end = iTime(_Symbol, _Period, 0) + PeriodSeconds(_Period) * ExtensionBars; //--- Calculate end time double dk_end = (double)(time_end - min_time); //--- Calculate end time difference double price_end = price_min + best_slope * dk_end; //--- Calculate end price string unique_name = prefix + TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); //--- Generate unique name if (ObjectFind(0, unique_name) < 0) { //--- Check if trendline exists ObjectCreate(0, unique_name, OBJ_TREND, 0, min_time, price_min, time_end, price_end); //--- Create trendline ObjectSetInteger(0, unique_name, OBJPROP_COLOR, lineColor); //--- Set color ObjectSetInteger(0, unique_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(0, unique_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, unique_name, OBJPROP_RAY_RIGHT, false); //--- Disable right ray ObjectSetInteger(0, unique_name, OBJPROP_RAY_LEFT, false); //--- Disable left ray ObjectSetInteger(0, unique_name, OBJPROP_BACK, false); //--- Set to foreground } ArrayResize(trendlines, numTrendlines + 1); //--- Resize trendlines array trendlines[numTrendlines].name = unique_name; //--- Set trendline name trendlines[numTrendlines].start_time = min_time; //--- Set start time trendlines[numTrendlines].end_time = time_end; //--- Set end time trendlines[numTrendlines].start_price = price_min; //--- Set start price trendlines[numTrendlines].end_price = price_end; //--- Set end price trendlines[numTrendlines].slope = best_slope; //--- Set slope trendlines[numTrendlines].is_support = isSupport; //--- Set type trendlines[numTrendlines].touch_count = touches; //--- Set touch count trendlines[numTrendlines].creation_time = TimeCurrent(); //--- Set creation time trendlines[numTrendlines].is_signaled = false; //--- Set signaled flag ArrayResize(trendlines[numTrendlines].touch_indices, touches); //--- Resize touch indices ArrayCopy(trendlines[numTrendlines].touch_indices, touch_indices); //--- Copy touch indices numTrendlines++; //--- Increment trendlines count ArrayResize(startingPoints, numStartingPoints + 1); //--- Resize starting points array startingPoints[numStartingPoints].time = start_time_check;//--- Set starting point time startingPoints[numStartingPoints].price = start_price_check; //--- Set starting point price startingPoints[numStartingPoints].is_support = isSupport; //--- Set starting point type numStartingPoints++; //--- Increment starting points count if (DrawTouchArrows) { //--- Check draw arrows for (int m = 0; m < touches; m++) { //--- Iterate through touches int idx = touch_indices[m]; //--- Get touch index datetime tk_time = swings[idx].time; //--- Get touch time double tk_price = swings[idx].price; //--- Get touch price string arrow_name = unique_name + "_touch" + IntegerToString(m); //--- Generate arrow name if (ObjectFind(0, arrow_name) < 0) { //--- Check if arrow exists ObjectCreate(0, arrow_name, OBJ_ARROW, 0, tk_time, tk_price); //--- Create touch arrow ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, 159); //--- Set arrow code ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, lineColor); //--- Set color ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false); //--- Set to foreground } } } double angle = CalculateAngle(min_time, price_min, max_time, price_max); //--- Calculate angle string type = isSupport ? "Support" : "Resistance"; //--- Set type string Print(type + " Trendline " + unique_name + " drawn with " + IntegerToString(touches) + " touches. Inclination angle: " + DoubleToString(angle, 2) + " degrees."); //--- Log trendline if (DrawLabels) { //--- Check draw labels datetime mid_time = min_time + (max_time - min_time) / 2; //--- Calculate mid time double dk_mid = (double)(mid_time - min_time); //--- Calculate mid time difference double mid_price = price_min + best_slope * dk_mid; //--- Calculate mid price double label_offset = 20 * _Point * (isSupport ? -1 : 1); //--- Calculate label offset double label_price = mid_price + label_offset; //--- Calculate label price int label_anchor = isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM;//--- Set label anchor string label_text = type + " Trendline"; //--- Set label text string label_name = unique_name + "_label"; //--- Generate label name if (ObjectFind(0, label_name) < 0) { //--- Check if label exists ObjectCreate(0, label_name, OBJ_TEXT, 0, mid_time, label_price); //--- Create label ObjectSetString(0, label_name, OBJPROP_TEXT, label_text); //--- Set text ObjectSetInteger(0, label_name, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8); //--- Set font size ObjectSetInteger(0, label_name, OBJPROP_ANCHOR, label_anchor); //--- Set anchor ObjectSetDouble(0, label_name, OBJPROP_ANGLE, angle); //--- Set angle ObjectSetInteger(0, label_name, OBJPROP_BACK, false); //--- Set to foreground } color point_label_color = isSupport ? clrSaddleBrown : clrDarkGoldenrod; //--- Set point label color double point_text_offset = 20.0 * _Point; //--- Set point text offset for (int m = 0; m < touches; m++) { //--- Iterate through touches int idx = touch_indices[m]; //--- Get touch index datetime tk_time = swings[idx].time; //--- Get touch time double tk_price = swings[idx].price; //--- Get touch price double text_price; //--- Initialize text price int point_text_anchor; //--- Initialize text anchor if (isSupport) { //--- Handle support text_price = tk_price - point_text_offset; //--- Set text price below point_text_anchor = ANCHOR_LEFT; //--- Set left anchor } else { //--- Handle resistance text_price = tk_price + point_text_offset; //--- Set text price above point_text_anchor = ANCHOR_BOTTOM; //--- Set bottom anchor } string text_name = unique_name + "_point_label" + IntegerToString(m); //--- Generate text name string point_text = "Pt " + IntegerToString(m + 1); //--- Set point text if (ObjectFind(0, text_name) < 0) { //--- Check if text exists ObjectCreate(0, text_name, OBJ_TEXT, 0, tk_time, text_price); //--- Create text ObjectSetString(0, text_name, OBJPROP_TEXT, point_text); //--- Set text ObjectSetInteger(0, text_name, OBJPROP_COLOR, point_label_color); //--- Set color ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 8); //--- Set font size ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, point_text_anchor); //--- Set anchor ObjectSetDouble(0, text_name, OBJPROP_ANGLE, 0); //--- Set angle ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground } } } }
Here, we implement the trendline detection and visualization logic. First, in the "FindAndDrawTrendlines" function, we check for existing trendlines of type "isSupport" in "trendlines", setting "has_active" to true and exiting if found. Then, we initialize a "swings" array, copying "swingLows" or "swingHighs" based on "isSupport", setting "lineColor" to "SupportLineColor" or "ResistanceLineColor" and "prefix" to "Trendline_Support_" or "Trendline_Resistance_", and exit if fewer than two swings exist.
Next, we calculate tolerances ("TouchTolerance" and "PenetrationTolerance" scaled by _Point) and iterate through pairs of swing points to compute "initial_slope", collecting touch points within "touch_tolerance" into "touch_indices". We validate touches with "MinTouches" and "MinBarSpacing" using iBarShift and ArraySort, compute "slope" and "intercept", and evaluate "CalculateRSquared" and "ValidateTrendline" to select the best trendline based on "max_touches" and "best_rsquared". If valid, we draw the trendline using "ObjectCreate" (OBJ_TREND) with "unique_name", set properties like OBJPROP_COLOR, "OBJPROP_STYLE", and disable rays, then store it in "trendlines" with details like "start_time", "end_time" (extended by "ExtensionBars"), and "touch_indices". We update "startingPoints" with "IsStartingPointUsed" to prevent duplicates, and if "DrawTouchArrows" is true, draw arrows (OBJ_ARROW) at touch points with "lineColor" and appropriate anchors.
If "DrawLabels" is true, we add a trendline label (OBJ_TEXT) with "type + ' Trendline'" at the midpoint, angled via "CalculateAngle", and point labels ("Pt 1", etc.) with colors "clrSaddleBrown" or "clrDarkGoldenrod", logging the trendline details. What now remains is the management of the existing trendlines via continuous updates and checking for crosses for signals. We will incorporate all the logic in a single function for simplicity.
//+------------------------------------------------------------------+ //| Update trendlines and check for signals | //+------------------------------------------------------------------+ void UpdateTrendlines() { datetime current_time = iTime(_Symbol, _Period, 0); //--- Get current time double pointValue = _Point; //--- Get point value double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance double touch_tolerance = TouchTolerance * pointValue; //--- Calculate touch tolerance for (int i = numTrendlines - 1; i >= 0; i--) { //--- Iterate trendlines backward string type = trendlines[i].is_support ? "Support" : "Resistance"; //--- Determine trendline type string name = trendlines[i].name; //--- Get trendline name if (current_time > trendlines[i].end_time) { //--- Check if expired PrintFormat("%s trendline %s is no longer valid (expired). End time: %s, Current time: %s.", type, name, TimeToString(trendlines[i].end_time), TimeToString(current_time)); //--- Log expiration RemoveTrendlineFromStorage(i); //--- Remove trendline continue; //--- Skip to next } datetime prev_bar_time = iTime(_Symbol, _Period, 1); //--- Get previous bar time double dk = (double)(prev_bar_time - trendlines[i].start_time); //--- Calculate time difference double line_price = trendlines[i].start_price + trendlines[i].slope * dk; //--- Calculate line price double prev_close = iClose(_Symbol, _Period, 1); //--- Get previous bar close double prev_low = iLow(_Symbol, _Period, 1); //--- Get previous bar low double prev_high = iHigh(_Symbol, _Period, 1); //--- Get previous bar high bool broken = false; //--- Initialize broken flag if (BreakoutType == BREAKOUT_CLOSE) { //--- Check breakout on close if (trendlines[i].is_support && prev_close < line_price) { //--- Support break by close PrintFormat("%s trendline %s is no longer valid (broken by close). Line price: %.5f, Prev close: %.5f.", type, name, line_price, prev_close); //--- Log break broken = true; //--- Set broken flag } else if (!trendlines[i].is_support && prev_close > line_price) { //--- Resistance break by close PrintFormat("%s trendline %s is no longer valid (broken by close). Line price: %.5f, Prev close: %.5f.", type, name, line_price, prev_close); //--- Log break broken = true; //--- Set broken flag } } else if (BreakoutType == BREAKOUT_CANDLE) { //--- Check breakout on entire candle if (trendlines[i].is_support && prev_high < line_price) { //--- Entire candle below support PrintFormat("%s trendline %s is no longer valid (entire candle below). Line price: %.5f, Prev high: %.5f.", type, name, line_price, prev_high); //--- Log break broken = true; //--- Set broken flag } else if (!trendlines[i].is_support && prev_low > line_price) { //--- Entire candle above resistance PrintFormat("%s trendline %s is no longer valid (entire candle above). Line price: %.5f, Prev low: %.5f.", type, name, line_price, prev_low); //--- Log break broken = true; //--- Set broken flag } } if (broken && EnableTradingSignals && !trendlines[i].is_signaled) { //--- Check for breakout signal bool signaled = false; //--- Initialize signaled flag string signal_type = ""; //--- Initialize signal type color signal_color = clrNONE; //--- Initialize signal color int arrow_code = 0; //--- Initialize arrow code int anchor = 0; //--- Initialize anchor double text_angle = 0.0; //--- Initialize text angle double text_offset = 0.0; //--- Initialize text offset double text_price = 0.0; //--- Initialize text price int text_anchor = 0; //--- Initialize text anchor if (trendlines[i].is_support) { //--- Support break: SELL signaled = true; //--- Set signaled flag signal_type = "SELL BREAK"; //--- Set sell break signal signal_color = clrRed; //--- Set red color arrow_code = 218; //--- Set down arrow anchor = ANCHOR_BOTTOM; //--- Set bottom anchor text_angle = 90.0; //--- Set vertical downward text_offset = 20 * pointValue; //--- Set text offset text_price = line_price + text_offset; //--- Calculate text price text_anchor = ANCHOR_BOTTOM; //--- Set bottom anchor double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get bid price double SL = NormalizeDouble(line_price + inpSLPoints * _Point, _Digits); //--- SL above the line double risk = SL - Bid; //--- Calculate risk double TP = NormalizeDouble(Bid - risk * inpRRRatio, _Digits); //--- Calculate take profit obj_Trade.Sell(inpLot, _Symbol, Bid, SL, TP); //--- Execute sell trade } else { //--- Resistance break: BUY signaled = true; //--- Set signaled flag signal_type = "BUY BREAK"; //--- Set buy break signal signal_color = clrBlue; //--- Set blue color arrow_code = 217; //--- Set up arrow anchor = ANCHOR_TOP; //--- Set top anchor text_angle = -90.0; //--- Set vertical upward text_offset = -20 * pointValue; //--- Set text offset text_price = line_price + text_offset; //--- Calculate text price text_anchor = ANCHOR_LEFT; //--- Set left anchor double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get ask price double SL = NormalizeDouble(line_price - inpSLPoints * _Point, _Digits); //--- SL below the line double risk = Ask - SL; //--- Calculate risk double TP = NormalizeDouble(Ask + risk * inpRRRatio, _Digits); //--- Calculate take profit obj_Trade.Buy(inpLot, _Symbol, Ask, SL, TP); //--- Execute buy trade } if (signaled) { //--- Check if signaled PrintFormat("Breakout signal generated for %s trendline %s: %s at price %.5f, time %s.", type, name, signal_type, line_price, TimeToString(current_time)); //--- Log signal string arrow_name = name + "_signal_arrow"; //--- Generate signal arrow name if (ObjectFind(0, arrow_name) < 0) { //--- Check if arrow exists ObjectCreate(0, arrow_name, OBJ_ARROW, 0, prev_bar_time, line_price); //--- Create signal arrow ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, arrow_code); //--- Set arrow code ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, anchor); //--- Set anchor ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, signal_color); //--- Set color ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false); //--- Set to foreground } string text_name = name + "_signal_text"; //--- Generate signal text name if (ObjectFind(0, text_name) < 0) { //--- Check if text exists ObjectCreate(0, text_name, OBJ_TEXT, 0, prev_bar_time, text_price); //--- Create signal text ObjectSetString(0, text_name, OBJPROP_TEXT, " " + signal_type); //--- Set text content ObjectSetInteger(0, text_name, OBJPROP_COLOR, signal_color); //--- Set color ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 10); //--- Set font size ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, text_anchor); //--- Set anchor ObjectSetDouble(0, text_name, OBJPROP_ANGLE, text_angle); //--- Set angle ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground } trendlines[i].is_signaled = true; //--- Set signaled flag } } if (broken) { //--- Remove if broken RemoveTrendlineFromStorage(i); //--- Remove trendline } } }
To implement the trendline update and breakout trading logic, in the "UpdateTrendlines" function, we retrieve the current bar’s time with iTime and calculate "pointValue", "pen_tolerance" ("PenetrationTolerance * pointValue"), and "touch_tolerance" ("TouchTolerance * pointValue"). Then, we iterate backward through "trendlines", determining the "type" (Support or Resistance) and "name", and check if the trendline has expired using "current_time > end_time", logging with PrintFormat and removing it with "RemoveTrendlineFromStorage" if expired.
Next, we calculate the trendline’s price at the previous bar ("prev_bar_time" from "iTime") using "start_price + slope * (prev_bar_time - start_time)", and check for breakouts: for "BreakoutType" as "BREAKOUT_CLOSE", we verify if the support trendline’s "prev_close" (iClose) is below "line_price" or resistance’s is above, logging with "PrintFormat" and setting "broken" to true; for "BREAKOUT_CANDLE", we check if the support’s "prev_high" ("iHigh") is below or resistance’s "prev_low" (iLow) is above "line_price", logging and setting it as broken.
If broken and "EnableTradingSignals" is true with "is_signaled" false, we set trade parameters: for support (sell), we use "signal_type" as "SELL BREAK", red color, down arrow (218), and calculate bid (SymbolInfoDouble), stop loss ("line_price + inpSLPoints * _Point"), risk, and take profit using "inpRRRatio", executing with "obj_Trade.Sell"; for resistance (buy), we use "BUY BREAK", blue color, up arrow (217), and calculate ask, stop loss, and take profit, executing with "obj_Trade.Buy". We then draw a signal arrow ("OBJ_ARROW") and text ("OBJ_TEXT") with "ObjectCreate", setting properties like "OBJPROP_ARROWCODE", "OBJPROP_ANCHOR", and "OBJPROP_COLOR", log the signal with "PrintFormat", set "is_signaled" to true, and remove broken trendlines from storage. The choice of the arrow codes to use is dependent on you. Here is a list of codes you could use from the MQL5-defined Wingdings codes.
We can now call these functions in the OnTick event handler for the system to give tick-based feedback.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (!IsNewBar()) return; //--- Exit if not new bar DetectSwings(); //--- Detect swings UpdateTrendlines(); //--- Update trendlines FindAndDrawTrendlines(true); //--- Find/draw support trendlines }
In the OnTick function, we first call "IsNewBar" to check for a new bar, exiting if none to optimize performance. If a new bar is detected, we invoke "DetectSwings" to identify swing highs and lows, followed by "UpdateTrendlines" to check for breakouts or expired trendlines and execute trades if applicable. Then, we call "FindAndDrawTrendlines" with "true" to detect and draw support trendlines, ensuring only valid trendlines are visualized. Upon compilation, we get the following outcome.
From the image, we can see that we find, analyze, draw, and trade the trendline upon breakout. Expired lines are also removed from the storage array successfully. We can achieve the same thing for resistance trendlines as well by calling the same function as support, but having the input parameter as false.
//--- other ontick functions FindAndDrawTrendlines(false); //--- Find/draw resistance trendlines //---
Upon passing the function and compilation, we get the following outcome.
From the image, we can see that we detect and trade the resistance trendlines as well. When we test and combine everything, we get the following outcome.
From the image, we can see that we detect the trendlines, visualize them, and act upon them when the price breaks them, 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:
Backtest report:
Conclusion
In conclusion, we’ve developed a trendline breakout system in MQL5 utilizing swing points to identify and validate support and resistance trendlines with R-squared goodness of fit, executing breakout trades with customizable risk parameters. The system enhances trading decisions with dynamic visualizations, including trendlines, touch point arrows, and labels, ensuring clear market analysis.
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.
By implementing this trendline breakout strategy, you’re equipped to capture market movements, ready for further customization in your trading journey. Happy trading!
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.





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