[MQL5 · Pine Script]

Hour Profile

Measures how much your symbol has actually moved at each hour of the day, from the history you have loaded. It shows the rhythm; it does not say when to trade.

Published on 7 August 2026 · By SuaVar

The full source is right above and you can copy it. Downloading the file needs a free account.

//+------------------------------------------------------------------+
//|                                                  HourProfile.mq5  |
//|                                          https://suavar.com       |
//+------------------------------------------------------------------+
#property copyright "SuaVar"
#property link      "https://suavar.com"
#property version   "1.00"
#property description "Measures how much this symbol has actually moved at each hour of the day,"
#property description "from the history the terminal has loaded, and how many bars it has for each"
#property description "hour."
#property description ""
#property description "The hours are your BROKER'S SERVER clock, not yours, and the tool cannot"
#property description "convert them: it does not know your server's DST policy. It reports where the"
#property description "movement has been. It does not say when to trade."
#property description ""
#property description "Educational tool. It does not trade, it does not give signals and it does not"
#property description "tell anyone what to buy or sell."

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1

#property indicator_label1  "Typical range of this hour (points)"
#property indicator_type1   DRAW_HISTOGRAM
//--- Azul y no rojo A PROPOSITO. Las otras dos piezas del canal senalan DEFECTOS —repintado, agujeros
//--- del historico— y usan un color de alarma. Esta no encuentra nada malo: describe actividad. Que
//--- comparta paleta con las de problemas invitaria a leer una hora tranquila como una averia.
#property indicator_color1  clrDeepSkyBlue
#property indicator_width1  2

//--- Constantes ---------------------------------------------------------------
#define HOURS_IN_DAY 24

//--- Tope del parametro "velas a examinar". Mismo criterio que en el auditor de historico: un
//--- indicador no puede examinar mas velas de las que el terminal ha cargado.
#define MAX_SCAN_BARS 20000

//--- Marco temporal por encima del cual el perfil deja de tener sentido. Con velas de un dia o mas,
//--- todas caen a la misma hora de apertura y las 24 ranuras se reducen a una: el "perfil" seria una
//--- sola barra y el usuario se llevaria una conclusion falsa de un grafico que parece correcto.
#define MAX_USEFUL_PERIOD_SECONDS 14400   // H4

//--- Inputs -------------------------------------------------------------------
input int InpScanBars   = 5000;   // Bars to examine (0 = all the terminal has loaded)
input bool InpPrintTable = true;  // Print the 24-hour table to the Experts log once

//--- Buffers ------------------------------------------------------------------
double ProfileBuffer[];

//--- Estado del ultimo barrido ------------------------------------------------
double g_sumRange[HOURS_IN_DAY];
long   g_bars[HOURS_IN_DAY];
double g_avg[HOURS_IN_DAY];

int      g_scanned   = 0;
int      g_busiest   = -1;
int      g_quietest  = -1;
bool     g_printed   = false;
datetime g_lastBarTime = 0;

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpScanBars < 0)
     {
      Print("HourProfile: 'Bars to examine' cannot be negative.");
      return(INIT_PARAMETERS_INCORRECT);
     }
   if(InpScanBars > MAX_SCAN_BARS)
     {
      PrintFormat("HourProfile: 'Bars to examine' is capped at %d.", MAX_SCAN_BARS);
      return(INIT_PARAMETERS_INCORRECT);
     }

//--- Se rechaza en OnInit en vez de dibujar algo enganoso. Un perfil por horas sobre velas diarias no
//--- es "menos preciso": es una sola ranura disfrazada de veinticuatro.
   if(PeriodSeconds() > MAX_USEFUL_PERIOD_SECONDS)
     {
      Print("HourProfile: use a timeframe of H4 or lower. Above that, every bar opens at the same hour and the profile has a single slot.");
      return(INIT_PARAMETERS_INCORRECT);
     }

   SetIndexBuffer(0, ProfileBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   IndicatorSetInteger(INDICATOR_DIGITS, 0);
   IndicatorSetString(INDICATOR_SHORTNAME, "Hour profile: scanning");
   g_printed = false;
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Hora del dia (0-23) de una fecha, en el reloj del SERVIDOR.        |
//+------------------------------------------------------------------+
int HourOf(const datetime t)
  {
   MqlDateTime dt;
   TimeToStruct(t, dt);
   return(dt.hour);
  }

//+------------------------------------------------------------------+
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[])
  {
   if(rates_total < 2) return(0);

   ArraySetAsSeries(time, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(ProfileBuffer, false);

//--- Solo al nacer una vela. El perfil de 5000 velas no cambia dentro de la misma vela, asi que
//--- recalcularlo por cotizacion seria trabajo tirado — la leccion que dejo el auditor de repintado
//--- al validarse sobre M1.
   if(prev_calculated > 0 && time[rates_total-1] == g_lastBarTime) return(rates_total);
   g_lastBarTime = time[rates_total-1];

   double point = _Point;
   if(point <= 0.0) return(rates_total);

   int scan = (InpScanBars <= 0) ? rates_total : InpScanBars;
   if(scan > rates_total) scan = rates_total;
   int start = rates_total - scan;
   if(start < 0) start = 0;

   for(int h = 0; h < HOURS_IN_DAY; h++)
     {
      g_sumRange[h] = 0.0;
      g_bars[h]     = 0;
      g_avg[h]      = 0.0;
     }
   g_scanned = 0;

//--- PRIMERA pasada: acumular por hora.
   for(int i = start; i < rates_total; i++)
     {
      int h = HourOf(time[i]);
      if(h < 0 || h >= HOURS_IN_DAY) continue;
      g_sumRange[h] += (high[i] - low[i]) / point;
      g_bars[h]++;
      g_scanned++;
     }

   for(int h = 0; h < HOURS_IN_DAY; h++)
      if(g_bars[h] > 0) g_avg[h] = g_sumRange[h] / (double)g_bars[h];

//--- Hora mas y menos activa. Solo se consideran horas CON velas: una hora sin datos tiene media 0 y
//--- saldria siempre como "la mas tranquila", que es justo lo contrario de lo que significa — no es
//--- que el mercado este parado, es que ahi no hay historico.
   g_busiest  = -1;
   g_quietest = -1;
   for(int h = 0; h < HOURS_IN_DAY; h++)
     {
      if(g_bars[h] == 0) continue;
      if(g_busiest  < 0 || g_avg[h] > g_avg[g_busiest])  g_busiest  = h;
      if(g_quietest < 0 || g_avg[h] < g_avg[g_quietest]) g_quietest = h;
     }

//--- SEGUNDA pasada: cada vela muestra lo que su hora suele moverse, asi que el grafico ensena el
//--- ritmo del dia repitiendose. Se rellena despues del barrido porque hasta que no estan todas las
//--- medias no hay nada que pintar.
   ArrayInitialize(ProfileBuffer, 0.0);
   for(int i = start; i < rates_total; i++)
     {
      int h = HourOf(time[i]);
      if(h >= 0 && h < HOURS_IN_DAY) ProfileBuffer[i] = g_avg[h];
     }

//--- La tabla completa va al log de Expertos UNA sola vez: 24 filas no caben ni en la cabecera de la
//--- subventana ni en el data window, y reimprimirlas en cada vela llenaria el log.
   if(InpPrintTable && !g_printed && g_busiest >= 0)
     {
      g_printed = true;
      PrintFormat("HourProfile %s %s — %d bars. Hours are SERVER time.",
                  _Symbol, EnumToString(_Period), g_scanned);
      for(int h = 0; h < HOURS_IN_DAY; h++)
         PrintFormat("  %02d:00  bars %5d  avg range %8.1f pts", h, (int)g_bars[h], g_avg[h]);
     }

//--- Cifras primero: la cabecera de la subventana trunca (~63 caracteres).
   if(g_busiest < 0)
      IndicatorSetString(INDICATOR_SHORTNAME, "Hour profile: no bars to profile");
   else
      IndicatorSetString(INDICATOR_SHORTNAME,
                         StringFormat("Hour profile: busiest %02d:00 (%.0f pts), quietest %02d:00 (%.0f pts)",
                                      g_busiest, g_avg[g_busiest], g_quietest, g_avg[g_quietest]));

   int emptyHours = 0;
   for(int h = 0; h < HOURS_IN_DAY; h++)
      if(g_bars[h] == 0) emptyHours++;

   PlotIndexSetString(0, PLOT_LABEL,
                      StringFormat("%d bars over %d hours with data | busiest %02d:00 | quietest %02d:00 | SERVER time, not yours",
                                   g_scanned, HOURS_IN_DAY - emptyHours, g_busiest < 0 ? 0 : g_busiest, g_quietest < 0 ? 0 : g_quietest));

   return(rates_total);
  }
//+------------------------------------------------------------------+

HourProfile.mq5 · MetaTrader 5 · MQL5

The time window almost nobody checks

Nearly every strategy has an hour buried in it somewhere: "only the London open", "close everything before the rollover", "skip the Asian session". That window is usually inherited from somebody else's article, written for somebody else's broker, and almost never checked against the symbol actually being traded.

This shows the rhythm of your symbol on your server: which hours carry the movement, which are dead, and which have no bars at all.

What it shows

A histogram where every bar shows the typical range of ITS hour, so the daily rhythm repeats across the chart and the shape is visible at a glance. In the header, the busiest and quietest hour with their average range in points. And a 24-row table in the Experts log — bars counted and average range per hour — printed once, not on every bar.

Which clock the hours belong to, and this is the part to read twice

Here the two implementations do not say the same thing, because they cannot.

In MetaTrader, bars are stamped with your broker's SERVER time, and servers sit in different time zones: the same symbol can show its busiest hour at 09:00 on one broker and at 16:00 on another. The tool cannot convert them, nor even tell you which zone it is, because the terminal does not expose that. Read the shape, not the number: if you need it to line up with a session, compare one event you know against your own clock and apply the offset yourself.

On TradingView most of that limitation goes away: bars carry the EXCHANGE's time, which is a known quantity, so the script prints it at the top of the table and there is nothing left to guess. What remains is a whole-hour shifter to match your own clock, which does not model daylight saving — so around a DST change the alignment moves by an hour.

That difference is exactly why the port exists and is not a copy: it removes the tool's biggest limitation.

What it does not do

It does not tell you when to trade. It reports where the movement HAS been over the bars you loaded, which is not a forecast of where it will be.

An hour with no bars is not a quiet hour: it is an hour with no history, and it is counted apart instead of being shown as zero movement — which would be the opposite reading.

And it refuses to run above H4. On daily bars every bar opens at the same hour, so the "profile" would collapse into a single slot while still looking like a chart. An honest refusal beats a misleading picture.

The two ports do not draw themselves the same way either

In MetaTrader the indicator walks the whole history at once, so the histogram comes out with each hour's final average already in place. In Pine a script only ever sees one bar at a time, so the histogram builds up as it moves across the chart: early bars show a partial average that settles as more bars of that same hour are counted. The table does not have that problem — it is computed on the last bar, once everything has been counted.

Educational

This is a measurement tool. It does not trade, it does not open or close positions, it does not produce buy or sell signals and it does not tell anyone what to trade.

Educational material. None of this is financial advice or an investment recommendation: these are measurement and analysis tools, not buy or sell signals.

Hour Profile · SuaVar