English Русский Deutsch 日本語
preview
De novato a experto: Períodos del mercado Forex

De novato a experto: Períodos del mercado Forex

MetaTrader 5Sistemas comerciales |
41 1
Clemence Benjamin
Clemence Benjamin

Contenido



Introducción

En mi ciudad, una vez me tomé un tiempo para observar cómo funciona el mercado local de hortalizas a diario, y lo que descubrí fue bastante revelador. Cada mañana, los agricultores llegan temprano con sus productos frescos, y los minoristas acuden igual de temprano para conseguir las mejores ofertas al por mayor. Durante las primeras horas tras la apertura del mercado, la actividad alcanza su punto álgido: las transacciones se producen con rapidez y los precios suelen fluctuar de forma dinámica. A medida que avanza el día y se acerca el mediodía y la hora de comer, el ritmo se va ralentizando poco a poco; llegan menos comerciantes y muchos agricultores empiezan a recoger. Sin embargo, a medida que se acerca la tarde y el mercado se aproxima a la hora de cierre, la actividad se intensifica una vez más, con compradores y vendedores de última hora realizando sus últimos movimientos antes de la puesta del sol.

Este sencillo comportamiento del mercado refleja de forma sorprendente cómo funciona el mercado Forex. Al igual que los agricultores y los minoristas, los operadores de todo el mundo operan dentro de sesiones bien definidas: períodos de alta y baja actividad que definen el ritmo del mercado. Existen horas punta en las que se realizan las transacciones más importantes, lo que determina el carácter y el ritmo de la jornada de trading.

El reto actual consiste en aprovechar MQL5 para visualizar estos periodos de sesión de mercado, alineándolos a la perfección con estructuras de marcos temporales superiores e inferiores. Al sincronizar la apertura y el cierre de estas sesiones, nuestro objetivo es comprender mejor cómo influye cada una en la siguiente y cómo el factor tiempo realmente impulsa el movimiento de los precios. Otro misterio fascinante por descubrir reside en la constatación de que las velas japonesas financieras representan barras basadas en el tiempo, que normalmente denotan períodos de horas, días o semanas. Aplicando el mismo concepto a las sesiones de mercado, podemos crear velas de sesión, cada una de las cuales refleja las características únicas de esa ventana de negociación. Al igual que los marcos temporales tradicionales, cada sesión posee su propia estructura de Apertura, Máximo, Mínimo y Cierre (OHLC), que cuenta una historia distinta de actividad, sentimiento y volatilidad dentro de ese período.

Sin embargo, la diferencia más notable es que las barras de sesión a menudo se superponen, especialmente en los mercados globales que comparten horarios de negociación. Por ejemplo, las sesiones de Londres y Nueva York (Fig. 1) se superponen durante varias horas, creando períodos de mayor volatilidad y liquidez. A diferencia de las barras de tiempo estándar que se suceden una tras otra en secuencia, las barras de sesión coexisten a lo largo del tiempo, lo que refleja la participación simultánea de múltiples regiones. Esta superposición es precisamente lo que da lugar a transiciones dinámicas en el sentimiento del mercado, lo que convierte al análisis basado en sesiones en un complemento esencial de las estructuras tradicionales basadas en el tiempo.

Concepto de las sesiones del mercado Forex de Londres y Nueva York

Figura 1: Superposición conceptual entre las sesiones de trading de Londres y Nueva York 

Mediante este desarrollo, nuestro objetivo es visualizar las barras de sesión en el mismo lenguaje analítico que las velas japonesas estándar, lo que permitirá a los traders reconocer el comportamiento específico de cada sesión, identificar dónde se alcanzan los picos de liquidez y comparar cómo el sentimiento de una sesión influye en la siguiente. En definitiva, esta función conecta el análisis convencional de marcos temporales y el ritmo oculto de las sesiones de mercado, fomentando una conexión más profunda entre el tiempo, la estructura y la dinámica de precios.

Para dar vida a esta idea, a continuación describiré la implementación completa, explicando en detalle el concepto subyacente y presentando el código MQL5 completo en la siguiente sección de este análisis.



Implementación

Nuestra exploración de los periodos de mercado continúa evolucionando a medida que introducimos otra potente función: una que combina el análisis convencional de marcos temporales con la dinámica de las sesiones de negociación de Forex globales. Esta mejora reduce la brecha entre los períodos de tiempo estructurados y el comportamiento de las sesiones en el mundo real, ofreciendo una visión más realista de cómo evolucionan los mercados a lo largo del día. Para implementar esta nueva función, primero la desarrollaremos y probaremos de forma aislada, garantizando su precisión y rendimiento, antes de integrarla en el actual Market Periods Synchronizer. El resultado será una versión más avanzada y detallada de la herramienta, capaz de reflejar tanto la estructura temporal como el comportamiento del mercado en función de las sesiones, en perfecta armonía.

Otra razón para implementar esta función de forma aislada es garantizar que todos los lectores puedan comprender completamente el concepto antes de su integración. También constituye una buena práctica de desarrollo, especialmente a medida que el programa se vuelve más complejo con cada nueva función. Al aislar los componentes, facilitamos las pruebas, la depuración y la comprensión del sistema, tanto para los usuarios que siguen el desarrollo como para los desarrolladores que amplían la herramienta en futuras versiones.

Para poner en práctica este concepto, seguiremos un flujo de trabajo en dos pasos: en primer lugar, desarrollaremos la función de forma aislada y, a continuación, integraremos el componente validado en el EA «Market Periods Synchronizer». Este enfoque garantiza que la lógica siga siendo modular, fácil de probar y adaptable a futuras mejoras.

  • Paso 1 — Desarrollo de forma aislada: Comenzamos creando una clase independiente CSessionVisualizer encargada de definir las sesiones de trading, calcular sus valores OHLC y representarlas como rectángulos rellenos que imitan las velas japonesas (incluyendo, opcionalmente, mechas y etiquetas). Esta versión independiente debe probarse por separado —por ejemplo, trazando las sesiones en un gráfico H1 de EURUSD— para verificar la precisión de los límites de las sesiones y la visualización de los datos.

  • Paso 2 — Fase de integración: Una vez verificado, el visualizador de sesiones se puede integrar en el EA principal. Esto implica añadir nuevos controles de interfaz de usuario, como un botón de activación/desactivación «Mostrar sesiones: ON/OFF», un selector de color para las sesiones y un ajuste de historial para determinar cuántas sesiones anteriores se van a mostrar. La función RefreshLines() se ampliará para llamar al visualizador de sesiones tras trazar las líneas mayores y menores, lo que garantizará que todos los elementos permanezcan sincronizados. Las variables globales compartidas (como g_Lookback) mantendrán la coherencia, mientras que las superposiciones visuales entre sesiones se gestionarán mediante capas semitransparentes para mayor claridad.

Paso 1: Desarrollo de forma aislada

En esta sección, crearemos una clase modular llamada CSessionVisualizer, diseñada para detectar, calcular y visualizar las sesiones del mercado Forex en un gráfico, con cuerpos de velas, mechas opcionales y etiquetas. Este enfoque hace hincapié en la modularidad, la legibilidad y la reutilización, lo que permite integrar posteriormente la función en marcos más amplios, como el EA «Market Periods Synchronizer».

1. Configuración y finalidad del encabezado

Todo buen módulo de MQL5 comienza con un encabezado adecuado que defina claramente qué es el script, quién lo ha escrito y para qué sirve. Esto ayuda a los demás a entender tu código y garantiza unas buenas prácticas de desarrollo en lo que respecta al control de versiones.

También definimos una macro ARGB pequeña pero potente, que nos permite crear fácilmente colores con transparencia, algo esencial para superponer varias capas visuales (como sesiones que se solapan en un gráfico).

//+------------------------------------------------------------------+
//| SessionVisualizerTest.mqh                                        |
//| Modular class for Forex session OHLC visualization               |
//| Author: Clemence Benjamin                                        |
//| Version: 1.00                                                    |
//+------------------------------------------------------------------+
#property strict

// ARGB Macro: Creates a color with alpha (transparency)
#define ARGB(a,r,g,b) ((color)(((uchar)(a))<<24)|(((uchar)(r))<<16)|(((uchar)(g))<<8)|((uchar)(b)))

#include <Object.mqh>  // Provides access to chart object manipulation

En este caso, utilizamos Object.mqh porque el visualizador crea y gestiona varios objetos de gráfico (rectángulos, etiquetas, líneas). La macro ARGB() nos permite generar colores semitransparentes, como ARGB(120,255,0,0), para obtener un relleno rojo transparente, algo fundamental para la superposición visual. Pero más adelante eliminaremos el ARGB para utilizar uno de CCanvas.

2. Definiciones de sesión y configuración de la estructura

Antes de dibujar nada, debemos definir qué significa una "sesión" en nuestro contexto. Cada sesión de Forex (Sídney, Tokio, Londres y Nueva York) tiene su propio horario de apertura y cierre. Utilizamos una enumeración para definirlas simbólicamente y una estructura para almacenar las propiedades de cada sesión, como su nombre, horario, color y si está activa en ese momento.

enum SESSION_TYPE {
   SESSION_SYDNEY = 0,  // 22:00-07:00 GMT
   SESSION_TOKYO   = 1,  // 00:00-09:00 GMT
   SESSION_LONDON  = 2,  // 08:00-17:00 GMT
   SESSION_NEWYORK = 3   // 13:00-22:00 GMT
};

struct SessionInfo {
   SESSION_TYPE type;
   string       name;       // Short code e.g., "LON"
   int          open_hour;  // GMT open hour
   int          close_hour; // GMT close hour
   color        sess_color; // Visual color on chart
   bool         enabled;    // Whether to render this session
};

El uso de enumeraciones garantiza que nuestro código sea fácil de leer y de ampliar posteriormente. Por ejemplo, agregar una "Sesión personalizada" solo requeriría una entrada adicional y la inicialización de la estructura.

3. Parámetros de entrada e inicialización de la clase

A continuación, definimos los parámetros de entrada y preparamos el constructor de nuestra clase. Esta configuración permite a los traders o desarrolladores personalizar los períodos de análisis histórico, los colores y el comportamiento de la mecha directamente desde el cuadro de diálogo de entrada del EA.

input int    InpSessionLookback = 10;  // Days of historical sessions
input bool   InpShowSessionWicks = false;
input int    InpWickAlpha = 120;
input color  InpFillBull = clrLime;
input color  InpFillBear = clrPink;

class CSessionVisualizer : public CObject {
private:
   SessionInfo m_sessions[4];
   int         m_gmt_offset;
   string      m_prefix;

public:
   CSessionVisualizer(string prefix = "SESS_") : m_prefix(prefix) {
      // Initialize all session parameters (GMT-based)
      m_sessions[SESSION_SYDNEY] = {SESSION_SYDNEY, "SYD", 22, 7, clrAqua, true};
      m_sessions[SESSION_TOKYO]  = {SESSION_TOKYO,  "TOK", 0, 9, clrYellow, true};
      m_sessions[SESSION_LONDON] = {SESSION_LONDON, "LON", 8, 17, clrRed, true};
      m_sessions[SESSION_NEWYORK]= {SESSION_NEWYORK,"NY", 13, 22, clrBlue, true};
      
      // Basic GMT offset estimation
      MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
      m_gmt_offset = -dt.hour % 24;  
   }

   ~CSessionVisualizer() {
      DeleteAllSessionObjects();
   }

En este caso, m_prefix ayuda a prevenir conflictos en la nomenclatura de objetos en el gráfico, especialmente cuando se utilizan varias herramientas simultáneamente. Por ejemplo, cada objeto puede estar etiquetado como "SESS_LON_O_1730764800".

4. Actualización y dibujo de sesiones

El método RefreshSessions() es el núcleo del visualizador. Borra los dibujos de la sesión anterior y los regenera durante un período de tiempo definido por el usuario. Este diseño modular permite actualizar los elementos visuales con cada nueva barra o intervalo de tiempo sin sobrecargar el gráfico.

void RefreshSessions(int lookback_days = 10) {
   DeleteAllSessionObjects();  // Clear previously drawn sessions
   datetime end_time = TimeCurrent();
   datetime start_time = end_time - (lookback_days * 86400);

   for (int day = 0; day < lookback_days; day++) {
      datetime day_start = start_time + (day * 86400);
      for (int s = 0; s < 4; s++) {
         if (!m_sessions[s].enabled) continue;
         DrawSessionForDay(m_sessions[s], day_start);
      }
   }
   ChartRedraw();
}

Esta estructura se basa en bucles para mayor eficiencia y modularidad. Cada día y cada sesión se procesa individualmente, una técnica que evita la superposición de artefactos de sesión o la falta de rangos de datos.

5. Dibujo de sesiones individuales

La función DrawSessionForDay() se encarga de renderizar cada sesión. Determina las horas de apertura y cierre (ajustadas según la diferencia horaria con GMT), recupera los datos de precios para ese período y visualiza la sesión como un rectángulo similar a una vela japonesa con mechas opcionales.

void DrawSessionForDay(const SessionInfo &sess, datetime day_start) {
   MqlDateTime dt_open, dt_close;
   TimeToStruct(day_start, dt_open);
   dt_open.hour = sess.open_hour + m_gmt_offset;
   datetime t_open = StructToTime(dt_open);
   
   TimeToStruct(day_start, dt_close);
   dt_close.hour = sess.close_hour + m_gmt_offset;
   datetime t_close = StructToTime(dt_close);
   
   if (sess.close_hour < sess.open_hour) t_close += 86400;  // Overnight fix

   double o,h,l,c;
   if (!GetOHLCInTimeRange(t_open, t_close, o,h,l,c)) return;

   color body_col = (c > o) ? InpFillBull : InpFillBear;
   string rect = m_prefix + sess.name + "_B_" + IntegerToString((int)t_open);
   ObjectCreate(0, rect, OBJ_RECTANGLE, 0, t_open, MathMin(o,c), t_close, MathMax(o,c));
   ObjectSetInteger(0, rect, OBJPROP_BGCOLOR, body_col);
   ObjectSetInteger(0, rect, OBJPROP_FILL, true);
   ObjectSetInteger(0, rect, OBJPROP_BACK, true);
}

Este enfoque nos proporciona barras de sesión que se comportan como velas japonesas: una nueva forma de analizar las sesiones como si fueran microperiodos de tiempo.

6. Recuperación de datos OHLC de la sesión

La función GetOHLCInTimeRange() extrae datos precisos de apertura, máximo, mínimo y cierre del período de tiempo seleccionado dentro de la duración de cada sesión. El uso de iBarShift() garantiza una alineación precisa con las barras del gráfico existentes.

bool GetOHLCInTimeRange(datetime start, datetime end, double &open, double &high, double &low, double &close) {
   int shift_start = iBarShift(_Symbol, _Period, start, false);
   int shift_end   = iBarShift(_Symbol, _Period, end, false);
   if (shift_start < 0 || shift_end < 0) return false;
   int count = shift_start - shift_end + 1;
   if (count <= 0) return false;

   double o[],h[],l[],c[];
   ArraySetAsSeries(o,true); ArraySetAsSeries(h,true); ArraySetAsSeries(l,true); ArraySetAsSeries(c,true);
   CopyOpen(_Symbol,_Period,shift_end,count,o);
   CopyHigh(_Symbol,_Period,shift_end,count,h);
   CopyLow(_Symbol,_Period,shift_end,count,l);
   CopyClose(_Symbol,_Period,shift_end,count,c);

   open = o[count-1]; high = h[ArrayMaximum(h,0,count)];
   low = l[ArrayMinimum(l,0,count)]; close = c[0];
   return true;
}

Esto proporciona los datos OHLC de "velas japonesas de sesión" a partir de series de precios estándar, sin necesidad de volver a consultar fuentes externas.

7. Limpieza y gestión de objetos

Un buen código siempre se encarga de limpiar después de sí mismo. La función DeleteAllSessionObjects() garantiza que los objetos antiguos se eliminen al actualizar o desinicializar. Sin esto, acabarías con miles de rectángulos sobrantes después de largas sesiones de prueba.

void DeleteAllSessionObjects() {
   int total = ObjectsTotal(0);
   for (int i = total - 1; i >= 0; i--) {
      string name = ObjectName(0, i);
      if (StringFind(name, m_prefix) == 0)
         ObjectDelete(0, name);
   }
}

Desarrollo del EA de prueba:

Comenzamos con un encabezado compacto y la inclusión. Este Asesor Experto (EA) es intencionalmente pequeño: solo inicia el visualizador, pone en marcha un temporizador para actualizaciones periódicas (para que puedas ver cómo se actualizan las sesiones mientras el mercado está en marcha) y realiza la limpieza al salir. Mantener los entornos de prueba pequeños ayuda a aislar problemas y validar el comportamiento de las clases antes de integrarlos en sistemas más grandes.

//+------------------------------------------------------------------+
//| SessionTest.mq5 - Standalone test for CSessionVisualizer         |
//| Purpose: lightweight EA to exercise the SessionVisualizer class  |
//+------------------------------------------------------------------+
#property strict

#include <SessionVisualizerTest.mqh>

A continuación, declaramos la instancia de tiempo de ejecución. El uso de un puntero y new proporciona un control explícito sobre el ciclo de vida: creamos el visualizador en OnInit() y lo eliminamos en OnDeinit(), de modo que el destructor se ejecuta inmediatamente y elimina los objetos que la clase creó. Este patrón resulta práctico para realizar pruebas y refleja la forma en que se gestionan los objetos en sistemas de trading más amplios que crean o destruyen subsistemas de forma dinámica.

// runtime pointer to the session visualizer
CSessionVisualizer *g_session_vis = NULL;

En OnInit() es donde construimos el visualizador, configuramos un temporizador de actualización y realizamos el dibujo inicial. El ejemplo utiliza un temporizador de demostración de 30 segundos, lo suficientemente corto como para ver las actualizaciones con frecuencia, pero lo suficientemente largo como para evitar un uso excesivo de la CPU. Le pasamos un prefijo único al visualizador para que sus objetos tengan un espacio de nombres claro (algo útil al probar varias herramientas en el mismo gráfico). Llamar inmediatamente a RefreshSessions(5) renderizará cinco días de sesiones para que pueda verificar de inmediato la representación histórica de las sesiones.

int OnInit()
{
   // allocate the session visualizer with a test-specific prefix to avoid name clashes
   g_session_vis = new CSessionVisualizer("TEST_SESS_");

   // Refresh every 30 seconds during testing (demo). Adjust for production usage.
   EventSetTimer(30);

   // initial rendering of the last 5 days (quick sanity check)
   if(g_session_vis != NULL)
      g_session_vis.RefreshSessions(5);

   return(INIT_SUCCEEDED);
}

Nota: Realizamos una comprobación de nulidad antes de llamar al método. Si la orden «new» falla por cualquier motivo (algo muy poco habitual en ordenadores de sobremesa normales), esto evita que el sistema se bloquee y facilita la detección del error en los registros.

OnTimer() controla las actualizaciones periódicas. Para la visualización de sesiones, esto es útil porque los OHLC de la sesión pueden cambiar ligeramente en las barras en vivo (dependiendo del marco temporal que se utilice para calcular el OHLC), y los redibujos frecuentes permiten observar la evolución de las mechas y los cuerpos de la sesión. En un EA de producción, es posible que prefiera actualizar solo con barras nuevas o con un intervalo más largo, pero para las pruebas, 30 segundos es suficiente.

void OnTimer()
{
   // periodic refresh of the last 5 days
   if(g_session_vis != NULL)
      g_session_vis.RefreshSessions(5);
}

OnDeinit() realiza la limpieza: detenemos el temporizador y eliminamos el objeto visualizador. Al eliminar el objeto, se invoca su destructor, que (tal como está implementado) elimina todos los objetos de sesión que la clase creó en el gráfico, lo que garantiza que el gráfico permanezca limpio después de la prueba. La limpieza explícita es esencial en entornos de carga repetida (carga/descarga de muchos EA) para evitar la sobrecarga del gráfico y la persistencia de objetos.

void OnDeinit(const int reason)
{
   EventKillTimer();

   if(g_session_vis != NULL)
   {
      // destructor will call DeleteAllSessionObjects()
      delete g_session_vis;
      g_session_vis = NULL;
   }
}

Resultado de la prueba inicial:

Nuestra fase de pruebas inicial fue sencilla: tras compilar correctamente el Asesor Experto de Prueba de Sesión, simplemente lo adjuntamos a un gráfico real de MetaTrader 5. El resultado fue una visualización inmediata y clara de las sesiones de Forex. En la imagen de abajo, se puede ver claramente cómo se superponen las sesiones, cada una distinguida por su propio color para facilitar su identificación. Los rectángulos de sesión rellenos también transmiten el sentimiento del mercado: el verde lima indica un cierre de sesión alcista, mientras que el rosa marca uno bajista. Esta visualización codificada por colores permite interpretar fácilmente la tendencia dominante de cada sesión de negociación de un vistazo.

Pruebas iniciales

Figura 2: Prueba de sesión

Paso 2: Fase de integración

Toda gran herramienta de trading pasa por una fase de transformación: desde un script experimental hasta un módulo de producción totalmente integrado. Nuestro visualizador de sesiones no es una excepción. Originalmente, el encabezado de prueba (SessionVisualizerTest.mqh) se centraba en establecer la base: definir las sesiones de negociación, recuperar los datos OHLC y dibujar rectángulos de colores que representaran cada período del mercado Forex. Si bien cumplió bien su función durante las pruebas, la integración en producción de nuestro Market Periods Synchronizer EA requirió mejoras para lograr una mayor claridad visual, extensibilidad y compatibilidad.

Por qué dejamos de usar cuerpos de sesión rellenos

En la versión de prueba, las sesiones se representaban con rectángulos rellenos de color sólido, utilizando los colores de cada sesión para resaltar cada bloque de negociación activo. Este enfoque parecía dinámico, pero rápidamente se volvió visualmente congestionado, especialmente cuando se mostraban sesiones superpuestas o capas históricas juntas. El estilo de relleno también ocultaba los detalles subyacentes del gráfico, lo que frustraba uno de nuestros objetivos: proporcionar contexto sin saturar la información.

Por lo tanto, en la nueva versión, introducimos rectángulos de cuerpo sin relleno con bordes punteados. Estas velas japonesas actúan como velas huecas, marcando el rango de apertura y cierre de cada sesión sin cubrir el gráfico de precios. La elección del diseño hace que el gráfico sea más legible y garantiza que el trader pueda seguir viendo claramente la acción del precio con claridad debajo de los marcadores de sesión.

Como desarrolladores, esta es una lección fundamental: a veces, menos color equivale a más información. Al eliminar los rellenos opacos, ganamos visibilidad y profesionalismo en la presentación.

Integración de CCanvas y ARGB: Un único motor visual

Un cambio técnico sutil pero importante en el nuevo encabezado es el uso de #include<Canvas/Canvas.mqh> para imágenes basadas en ARGB. Esto garantiza la compatibilidad con la función CCanvas existente de nuestro EA, que también gestiona la representación del fondo y la transparencia para otros módulos. En lugar de redefinir ARGB por separado, la nueva versión delega esta función a la biblioteca Canvas, lo que crea coherencia en todos los gráficos utilizados por EA.

Esta decisión es tanto arquitectónica como práctica. Al crear EAs modulares que reutilizan componentes, las macros ARGB duplicadas pueden provocar conflictos o un comportamiento impredecible, especialmente si un componente define la transparencia de manera diferente. El uso de un único motor compartido, CCanvas, ayuda a mantener una lógica de renderizado unificada y reduce los costes de mantenimiento.

En pocas palabras, declaramos la inclusión de Canvas de forma global para que cada componente visual, ya sean cuadros de sesión, mechas o superposiciones, utilice el mismo "lenguaje" gráfico.

Redefiniendo las sesiones: De bloques de color a marcos de mercado

La estructura de la representación de la sesión sigue siendo similar: seguimos utilizando una estructura SessionInfo que contiene el nombre, el tipo, las horas de apertura/cierre y el color. Sin embargo, hemos reinventado la forma en que se visualizan estos atributos.

Cada sesión ahora genera:

  • Un rectángulo punteado que marca el rango de precios de apertura y cierre.
  • Rectángulos opcionales rellenos de gris que muestran la excursión total máxima-mínima de la sesión.
  • Etiquetas de texto y líneas verticales que marcan los horarios de apertura y cierre.
  • Superposición de primer plano y fondo para que los rectángulos permanezcan detrás de las velas de precios mientras que las líneas y etiquetas permanecen visibles para el usuario.

Esto crea una apariencia analítica pero elegante, como un plano transparente del ritmo del mercado. Los traders pueden ver qué sesiones se están expandiendo o contrayendo sin perder detalle en el gráfico. El nuevo concepto de "espacio vacío" actúa como un marco para el mercado, en lugar de una capa que cubra el mercado.

Configuración en tiempo de ejecución: Cómo hacer que el visualizador sea dinámico.

Para brindar mayor flexibilidad a los usuarios y a los asesores expertos, hemos convertido configuraciones que antes eran estáticas en variables de tiempo de ejecución. Por ejemplo, ahora se pueden configurar globalmente las variables m_show_wicks, m_wick_alpha y m_gmt_offset. Un operador o desarrollador puede activar o desactivar la visibilidad de la mecha, cambiar la transparencia o ajustar el desfase horario sin necesidad de recompilar.

Notarás funciones como

void SetShowWicks(bool show) { m_show_wicks = show; }
void SetWickAlpha(int alpha) { m_wick_alpha = MathMax(0, MathMin(255, alpha)); }
void SetGMTOffset(int offset){ m_gmt_offset = offset; }

Estas pequeñas pero significativas adiciones permiten la interacción en tiempo real entre la interfaz de EA y la capa de visualización, un aspecto fundamental del diseño modular moderno en MQL5.

Información contextual: Visualización de las sesiones actuales y anteriores

A diferencia de la versión de prueba, que solo mostraba sesiones históricas estáticas, el nuevo encabezado introduce el seguimiento de las sesiones actuales y recientes. Durante las operaciones en tiempo real, el sistema identifica qué sesión está activa y le añade la etiqueta "En vivo". Además, mantiene visible la sesión que se cerró recientemente durante una hora después de su cierre, lo que ofrece una referencia visual rápida del rendimiento de la sesión anterior.

Ahora que comprendemos los ajustes realizados para lograr una integración efectiva, procederé a desglosar y explicar en detalle el nuevo encabezado SessionVisualizer. A continuación, hablaré sobre su integración en el Asesor Experto (EA) y destacaré las nuevas funciones del EA diseñadas para ayudar a los traders a aprovechar de forma más eficiente los periodos de sesión del mercado y así tomar mejores decisiones comerciales.

1) Descripción del módulo y rigor de compilación

Comenzamos con una descripción clara porque el código publicado también es documentación. El control de versiones informa a los lectores sobre las novedades (por ejemplo, "rectángulos sin relleno, bordes punteados y mechas grises"), y #property strict garantiza la seguridad de tipos de MQL5 moderna, detectando errores silenciosos que a menudo se cuelan en los encabezados experimentales.

//+------------------------------------------------------------------+
//| SessionVisualizer.mqh                                            |
//| Modular class for Forex session OHLC visualization               |
//| Copyright 2025: Clemence Benjamin                                |
//| Version: 1.02                                                    |
//+------------------------------------------------------------------+
#property strict

2) Dependencias compartidas (API de objetos + ARGB unificado a través de Canvas)

En la fase de pruebas, es habitual crear nuestra propia macro ARGB; en producción, nos adaptamos al motor visual único de EA para evitar conflictos. La inclusión de Canvas.mqh nos proporciona el estándar ARGB() y una compatibilidad perfecta con otras funciones de dibujo en el Asesor Experto Market Periods Synchronizer.

#include <Object.mqh>
#include <Canvas/Canvas.mqh>   // for ARGB()

3) Modelo de dominio de sesión (enumeración + estructura)

Formalizamos el espacio del problema: cuatro sesiones principales, nombres fáciles de entender para los humanos y un contenedor SessionInfo sencillo. Aunque una propiedad no se utilice en todas partes (por ejemplo, sess_color ya no colorea el relleno del cuerpo), la mantenemos: los colores siguen controlando las etiquetas/líneas y manteniendo la compatibilidad con versiones anteriores de herramientas y hábitos de usuario.

//----------------------------------------------
// Session enum and info
//----------------------------------------------
enum SESSION_TYPE {
   SESSION_SYDNEY = 0,   // 22:00-07:00 GMT
   SESSION_TOKYO  = 1,   // 00:00-09:00 GMT
   SESSION_LONDON = 2,   // 08:00-17:00 GMT
   SESSION_NEWYORK= 3    // 13:00-22:00 GMT
};

struct SessionInfo {
   SESSION_TYPE type;
   string       name;       // "SYD","TOK","LON","NY"
   int          open_hour;  // GMT open hour
   int          close_hour; // GMT close hour
   color        sess_color; // base tint (not used for border now)
   bool         enabled;
};

4) Parámetros de ejecución (parámetros ajustables por el EA)

Declaramos variables globales y ajustables mediante EA para que las funcionalidades se activen o desactiven sin necesidad de recompilar. La propiedad lookback controla la densidad del historial, m_show_wicks/m_wick_alpha controlan la visibilidad y la transparencia de los rangos de mechas, y m_gmt_offset permite que el EA armonice la hora del servidor con GMT, el clásico punto problemático del mundo real.

//----------------------------------------------
// Runtime params (configured by EA)
//----------------------------------------------
int    m_session_lookback = 10;
bool   m_show_wicks       = false;
int    m_wick_alpha       = 120;
int    m_gmt_offset       = 0;

5) Estructura de clase y valores predeterminados del constructor

Mantenemos la API pública compacta y el estado privado explícito. En el constructor, inicializamos los horarios GMT canónicos y las etiquetas legibles de tres letras. Observará que almacenamos un prefijo m_prefix: este es el secreto para nombrar los objetos de forma segura y eliminarlos correctamente más adelante, evitando colisiones accidentales con otros objetos del gráfico.

class CSessionVisualizer : public CObject
{
private:
   SessionInfo m_sessions[4];
   string      m_prefix;

public:
   CSessionVisualizer(string prefix="SESS_") : m_prefix(prefix)
   {
      // Defaults (GMT schedule)
      m_sessions[SESSION_SYDNEY] .type=SESSION_SYDNEY;  m_sessions[SESSION_SYDNEY] .name="SYD"; m_sessions[SESSION_SYDNEY] .open_hour=22; m_sessions[SESSION_SYDNEY] .close_hour=7;  m_sessions[SESSION_SYDNEY] .sess_color=clrAqua;  m_sessions[SESSION_SYDNEY] .enabled=true;
      m_sessions[SESSION_TOKYO]  .type=SESSION_TOKYO;   m_sessions[SESSION_TOKYO]  .name="TOK"; m_sessions[SESSION_TOKYO]  .open_hour=0;  m_sessions[SESSION_TOKYO]  .close_hour=9;  m_sessions[SESSION_TOKYO]  .sess_color=clrYellow; m_sessions[SESSION_TOKYO]  .enabled=true;
      m_sessions[SESSION_LONDON] .type=SESSION_LONDON;  m_sessions[SESSION_LONDON] .name="LON"; m_sessions[SESSION_LONDON] .open_hour=8;  m_sessions[SESSION_LONDON] .close_hour=17; m_sessions[SESSION_LONDON] .sess_color=clrRed;   m_sessions[SESSION_LONDON] .enabled=true;
      m_sessions[SESSION_NEWYORK].type=SESSION_NEWYORK; m_sessions[SESSION_NEWYORK].name="NY";  m_sessions[SESSION_NEWYORK].open_hour=13; m_sessions[SESSION_NEWYORK].close_hour=22; m_sessions[SESSION_NEWYORK].sess_color=clrBlue;  m_sessions[SESSION_NEWYORK].enabled=true;
   }

   ~CSessionVisualizer() { DeleteAllSessionObjects(); }

6) API pública: actualización + interruptores (diseñada para gráficos en tiempo real)

RefreshSessions() es la función de reconstrucción con un solo botón: borra, vuelve a dibujar el historial y luego dibuja la ventana en vivo. Ponemos a disposición pequeños y expresivos métodos de configuración para que un panel de EA pueda cambiar de sesión, modificar el color de los temas o sincronizar la hora GMT sobre la marcha. Un cambio notable con respecto al encabezado de prueba: no hay rellenos de cuerpo; SetFillColors() ahora es una operación nula que se mantiene únicamente por compatibilidad con versiones anteriores.

   //---------------------------------------------------------------
   // PUBLIC API
   //---------------------------------------------------------------
   void RefreshSessions(int lookback_days=10)
   {
      m_session_lookback = lookback_days;
      DeleteAllSessionObjects();

      const datetime now   = TimeCurrent();
      const datetime start = now - (m_session_lookback*86400);

      // Historical completed sessions
      for(int d=0; d<m_session_lookback; ++d)
      {
         datetime day_start = start + d*86400;
         for(int s=0; s<4; ++s)
         {
            if(!m_sessions[s].enabled) continue;
            DrawHistoricalSessionForDay(m_sessions[s], day_start);
         }
      }

      // Active session windows
      DrawCurrentSessions();

      ChartRedraw();
   }

   void SetSessionEnabled(SESSION_TYPE type, bool enabled)
   {
      for(int i=0;i<4;i++) if(m_sessions[i].type==type){ m_sessions[i].enabled=enabled; break; }
   }

   void SetSessionColor(SESSION_TYPE type, color col)
   {
      for(int i=0;i<4;i++) if(m_sessions[i].type==type){ m_sessions[i].sess_color=col; break; }
   }

   // Kept for compatibility with EA; ignored (we no longer fill bodies).
   void SetFillColors(color /*bull*/, color /*bear*/) { /* no-op */ }

   void SetShowWicks(bool show) { m_show_wicks = show; }
   void SetWickAlpha(int alpha) { m_wick_alpha = MathMax(0, MathMin(255, alpha)); }
   void SetGMTOffset(int offset){ m_gmt_offset = offset; }

   // public cleanup
   void ClearAll() { DeleteAllSessionObjects(); }

7) Dibujo histórico (solo sesiones completadas)

Un error común en las herramientas de sesión es incorporar datos parciales al "historial". Aquí calculamos la apertura/cierre con m_gmt_offset, gestionamos el caso de "desplazamiento a medianoche" y luego omitimos todo lo que no esté completamente en el pasado. Solo después de confirmar que la ventana está completa, calculamos OHLC y renderizamos.

private:
   //---------------------------------------------------------------
   // CORE DRAWING
   //---------------------------------------------------------------
   void DrawHistoricalSessionForDay(const SessionInfo &sess, datetime day_start)
   {
      // build open/close datetimes adjusted by GMT offset
      MqlDateTime dto, dtc; TimeToStruct(day_start, dto); dtc = dto;
      dto.hour = sess.open_hour  + m_gmt_offset; dto.min=0; dto.sec=0;
      dtc.hour = sess.close_hour + m_gmt_offset; dtc.min=0; dtc.sec=0;

      datetime t_open  = StructToTime(dto);
      datetime t_close = StructToTime(dtc);
      if(sess.close_hour < sess.open_hour) t_close += 86400; // wraps midnight

      // Only draw if fully in the past
      if(t_close >= TimeCurrent()) return;
      if(t_close < TimeCurrent() - (m_session_lookback*86400)) return;

      double o,h,l,c;
      if(!GetOHLCInTimeRange(t_open, t_close, o,h,l,c)) return;

      DrawSessionVisuals(sess, t_open, t_close, o,h,l,c, /*is_current=*/false);
   }

8) Gestión de sesiones en directo (y 1 hora de margen para la última).

Detectamos la sesión activa en tiempo real creando la ventana de tiempo del día y ajustándola para las sesiones nocturnas. Si estamos dentro, transmitimos OHLC hasta ahora, ajustamos h/l con el precio bid actual para que el recuadro refleje la evolución del precio en tiempo real y lo etiquetamos como “(Live)”. Además, mantenemos la sesión recién cerrada en pantalla durante una hora adicional, lo cual resulta muy útil durante el rollover, cuando los traders necesitan contexto.

   void DrawCurrentSessions()
   {
      const datetime now = TimeCurrent();
      MqlDateTime cur; TimeToStruct(now, cur);

      for(int i=0;i<4;i++)
      {
         if(!m_sessions[i].enabled) continue;
         const SessionInfo sess = m_sessions[i];

         // compute session window for "today" (adjusted for wrap)
         MqlDateTime ds, de; TimeToStruct(now, ds); de = ds;
         ds.hour = sess.open_hour  + m_gmt_offset; ds.min=0; ds.sec=0;
         de.hour = sess.close_hour + m_gmt_offset; de.min=0; de.sec=0;

         datetime session_start = StructToTime(ds);
         datetime session_end   = StructToTime(de);

         if(sess.close_hour < sess.open_hour)
         {
            if(cur.hour >= sess.open_hour + m_gmt_offset) session_end += 86400; // ends tomorrow
            else session_start -= 86400; // started yesterday
         }

         if(now >= session_start && now <= session_end)
         {
            // pull OHLC until now (close = current)
            double o,h,l,c;
            if(!GetOHLCInTimeRange(session_start, now, o,h,l,c))
               o = h = l = c = SymbolInfoDouble(_Symbol, SYMBOL_BID);

            const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            h = MathMax(h, bid);
            l = MathMin(l, bid);
            c = bid;

            DrawSessionVisuals(sess, session_start, session_end, o,h,l,c, /*is_current=*/true);
         }
         else if(now > session_end && now <= session_end + 3600)
         {
            // Keep just-closed session visible for 1 hour
            double o,h,l,c;
            if(!GetOHLCInTimeRange(session_start, session_end, o,h,l,c)) continue;
            DrawSessionVisuals(sess, session_start, session_end, o,h,l,c, /*is_current=*/false);
         }
      }
   }

9) Gramática visual: cuerpos sin relleno, bordes punteados, mecha gris opcional

Este es el cambio más característico respecto al encabezado de prueba. Los cuerpos están vacíos y colocados detrás del precio (OBJPROP_BACK=true) para que nada oculte las velas. Los bordes codifican el sentimiento (verde oscuro para alcista, rojo para bajista; de lo contrario, color de sesión). Cuando están habilitadas, las mechas son rectángulos ARGB de color gris suave dibujados en el fondo para revelar todo el recorrido entre el máximo y el mínimo sin distracciones. Las etiquetas y las líneas de tiempo permanecen en primer plano para mayor claridad.

   // Renders:
   //  - unfilled body rectangle (Open..Close) with dashed border
   //    * border = clrDarkGreen if bullish, clrRed if bearish (Neutral uses session color)
   //  - optional GREY filled wick rectangles in background for visibility
   //  - open/close vlines + labels in foreground (thin)
   void DrawSessionVisuals(const SessionInfo &sess,
                           datetime t_open, datetime t_close,
                           double o,double h,double l,double c,
                           bool is_current)
   {
      const bool bullish = (c > o);
      const bool bearish = (c < o);

      // ---------- vertical lines ----------
      string vopen = m_prefix + sess.name + "_O" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
      if(ObjectFind(0, vopen) == -1) ObjectCreate(0, vopen, OBJ_VLINE, 0, t_open, 0);
      ObjectSetInteger(0, vopen, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, vopen, OBJPROP_WIDTH, is_current?2:1);
      ObjectSetInteger(0, vopen, OBJPROP_STYLE, is_current?STYLE_SOLID:STYLE_DASH);
      ObjectSetInteger(0, vopen, OBJPROP_BACK,  false);
      ObjectSetInteger(0, vopen, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, vopen, OBJPROP_HIDDEN, false);

      string vclose = m_prefix + sess.name + "_C" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_close);
      if(ObjectFind(0, vclose) == -1) ObjectCreate(0, vclose, OBJ_VLINE, 0, t_close, 0);
      ObjectSetInteger(0, vclose, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, vclose, OBJPROP_WIDTH, is_current?2:1);
      ObjectSetInteger(0, vclose, OBJPROP_STYLE, is_current?STYLE_SOLID:STYLE_DASH);
      ObjectSetInteger(0, vclose, OBJPROP_BACK,  false);
      ObjectSetInteger(0, vclose, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, vclose, OBJPROP_HIDDEN, false);

      // ---------- body rectangle (UNFILLED, dashed) ----------
      const double body_top = MathMax(o,c);
      const double body_bot = MathMin(o,c);

      color border_col = sess.sess_color;  // neutral fallback
      if(bullish) border_col = clrDarkGreen;
      else if(bearish) border_col = clrRed;

      string rect = m_prefix + sess.name + "_R" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
      if(ObjectFind(0, rect) == -1)
      {
         if(!ObjectCreate(0, rect, OBJ_RECTANGLE, 0, t_open, body_bot, t_close, body_top))
            PrintFormat("Failed to create session rectangle %s err=%d", rect, GetLastError());
      }
      // unfilled rectangle with dashed edge, kept in background
      ObjectSetInteger(0, rect, OBJPROP_FILL,   false);
      ObjectSetInteger(0, rect, OBJPROP_COLOR,  border_col);
      ObjectSetInteger(0, rect, OBJPROP_STYLE,  STYLE_DASH);
      ObjectSetInteger(0, rect, OBJPROP_WIDTH,  1);
      ObjectSetInteger(0, rect, OBJPROP_BACK,   true);
      ObjectSetInteger(0, rect, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, rect, OBJPROP_HIDDEN, false);
      // keep coordinates fresh
      ObjectMove(0, rect, 0, t_open,  body_bot);
      ObjectMove(0, rect, 1, t_close, body_top);

      // ---------- WICKS (optional) as GREY filled rectangles in background ----------
      if(m_show_wicks)
      {
         uint wick_col = ARGB(m_wick_alpha, 128,128,128); // semi-transparent grey

         // Upper wick: [body_top .. high]
         string wu = m_prefix + sess.name + "_WU" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
         if(h > body_top)
         {
            if(ObjectFind(0, wu) == -1)
            {
               if(!ObjectCreate(0, wu, OBJ_RECTANGLE, 0, t_open, body_top, t_close, h))
                  PrintFormat("Failed to create upper wick %s err=%d", wu, GetLastError());
            }
            ObjectSetInteger(0, wu, OBJPROP_BGCOLOR, wick_col);
            ObjectSetInteger(0, wu, OBJPROP_COLOR,  wick_col);
            ObjectSetInteger(0, wu, OBJPROP_FILL,   true);
            ObjectSetInteger(0, wu, OBJPROP_BACK,   true);
            ObjectSetInteger(0, wu, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, wu, OBJPROP_HIDDEN, false);
            ObjectMove(0, wu, 0, t_open,  body_top);
            ObjectMove(0, wu, 1, t_close, h);
         }
         else ObjectDelete(0, wu);

         // Lower wick: [low .. body_bot]
         string wl = m_prefix + sess.name + "_WL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
         if(l < body_bot)
         {
            if(ObjectFind(0, wl) == -1)
            {
               if(!ObjectCreate(0, wl, OBJ_RECTANGLE, 0, t_open, l, t_close, body_bot))
                  PrintFormat("Failed to create lower wick %s err=%d", wl, GetLastError());
            }
            ObjectSetInteger(0, wl, OBJPROP_BGCOLOR, wick_col);
            ObjectSetInteger(0, wl, OBJPROP_COLOR,  wick_col);
            ObjectSetInteger(0, wl, OBJPROP_FILL,   true);
            ObjectSetInteger(0, wl, OBJPROP_BACK,   true);
            ObjectSetInteger(0, wl, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, wl, OBJPROP_HIDDEN, false);
            ObjectMove(0, wl, 0, t_open,  l);
            ObjectMove(0, wl, 1, t_close, body_bot);
         }
         else ObjectDelete(0, wl);
      }
      else
      {
         // Ensure wicks are removed if disabled
         ObjectDelete(0, m_prefix + sess.name + "_WU" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open));
         ObjectDelete(0, m_prefix + sess.name + "_WL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open));
      }

      // ---------- Labels in foreground ----------
      string session_tag = sess.name + (is_current ? " (Live)" : "");
      string lbl_o = m_prefix + sess.name + "_OL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
      if(ObjectFind(0, lbl_o) == -1) ObjectCreate(0, lbl_o, OBJ_TEXT, 0, t_open, o);
      ObjectSetString (0, lbl_o, OBJPROP_TEXT, session_tag + " Open");
      ObjectSetInteger(0, lbl_o, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, lbl_o, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, lbl_o, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, lbl_o, OBJPROP_HIDDEN, false);
      ObjectSetInteger(0, lbl_o, OBJPROP_BACK, false);

      string lbl_c = m_prefix + sess.name + "_CL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_close);
      if(ObjectFind(0, lbl_c) == -1) ObjectCreate(0, lbl_c, OBJ_TEXT, 0, t_close, c);
      ObjectSetString (0, lbl_c, OBJPROP_TEXT, session_tag + " Close");
      ObjectSetInteger(0, lbl_c, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, lbl_c, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, lbl_c, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, lbl_c, OBJPROP_HIDDEN, false);
      ObjectSetInteger(0, lbl_c, OBJPROP_BACK, false);
   }

10) Agregación OHLC robusta (basada en el desplazamiento de barras)

Para garantizar la exactitud en diferentes intervalos de tiempo, calculamos el OHLC de la sesión utilizando los límites de iBarShift y obtenemos una porción compacta de matrices; luego, las agregamos con ArrayMaximum/Minimum. Esto evita errores de desfase de uno y funciona incluso si la granularidad del marco temporal no coincide perfectamente con los límites de la sesión.

   //---------------------------------------------------------------
   // Data helpers
   //---------------------------------------------------------------
   bool GetOHLCInTimeRange(datetime start, datetime end,
                           double &open, double &high, double &low, double &close)
   {
      int shift_start = iBarShift(_Symbol, _Period, start, false);
      int shift_end   = iBarShift(_Symbol, _Period, end,   false);
      if(shift_start < 0 || shift_end < 0) return false;

      int bars = shift_start - shift_end + 1;
      if(bars <= 0) return false;

      double opens[], highs[], lows[], closes[];
      ArraySetAsSeries(opens,  true);
      ArraySetAsSeries(highs,  true);
      ArraySetAsSeries(lows,   true);
      ArraySetAsSeries(closes, true);

      if(CopyOpen (_Symbol,_Period, shift_end, bars, opens)  != bars) return false;
      if(CopyHigh (_Symbol,_Period, shift_end, bars, highs)  != bars) return false;
      if(CopyLow  (_Symbol,_Period, shift_end, bars, lows)   != bars) return false;
      if(CopyClose(_Symbol,_Period, shift_end, bars, closes) != bars) return false;

      open  = opens[bars-1];
      high  = highs[ArrayMaximum(highs,0,bars)];
      low   = lows [ArrayMinimum(lows ,0,bars)];
      close = closes[0];
      return true;
   }

11) Limpieza determinista (recolección de basura con ámbito de prefijo)

Las herramientas profesionales para la creación de gráficos deben dejar el escenario limpio. Al actualizar o eliminar EA, iteramos los objetos en orden inverso y eliminamos solo aquellos que llevan nuestro prefijo m_prefix. Esto implica una convivencia respetuosa con otros indicadores/EAs y evita los "dibujos fantasma" que frustran a los usuarios.

   //---------------------------------------------------------------
   // Cleanup
   //---------------------------------------------------------------
   void DeleteAllSessionObjects()
   {
      int total = ObjectsTotal(0);
      for(int i = total-1; i >= 0; --i)
      {
         string name = ObjectName(0, i);
         if(StringFind(name, m_prefix) == 0) ObjectDelete(0, name);
      }
   }
};

Integración de CSessionVisualizer y la información de sesión en MarketPeriodsSynchronizerEA

Antes de esta actualización, nuestra versión anterior de EA se centraba en la estructura de períodos mayores/menores, rellenos de cuerpo, mechas y controles de interfaz de usuario, sin ninguna noción de sesiones de mercado o resúmenes de sesión. En esta versión, incorporamos la nueva clase CSessionVisualizer y añadimos un panel compacto de "Información de sesión", además de prácticos interruptores de control para que las sesiones y sus resaltados de mecha se puedan gestionar independientemente de los elementos visuales de los períodos principales. A continuación, les explicaré la integración paso a paso, como si estuviéramos evolucionando la versión anterior de EA, indicando el motivo de cada cambio y pegando el fragmento exacto que lo implementa. (Como referencia, la versión base anterior de EA es la v1.01, sin sesiones y sin el panel de información). 

1) Integrar el visualizador de sesiones en EA

Incluimos el encabezado de nuestro nuevo renderizador de sesión junto con el asistente Canvas. El encabezado de prueba ya utiliza CSessionVisualizer con rectángulos de cuerpo punteados sin relleno y rellenos de mecha grises opcionales en el fondo, por lo que visualmente no entrará en conflicto con nuestras superposiciones HTF. Mantenemos las inclusiones mínimas y explícitas para evitar colisiones de nombres/ODR.

#include <Canvas/Canvas.mqh>   // Canvas helper library (expects Canvas.mqh to be present)
#include <SessionVisualizer.mqh>  // Integrated Session Visualizer class

2) Nuevas aportaciones para las sesiones y el panel informativo.

Los insumos son el contrato con los usuarios. Agregamos interruptores específicos para mostrar las sesiones, su ventana de análisis retrospectivo, el desfase GMT del broker (para que las sesiones coincidan), la visibilidad/alfa de la mecha solo para las sesiones y el indicador para el panel "Información de la sesión". Observe que separamos deliberadamente la configuración de las mechas para los pares principales y las sesiones; los traders a menudo desean mechas para unos, pero no para los otros.

// Session inputs
input bool           InpShowSessions      = true;     // Show Forex sessions
input int            InpSessionLookback   = 10;       // Session lookback (days)
input int            InpGMTOffset         = 0;        // GMT offset (broker hours)

// Wick visualization (separated for majors and sessions)
input bool           InpShowSessionWicks  = false;    // Show wick regions for sessions
input int            InpSessionWickAlpha  = 120;      // Alpha transparency for session wicks 0..255

// Session information panel
input bool           InpShowSessionInfo   = true;     // Show session info panel

Las versiones anteriores no tenían entradas específicas para cada sesión; todo giraba en torno a elementos principales y secundarios, y un interruptor para la mecha. Ahora separamos esas preocupaciones para que el diseño se mantenga limpio incluso cuando ambas funciones estén activas. 

3) Las variables globales de tiempo de ejecución reflejan las nuevas entradas.

El panel de control del EA modifica el estado en tiempo de ejecución, por lo que mantenemos copias mutables de las entradas. Aquí añadimos g_ShowSessions, session-wick runtime (g_ShowSessionWicks, g_SessionWickAlpha) y un indicador para el panel de información. Mantener estas variables cerca de las demás variables globales preserva el modelo mental: entradas → tiempo de ejecución → interfaz de usuario.

// Wick runtime (separated for majors and sessions)
bool            g_ShowWicks;
int             g_WickAlpha;
bool            g_ShowSessionWicks;
int             g_SessionWickAlpha;

// Session runtime
bool            g_ShowSessions;

// Session information panel
bool            g_ShowSessionInfo;

4) Una única instancia compartida de CSessionVisualizer

Todos los dibujos de sesión deben compartir un prefijo de nomenclatura y un ciclo de vida estables. Instanciamos un visualizador con un prefijo corto para que los nombres de los objetos sigan siendo únicos y fáciles de eliminar sin afectar a otros objetos del gráfico.

// Session visualizer instance
CSessionVisualizer g_sessions("SESS_");

5) Información de la sesión: estado compacto que podemos actualizar en cada ciclo

A los traders les encanta tener el contexto a la vista: "¿En qué sesión estoy?" ¿Cómo cerró la anterior? Almacenamos los datos mínimos que necesitamos para dibujar el panel: nombres, O/C, naturaleza (alcista/bajista/neutral) y la hora de la última actualización para evitar trabajo redundante.

// Session data storage
string   g_CurrentSessionName = "";
double   g_CurrentSessionOpen = 0;
double   g_CurrentSessionCurrent = 0;
string   g_CurrentSessionNature = "";
datetime g_CurrentSessionStart = 0;

string   g_PrevSessionName = "";
double   g_PrevSessionOpen = 0;
double   g_PrevSessionClose = 0;
string   g_PrevSessionNature = "";
datetime g_LastSessionUpdate = 0;

6) Fondo de lienzo más ancho para adaptarse al nuevo panel

El bloque de información de la sesión añade tres líneas concisas para los resúmenes de la sesión actual y la anterior. Ajustamos ligeramente el ancho y la altura del panel para que el diseño respire y el texto no se ajuste de forma extraña.

// Canvas background - INCREASED WIDTH
CCanvas g_bgCanvas;
string  g_bg_name = "";
int     g_bg_x = 6;
int     g_bg_y = 44;  // uses Y_OFFSET in OnInit
int     g_bg_w = 450; // increased from 430
int     g_bg_h = 340; // increased from 300 for session info

7) Inicializa los nuevos controles en OnInit().

OnInit() es donde las entradas se convierten en estado activo. También preparamos el visualizador para que sepa qué ajustes de canal lateral se aplican a las sesiones (no a las principales): desplazamiento GMT, alternancia de mecha y alfa de mecha. Aunque la clase de sesión expone SetFillColors(), nuestros elementos visuales de sesión actuales utilizan rectángulos sin relleno, pero mantenemos la llamada por compatibilidad futura.

// Initialize session visualizer with separate session wick params
g_sessions.SetGMTOffset(InpGMTOffset);
g_sessions.SetFillColors(g_FillBull, g_FillBear);   // kept for compatibility (no body fill in visualizer)
g_sessions.SetShowWicks(g_ShowSessionWicks);        // session-specific wick setting
g_sessions.SetWickAlpha(g_SessionWickAlpha);        // session-specific wick alpha

// copy panel flag
g_ShowSessionInfo = InpShowSessionInfo;

8) Agregar la interfaz de usuario de "Información de sesión"

 Dibujamos pequeñas etiquetas dentro del panel de control para mostrar la sesión actual (nombre, abierta, naturaleza) y la sesión anterior (nombre, O/C, naturaleza). Los colores refuerzan sutilmente la imagen del toro y el oso. El panel se crea una sola vez; el contenido se actualiza en cada ciclo.

void UpdateSessionInfo()
{
   if(!g_ShowSessionInfo) return;

   datetime now = TimeCurrent();

   // Always refresh current session nature
   UpdateCurrentSessionInfo(now);

   // Update previous session only at boundaries
   if(g_LastSessionUpdate == 0 || IsNewSessionStart(now))
   {
      UpdatePreviousSessionInfo(now);
      g_LastSessionUpdate = now;
   }

   UpdateSessionDisplay();
}

9) Contabilidad inteligente de sesiones (actual + anterior)

Dividimos las preocupaciones en tres tipos de ayuda:

  1. UpdateCurrentSessionInfo: averigua en qué sesión estamos (basada en GMT), almacena en caché el precio de apertura de la sesión y clasifica la naturaleza utilizando la oferta en tiempo real frente a la apertura de la sesión.
  2. UpdatePreviousSessionInfo: cuando se inicia una nueva sesión, se guarda una instantánea de la configuración y la naturaleza de la sesión anterior.
  3. UpdateSessionDisplay: actualiza los textos y colores más recientes en los objetos de etiqueta.

Está diseñado intencionadamente para ser ligero, sin bucles pesados, por lo que funciona bien con el ciclo de actualización HTF habitual de EA. Por el contrario, la versión anterior de EA no tenía ese concepto, razón por la cual la lógica de paneles y límites es completamente nueva en esta versión. 

void UpdateSessionInfo()
{
   if(!g_ShowSessionInfo) return;

   datetime now = TimeCurrent();

   // Always refresh current session nature
   UpdateCurrentSessionInfo(now);

   // Update previous session only at boundaries
   if(g_LastSessionUpdate == 0 || IsNewSessionStart(now))
   {
      UpdatePreviousSessionInfo(now);
      g_LastSessionUpdate = now;
   }

   UpdateSessionDisplay();
}

10) Controles de sesión en el panel de control

El poder debe estar al alcance de la mano del operador. Añadimos dos botones nuevos: uno para activar/desactivar las sesiones y otro para activar/desactivar las mechas de sesión. Mantenemos la independencia de los controles de los elementos principales Los botones se integran con los interruptores existentes para preservar la memoria muscular.

// NEW ROW: Session wicks, Sessions, Major VLines (swapped positions for clarity)
CreateButton(btn_toggle_session_wicks, 12,  86 + Y_OFFSET, 130, 22, g_ShowSessionWicks ? "Sess Wicks: ON" : "Sess Wicks: OFF");
CreateButton(btn_toggle_sessions,      152, 86 + Y_OFFSET, 130, 22, g_ShowSessions ? "Sessions: ON" : "Sessions: OFF");
CreateButton(btn_toggle_maj_vlines,    292, 86 + Y_OFFSET, 130, 22, g_ShowMajorVLines ? "Maj VLines: ON" : "Maj VLines: OFF");

Y los controladores de clics simplemente invierten el bit y actualizan:

if(obj == btn_toggle_session_wicks)
{
   g_ShowSessionWicks = !g_ShowSessionWicks;
   ObjectSetString(main_chart_id, btn_toggle_session_wicks, OBJPROP_TEXT,
                   g_ShowSessionWicks ? "Sess Wicks: ON" : "Sess Wicks: OFF");
   RefreshLines();
   return;
}

if(obj == btn_toggle_sessions)
{
   g_ShowSessions = !g_ShowSessions;
   ObjectSetString(main_chart_id, btn_toggle_sessions, OBJPROP_TEXT,
                   g_ShowSessions ? "Sessions: ON" : "Sessions: OFF");
   RefreshLines();
   return;
}

11) Dibujar sesiones dentro de RefreshLines() (un lugar para controlarlas todas)

La función principal de actualización de EA es RefreshLines(). Una vez que gestionamos los objetos HTF, le pedimos al visualizador que renderice las sesiones (con la configuración más reciente para cada sesión) o que las borre. Mantener esto aquí garantiza una única frecuencia de actualización y una sola pasada de limpieza.

// Draw sessions if toggled - NOW WITH SEPARATE WICK SETTINGS
if(g_ShowSessions)
{
   g_sessions.SetFillColors(g_FillBull, g_FillBear);     // no-op for bodies today; future-proof
   g_sessions.SetShowWicks(g_ShowSessionWicks);
   g_sessions.SetWickAlpha(g_SessionWickAlpha);
   g_sessions.RefreshSessions(InpSessionLookback);
}
else
{
   g_sessions.ClearAll();
}

En la versión anterior de EA, RefreshLines() solo gestionaba elementos visuales HTF/menores. Al centralizarse las sesiones aquí, un único temporizador/tick controla todos los elementos visuales, evitando parpadeos y conflictos de sincronización. 

12) Actualización y limpieza en OnTick y OnDeinit

En cada ciclo recalculamos el estado de alto nivel: nuevas barras y ahora el bloque de información de la sesión. Durante la desinicialización también borramos los dibujos de la sesión para que la reconexión comience desde cero.

void OnTick()
{
   bool need_refresh = false;
   // ... (bar-change checks for majors/minors)

   // Always update session info on tick for real-time current session nature
   UpdateSessionInfo();

   if(need_refresh) RefreshLines();
}

void DeleteAllHTFLines()
{
   // remove HTF objects
   // ...
   // Also clear sessions
   g_sessions.ClearAll();
}

13) Panel más alto una vez restaurado, para que encaje en la nueva sección

Al minimizar/restaurar, la altura del fondo debe coincidir con la del contenido. Ajustamos la altura restaurada para que la información de la sesión sea completamente visible sin superponerse a otros elementos del gráfico.

void RestoreUI()
{
   UpdateBackgroundHeight(340);  // was 250; space for the Session Info
   CreateAllOtherUIObjects();
   ObjectSetString(main_chart_id, btn_minimize, OBJPROP_TEXT, "Minimize");
}


Pruebas

Para esta prueba, adjuntamos el EA directamente a un gráfico en tiempo real (no al Probador de Estrategias), ya que nuestro enfoque se centra exclusivamente en los elementos visuales del gráfico. Esto nos permite verificar que la representación y los controles se ajustan a nuestros objetivos. A continuación se muestra la característica final en funcionamiento.

MPS con SessionVisualizer

Figura 3: Resultado final con visualización de sesiones de Forex e información



Conclusión

Las sesiones del mercado Forex y de valores pueden proporcionar información más profunda a los traders y analistas financieros cuando se comprenden y visualizan adecuadamente. La idea que se implementa aquí nos ayuda a representar las sesiones de negociación como períodos de velas japonesas, revelando detalles valiosos sobre el máximo, el mínimo, la apertura y el cierre de cada sesión. Este enfoque nos permite aplicar nuestros conocimientos tradicionales sobre gráficos de velas japonesas al análisis a nivel de sesión, lo que ayuda a predecir posibles movimientos en la próxima sesión basándose en la naturaleza de las anteriores.

Mediante este desarrollo, introdujimos marcadores visuales y una utilidad de control dinámico (Sincronizador de períodos de mercado) que nos permite alternar, ajustar y sincronizar las visualizaciones de las sesiones directamente en el gráfico. Además, el panel de información de la sesión resume la apertura, el cierre y la dirección (alcista o bajista) de las sesiones actuales y anteriores, ofreciendo a los traders una visión rápida e intuitiva de las fuerzas subyacentes del mercado.

Dado que el carácter alcista o bajista de una sesión se mide a lo largo de períodos de tiempo prolongados, a menudo refleja un sesgo de mercado más amplio y una mayor tendencia direccional. Comprender estos comportamientos no solo agudiza la perspectiva técnica, sino que también fortalece la psicología del trading mediante la observación directa y visual de la dinámica del mercado.

Puedes experimentar, modificar y compartir tus ideas en los comentarios a continuación. Los archivos fuente adjuntos se encuentran disponibles para su consulta y para que pueda seguir explorando el tema.



Lecciones clave

Lección clave: Descripción:
Integración de clases y diseño modular La creación de módulos reutilizables como CSessionVisualizer fomenta una arquitectura más limpia y un mantenimiento más sencillo. Al separar la lógica de visualización en una clase, EA gana flexibilidad: los desarrolladores pueden modificar los elementos visuales de la sesión o reutilizar el componente en otros proyectos sin tener que reescribir el código principal.
Variables de tiempo de ejecución y sincronización de la interfaz de usuario Al reflejar los parámetros de entrada en variables globales de tiempo de ejecución, se garantiza que los controles de la interfaz de usuario en tiempo real (como deslizadores, botones e interruptores) puedan actualizar dinámicamente su comportamiento visual sin necesidad de reinicializar el EA. Este enfoque enseña cómo diseñar utilidades de gráficos responsivas utilizando interfaces de usuario basadas en objetos.
Gestión de sesiones y mapeo de datos El uso de datos de sesión estructurados (por ejemplo, horas de inicio/fin basadas en GMT, cálculos de apertura/cierre) demuestra cómo conectar los datos de gráficos en tiempo real con la lógica de negociación basada en el tiempo. Refuerza la idea de que los desarrolladores pueden combinar análisis y elementos visuales para hacer que el contexto del mercado sea más intuitivo.


Archivos adjuntos

Nombre del archivo de origen Versión Descripción
SessionVisualizerTest.mqh 1.0 Un script de prueba independiente diseñado para verificar la representación y el comportamiento de la clase CSessionVisualizer. Permite a los desarrolladores aislar la lógica visual, garantizando que los rectángulos de sesión, los colores y las asignaciones de tiempo se muestren correctamente antes de la integración en sistemas más grandes.
SessionTest.mq5 1.0 Un Asesor Experto simplificado diseñado específicamente para probar y demostrar la funcionalidad del encabezado SessionVisualizerTest.
SessionVisualizer.mqh 1.0 La implementación de la clase principal que gestiona la visualización de sesiones de Forex. Gestiona los ajustes de zona horaria, la asignación de colores y la representación gráfica de los límites de sesión en el gráfico, sirviendo como un módulo visual reutilizable para cualquier EA o indicador que necesite contexto basado en la sesión.
MarketPeriodsSychronizer_EA.mq5 1.02 El Asesor Experto actualizado integra el Visualizador de Sesión y un nuevo panel de Información de Sesión. Sincroniza múltiples marcadores de marcos temporales, rellenos de sesión y análisis en tiempo real en un panel de control unificado, proporcionando a los traders una herramienta interactiva y educativa de visualización del período de mercado.
Volver al índice

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/20005

stu48
stu48 | 16 nov 2025 en 07:39
Hola, Clemence: Un contenido estupendo y una lectura muy interesante, ¡genial! Gracias.
Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR) Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR)
Descubra cómo los modelos de autorregresión vectorial (VAR) pueden pronosticar datos de series temporales OHLC (apertura, máximo, mínimo y cierre) del mercado Forex. Este artículo trata sobre la implementación de VAR, el entrenamiento de modelos y la previsión en tiempo real en MetaTrader 5, lo que ayuda a los traders a analizar los movimientos interdependientes de las divisas y a mejorar sus estrategias de trading.
Simulación de mercado: Position View (XV) Simulación de mercado: Position View (XV)
En este artículo intentaré explicar, de la forma más sencilla posible, cómo puedes utilizar el intercambio de mensajes entre aplicaciones. El objetivo es que puedas desarrollar algo funcional de la manera más simple y eficaz, siempre que sea posible. No sé si lograré transmitir la idea que sustenta este concepto, pues no resulta fácil de asimilar para quien lo encuentra por primera vez. También aprovecharé para mostrarte cómo modificar el sistema de repetición/simulador, de modo que puedas depurar un Asesor Experto o cualquier otro código que estés desarrollando. Todo ello de una forma igualmente sencilla y directa.
Del básico al intermedio: Clases (II) Del básico al intermedio: Clases (II)
Este artículo busca ser lo más didáctico posible, porque el tema que abordaremos ya suele generar bastante confusión. Por eso, mi querido lector, intenta poner en práctica lo que se explica aquí. Ante cualquier duda, no dejes de comentar, pues comprender los destructores no es precisamente una de las tareas más sencillas.
Dominando los registros (Parte 8): Registros de errores con traducción automática Dominando los registros (Parte 8): Registros de errores con traducción automática
En esta octava entrega de la serie «Dominando los registros», exploramos la implementación de mensajes de error multilingües en Logify, una potente biblioteca de registro de eventos para MQL5. Aprenderás a estructurar los errores con contexto, traducir mensajes a varios idiomas y formatear registros dinámicamente según su nivel de gravedad. Todo ello con un diseño limpio, extensible y listo para la producción.