You are currently viewing the resource titled "Creating a Pivot-Based Trend Indicator with Canvas Gradient in MQL5 for MT5". This page provides detailed information about the resource, including its content, attached files, and recent discussions. Feel free to explore, download available files if logged in, and join the community conversation below!
Banner Image

Introduction


In this article, we develop a Pivot-Based Trend Indicator in MetaQuotes Language 5 (MQL5) that not only calculates fast and slow pivot lines but also detects trend directions with clear directional arrows 📈➡️. The indicator can extend pivot lines forward on the chart for better visibility, and—for enhanced readability—it offers optional canvas gradients to highlight bullish or bearish areas, making trend zones easy to spot at a glance 🎨✨. Here’s what we’ll cover in this article:

  1. Understanding the Pivot-Based Trend Indicator Framework – grasp how pivot calculations and trend detection work together.
  2. Implementation in MQL5 – step-by-step coding of the indicator with all visual features.
  3. Backtesting – testing the indicator on historical data to validate its effectiveness.
  4. Conclusion – summarizing insights and visual setup options.

By the end, you’ll have a fully functional MQL5 indicator for pivot trend detection, complete with flexible visual settings and trend clarity. Let’s dive in and start building this powerful tool! 🚀

Understanding the Pivot-Based Trend Indicator Framework


The Pivot Trend Detector indicator is a technical tool designed to make trend detection intuitive and visually engaging 📊✨. It uses fast and slow pivot lines, calculated from high/low ranges over user-defined periods, to identify trend directions and potential reversals, smoothing out price data while highlighting shifts with color-coded lines and arrows. The indicator features three main elements:

  • Slow line – acts as the primary trend reference, showing up or down depending on the price position.
  • Fast dotted line – changes color whenever the trend flips, providing early warnings of momentum shifts.
  • Arrows – mark the start of new trends whenever the price crosses both lines, offering clear entry signals 🚀.

This setup helps spot momentum changes: the slow line acts as dynamic support/resistance, while the fast line gives early indications of potential trend flips. Users can adjust the periods to adapt to market volatility. In practice, an uptrend is confirmed when the price stays above the slow line (drawn in the uptrend color), and a downtrend is confirmed when the price remains below the slow line (drawn in the downtrend color). Arrows signal entry points on crosses, with optional extensions projecting the lines forward for future trend anticipation. Gradient filling between the lines visualizes trend strength, fading from slow to fast, making it easy to “read” the trend area at a glance 🎨👀. The indicator’s architecture is built on a clear separation of responsibilities: input parameters, indicator buffers, and graphical properties. We start by defining key inputs—fast/slow periods, colors, opacity, arrow code, and line extensions—which dictate the indicator’s behavior. We then allocate eight buffers to store slow up/down lines, fast lines with colors, trend arrows with colors, and internal calculations for trend and slow values. These buffers are linked to graphical plots, with properties such as type (line, color line, arrow), color, width, and shift configured using MQL5’s built-in functions. Additionally, a canvas class is used to fill the space between lines with gradients, allowing the indicator to dynamically adapt to market volatility 🌊📈. In a nutshell, this structure gives us a powerful, flexible, and visually informative pivot trend indicator. Here’s an example of what we will achieve with this setup:
INDICATOR'S ARCHITECTURE

Implementation in MQL5


To create the indicator in MQL5, start by opening MetaEditor. In the Navigator, locate the Indicators folder, click on the "New" tab, and follow the prompts to create a new indicator file 📝✨. Once the file is ready, we move into the coding environment to define the indicator’s properties and settings. This includes specifying the number of buffers, the plots, and the individual line properties such as color, width, and label. These settings form the backbone of the indicator, ensuring each line, arrow, and visual element is drawn correctly and responds dynamically to price action 📊🎨.
//+------------------------------------------------------------------+
//|                                      1. Pivot Trend Detector.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 indicator_chart_window
#property indicator_buffers 8
#property indicator_plots 4

#property indicator_label1 "PTD slow line up"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2

#property indicator_label2 "PTD slow line down"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrCrimson
#property indicator_width2 2

#property indicator_label3 "PTD fast line"
#property indicator_type3 DRAW_COLOR_LINE
#property indicator_color3 clrDodgerBlue,clrCrimson
#property indicator_style3 STYLE_DOT

#property indicator_label4 "PTD trend start"
#property indicator_type4 DRAW_COLOR_ARROW
#property indicator_color4 clrDodgerBlue,clrCrimson
#property indicator_width4 2
We start the implementation by defining the indicator’s metadata using property directives in MQL5 📄✨. We specify that the indicator will draw in the main chart window with indicator_chart_window, allocate 8 buffers using indicator_buffers, and configure 4 plots through indicator_plots. The plots are defined as follows:
  1. First plot – labeled "PTD slow line up", type DRAW_LINE, color DodgerBlue, width 2.
  2. Second plot – labeled "PTD slow line down", type DRAW_LINE, color Crimson, width 2.
  3. Third plot – labeled "PTD fast line", type DRAW_COLOR_LINE, colors DodgerBlue and Crimson, style dot, to visually indicate trend flips.
  4. Fourth plot – labeled "PTD trend start", type DRAW_ARROW, colors DodgerBlue and Crimson, width 2, to mark the start of new trends clearly.

These settings establish the visual structure: slow up/down lines for trend reference, a fast line that changes color with momentum shifts, and arrows marking trend starts 🚀📊. Next, we define the input parameters and global variables that will be used throughout the program, setting the stage for calculations, buffer management, and dynamic plotting.
#include <Canvas/Canvas.mqh>
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas obj_Canvas;                                               //--- Canvas object
//--- input parameters
input int    fastPeriod       = 5;                                // Fast period
input int    slowPeriod       = 10;                               // Slow period
input color  upColor          = clrDodgerBlue;                    // Up trend color
input color  downColor        = clrCrimson;                       // Down trend color
input int    fillOpacity      = 128;                              // Fill opacity (0-255)
input int    arrowCode        = 77;                               // Arrow code for trend start
input bool   showExtensions   = true;                             // Show line extensions
input bool   enableFilling    = true;                             // Enable canvas fill (disable for speed)
input int    extendBars       = 1;                                // Extension bars to protrude lines/fill

//--- indicator buffers
double slowLineUpBuffer[],slowLineDownBuffer[],slowLineBuffer[],fastLineBuffer[],fastLineColorBuffer[],trendArrowColorBuffer[],trendArrowBuffer[],trendBuffer[]; //--- Indicator buffers

//--- chart properties
int    currentChartWidth = 0;                                     //--- Current chart width
int    currentChartHeight = 0;                                    //--- Current chart height
int    currentChartScale = 0;                                     //--- Current chart scale
int    firstVisibleBarIndex = 0;                                  //--- First visible bar index
int    visibleBarsCount = 0;                                      //--- Visible bars count
double minPrice = 0.0;                                            //--- Minimum price
double maxPrice = 0.0;                                            //--- Maximum price

//--- optimization flags
static datetime lastRedrawTime = 0;                               //--- Last redraw time
static double   previousTrend = -1;                               //--- Previous trend
string objectPrefix = "PTD_";                                     //--- Object prefix
First, we bring in the magic ✨ by including the canvas library with #include <Canvas/Canvas.mqh>. This little gem lets us do some custom graphical wizardry, like painting smooth gradient fills between indicator lines — perfect for making your charts not just informative, but also eye-candy. Next, we declare "obj_Canvas" as a global instance of the "CCanvas" class. Think of it as our personal painter’s canvas 🎨, where all the area fills and drawing fun happen. Then come the input parameters, our customization toolbox 🛠️:
  • "fastPeriod" defaults to 5 — this sets the speed for our fast pivot calculation window.
  • "slowPeriod" is 10 — the chill, slower sibling for trend tracking.
  • "upColor" is set to dodger blue, because uptrends deserve some bright vibes 🌊.
  • "downColor" is crimson, because downtrends need a dramatic flair 🔥.
  • "fillOpacity" is 128 — a nice half-transparent fill (remember, 0 is invisible, 255 is fully solid).
  • "arrowCode" is 77, picking the Wingdings symbol for trend starts — arrows that point you in the right direction 🏹.
  • "showExtensions" is true, letting lines sneak a little beyond the current bar, because sometimes charts like to stretch their legs 😏.
  • "enableFilling" is true — turn it off if you’re feeling lazy and want performance boost mode 🚀.
  • "extendBars" is 1 — this tells your lines how far to politely extend into the future.

And hey, if Wingdings isn’t your thing, you can swap "arrowCode" for any MQL5-defined symbol — make your chart your own emoji playground 😉.
MQL5 WINGDINGS
Next up, we roll out eight global arrays to act as our trusty indicator buffers 🧰:
  • "slowLineUpBuffer" and "slowLineDownBuffer" handle the up and down slow lines separately, because trends like their personal space 😉.
  • "slowLineBuffer" works behind the scenes for internal slow calculations — think of it as the backstage crew 🎭.
  • "fastLineBuffer" takes care of the fast line, our nimble little trend scout 🏃‍♂️.
  • "fastLineColorBuffer" keeps track of its colors, because even lines need a wardrobe 👗.
  • "trendArrowColorBuffer" and "trendArrowBuffer" manage arrow positions and colors, guiding you with flair 🏹.
  • "trendBuffer" monitors the overall trend state, the big picture checker 🧐.

We also define some globals for chart properties to keep everything tidy on the screen:
  • "currentChartWidth" and "currentChartHeight" start at 0 — the canvas is blank at first 🖼️.
  • "currentChartScale" at 0, "firstVisibleBarIndex" at 0 for the leftmost bar, and "visibleBarsCount" at 0 — because we like to know exactly what’s in view 👀.
  • "minPrice" and "maxPrice" at 0.0 define the chart’s price range — think of them as the floor and ceiling 🏠.

For a touch of optimization magic ✨, we use:
  • static "lastRedrawTime" at 0 to debounce redraws (no chart overreaction here 😅).
  • static "previousTrend" at -1 for trend change detection, so we only act when necessary.
  • "objectPrefix" as "PTD_" to neatly label any extensions, keeping things organized 🏷️.

And voilà! Once we hit compilation, the input parameters window pops up, ready for your customization adventure 🚀.
INPUTS WINDOW
With the inputs done, we can move on to the initialization event handler and initialize the program. Here is the logic we use for that.
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit() {
// Set chart properties
   currentChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);      //--- Get chart width
   currentChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);    //--- Get chart height
   currentChartScale = (int)ChartGetInteger(0, CHART_SCALE);                //--- Get chart scale
   firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get first visible bar
   visibleBarsCount = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);          //--- Get visible bars
   minPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);                        //--- Get min price
   maxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);                        //--- Get max price
   
// Indicator buffers
   SetIndexBuffer(0,slowLineUpBuffer,INDICATOR_DATA);             //--- Set slow up buffer
   SetIndexBuffer(1,slowLineDownBuffer,INDICATOR_DATA);           //--- Set slow down buffer
   SetIndexBuffer(2,fastLineBuffer,INDICATOR_DATA);               //--- Set fast buffer
   SetIndexBuffer(3,fastLineColorBuffer,INDICATOR_COLOR_INDEX);   //--- Set fast color buffer
   SetIndexBuffer(4,trendArrowBuffer,INDICATOR_DATA);             //--- Set arrow buffer
   SetIndexBuffer(5,trendArrowColorBuffer,INDICATOR_COLOR_INDEX); //--- Set arrow color buffer
   SetIndexBuffer(6,trendBuffer,INDICATOR_CALCULATIONS);          //--- Set trend buffer
   SetIndexBuffer(7,slowLineBuffer,INDICATOR_CALCULATIONS);       //--- Set slow buffer
   
// Plot settings
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,slowPeriod);             //--- Set slow draw begin
   PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,slowPeriod);             //--- Set slow draw begin
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,fastPeriod);             //--- Set fast draw begin
   PlotIndexSetInteger(3,PLOT_DRAW_BEGIN,fastPeriod);             //--- Set fast draw begin
   PlotIndexSetInteger(4,PLOT_DRAW_BEGIN,slowPeriod);             //--- Set arrow draw begin
   PlotIndexSetInteger(3,PLOT_ARROW,arrowCode);                   //--- Set arrow code
   
// Line extensions
   PlotIndexSetInteger(0,PLOT_SHIFT,extendBars);                  //--- Set slow up shift
   PlotIndexSetInteger(1,PLOT_SHIFT,extendBars);                  //--- Set slow down shift
   PlotIndexSetInteger(2,PLOT_SHIFT,extendBars);                  //--- Set fast shift
   PlotIndexSetInteger(3,PLOT_SHIFT,0);                           //--- Set arrow shift
   
// Set plot colors dynamically
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, upColor);           //--- Set slow up color
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, downColor);         //--- Set slow down color
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, upColor);           //--- Set fast up color
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, downColor);         //--- Set fast down color
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, upColor);           //--- Set arrow up color
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 1, downColor);         //--- Set arrow down color
   
// Short name
   string shortName = "PTD(" + IntegerToString(fastPeriod) + "," + IntegerToString(slowPeriod) + ")"; //--- Set short name
   IndicatorSetString(INDICATOR_SHORTNAME, shortName);            //--- Set indicator short name

   return(INIT_SUCCEEDED);                                        //--- Return success
}
In the "OnInit" event handler — which springs to life whenever the indicator is attached to a chart or reloaded 🔄 — our first mission is to capture the current chart’s dimensions and view parameters. Why? Because these values are essential for adaptive canvas rendering later on 🎨. We grab the chart’s width in pixels with "ChartGetInteger" using "CHART_WIDTH_IN_PIXELS" and store it in "currentChartWidth", the height with "CHART_HEIGHT_IN_PIXELS" into "currentChartHeight", and the scale with "CHART_SCALE" into "currentChartScale". Next, we fetch the "firstVisibleBarIndex" with "CHART_FIRST_VISIBLE_BAR" and the number of "visibleBarsCount" with "CHART_VISIBLE_BARS". For the price range, "minPrice" comes from "ChartGetDouble" with "CHART_PRICE_MIN" and "maxPrice" from "CHART_PRICE_MAX". With these values in hand, our indicator can adapt its drawings to whatever part of the chart you’re looking at — smart and flexible 😎. Once the chart’s dimensions are safely stored, we move on to mapping our eight buffers to plots. Using "SetIndexBuffer" with the appropriate types, we assign: "slowLineUpBuffer" to index 0 as data, "slowLineDownBuffer" to 1 as data, "fastLineBuffer" to 2 as data, "fastLineColorBuffer" to 3 as color index, "trendArrowBuffer" to 4 as data, "trendArrowColorBuffer" to 5 as color index, "trendBuffer" to 6 as calculations, and "slowLineBuffer" to 7 as calculations. This neat setup ensures every line, arrow, and trend state has its own special home 🏠 on the chart. Next, we fine-tune how the plots are drawn. Using "PlotIndexSetInteger" and "PLOT_DRAW_BEGIN", we make the slow plots start from "slowPeriod", while the fast and arrow plots start from "fastPeriod" or "slowPeriod", depending on their role. The arrow plot gets its symbol from "PLOT_ARROW" and "arrowCode", while line extensions use "PLOT_SHIFT" — "extendBars" for slow up/down lines and fast lines, 0 for arrows. Colors are dynamically set with "PlotIndexSetInteger" and "PLOT_LINE_COLOR": index 0 gets "upColor", index 1 "downColor", index 2 (fast line) uses "upColor" at 0 and "downColor" at 1, and arrows (index 4) follow the same scheme. Finally, we give the indicator a friendly, descriptive short name ✨. We create a string "PTD(" plus "fastPeriod" and "slowPeriod" separated by a comma plus ")", then set it with "IndicatorSetString" and "INDICATOR_SHORTNAME". With everything ready, "INIT_SUCCEEDED" is returned to confirm the initialization was a success ✅. And voilà! Upon compilation, the input parameters and indicator plots appear as expected, ready for action on your chart.
INDICATOR INITIALIZATION
From the chart image, it’s clear that our indicator loads perfectly on attach ✅. If you peek into the data window, you’ll notice all the buffers are already there, patiently waiting for their values to come alive. The next step is to populate these buffers and perform the indicator calculations according to our strategy 🔍. This is where the real magic happens — turning empty arrays into meaningful trend lines, arrows, and color-coded signals that actually tell us what the market is doing.

All of this action takes place in the "OnCalculate" event handler, which we’ll tackle step by step next. Think of it as the engine room of our indicator 🚂, where all the number crunching and strategy logic breathe life into our charts.
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]) {
// Always calculate buffers
   int startBar = prev_calculated - 1;                            //--- Set start bar
   if(startBar < 0) startBar = 0;                                 //--- Adjust start bar
   for(int barIndex = startBar; barIndex < rates_total && !_StopFlag; barIndex++) {
      int fastStartBar = barIndex - fastPeriod + 1;               //--- Calc fast start
      if(fastStartBar < 0) fastStartBar = 0;                      //--- Adjust fast start
      int slowStartBar = barIndex - slowPeriod + 1;               //--- Calc slow start
      if(slowStartBar < 0) slowStartBar = 0;                      //--- Adjust slow start
      double slowHigh = high[ArrayMaximum(high, slowStartBar, slowPeriod)]; //--- Get slow high
      double slowLow = low[ArrayMinimum(low, slowStartBar, slowPeriod)];    //--- Get slow low
      double fastHigh = high[ArrayMaximum(high, fastStartBar, fastPeriod)]; //--- Get fast high
      double fastLow = low[ArrayMinimum(low, fastStartBar, fastPeriod)];    //--- Get fast low
      if(barIndex > 0) {
         slowLineBuffer[barIndex] = (close[barIndex] > slowLineBuffer[barIndex-1]) ? slowLow : slowHigh; //--- Set slow line
         fastLineBuffer[barIndex] = (close[barIndex] > fastLineBuffer[barIndex-1]) ? fastLow : fastHigh; //--- Set fast line
         trendBuffer[barIndex] = trendBuffer[barIndex-1];          //--- Set trend
         if(close[barIndex] < slowLineBuffer[barIndex] && close[barIndex] < fastLineBuffer[barIndex]) trendBuffer[barIndex] = 1; //--- Set up trend
         if(close[barIndex] > slowLineBuffer[barIndex] && close[barIndex] > fastLineBuffer[barIndex]) trendBuffer[barIndex] = 0; //--- Set down trend
         trendArrowBuffer[barIndex] = (trendBuffer[barIndex] != trendBuffer[barIndex-1]) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set arrow
         slowLineUpBuffer[barIndex] = (trendBuffer[barIndex] == 0) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow up
         slowLineDownBuffer[barIndex] = (trendBuffer[barIndex] == 1) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow down
      } else {
         trendArrowBuffer[barIndex] = slowLineUpBuffer[barIndex] = slowLineDownBuffer[barIndex] = EMPTY_VALUE; //--- Set empties
         trendBuffer[barIndex] = fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = 0; //--- Set zeros
         fastLineBuffer[barIndex] = slowLineBuffer[barIndex] = close[barIndex]; //--- Set first lines
      }
      fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = trendBuffer[barIndex]; //--- Set colors
   }
   
   return(rates_total);                                           //--- Return total rates
}
In the "OnCalculate" event handler — the heart of our indicator ❤️ — all the magic happens. This handler runs on every new tick or bar, updating the indicator buffers so that our plots always reflect the latest market conditions 📈. We start by determining the starting bar for calculations: "prev_calculated - 1". If that value happens to be negative, we reset it to 0 to avoid invalid indices 🚫. Then we loop from "startBar" to "rates_total - 1" while the process isn’t stopped. For each "barIndex", we calculate the starting points for the fast and slow periods: "barIndex - fastPeriod + 1" and "barIndex - slowPeriod + 1", clamping them to 0 if necessary. Using these, we find the slow high as the maximum high over the slow period with "ArrayMaximum" on the high array, the slow low as the minimum low with "ArrayMinimum", and similarly the fast high and fast low over the fast period. For bars where "barIndex > 0", we populate the buffers with logic based on price action:

  1. "slowLineBuffer[barIndex]" is set to the slow low if the close is above the prior slow line (an up pivot), or the slow high otherwise (down pivot).
  2. "fastLineBuffer[barIndex]" is the fast low if the close is above the prior fast line, or fast high otherwise.
  3. We copy the prior trend into "trendBuffer[barIndex]", then update it: 1 for up if the close is below both current slow and fast lines, or 0 for down if above both.
  4. Arrows are placed in "trendArrowBuffer[barIndex]" at the slow line value if the trend changed from the prior bar, otherwise they remain empty.
  5. "slowLineUpBuffer[barIndex]" shows the slow line if the trend is 0, else empty; "slowLineDownBuffer[barIndex]" shows it if trend is 1, else empty.

For the first bar ("barIndex == 0"), we initialize values: arrows and slow up/down buffers are empty, trend, fast color, and arrow color are set to 0, and fast/slow lines are initialized to "close[0]". Finally, we assign "fastLineColorBuffer[barIndex]" and "trendArrowColorBuffer[barIndex]" to the current trend value for proper color indexing. Once all bars are processed, we return "rates_total", signaling that all calculations are complete ✅. Upon compilation, the indicator is now fully ready and produces the expected results on the chart.
CALCULATED INDICATOR
From the chart image, it’s obvious that our indicator calculations worked perfectly 🎯. The plots are beautifully visualized, and all buffer arrays are filled with their respective data values — our little number army is standing at attention! 🧮 What’s left? Well, now we want to display the exact prices to the right of the indicator lines. This way, we can instantly see the precise value of each line, making our chart not just pretty, but also highly informative 💡.

No worries — this is easy-peasy 🍋. We’ll wrap this logic inside a function, keeping things modular, tidy, and reusable. Think of it as giving our indicator a little post-it note on the side with all the info you need 📌.
//+------------------------------------------------------------------+
//| Draw right price extension line/label                            |
//+------------------------------------------------------------------+
bool drawRightPrice(string objectName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID) {
   bool objectExists = (ObjectFind(0, objectName) >= 0);          //--- Check exists
   if(!objectExists) {
      if(!ObjectCreate(0, objectName, OBJ_ARROW_RIGHT_PRICE, 0, lineTime, linePrice)) {
         Print("Failed to create ", objectName);                  //--- Log failure
         return false;                                            //--- Return failure
      }
   } else {
      ObjectSetInteger(0, objectName, OBJPROP_TIME, 0, lineTime);  //--- Set time
      ObjectSetDouble(0, objectName, OBJPROP_PRICE, 0, linePrice); //--- Set price
   }
   long currentScale = ChartGetInteger(0, CHART_SCALE);           //--- Get scale
   int lineWidth = 1;                                             //--- Init width
   if(currentScale <= 1) lineWidth = 1;                           //--- Set width small
   else if(currentScale <= 3) lineWidth = 2;                      //--- Set width medium
   else lineWidth = 3;                                            //--- Set width large
   ObjectSetInteger(0, objectName, OBJPROP_COLOR, lineColor);     //--- Set color
   ObjectSetInteger(0, objectName, OBJPROP_WIDTH, lineWidth);     //--- Set width
   ObjectSetInteger(0, objectName, OBJPROP_STYLE, lineStyle);     //--- Set style
   ObjectSetInteger(0, objectName, OBJPROP_BACK, false);          //--- Set foreground
   ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);    //--- Set not selectable
   ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false);      //--- Set not selected
   ChartRedraw(0);                                                //--- Redraw chart
   return true;                                                   //--- Return success
}


// we then call this function in the "OnCalculate" event handler

// Draw line extensions if enabled
   if(showExtensions && rates_total > 0) {
      int latestBarIndex = rates_total - 1;                       //--- Get latest index
      double slowLineValue = slowLineBuffer[latestBarIndex];      //--- Get slow value
      double fastLineValue = fastLineBuffer[latestBarIndex];      //--- Get fast value
      double currentTrend = trendBuffer[latestBarIndex];          //--- Get trend
      color lineColor = (currentTrend == 0.0) ? upColor : downColor; //--- Set line color
      datetime currentBarTime = iTime(_Symbol, _Period, 0);       //--- Get current time
      long timeOffset = (long)extendBars * PeriodSeconds(_Period); //--- Calc offset
      datetime extensionTime = currentBarTime + (datetime)timeOffset; //--- Calc extension time
      drawRightPrice(objectPrefix + "SLOW", extensionTime, slowLineValue, lineColor, STYLE_SOLID); //--- Draw slow extension
      drawRightPrice(objectPrefix + "FAST", extensionTime, fastLineValue, lineColor, STYLE_DOT); //--- Draw fast extension
   }
For rendering prices to the right of the indicator lines, we define the "drawRightPrice" function — our little helper that creates or updates a right price arrow object 🏹. This object extends the indicator lines horizontally into the future, giving a visual hint of line levels based on your input settings. First, we check if the object already exists using "ObjectFind". If it doesn’t, we create an "OBJ_ARROW_RIGHT_PRICE" at the specified "lineTime" and "linePrice" using "ObjectCreate". If creation fails, we log the failure and return false 🚫. If the object exists, we simply update its time and price anchors with "ObjectSetInteger" for "OBJPROP_TIME" and "ObjectSetDouble" for "OBJPROP_PRICE" — no need to reinvent the wheel! Next, we grab the current chart scale with "ChartGetInteger" and "CHART_SCALE" into "currentScale". Based on this, we determine "lineWidth" to ensure visibility at any zoom level: 1 for scale ≤1, 2 for ≤3, and 3 for larger scales 🔍.

We then configure the object for perfect display:

  • Set the color with "OBJPROP_COLOR" to "lineColor".
  • Set the width to "lineWidth".
  • Set the style to "lineStyle" (default is solid).
  • Set "OBJPROP_BACK" to false so the object stays in the foreground.
  • Make it non-selectable and not currently selected with "OBJPROP_SELECTABLE" and "OBJPROP_SELECTED" set to false.

Finally, we redraw the chart with "ChartRedraw" and return true if everything worked ✅. This function is then called inside the "OnCalculate" event handler whenever "showExtensions" is true and there are bars to display. We determine the latest bar index as "rates_total - 1", fetch slow and fast values from their buffers, and the trend from "trendBuffer". The "lineColor" is chosen as "upColor" if trend is 0.0, else "downColor". Using "iTime" at shift 0, we get the current bar time, calculate an offset as "extendBars * PeriodSeconds(_Period)", then determine the extension time as the current bar plus the offset. Finally, we invoke "drawRightPrice" for the slow line with solid style and the fast line with dot style, naming the objects with "objectPrefix + "SLOW"" and "objectPrefix + "FAST"". Upon compilation, the indicator now beautifully shows right-hand price levels alongside your extended trend lines, fully functional and visually clear 🎨.
INDICATOR WITH RIGHT PRICE RENDERING
With the right prices now rendered, our main indicator is officially all set and looking sharp ✅. The lines are calculated, buffers are filled, arrows and trend colors are in place, and the prices peek nicely on the right — basically, our indicator is doing its job like a pro 🏆. What’s left? Well, the final flourish ✨: rendering the canvas to fill the indicator boundaries as we envisioned, giving our chart that polished, gradient-filled look that makes it both beautiful and informative 🎨. To make this happen, we’ll define a few helper functions, keeping the logic tidy, modular, and easy to manage. These functions will handle the canvas drawing, filling the spaces between lines just right, so your indicator truly stands out.
//+------------------------------------------------------------------+
//| Convert chart scale to bar width                                 |
//+------------------------------------------------------------------+
int BarWidth(int chartScale) {
   return (int)MathPow(2.0, chartScale);                          //--- Return bar width
}

//+------------------------------------------------------------------+
//| Convert bar shift to x pixel                                     |
//+------------------------------------------------------------------+
int ShiftToX(int barShift) {
   return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1); //--- Return x pixel
}

//+------------------------------------------------------------------+
//| Convert price to y pixel                                         |
//+------------------------------------------------------------------+
int PriceToY(double price) {
   if(maxPrice - minPrice == 0.0) return 0;                      //--- Return zero if no range
   return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1); //--- Return y pixel
}
First, we define the "BarWidth" function — our handy little calculator for determining the pixel width of each bar based on the current chart scale 📏. It returns an integer from "MathPow(2.0, chartScale)", giving an exponential estimate (1 at scale 0, 2 at scale 1, 4 at scale 2, and so on). This helps us position elements precisely on the canvas, no guesswork needed 🎯. Next up is "ShiftToX", which converts a bar shift (relative to the leftmost visible bar) into an x-pixel coordinate on the chart. The calculation is "(firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1", cast to int. This ensures that elements are positioned from right (recent bars) to left (older bars), with a little adjustment (-1) for perfect alignment ✨.
Finally, we have "PriceToY", which maps a price value to a y-pixel coordinate on the canvas 📊. If the price range is zero ("maxPrice - minPrice == 0.0"), it returns 0. Otherwise, it computes "currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1", rounding with "MathRound" and casting to int. This inverts the y-axis so higher prices appear at the top, and the -1 adjustment ensures pixel-perfect placement 🎨. With these helper functions ready, we’re now set to tackle the main function that will do all the heavy lifting — filling the indicator boundaries with smooth canvas rendering, making our chart truly pop! 💪
//+-----------------------------------------------------------------------------------------+
//| Fill area between two lines using trend for color with gradient alpha from slow to fast |
//+-----------------------------------------------------------------------------------------+
void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0) {
   int firstVisibleBar = firstVisibleBarIndex;                    //--- Get first visible
   int totalBarsToDraw = visibleBarsCount + extendShift;          //--- Calc bars to draw
   int bufferSize = (int)ArraySize(slowLineValues);               //--- Get buffer size
   if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return; //--- Return if invalid
   int previousX = -1;                                            //--- Init previous X
   int previousY1 = -1;                                           //--- Init previous Y1
   int previousY2 = -1;                                           //--- Init previous Y2
   for(int offset = 0; offset < totalBarsToDraw; offset++) {
      int barPosition = firstVisibleBar - offset;                 //--- Calc bar position
      int x = ShiftToX(barPosition);                              //--- Calc x
      if(x >= currentChartWidth) break;                           //--- Break if beyond width
      int dataBarShift = firstVisibleBar - offset + extendShift;  //--- Calc data shift
      int bufferBarIndex = bufferSize - 1 - dataBarShift;         //--- Calc buffer index
      if(bufferBarIndex < 0 || bufferBarIndex >= bufferSize) {
         previousX = -1;                                          //--- Reset previous X
         continue;                                                //--- Continue
      }
      double value1 = slowLineValues[bufferBarIndex];             //--- Get value1
      double value2 = fastLineValues[bufferBarIndex];             //--- Get value2
      if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE) {
         previousX = -1;                                          //--- Reset previous X
         continue;                                                //--- Continue
      }
      int y1 = PriceToY(value1);                                  //--- Calc y1
      int y2 = PriceToY(value2);                                  //--- Calc y2
      double currentTrend = trendValues[bufferBarIndex];          //--- Get trend
      uint baseColorRGB = (currentTrend == 0.0) ? (ColorToARGB(fillUpColor, 255) & 0x00FFFFFF) : (ColorToARGB(fillDownColor, 255) & 0x00FFFFFF); //--- Set base RGB
      if(previousX != -1 && x > previousX) {
         double deltaX = x - previousX;                           //--- Calc delta X
         int endColumn = MathMin(x, currentChartWidth - 1);       //--- Calc end column
         double maxT = (double)(endColumn - previousX) / deltaX;  //--- Calc max T
         for(int column = previousX; column <= endColumn; column++) {
            double t = (column - previousX) / deltaX;             //--- Calc t
            double interpolatedY1 = previousY1 + t * (y1 - previousY1); //--- Interpolate Y1
            double interpolatedY2 = previousY2 + t * (y2 - previousY2); //--- Interpolate Y2
            int upperY = (int)MathRound(MathMin(interpolatedY1, interpolatedY2)); //--- Calc upper Y
            int lowerY = (int)MathRound(MathMax(interpolatedY1, interpolatedY2)); //--- Calc lower Y
            if(upperY > lowerY) continue;                        //--- Continue if invalid
            double slowLineY = interpolatedY1;                    //--- Set slow Y
            double height = MathAbs(interpolatedY1 - interpolatedY2); //--- Calc height
            if(height == 0.0) continue;                          //--- Continue if no height
            // Fill per row with gradient from slow (opaque) to fast (transparent)
            for(int row = upperY; row <= lowerY; row++) {
               double distanceFromSlow = MathAbs(row - slowLineY); //--- Calc distance
               double gradientFraction = distanceFromSlow / height; //--- Calc fraction
               uchar alphaValue = (uchar)(fillAlpha * (1.0 - gradientFraction)); //--- Calc alpha
               if(alphaValue > fillAlpha) alphaValue = fillAlpha; //--- Cap alpha
               uint pixelColor = ((uint)alphaValue << 24) | baseColorRGB; //--- Set pixel color
               obj_Canvas.FillRectangle(column, row, column, row, pixelColor); //--- Fill pixel
            }
         }
      }
      previousX = x;                                              //--- Update previous X
      previousY1 = y1;                                            //--- Update previous Y1
      previousY2 = y2;                                            //--- Update previous Y2
   }
}

//+------------------------------------------------------------------+
//| Redraw the canvas                                                |
//+------------------------------------------------------------------+
void Redraw(void) {
   if(currentChartWidth <= 0 || currentChartHeight <= 0) return;  //--- Return if invalid size
   uint defaultColor = 0;                                         //--- Default color
   obj_Canvas.Erase(defaultColor);                                //--- Erase canvas
   DrawFilling(slowLineBuffer, fastLineBuffer, trendBuffer, upColor, downColor, (uchar)fillOpacity, extendBars); //--- Draw filling
   obj_Canvas.Update();                                           //--- Update canvas
}
Next, we define the "DrawFilling" function — the artist behind the scenes 🎨 — which renders the area between the slow and fast lines on the canvas. Using the trend, it selects either the up or down colors and creates a smooth gradient fade: full "fillAlpha" at the slow line tapering to transparent at the fast line. If enabled, it also extends the fill by "extendShift" bars, giving the chart a polished, flowing look ✨. We start by fetching the first visible bar and calculating "totalBarsToDraw" as the visible bar count plus "extendShift". The buffer size is taken from "slowLineValues", and we return early if the data is invalid or mismatched with the fast/trend buffers — no point in drawing a ghost 🫣. Previous X/Y1/Y2 are initialized to -1 to track interpolation. Next, we loop over offsets from 0 to "totalBarsToDraw - 1": for each offset, we compute the bar position as "firstVisibleBar - offset" and the x-pixel position using "ShiftToX". If the x exceeds the chart width, we break early. We calculate the data shift as "visibleBarsCount - offset + extendShift", and determine the buffer index as "size - 1 - dataShift". If the index is out of bounds or values are empty, we skip and reset the previous X 🏃‍♂️. For each valid bar, we get the slow value (value1) and fast value (value2) from the buffers and convert them to y-pixels with "PriceToY". The trend is determined from "trendValues", and the base RGB color comes from "fillUpColor" or "fillDownColor", masked with "ColorToARGB" to keep only RGB. If the previous X is valid and the current X is greater, we interpolate across the pixels:
  1. Calculate "delta" X, set the end column as min(X, width-1), and compute "tMax" as (end - previous) / delta.
  2. For each column from previous to end, compute "t" as (column - previous) / delta, then interpolate "y1" and "y2".
  3. Round min/max to "upper"/"lower" Y, skipping if "upper > lower".
  4. Set "slowLineY" to interpolated y1, calculate height as abs(y1 - y2), skipping if zero.
  5. For each row from upper to lower, calculate the distance from slow, fraction as distance / height, alpha as "fillAlpha * (1.0 - fraction)" cast to uchar, capped at "fillAlpha"
  6. Combine pixel color as alpha << 24 | base RGB and fill a single pixel at column/row using "obj_Canvas.FillRectangle" (1x1).

Finally, we update previous X/Y1/Y2 for the next iteration. We also implement a "Redraw" function to refresh the canvas whenever needed 🔄. It returns early if width or height is invalid (≤0). Otherwise, it sets the default color to 0 (transparent), erases the canvas with "obj_Canvas.Erase", calls "DrawFilling" with slow/fast/trend buffers, up/down colors, "fillOpacity" cast to uchar, and "extendBars". Finally, it updates the canvas display with "obj_Canvas.Update". This "Redraw" function is our go-to tool whenever we want to fill the indicator boundaries, as shown in the "OnCalculate" event handler — giving our chart that final, smooth, gradient finish 🏆.
if(!enableFilling) return(rates_total);                       //--- Return if no filling

// Canvas logic only if enabled
bool isNewBar = (rates_total > prev_calculated);               //--- Check new bar
bool hasTrendChanged = false;                                  //--- Init trend changed
if(rates_total > 0 && trendBuffer[rates_total-1] != previousTrend) {
   hasTrendChanged = true;                                     //--- Set changed
   previousTrend = trendBuffer[rates_total-1];                 //--- Update previous trend
}

// Update chart properties (only if changed)
bool hasChartChanged = false;                                  //--- Init chart changed
int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width
int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height
int newChartScale = (int)ChartGetInteger(0, CHART_SCALE);     //--- Get new scale
int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible
int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars
double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);    //--- Get new min price
double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);    //--- Get new max price
if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) {
   obj_Canvas.Resize(newChartWidth, newChartHeight);               //--- Resize canvas
   currentChartWidth = newChartWidth;                          //--- Update width
   currentChartHeight = newChartHeight;                        //--- Update height
   hasChartChanged = true;                                     //--- Set changed
}
if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
      newMinPrice != minPrice || newMaxPrice != maxPrice) {
   currentChartScale = newChartScale;                          //--- Update scale
   firstVisibleBarIndex = newFirstVisibleBar;                  //--- Update first visible
   visibleBarsCount = newVisibleBars;                          //--- Update visible bars
   minPrice = newMinPrice;                                     //--- Update min price
   maxPrice = newMaxPrice;                                     //--- Update max price
   hasChartChanged = true;                                     //--- Set changed
}

// Redraw only on: new bar, trend change, or chart resize/scroll. Debounce to 1x/sec max.
datetime currentTime = TimeCurrent();                          //--- Get current time
if((isNewBar || hasTrendChanged || hasChartChanged) && (currentTime - lastRedrawTime >= 1)) {
   Redraw();                                                   //--- Redraw canvas
   lastRedrawTime = currentTime;                               //--- Update last redraw
}
Here, we first check if filling is enabled. If "enableFilling" is false, we return "rates_total" early, skipping all canvas logic to improve performance ⚡. No need to paint the canvas if the user doesn’t want it — efficiency first! When filling is enabled, we handle canvas-specific operations carefully. We check for a new bar with "rates_total > prev_calculated" and store the result in "isNewBar". Next, we detect trend changes by comparing "trendBuffer[rates_total-1]" to "previousTrend", setting "hasTrendChanged" to true if different and updating "previousTrend" accordingly 🔄. We also monitor for chart changes. Initially, "hasChartChanged" is set to false. We fetch the latest width, height, scale, first visible bar, visible bars count, min price, and max price using "ChartGetInteger" and "ChartGetDouble". If the width or height has changed, we resize the canvas with "obj_Canvas.Resize", update "currentChartWidth" and "currentChartHeight", and mark "hasChartChanged" as true. Similarly, if scale, first visible bar, visible bars count, or min/max price has changed, we update the corresponding globals and set "hasChartChanged" to true ✅. Finally, we optimize redraws to avoid unnecessary computation. We get the current time with "TimeCurrent" into "currentTime", and if there’s a new bar, trend change, or chart change, and at least 1 second has passed since "lastRedrawTime", we call "Redraw" to refresh the canvas. Afterward, we update "lastRedrawTime" to the current time. This debouncing ensures redraws happen at most once per second, keeping performance smooth 🏎️. Now, all that remains is to re-render changes when chart events are detected and delete them on de-initialization, keeping everything clean and responsive, as shown below.
//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
   if(id != CHARTEVENT_CHART_CHANGE || !enableFilling) return;
   int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width
   int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height
   if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) {
      obj_Canvas.Resize(newChartWidth, newChartHeight);               //--- Resize canvas
      currentChartWidth = newChartWidth;                          //--- Update width
      currentChartHeight = newChartHeight;                        //--- Update height
      Redraw();                                                   //--- Redraw canvas
      return;                                                     //--- Return
   }
   int newChartScale = (int)ChartGetInteger(0, CHART_SCALE);      //--- Get new scale
   int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible
   int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars
   double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);    //--- Get new min price
   double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);    //--- Get new max price
   if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
         newMinPrice != minPrice || newMaxPrice != maxPrice) {
      currentChartScale = newChartScale;                          //--- Update scale
      firstVisibleBarIndex = newFirstVisibleBar;                  //--- Update first visible
      visibleBarsCount = newVisibleBars;                          //--- Update visible bars
      minPrice = newMinPrice;                                     //--- Update min price
      maxPrice = newMaxPrice;                                     //--- Update max price
      Redraw();                                                   //--- Redraw canvas
   }
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if(enableFilling) obj_Canvas.Destroy();                            //--- Destroy canvas if enabled
   ObjectsDeleteAll(0,objectPrefix,0,OBJ_ARROW_RIGHT_PRICE);      //--- Delete right price arrows
   ChartRedraw(0);                                                //--- Redraw chart
}
Here, we handle chart-related events in the "OnChartEvent" event handler. We respond to changes only if "enableFilling" is true; otherwise, we return early and let the chart rest 🛌. First, we fetch the new chart width and height using "ChartGetInteger" with "CHART_WIDTH_IN_PIXELS" and "CHART_HEIGHT_IN_PIXELS". If either has changed from "currentChartWidth" or "currentChartHeight", we resize the canvas with "obj_Canvas.Resize", update the globals, call "Redraw" to refresh the fill, and return — easy-peasy and pixel-perfect ✨. Next, we get the new scale ("CHART_SCALE"), "firstVisibleBar" ("CHART_FIRST_VISIBLE_BAR"), "visibleBarsCount" ("CHART_VISIBLE_BARS"), "minPrice" ("CHART_PRICE_MIN"), and "maxPrice" ("CHART_PRICE_MAX"). If any of these values differ from the stored globals, we update "currentChartScale", "firstVisibleBarIndex", "visibleBarsCount", "minPrice", and "maxPrice", then call "Redraw" to adapt the canvas fill to the new chart view 🎨. Finally, in the "OnDeinit" event handler — which runs when the indicator is removed or the terminal closes — we clean up resources 🧹. If "enableFilling" is true, we destroy the canvas with "obj_Canvas.Destroy". Then, we delete all right price arrow objects starting with "objectPrefix" using "ObjectsDeleteAll" with chart 0, window 0, and type "OBJ_ARROW_RIGHT_PRICE". To finish off, we redraw the chart with "ChartRedraw", leaving everything neat and tidy ✅. Upon compilation, the indicator now runs smoothly, fully functional, and visually polished, ready for market action.
PIVOT TREND DETECTOR INDICATOR TEST GIF
From the chart visualization, it’s clear that our indicator calculations are spot on 🎯, and the canvas fills beautifully whenever "enableFilling" is true. All our objectives — from accurate lines to right-hand prices and gradient fills — are fully achieved ✅. What’s left? The final step is backtesting the program 📊, to see how our indicator performs with historical data and ensure it behaves as expected under real market conditions. That’s what we’ll tackle in the next section, bringing our workflow full circle 🔄.


Backtesting


We went ahead and ran the testing, and the results speak for themselves 🎉. Below, you can see the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image — showing the indicator, canvas fills, and right-hand prices all in action, ready to impress 👀.
BACKTESTING GIF

Conclusion


In conclusion, we’ve successfully crafted a Pivot-Based Trend Indicator in MQL5 🛠️. It calculates fast and slow pivot lines from high/low ranges, identifies trend directions with color-coded lines and arrows, optionally extends lines for projections, and even fills areas with a gradient canvas for added visual depth 🎨. All of this happens while optimizing redraws for new bars or chart changes, keeping performance smooth and efficient ⚡.

This indicator is a flexible, customizable tool for trend detection, letting you adjust inputs to match your trading style. And we’re not stopping here — in upcoming parts, we’ll dive into advanced indicators, like volatility channels and momentum oscillators, even exploring machine learning elements 🤖. Stay tuned for more market magic! ✨📈

Disclaimer: The ideas and strategies presented in this resource are solely those of the author and are intended for informational and educational purposes only. They do not constitute financial advice, and past performance is not indicative of future results. All materials, including but not limited to text, images, files, and any downloadable content, are protected by copyright and intellectual property laws and are the exclusive property of Forex Algo-Trader or its licensors. Reproduction, distribution, modification, or commercial use of these materials without prior written consent from Forex Algo-Trader is strictly prohibited and may result in legal action. Users are advised to exercise extreme caution, perform thorough independent research, and consult with qualified financial professionals before implementing any trading strategies or decisions based on this resource, as trading in financial markets involves significant risk of loss.

Recent Comments

Go to discussion to Comment or View other Comments

No comments yet. Be the first to comment!

Banner
🤖 Building a ChatGPT AI Trade Brain in MQL5
What’s the point of an AI trading assistant that talks but never acts? In this article, we transfo...
2026-05-15 16:50:14
Banner
Grid Scalper MA MT5 EA Review (2026): The Forever-Free MetaTrader 5 Robot
Grid Scalper MA MT5 EA Review (2026): The forever-free MetaTrader 5 robot that's quietly earning a 4...
2026-05-14 19:35:18
Banner
Supercharging Your TPO Market Profile With Volume Intelligence
You've got a TPO market profile that tracks where price spent its time — now imagine giving it a f...
2026-04-03 16:05:42