[MQL5 · Pine Script]

Perfil horario

Mide cuánto se mueve de verdad tu símbolo a cada hora del día, según el histórico que tengas cargado. Enseña el ritmo, no dice cuándo operar.

Publicado el 7 de agosto de 2026 · Por SuaVar

Código fuente

El código está completo aquí arriba y puedes copiarlo. Para descargar el archivo hace falta una cuenta gratuita.

//+------------------------------------------------------------------+
//|                                                  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

La ventana horaria que casi nadie comprueba

Casi toda estrategia lleva una hora metida en algún sitio: «solo en la apertura de Londres», «cerrar todo antes del rollover», «no operar la sesión asiática». Esa ventana suele venir heredada del artículo de otro, escrito para el bróker de otro, y casi nunca se comprueba contra el símbolo que uno opera de verdad.

Esto enseña el ritmo de tu símbolo en tu servidor: qué horas cargan con el movimiento, cuáles están muertas y cuáles no tienen ni una vela.

Qué enseña

Un histograma donde cada vela muestra el recorrido típico de SU hora, así que el ritmo del día se repite a lo largo del gráfico y la forma se ve de un vistazo. En la cabecera, la hora más y la menos activa con su recorrido medio en puntos. Y una tabla de 24 filas en el registro de Expertos —velas contadas y recorrido medio por hora—, impresa una sola vez y no en cada vela.

De qué reloj son las horas, que es la parte a leer dos veces

Y aquí las dos implementaciones no dicen lo mismo, porque no pueden.

En MetaTrader, las velas llevan la hora del SERVIDOR de tu bróker, y los servidores están en husos distintos: el mismo símbolo puede tener su hora más activa a las 09:00 en un bróker y a las 16:00 en otro. La herramienta no puede convertirlas ni decirte cuál es, porque el terminal no expone esa política. Léela como forma, no como número: si necesitas cuadrarla con una sesión, compara un evento que conozcas contra tu propio reloj y aplica el desfase tú.

En TradingView esa limitación desaparece en su mayor parte: las velas llevan la hora del EXCHANGE, que sí es un dato conocido, así que el script la imprime en la tabla y ya no hay que adivinar nada. Queda un desplazador de horas enteras para cuadrarla con tu reloj, que no modela el horario de verano — así que alrededor de un cambio de hora la alineación se mueve una hora.

Esa diferencia es justo el motivo de que el puerto exista y no sea una copia: le quita a la herramienta su mayor limitación.

Lo que no hace

No dice cuándo operar. Informa de dónde HA estado el movimiento en las velas que cargaste, que no es una previsión de dónde estará.

Una hora sin velas no es una hora tranquila: es una hora sin histórico, y se cuenta aparte en vez de enseñarse como movimiento cero — que es justo la lectura contraria.

Y se niega a funcionar por encima de H4. En velas diarias todas abren a la misma hora, así que el «perfil» se reduciría a una sola ranura sin dejar de parecer un gráfico. Una negativa honesta vale más que una imagen engañosa.

Los dos puertos tampoco se comportan igual al dibujarse

En MetaTrader el indicador recorre el histórico de una vez, así que el histograma sale ya con la media definitiva de cada hora. En Pine un script solo ve una vela cada vez, de modo que el histograma se va formando a medida que avanza por el gráfico: las primeras velas muestran una media parcial que se asienta según se cuentan más velas de esa misma hora. La tabla no tiene ese problema — se calcula en la última vela, cuando ya está todo contado.

Educativo

Es una herramienta de medición. No opera, no abre ni cierra posiciones, no produce señales de compra o venta y no indica a nadie qué operar.

Material educativo. Nada de esto constituye asesoramiento financiero ni una recomendación de inversión: son herramientas de medición y análisis, no señales de compra o venta.

Perfil horario · SuaVar