[Free Source Code] Multi‑TF Linear Regression HUD (Canvas) – Trend Strength, σ Deviation, Candle Count Dashboard

 

Overview

I’m sharing a free indicator I built: a Multi‑Timeframe Linear Regression HUD rendered with Canvas, designed to show trend strength, deviation from regression, and recent candle bias — all in one compact dashboard.

Useful for discretionary traders who want a quick “market condition snapshot” across multiple timeframes.

Features

  • 5 Timeframes: M1 / M5 / M15 / M30 / H1

  • 3 Linear Regression Periods: configurable (default 20 / 50 / 100)

  • σ‑Deviation Position (%): shows how far the current price is from LR (normalized by σ × deviation factor)

  • Slope Direction: ↑ for positive slope, ↓ for negative

  • Candle Count: Up/Down count for the last N candles

  • Canvas HUD: clean, compact, always-on-screen display

  • Auto‑refresh: updates on every tick + timer

Screenshot

scshot

Source Code (Full MQ5)

Feel free to modify, reuse, or integrate into your own tools.

//+------------------------------------------------------------------+
//|                               TrendlineHD_Hybrid_Wide_V1.mq5    |
//|                                  Copyright 2026, Trading Tools  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Trading Tools"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Canvas\Canvas.mqh>

//--- Input parameters
input int      InpPeriod1Len = 20;   // Linear Regression Period 1
input int      InpPeriod2Len = 50;   // Linear Regression Period 2
input int      InpPeriod3Len = 100;  // Linear Regression Period 3
input int      InpCountLen   = 10;   // Candle count period (Up/Down)
input double   InpDev        = 2.0;  // Deviation multiplier
input int      InpX          = 800;  // Initial X position
input int      InpY          = 100;  // Initial Y position

//--- Internal settings
CCanvas   m_canvas;
string    m_name = "LR_HUD_Canvas_V2";
int       m_width  = 280; 
int       m_height = 125; 
int       m_row_h  = 18;

string m_tf_names[5] = {"M1","M5","M15","M30","H1"};
ENUM_TIMEFRAMES m_tfs[5] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1};
int m_periods[3];

//+------------------------------------------------------------------+
int OnInit()
{
   m_periods[0] = InpPeriod1Len;
   m_periods[1] = InpPeriod2Len;
   m_periods[2] = InpPeriod3Len;

   // Create Canvas HUD
   if(!m_canvas.CreateBitmapLabel(m_name, InpX, InpY, m_width, m_height, COLOR_FORMAT_ARGB_NORMALIZE))
      return(INIT_FAILED);

   string obj_name = m_canvas.ChartObjectName();
   ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, true);
   ObjectSetInteger(0, obj_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);

   DrawHUD();
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   m_canvas.Destroy();
}

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[])
{
   DrawHUD();
   return(rates_total);
}

void OnTimer()
{
   DrawHUD();
}

//--- Main drawing function
void DrawHUD()
{
   m_canvas.Erase(ColorToARGB(C'20,20,20', 220));
   m_canvas.Rectangle(0, 0, m_width-1, m_height-1, ColorToARGB(clrDimGray));

   int x_base = 45; 
   int col_w  = 58; 
   int y = 8;

   m_canvas.FontSet("Consolas", -90, FW_BOLD);
   
   // Header: LR periods and candle count
   for(int p=0; p<3; p++)
      m_canvas.TextOut(x_base + (p*col_w) + 35, y, "P:"+IntegerToString(m_periods[p]), ColorToARGB(clrGray), TA_RIGHT);
   
   m_canvas.TextOut(x_base + (3*col_w) + 55, y, "Count("+IntegerToString(InpCountLen)+")", ColorToARGB(clrGray), TA_RIGHT);

   y += m_row_h;

   // Loop through timeframes
   for(int t=0; t<5; t++)
   {
      m_canvas.TextOut(8, y, m_tf_names[t], ColorToARGB(clrWhite));

      MqlRates rates[];
      ArraySetAsSeries(rates, true);

      // Fetch required number of bars
      if(CopyRates(_Symbol, m_tfs[t], 0, InpPeriod3Len, rates) >= InpPeriod3Len)
      {
         // Linear Regression calculations for each period
         for(int p=0; p<3; p++)
         {
            int period = m_periods[p];
            double sumX=0, sumY=0, sumXY=0, sumX2=0;
            
            // Linear regression (i=0 is the latest bar)
            for(int i=0; i<period; i++)
            {
               double xx = (period - 1) - i; // Time axis (positive direction)
               double yy = rates[i].close;
               sumX  += xx;
               sumY  += yy;
               sumXY += xx * yy;
               sumX2 += xx * xx;
            }
            
            double denom = (period * sumX2 - sumX * sumX);
            if(denom == 0) continue;

            double slope = (period * sumXY - sumX * sumY) / denom;
            double intercept = (sumY - slope * sumX) / period;
            double lr_curr = intercept + slope * (period - 1);

            // Standard deviation
            double sumRes=0;
            for(int j=0; j<period; j++)
            {
               double fit = intercept + slope * ((period - 1) - j);
               double res = rates[j].close - fit;
               sumRes += res * res;
            }
            double sigma = MathSqrt(sumRes / period);

            // Position relative to LR (in %)
            double pos_pct = (sigma > 0) ? (rates[0].close - lr_curr) / (sigma * InpDev) * 100.0 : 0;

            uint aCol = (slope > 0) ? ColorToARGB(clrLime) : ColorToARGB(clrRed);
            uint nCol = (pos_pct >= 100) ? ColorToARGB(clrYellow) : (pos_pct <= -100 ? ColorToARGB(clrAqua) : ColorToARGB(clrWhite));
            string arr = (slope > 0) ? "↑" : "↓";

            m_canvas.TextOut(x_base + (p*col_w) + 35, y, DoubleToString(pos_pct, 0), nCol, TA_RIGHT);
            m_canvas.TextOut(x_base + (p*col_w) + 38, y, arr, aCol);
         }

         // Candle count (Up/Down)
         int up=0, dn=0;
         for(int i=0; i<InpCountLen; i++)
         {
            if(rates[i].close > rates[i].open) up++;
            else if(rates[i].close < rates[i].open) dn++;
         }

         string cntStr = IntegerToString(up) + "u:" + IntegerToString(dn) + "d";
         uint cCol = (up > dn) ? ColorToARGB(clrLime) : (dn > up ? ColorToARGB(clrRed) : ColorToARGB(clrWhite));

         m_canvas.TextOut(x_base + (3*col_w) + 55, y, cntStr, cCol, TA_RIGHT);
      }

      y += m_row_h;
   }

   m_canvas.Update();
}

Notes

  • Works on any symbol

  • No chart objects except the Canvas label

  • Lightweight and fast


License

Free to use, modify, and redistribute. No restrictions.

Enjoy!

If this helps your workflow or you create a modified version, I’d love to see it.

 
The position% values looked a bit mysterious on their own, so I decided to draw them directly on the chart — much easier to see what’s going on now.

//+------------------------------------------------------------------+
//|                              TrendlineHD_Hybrid_Wide_V2.mq5      |
//|                               Copyright 2026, Trading Tools      |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Trading Tools"
#property version   "1.20"
#property indicator_chart_window
#property indicator_plots 0

#include <Canvas\Canvas.mqh>

//+------------------------------------------------------------------+
//| Linear Regression Calculation (slope, intercept, sigma)         |
//+------------------------------------------------------------------+
bool CalcLinearRegression(const double &close[],
                          int period,
                          bool reverseX,
                          double &slope,
                          double &intercept,
                          double &sigma)
{
   if(ArraySize(close) < period)
      return false;

   double sumX=0, sumY=0, sumXY=0, sumX2=0;

   for(int i=0; i<period; i++)
   {
      double x = reverseX ? (period - 1 - i) : i;
      double y = close[i];

      sumX  += x;
      sumY  += y;
      sumXY += x * y;
      sumX2 += x * x;
   }

   double denom = (period * sumX2 - sumX * sumX);
   if(denom == 0.0)
      return false;

   slope     = (period * sumXY - sumX * sumY) / denom;
   intercept = (sumY - slope * sumX) / period;

   double sumRes = 0;
   for(int j=0; j<period; j++)
   {
      double x = reverseX ? (period - 1 - j) : j;
      double fit = intercept + slope * x;
      double res = close[j] - fit;
      sumRes += res * res;
   }

   sigma = MathSqrt(sumRes / period);
   return true;
}


//--- HUD input parameters
input int      InpPeriod1Len = 20;   // Period 1 length
input int      InpPeriod2Len = 50;   // Period 2 length
input int      InpPeriod3Len = 100;  // Period 3 length
input int      InpCountLen   = 10;   // Candle count window (bull/bear count)
input double   InpDev        = 2.0;  // Deviation multiplier (σ)
input int      InpX          = 10;   // HUD initial X position
input int      InpY          = 25;   // HUD initial Y position

//--- Regression line drawing parameters
input bool  show      = true;   // Master switch: show/hide all regression lines
input bool  show1     = true;   // Show Line 1
input bool  show2     = true;   // Show Line 2
input bool  show3     = true;   // Show Line 3
input bool  extend1   = false;  // Extend Line 1 to the right
input bool  extend2   = false;  // Extend Line 2 to the right
input bool  extend3   = false;  // Extend Line 3 to the right

input color line1Color = clrYellow;  // Color for Line 1
input color line2Color = clrGreen;   // Color for Line 2
input color line3Color = clrRed;     // Color for Line 3

input double BandMultiplier = 2.0;   // Standard deviation band multiplier

//--- Button display parameters
input int            btnX      = 80;                     // Button X offset
input int            btnY      = 30;                     // Button Y offset
input ENUM_BASE_CORNER btnCorner = CORNER_RIGHT_UPPER;   // Button anchor corner

//--- Internal HUD settings
CCanvas   m_canvas;
string    m_name   = "LR_HUD_Canvas_V2";
int       m_width  = 280;
int       m_height = 125;
int       m_row_h  = 18;

string m_tf_names[5] = {"M1","M5","M15","M30","H1"};  // Displayed timeframes
ENUM_TIMEFRAMES m_tfs[5] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1};
int m_periods[3];  // Period lengths for LR calculations

//--- Regression line control
bool   EnableLine;          // ON/OFF state for regression lines
string btnName = "lineToggleBtn";   // Button object name

//+------------------------------------------------------------------+
//| Regression Line Set Drawing                                      |
//+------------------------------------------------------------------+
void DrawRegressionSet(const double &close[], const datetime &time[],
                       int period, string prefix, color col,
                       bool showFlag, bool extendRight)
{
   string cName = prefix+"_center";
   string uName = prefix+"_upper";
   string lName = prefix+"_lower";

   if(!showFlag || !EnableLine)
   {
      ObjectDelete(0, cName);
      ObjectDelete(0, uName);
      ObjectDelete(0, lName);
      return;
   }

   if(Bars(_Symbol,_Period) < period)
      return;

   double slope = 0, intercept = 0, sigma = 0;

   // ★ reverseX = false(描画は正方向)
   if(!CalcLinearRegression(close, period, false, slope, intercept, sigma))
      return;

   double band = sigma * BandMultiplier;

   datetime t0 = time[period-1];
   double   y0 = intercept + slope*(period-1);

   datetime t1 = time[0];
   double   y1 = intercept;

   ObjectDelete(0, cName);
   ObjectCreate(0, cName, OBJ_TREND, 0, t0, y0, t1, y1);
   ObjectSetInteger(0, cName, OBJPROP_COLOR, col);
   ObjectSetInteger(0, cName, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, cName, OBJPROP_STYLE, STYLE_DOT);
   ObjectSetInteger(0, cName, OBJPROP_RAY_RIGHT, extendRight);

   ObjectDelete(0, uName);
   ObjectCreate(0, uName, OBJ_TREND, 0, t0, y0+band, t1, y1+band);
   ObjectSetInteger(0, uName, OBJPROP_COLOR, col);
   ObjectSetInteger(0, uName, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, uName, OBJPROP_RAY_RIGHT, extendRight);

   ObjectDelete(0, lName);
   ObjectCreate(0, lName, OBJ_TREND, 0, t0, y0-band, t1, y1-band);
   ObjectSetInteger(0, lName, OBJPROP_COLOR, col);
   ObjectSetInteger(0, lName, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, lName, OBJPROP_RAY_RIGHT, extendRight);
}

//+------------------------------------------------------------------+
//| HUD Rendering                                                    |
//+------------------------------------------------------------------+
void DrawHUD()
{
   m_canvas.Erase(ColorToARGB(C'20,20,20', 220));
   m_canvas.Rectangle(0, 0, m_width-1, m_height-1, ColorToARGB(clrDimGray));

   int x_base = 45;
   int col_w  = 58;
   int y = 8;

   m_canvas.FontSet("Consolas", -90, FW_BOLD);

   for(int p=0; p<3; p++)
      m_canvas.TextOut(x_base + (p*col_w) + 35, y, "P:"+IntegerToString(m_periods[p]), ColorToARGB(clrGray), TA_RIGHT);

   m_canvas.TextOut(x_base + (3*col_w) + 55, y, "Count("+IntegerToString(InpCountLen)+")", ColorToARGB(clrGray), TA_RIGHT);

   y += m_row_h;

   for(int t=0; t<5; t++)
   {
      m_canvas.TextOut(8, y, m_tf_names[t], ColorToARGB(clrWhite));

      MqlRates rates[];
      ArraySetAsSeries(rates, true);

      if(CopyRates(_Symbol, m_tfs[t], 0, InpPeriod3Len, rates) >= InpPeriod3Len)
      {
         for(int p=0; p<3; p++)
         {
            int period = m_periods[p];

            double closeArr[];
            ArrayResize(closeArr, period);
            for(int i=0; i<period; i++)
               closeArr[i] = rates[i].close;

            double slope = 0, intercept = 0, sigma = 0;

            // ★ reverseX = true(HUD は逆方向)
            if(!CalcLinearRegression(closeArr, period, true, slope, intercept, sigma))
               continue;

            double lr_curr = intercept + slope * (period - 1);
            double pos_pct = (sigma > 0.0)
                             ? (rates[0].close - lr_curr) / (sigma * InpDev) * 100.0
                             : 0.0;

            uint aCol = (slope > 0) ? ColorToARGB(clrLime) : ColorToARGB(clrRed);
            uint nCol = (pos_pct >= 100.0) ? ColorToARGB(clrYellow)
                                           : (pos_pct <= -100.0 ? ColorToARGB(clrAqua) : ColorToARGB(clrWhite));
            string arr = (slope > 0) ? "↑" : "↓";

            m_canvas.TextOut(x_base + (p*col_w) + 35, y, DoubleToString(pos_pct, 0), nCol, TA_RIGHT);
            m_canvas.TextOut(x_base + (p*col_w) + 38, y, arr, aCol);
         }

         int up=0, dn=0;
         for(int i=0; i<InpCountLen; i++)
         {
            if(rates[i].close > rates[i].open)      up++;
            else if(rates[i].close < rates[i].open) dn++;
         }
         string cntStr = IntegerToString(up) + "u:" + IntegerToString(dn) + "d";
         uint cCol = (up > dn) ? ColorToARGB(clrLime)
                               : (dn > up ? ColorToARGB(clrRed) : ColorToARGB(clrWhite));
         m_canvas.TextOut(x_base + (3*col_w) + 55, y, cntStr, cCol, TA_RIGHT);
      }
      y += m_row_h;
   }
   m_canvas.Update();
}



//+------------------------------------------------------------------+
//| Draw All Regression Lines                                        |
//+------------------------------------------------------------------+
void DrawAllRegressionLines()
{
   if(!EnableLine)
   {
      ObjectDelete(0, "LR1_center");
      ObjectDelete(0, "LR1_upper");
      ObjectDelete(0, "LR1_lower");

      ObjectDelete(0, "LR2_center");
      ObjectDelete(0, "LR2_upper");
      ObjectDelete(0, "LR2_lower");

      ObjectDelete(0, "LR3_center");
      ObjectDelete(0, "LR3_upper");
      ObjectDelete(0, "LR3_lower");
      return;
   }

   int rates_total = Bars(_Symbol, _Period);
   if(rates_total <= 0)
      return;

   if(show1 && rates_total >= InpPeriod1Len)
   {
      double c1[]; datetime t1[];
      CopyClose(_Symbol, _Period, 0, InpPeriod1Len, c1);
      CopyTime (_Symbol, _Period, 0, InpPeriod1Len, t1);
      DrawRegressionSet(c1, t1, InpPeriod1Len, "LR1", line1Color, show1, extend1);
   }

   if(show2 && rates_total >= InpPeriod2Len)
   {
      double c2[]; datetime t2[];
      CopyClose(_Symbol, _Period, 0, InpPeriod2Len, c2);
      CopyTime (_Symbol, _Period, 0, InpPeriod2Len, t2);
      DrawRegressionSet(c2, t2, InpPeriod2Len, "LR2", line2Color, show2, extend2);
   }

   if(show3 && rates_total >= InpPeriod3Len)
   {
      double c3[]; datetime t3[];
      CopyClose(_Symbol, _Period, 0, InpPeriod3Len, c3);
      CopyTime (_Symbol, _Period, 0, InpPeriod3Len, t3);
      DrawRegressionSet(c3, t3, InpPeriod3Len, "LR3", line3Color, show3, extend3);
   }
}


//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
{
   m_periods[0] = InpPeriod1Len;
   m_periods[1] = InpPeriod2Len;
   m_periods[2] = InpPeriod3Len;

   if(!m_canvas.CreateBitmapLabel(m_name, InpX, InpY, m_width, m_height, COLOR_FORMAT_ARGB_NORMALIZE))
      return(INIT_FAILED);

   string obj_name = m_canvas.ChartObjectName();
   ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, true);
   ObjectSetInteger(0, obj_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);

   EnableLine = show;

   string btnText = (EnableLine ? "Line: ON" : "Line: OFF");
   if(ObjectFind(0, btnName) == -1)
   {
      ObjectCreate(0, btnName, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0, btnName, OBJPROP_XDISTANCE, btnX);
      ObjectSetInteger(0, btnName, OBJPROP_YDISTANCE, btnY);
      ObjectSetInteger(0, btnName, OBJPROP_XSIZE, 80);
      ObjectSetInteger(0, btnName, OBJPROP_YSIZE, 20);
      ObjectSetInteger(0, btnName, OBJPROP_CORNER, btnCorner);
      ObjectSetString (0, btnName, OBJPROP_TEXT, btnText);
   }

   DrawHUD();
   DrawAllRegressionLines();
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   m_canvas.Destroy();

   ObjectDelete(0, "LR1_center");
   ObjectDelete(0, "LR1_upper");
   ObjectDelete(0, "LR1_lower");

   ObjectDelete(0, "LR2_center");
   ObjectDelete(0, "LR2_upper");
   ObjectDelete(0, "LR2_lower");

   ObjectDelete(0, "LR3_center");
   ObjectDelete(0, "LR3_upper");
   ObjectDelete(0, "LR3_lower");

   ObjectDelete(0, btnName);
}

//+------------------------------------------------------------------+
//| OnCalculate                                                      |
//+------------------------------------------------------------------+
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[])
{
   DrawHUD();
   DrawAllRegressionLines();
   return(rates_total);
}

//+------------------------------------------------------------------+
//| OnTimer                                                          |
//+------------------------------------------------------------------+
void OnTimer()
{
   DrawHUD();
   DrawAllRegressionLines();
}

//+------------------------------------------------------------------+
//| OnChartEvent                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == btnName)
   {
      EnableLine = !EnableLine;
      if(EnableLine)
         ObjectSetString(0, btnName, OBJPROP_TEXT, "Line: ON");
      else
         ObjectSetString(0, btnName, OBJPROP_TEXT, "Line: OFF");

      DrawAllRegressionLines();
   }
}
 

“I also put together two derivative builds — a TF‑switching version and a seconds‑chart version. They’ll stay active until the end of the month, so feel free to play with them and share your thoughts.”


TF‑switching version

TFver


seconds‑chart version

Secver

The attached .ex5 files have been removed by a moderator. Only source code is allowed on the forum.
 
This is the version referenced above.
 
Kazutaka Okuno #:
This is the version referenced above.
Only source code is allowed on the forum.
 

Usually free public code should be published into the codebase

https://www.mql5.com/en/code

MQL5 Code Base
MQL5 Code Base
  • www.mql5.com
MQL5 Source Code Library for MetaTrader 5
 
Kazutaka Okuno #:
This is the version referenced above.

Try publishing this to codebase. It's a more appropriate way to share open source developments than simply posting source code to a forum. For each publication in the codebase, a forum topic is automatically created for its discussion.

Moreover, if you publish your application to codebase, many more people will know about it (this is guaranteed).

  • Firstly, codebase publications are available for download directly from MetaEditor.
  • Secondly, MetaQuotes is highly likely to promote your codebase publication in their channels with hundreds of thousands or even millions of subscribers. Here alone they have 400 thousand subscribers; they also have Facebook, Telegram, and X.
  • Thirdly, if MetaQuotes editors pick your publication, it will be translated and available for all 11 language sections of this site.
 

Thank you all so much for the advice!

I’ve only just started here, so I was completely unaware of the proper way to share code.

I’ll definitely check out the CodeBase!