English Русский Deutsch 日本語
preview
De novato a experto: Trading con filtrado temporal

De novato a experto: Trading con filtrado temporal

MetaTrader 5Sistemas comerciales |
27 0
Clemence Benjamin
Clemence Benjamin

Contenido



Introducción

Aprovechando las capacidades de MQL5, podemos definir con precisión los momentos en que queremos operar y recibir alertas en consecuencia. En nuestra discusión anterior, establecimos las bases para la visualización de sesiones, donde descubrimos la estructura de cada sesión de negociación: su cuerpo, mecha superior y mecha inferior. Ese proyecto nos permitió visualizar la estructura más amplia del mercado en sesiones de marcos temporales superiores, sincronizadas con datos de marcos temporales inferiores para revelar los movimientos de precios internos amplificados y los patrones que se forman en su interior.

Estos conceptos pueden no ser exhaustivos, pero sirven como un valioso punto de partida para otros investigadores ambiciosos que buscan visualizar y diseñar herramientas de análisis de mercado más avanzadas. El precio, por su naturaleza, es un fenómeno de series temporales: evoluciona continuamente, tic a tic. Nuestros asesores expertos e indicadores están diseñados para monitorear estas fluctuaciones sin descanso, reaccionando ante cada patrón o condición que detectan. Sin embargo, si no se introducen filtros basados en el tiempo, ejecutarán acciones indiscriminadamente siempre que se cumplan las condiciones técnicas, incluso durante períodos de escasa liquidez, spreads erráticos o desplazamientos de bajo volumen.

Es en este momento cuando la referencia temporal se vuelve crucial. Al anclar nuestros algoritmos a ventanas o sesiones de negociación bien definidas, los guiamos para que actúen solo cuando el mercado está más activo, más estructurado y ofrece señales más significativas. Por lo tanto, el filtrado temporal sirve como marco disciplinario, asegurando que la ejecución se produzca cuando el comportamiento del mercado se alinee no solo con la lógica de la estrategia, sino con el ritmo del tiempo mismo.

Los eventos del calendario conocidos pueden servir como puntos de referencia muy útiles para las operaciones filtradas por tiempo. Los acontecimientos económicos, como las nóminas no agrícolas, el IPC, las decisiones del FOMC o la publicación del PIB, evolucionan con el tiempo y pueden utilizarse para definir ventanas de negociación precisas: períodos en los que nuestras herramientas tienen permitido o restringido realizar órdenes y generar señales.

Del mismo modo que los marcadores de sesión definen el ritmo del mercado, los marcadores del calendario económico definen zonas de volatilidad impulsadas por eventos. Por ejemplo, un algoritmo podría suspender automáticamente las operaciones 30 minutos antes de un evento de alto impacto y reanudarlas solo después de que la volatilidad se estabilice. MQL5 ofrece acceso directo a dichos datos a través de la estructura MqlCalendarValue y la función CalendarValueById(), lo que permite a los EA detectar las próximas publicaciones y adaptar sus operaciones en consecuencia.

Más allá de la programación basada en eventos, la hora en sí misma es otro factor de control crucial. Un operador o investigador podría definir intervalos de tiempo fijos, como de 10:00 a 12:00 horas, para la generación o ejecución de señales. Esto no solo reduce el ruido, sino que también ayuda a aislar los períodos estadísticamente favorables dentro de una jornada de negociación.

TFT

En conjunto, estos mecanismos —filtros de eventos basados en calendarios y ventanas de tiempo basadas en horas— otorgan a nuestros sistemas una doble percepción del tiempo: una derivada del comportamiento económico programado y la otra del ritmo estructural intradiario. La integración de ambos permite a nuestros Asesores Expertos actuar no solo en función de las condiciones de los precios, sino también del contexto temporal, que suele ser el factor decisivo entre la precisión y la aleatoriedad en el trading automatizado.

Hoy nos centraremos en las operaciones con filtrado temporal. Analizaremos cómo utilizar los puntos de referencia temporales establecidos en nuestros proyectos anteriores para filtrar la frecuencia de las operaciones y controlar la entrega de señales. Mediante algoritmos, podemos definir ventanas de tiempo específicas en las que se permite la actividad de trading, lo que nos brinda mayor control, precisión y adaptabilidad en las estrategias de trading automatizadas. Esto nos anima a ir más allá de los simples activadores basados en condiciones y a adoptar una perspectiva temporal, permitiendo que nuestros sistemas de trading respeten tanto el comportamiento del mercado como el momento en que se produce.

Aprovechando el marco de trabajo MQL5, podemos crear sistemas que:

  • Opere únicamente durante las sesiones seleccionadas (por ejemplo, Londres o Nueva York).
  • Suspenda las operaciones antes de la publicación de noticias importantes o en horas de baja liquidez.
  • Emitan alertas, señales o entradas solo cuando el reloj y las condiciones coincidan.

Fundamento del filtrado temporal

El filtrado por tiempo garantiza que la lógica de su estrategia se ejecute solo durante períodos de tiempo específicos. En lugar de dejar que un EA o indicador se ejecute de forma continua, puede incluir comprobaciones de tiempo en su ejecución que confirmen si la hora actual del mercado se encuentra dentro del período de negociación deseado.

Este enfoque es útil para:
  • Evitar períodos fuera de horas punta o de baja liquidez (por ejemplo, después del cierre de la bolsa de Nueva York).
  • Capturar picos de volatilidad basados en sesiones (por ejemplo, la superposición entre Londres y Nueva York).
  • Controlar la frecuencia de la señal para reducir el ruido o el exceso de operaciones.

En el enfoque actual, nuestra atención se centra por completo en la implementación. En esta etapa, no introduciremos más actualizaciones en el Sincronizador de Períodos de Mercado. En cambio, nos basamos en el trabajo establecido en nuestra publicación anterior, que ahora sirve de fundamento para el presente estudio.

En concreto, haremos referencia al archivo de cabecera SessionVisualizer, el módulo responsable de definir y marcar nuestros periodos de sesión de mercado. Los marcadores de tiempo que se creen en su interior servirán como puntos de referencia clave para este nuevo concepto de negociación con filtrado temporal. Mediante la utilización de estos marcadores, ahora podemos definir y controlar las ventanas de negociación que se producen antes o después del inicio o el cierre de una sesión.

En la siguiente sección, exploraremos una implementación detallada de esta idea, demostrando cómo se pueden integrar controles basados en el tiempo directamente en nuestra lógica de negociación. Estos son los pasos de implementación que seguiremos:

  1. Construir una capa TimeFilter modular que funcione conjuntamente con SessionVisualizer para proporcionar ventanas de tiempo basadas en sesiones, en el reloj y (opcionalmente) en eventos.
  2. Crear un Asesor Experto de ejemplo que ejecute operaciones solo cuando los filtros de tiempo activos lo permitan.
  3. Crear un indicador de ejemplo que muestre o active señales únicamente dentro de las ventanas de tiempo filtradas permitidas.



Implementación

Ahora comenzaremos implementando la clase TimeFilter, luego la integraremos en dos proyectos de ejemplo (un Asesor Experto y un indicador) y, finalmente, presentaremos los resultados de nuestras pruebas junto con los archivos fuente completos.

Filtro de tiempo

1. Encabezado del módulo y protección de inclusión

Comenzamos tratando este archivo como una biblioteca reutilizable, no como un fragmento de código desechable. El uso de #property strict impone reglas de compilación más seguras, mientras que la protección de inclusión evita definiciones duplicadas cuando el archivo se utiliza en varios proyectos. También incluimos SessionVisualizer.mqh porque nuestro filtro de tiempo está diseñado para funcionar en conjunto con la lógica de visualización de sesiones existente. Este es el tipo de disciplina estructural que hace que un código base sea escalable.

// ============================================================================
// TimeFilters.mqh — modular helpers for time-filtered trading
// Author—Clemence Benjamin
// ============================================================================

#property strict
#ifndef __TIMEFILTERS_MQH__
#define __TIMEFILTERS_MQH__

#include "SessionVisualizer.mqh"

2. Entradas y configuración controlada por el usuario

A continuación, mostramos un pequeño panel de control mediante parámetros de entrada. En lugar de codificar el comportamiento de forma rígida, permitimos que el trader (o quien realiza las pruebas) decida si desea filtrar por sesiones, horas fijas o eventos. El inicio y el final del reloj, el margen previo y posterior a la sesión, y los filtros de noticias (futuras) se pueden ajustar sin necesidad de modificar el código fuente. Los nombres siempre llevan el prefijo Inp y cada entrada está documentada; así es precisamente como se consigue que los módulos sean legibles en la pestaña "Entradas" de MetaTrader 5 y que se expliquen por sí mismos dentro del código.

// --------------------------- Inputs / Settings ------------------------------
input bool   InpUseSessionFilter   = true;      // gate by session times
input bool   InpUseClockFilter     = true;      // gate by fixed clock window
input bool   InpUseEventFilter     = false;     // gate by economic calendar (stubbed safe)

input int    InpClockStartHour     = 10;        // e.g., 10:00
input int    InpClockEndHour       = 12;        // e.g., 12:00
input int    InpPreSessionMins     = 0;         // allow N minutes before session start
input int    InpPostSessionMins    = 0;         // allow N minutes after session end

// Event filter parameters (used when you enable InpUseEventFilter)
input int    InpCalLookAheadMin    = 60;
input int    InpCalBlockBeforeMin  = 30;
input int    InpCalBlockAfterMin   = 30;
input int    InpCalMinImportance   = 2;         // 0=low,1=medium,2=high

3. Envoltorio de contexto de filtro de tiempo

En lugar de dispersar las variables globales por todo su EA, encapsulamos el estado relacionado con el tiempo en CTimeFilterContext. Esta clase puede almacenar opcionalmente un puntero a CSessionVisualizer y el gmt_offset activo. Esto significa que cualquier EA o indicador puede pasar un único objeto de contexto ligero, y todas las decisiones tomadas en cada momento se mantienen consistentes. SetGMTOffset envía el desplazamiento al visualizador cuando existe, manteniendo así los dibujos y los filtros sincronizados sin lógica duplicada.

// --------------------------- Context wrapper --------------------------------
class CTimeFilterContext
{
public:
   CSessionVisualizer *viz;   // optional visualizer pointer
   int                 gmt_offset;

   CTimeFilterContext() : viz(NULL), gmt_offset(0) {}

   void AttachVisualizer(CSessionVisualizer &ref) { viz = &ref; }

   void SetGMTOffset(int off)
   {
      gmt_offset = off;
      if(viz != NULL)
      {
         // safest explicit dereference form
         (*viz).SetGMTOffset(off);
      }
   }
};

4. Filtro de ventana del reloj principal

La ventana horaria es nuestra primera y más sencilla barrera temporal: "operar únicamente entre estas horas". Aquí convertimos la hora del servidor a un formato estructurado y la comparamos con el rango definido por el usuario. Cabe destacar dos comportamientos: si startHour == endHour, lo interpretamos como "sin restricciones", y si startHour > endHour, consideramos que la ventana abarca la medianoche (por ejemplo, de 22:00 a 02:00). Muchos principiantes olvidan el caso de envoltura; manejarlo aquí significa que el resto de su lógica puede confiar plenamente en esta utilidad.

// --------------------------- Utility: Clock Window --------------------------
bool InClockWindow(const int startHour, const int endHour)
{
   MqlDateTime t;
   TimeToStruct(TimeCurrent(), t);

   if(startHour == endHour)
      return true; // treat as "always on"

   if(startHour < endHour)
      return (t.hour >= startHour && t.hour < endHour);

   // wrap across midnight, e.g., 22..02
   return (t.hour >= startHour || t.hour < endHour);
}

5. Filtro de ventana basado en sesión

Esta función conecta nuestro motor de filtrado de tiempo con la lógica de la sesión. Requiere el objeto CTimeFilterContext (para la configuración) y un SESSION_TYPE (Sídney, Tokio, Londres, Nueva York). Dependiendo de cómo lo hayas implementado, GetTodaySessionWindow (en tu SessionVisualizer) devuelve la hora de inicio y fin de la sesión del día actual. Además, añadimos un margen en minutos antes y después de la sesión, que es donde los operadores suelen establecer reglas como «empezar a analizar 15 minutos antes de que abra Londres» o «evitar los últimos 30 minutos de Nueva York». Fíjate en cómo gestionamos los errores: si no podemos obtener una ventana de sesión, devolvemos «true» para que la falta de un visualizador o de una configuración no bloquee silenciosamente todas las operaciones.

// --------------------------- Utility: Session Window ------------------------
bool InSessionWindow(CTimeFilterContext &ctx, const SESSION_TYPE sType,
                     const int preMins = 0, const int postMins = 0)
{
   if(ctx.viz == NULL)
      return true; // no visualizer attached -> don't block

   datetime ss, se;

   if(!(*ctx.viz).GetTodaySessionWindow(sType, ss, se))
      return true; // session not available -> don't block

   // Apply pre/post-minute padding
   ss -= (preMins * 60);
   se += (postMins * 60);

   datetime now = TimeCurrent();
   return (now >= ss && now <= se);
}

6. Implementación provisional del filtro de eventos (hook ampliable)

Aquí definimos EventWindowAllowed como un stub seguro: por ahora, siempre devuelve «true». La decisión de diseño es intencional. Muchos lectores utilizan brokers o entornos de prueba sin soporte para calendarios económicos; una dependencia estricta rompería la portabilidad. Al proporcionar este gancho con la firma correcta, facilitamos la posterior integración de lógica real basada en calendarios (bloqueos en torno a NFP, FOMC, IPC, etc.) sin modificar ningún código EA que ya dependa de IsTradingAllowed.

// --------------------------- Utility: Economic Calendar ---------------------
bool EventWindowAllowed(const int /*lookAheadMin*/,
                        const int /*blockBeforeMin*/,
                        const int /*blockAfterMin*/,
                        const int /*minImportance*/)
{
   // Stubbed: always allow for safe compilation
   return true;
}

7. Decisión compuesta: ¿Está permitido negociar?

Por último, todas las piezas se unen en una única función bien estructurada: IsTradingAllowed. Este es el método que su EA o indicador debería invocar en cada tick o señal. Aplica secuencialmente los filtros habilitados:

  • Si el filtro de reloj está activado, debemos estar dentro del horario permitido.
  • Si el filtro de sesión está activado, debemos estar dentro de al menos una ventana de sesión configurada (con relleno).
  • Si el filtro de eventos está activado, debemos pasar la regla de evento (futuro).

El patrón ok = ok && ... permite que la lógica sea legible: en cuanto falla un filtro, seguimos devolviendo «false» sin ramificaciones especiales. El resultado es una condición booleana clara que convierte tu estrategia sin procesar en una estrategia que tiene en cuenta el tiempo con una sola llamada.

// --------------------------- Composite Gate --------------------------------
bool IsTradingAllowed(CTimeFilterContext &ctx)
{
   bool ok = true;

   if(InpUseClockFilter)
      ok = ok && InClockWindow(InpClockStartHour, InpClockEndHour);

   if(InpUseSessionFilter)
   {
      bool any = false;
      any = any || InSessionWindow(ctx, SESSION_LONDON,  InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_NEWYORK, InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_TOKYO,   InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_SYDNEY,  InpPreSessionMins, InpPostSessionMins);
      ok = ok && any;
   }

   if(InpUseEventFilter)
      ok = ok && EventWindowAllowed(InpCalLookAheadMin,
                                    InpCalBlockBeforeMin,
                                    InpCalBlockAfterMin,
                                    InpCalMinImportance);

   return ok;
}

#endif // __TIMEFILTERS_MQH__

Ejemplo de Asesor Experto que utiliza filtros de tiempo

1. Encabezado de EA e intención de alto nivel

Todo Asesor Experto profesional debe presentarse claramente al principio. Aquí declaramos la propiedad, describimos el propósito y establecemos la versión. Esto va más allá de la estética: cuando regresas meses después, o cuando alguien más descarga tu código fuente de CodeBase, estos campos les indican que no se trata de un experimento aleatorio, sino de un sistema estructurado y documentado, diseñado específicamente para el comercio con filtrado temporal y una integración impecable con CTrade.

// TimeFilteredEA.mq5 -                                             
#property copyright  "Clemence Benjamin"
#property description "Professional Time-Filtered EA with streamlined CTrade usage."
#property version     "1.0"

2. Inclusiones estratégicas: trading, información del símbolo, sesiones y filtros temporales.

A continuación, incluimos selectivamente solo los componentes básicos de los que realmente depende este EA. Trade.mqh nos proporciona la envoltura de alto nivel de CTrade; SymbolInfo.mqh nos ayuda con las propiedades de los símbolos específicas de cada bróker; SessionVisualizer.mqh se encarga de nuestro contexto visual; TimeFilters.mqh proporciona el filtro central IsTradingAllowed. Estamos componiendo el comportamiento a partir de módulos bien definidos, en lugar de reinventar todo dentro de un único archivo sobrecargado.

#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>
#include <SessionVisualizer.mqh>
#include <TimeFilters.mqh>

3. Entradas expuestas: configurables

Aquí ofrecemos un panel de entrada de datos sencillo para los operadores. No ocultamos el tamaño del lote, el límite de pérdidas ni los indicadores de visualización dentro de la lógica. En cambio, las declaramos como variables de entrada para que aparezcan en el cuadro de diálogo de configuración de EA. Observe cómo cada entrada tiene un comentario claro que explica su propósito. También utilizamos un número mágico específico y una desviación, ambos considerados buenas prácticas fundamentales para gestionar órdenes de forma segura en entornos multi-EA.

input int    InpGMTOffsetHours = 0;     // GMT offset for session alignment
input bool   InpDrawSessions    = true; // Enable session visualization
input int    InpLookbackDays    = 5;    // Days to draw sessions
input double InpLotSize         = 0.01; // Fixed lot size
input int    InpStopLossPips    = 50;   // SL in pips (0 = none; TP auto-set to 2x if >0)
input int    InpTakeProfitPips  = 0;    // TP in pips (0 = auto 2x SL if SL>0)
input int    InpMagicNumber     = 12345; // Magic number for trades
input int    InpDeviationPips   = 10;   // Max slippage in pips

4. Objetos principales y estado

A continuación declaramos los objetos clave que utiliza el EA: CTrade para la ejecución de órdenes, CSymbolInfo para los datos de spread, tick y volumen, CSessionVisualizer para trazar bloques de sesión y CTimeFilterContext para coordinar las decisiones relacionadas con el tiempo. También preparamos los identificadores MA y gLastSignalBar para asegurarnos de que no se activen señales dos veces en la misma vela. Este patrón es ordenado: las herramientas a nivel de entorno son globales, mientras que la lógica de trading y los filtros se mantienen modulares.

CTrade              trade;
CSymbolInfo         gSymbolInfo;
CSessionVisualizer  gSV("TF_SESS_");
CTimeFilterContext  gCTX;

// MA handles
int       gFastMAHandle   = INVALID_HANDLE;
int       gSlowMAHandle   = INVALID_HANDLE;
datetime  gLastSignalBar  = 0;

5. Gestor de operaciones profesional: Encapsulando la lógica de ejecución

En lugar de realizar llamadas directas a trade.Buy() por todo el EA, introducimos CTradeManager. Esta clase centraliza la normalización de lotes, el deslizamiento, la asignación de números mágicos, el cálculo de SL/TP y la validación de la cuenta. Así es como se mantiene limpio el código de la estrategia: la lógica de las señales indica una compra y el gestor se encarga de todos los detalles técnicos en un único lugar, incluido un registro exhaustivo de los motivos de los fallos.

//+------------------------------------------------------------------+
//| Professional Trade Manager Wrapper                               |
//+------------------------------------------------------------------+
class CTradeManager
{
private:
   CTrade* m_trade;
   int     m_magic;
   int     m_deviation;
   double  m_minVolume;
   double  m_maxVolume;
   double  m_volumeStep;
   int     m_digits;
   double  m_point;

public:
   CTradeManager() : m_trade(NULL), m_magic(0), m_deviation(0) {}
   ~CTradeManager() {}

   bool Init(int magic, int deviation)
   {
      m_trade = new CTrade();
      if(m_trade == NULL) return false;
      
      m_magic = magic;
      m_deviation = deviation;
      
      m_trade.SetExpertMagicNumber(m_magic);
      m_trade.SetDeviationInPoints(m_deviation);
      m_trade.LogLevel(LOG_LEVEL_ERRORS);  // Match reference: log errors only
      
      // Cache symbol props (no filling set - default to broker/symbol)
      m_minVolume  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      m_maxVolume  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
      m_volumeStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
      m_digits     = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
      m_point      = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      
      Print("TradeManager initialized: Magic=", m_magic,
            " Deviation=", m_deviation, "pts (default filling)");
      return true;
   }

   void Deinit()
   {
      if(m_trade != NULL)
      {
         delete m_trade;
         m_trade = NULL;
      }
   }

6. Seguridad del volumen y validación de cuentas

Antes de realizar cualquier operación, un EA robusto debe responder a dos preguntas: ¿Es válido este volumen para este símbolo? ¿Y esta cuenta está en buen estado? Las funciones IsValidVolume y NormalizeVolume alinean el volumen solicitado con las restricciones SYMBOL_VOLUME_STEP del agente. ValidateAccount confirma que el saldo, el patrimonio y el margen libre no se encuentran en niveles sin sentido. 

   bool IsValidVolume(double volume)
   {
      if(volume < m_minVolume || volume > m_maxVolume) return false;
      double normalized = NormalizeDouble(volume / m_volumeStep, 0) * m_volumeStep;
      return (MathAbs(volume - normalized) < m_point);
   }

   double NormalizeVolume(double volume)
   {
      return NormalizeDouble(
               MathMax(m_minVolume,
                       MathMin(m_maxVolume,
                               NormalizeDouble(volume / m_volumeStep, 0) * m_volumeStep)),
               2);
   }

   bool ValidateAccount()
   {
      double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
      double equity     = AccountInfoDouble(ACCOUNT_EQUITY);
      double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      
      if(balance <= 0 || equity <= 0 || freeMargin < 0)
      {
         Print("TradeManager: Invalid account state - Balance=", balance,
               " Equity=", equity, " FreeMargin=", freeMargin);
         return false;
      }
      
      Print("TradeManager: Account validated - Balance=", balance,
            " FreeMargin=", freeMargin);
      return true;
   }

7. ExecuteBuy: lógica SL/TP, conocimiento del spread e informes de errores

ExecuteBuy: aquí es donde todas las piezas encajan. Validamos la cuenta, normalizamos y verificamos el volumen, obtenemos precios actualizados del símbolo y calculamos el stop-loss y el take-profit respetando el spread, el tamaño del pip y los niveles mínimos de stop. Si solo se proporciona SL y TP es cero, el EA deriva automáticamente un TP de riesgo-recompensa de 1:2; esto introduce buenas prácticas directamente en el marco. Y si algo sale mal, la función imprime tanto ResultRetcodeDescription() como GetLastError().

   bool ExecuteBuy(double volume, double sl = 0, double tp = 0, string comment = "")
   {
      if(!ValidateAccount()) return false;
      
      volume = NormalizeVolume(volume);
      if(!IsValidVolume(volume))
      {
         Print("TradeManager: Invalid volume ", volume);
         return false;
      }
      
      gSymbolInfo.Name(_Symbol);
      gSymbolInfo.RefreshRates();
      double ask    = gSymbolInfo.Ask();
      double bid    = gSymbolInfo.Bid();
      double spread = ask - bid;
      
      double price      = NormalizeDouble(ask, m_digits);
      double stoploss   = 0.0;
      double takeprofit = 0.0;
      
      double pipValue = (m_digits == 3 || m_digits == 5) ? m_point * 10 : m_point;

      // --- SL/TP logic (spread-aware, 1:2 RR auto if desired) ---
      if(InpStopLossPips > 0)
      {
         if(spread >= InpStopLossPips * m_point)
         {
            PrintFormat("StopLoss (%d points) <= current spread = %.0f points. Spread value will be used",
                        InpStopLossPips, spread / m_point);
            stoploss = NormalizeDouble(price - spread, m_digits);
         }
         else
         {
            stoploss = NormalizeDouble(price - InpStopLossPips * pipValue, m_digits);
         }

         if(InpTakeProfitPips == 0)
         {
            takeprofit = NormalizeDouble(price + (InpStopLossPips * 2 * pipValue), m_digits);
            Print("TradeManager: Auto-set TP for 1:2 RR: ", takeprofit);
         }
         else
         {
            if(spread >= InpTakeProfitPips * m_point)
            {
               PrintFormat("TakeProfit (%d points) < current spread = %.0f points. Spread value will be used",
                           InpTakeProfitPips, spread / m_point);
               takeprofit = NormalizeDouble(price + spread, m_digits);
            }
            else
            {
               takeprofit = NormalizeDouble(price + InpTakeProfitPips * pipValue, m_digits);
            }
         }
      }
      else if(InpTakeProfitPips > 0)
      {
         if(spread >= InpTakeProfitPips * m_point)
         {
            PrintFormat("TakeProfit (%d points) < current spread = %.0f points. Spread value will be used",
                        InpTakeProfitPips, spread / m_point);
            takeprofit = NormalizeDouble(price + spread, m_digits);
         }
         else
         {
            takeprofit = NormalizeDouble(price + InpTakeProfitPips * pipValue, m_digits);
         }
      }

      // Directional sanity checks
      if(stoploss > 0 && stoploss >= price)
      {
         Print("TradeManager: Invalid SL for BUY - resetting to 0");
         stoploss = 0;
      }
      if(takeprofit > 0 && takeprofit <= price)
      {
         Print("TradeManager: Invalid TP for BUY - resetting to 0");
         takeprofit = 0;
      }

      // Respect broker minimum stop levels
      long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
      if(stopsLevel > 0)
      {
         double minDist = stopsLevel * m_point;
         if(stoploss > 0 && (price - stoploss) < minDist)
         {
            stoploss = NormalizeDouble(price - minDist, m_digits);
            Print("TradeManager: SL adjusted to min dist: ", stoploss);
         }
         if(takeprofit > 0 && (takeprofit - price) < minDist)
         {
            takeprofit = NormalizeDouble(price + minDist, m_digits);
            Print("TradeManager: TP adjusted to min dist: ", takeprofit);
         }
      }

      Print("TradeManager: Executing BUY - Entry=", price,
            " Vol=", volume, " SL=", stoploss, " TP=", takeprofit);

      ResetLastError();
      bool result = m_trade.Buy(volume, _Symbol, price, stoploss, takeprofit, comment);
      
      if(!result)
      {
         uint   retcode = m_trade.ResultRetcode();
         string ret_desc = m_trade.ResultRetcodeDescription();
         PrintFormat("Failed %s buy %G at %G (sl=%G tp=%G) Retcode=%u (%s) MQL Error=%d",
                     _Symbol, volume, price, stoploss, takeprofit,
                     retcode, ret_desc, GetLastError());
         m_trade.PrintResult();
         Print("   ");
      }
      else
      {
         Print("TradeManager: BUY success - Deal=", m_trade.ResultDeal(),
               " Price=", m_trade.ResultPrice());
      }
      
      return result;
   }
};

8. Inicialización: Cableado de filtros de tiempo, visualizador, gestor de operaciones y EMAs

OnInit es donde el EA cobra vida. Adjuntamos el CSessionVisualizer a gCTX, configuramos la diferencia horaria con respecto al GMT para que tanto los elementos visuales como la lógica coincidan en la hora, e inicializamos el CTradeManager con nuestro número mágico y nuestra desviación. También validamos la cuenta (algo especialmente útil en el Strategy Tester) y creamos identificadores de media móvil que controlan nuestra lógica de señales. Los mensajes que aparecen en pantalla son intencionados: informan al usuario no solo de que el EA está «listo», sino también de qué modelo de riesgo y qué configuración está utilizando.

// Global Trade Manager
CTradeManager gTradeMgr;

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("=== PIONEER EA INITIALIZATION (v2.11) ===");
   
   // Time filter setup
   gCTX.AttachVisualizer(gSV);
   gCTX.SetGMTOffset(InpGMTOffsetHours);
   if(InpDrawSessions)
      gSV.RefreshSessions(InpLookbackDays);
   
   // Initialize Trade Manager
   if(!gTradeMgr.Init(InpMagicNumber, InpDeviationPips))
   {
      Print("FAIL: TradeManager initialization failed");
      return INIT_FAILED;
   }
   
   // Validate account early
   if(!gTradeMgr.ValidateAccount())
   {
      Print("FAIL: Account validation failed - Check deposit in tester");
      return INIT_FAILED;
   }
   
   // MA indicators
   gFastMAHandle = iMA(_Symbol, _Period, 9, 0, MODE_EMA, PRICE_CLOSE);
   gSlowMAHandle = iMA(_Symbol, _Period, 21, 0, MODE_EMA, PRICE_CLOSE);
   
   if(gFastMAHandle == INVALID_HANDLE || gSlowMAHandle == INVALID_HANDLE)
   {
      Print("FAIL: MA handles creation failed");
      return INIT_FAILED;
   }
   
   Print("SUCCESS: Pioneer EA ready - Clean CTrade integration active (default filling)");
   Print("RR Logic: SL=", InpStopLossPips, " pips; TP=",
         (InpStopLossPips > 0 && InpTakeProfitPips == 0
            ? InpStopLossPips * 2
            : InpTakeProfitPips),
         " pips (1:2 auto if TP=0)");
   return INIT_SUCCEEDED;
}

9. Limpieza: Gestión de recursos

En OnDeinit, liberamos los identificadores de los indicadores, desinicializamos el gestor de operaciones y borramos los objetos de sesión. Este EA se encarga de limpiar lo que genera. 

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(gFastMAHandle != INVALID_HANDLE) IndicatorRelease(gFastMAHandle);
   if(gSlowMAHandle != INVALID_HANDLE) IndicatorRelease(gSlowMAHandle);
   gTradeMgr.Deinit();
   gSV.ClearAll();
}

10. Lógica de entrada: cruce de la media móvil exponencial (EMA) con una señal por barra

La lógica de la señal es deliberadamente sencilla: un cruce de la media móvil exponencial (EMA) de 9 por encima de la EMA de 21. Lo que importa es cómo se lleva a la práctica. Extraemos dos valores de cada búfer de la media móvil, detectamos el cruce comparando los valores anteriores con los actuales y, a continuación, utilizamos gLastSignalBar como filtro para evitar que se generen múltiples entradas en la misma vela. 

//+------------------------------------------------------------------+
//| Signal Detection: EMA Crossover                                  |
//+------------------------------------------------------------------+
bool EntrySignalDetected()
{
   double fast[2], slow[2];
   if(CopyBuffer(gFastMAHandle, 0, 0, 2, fast) != 2 ||
      CopyBuffer(gSlowMAHandle, 0, 0, 2, slow) != 2)
      return false;
   
   bool crossover = (fast[1] <= slow[1] && fast[0] > slow[0]);
   if(!crossover) return false;
   
   datetime barTime = iTime(_Symbol, _Period, 0);
   if(barTime == gLastSignalBar) return false;
   gLastSignalBar = barTime;
   return true;
}

11. Filtrado de posiciones por magia y símbolo

HasOpenPosition recorre las posiciones abiertas y comprueba tanto el símbolo como el número mágico. Esto garantiza que el EA solo reaccione ante sus propias operaciones y no interfiera con otros sistemas. Es importante aislar siempre tu estrategia utilizando «números mágicos»; nunca des por sentado que «eres dueño» de todas las posiciones de la cuenta.

//+------------------------------------------------------------------+
//| Position Check                                                   |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
   int total = PositionsTotal();
   for(int i = 0; i < total; i++)
   {
      if(PositionGetSymbol(i) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
         return true;
   }
   return false;
}

12. OnTick: primero el filtro de tiempo, luego la lógica

El cuerpo de OnTick es deliberadamente breve y fácil de leer: esta es la recompensa de todas las abstracciones. En primer lugar, actualizamos los elementos visuales de la sesión (si están activados). A continuación, pasamos por la puerta del filtro de tiempo: si IsTradingAllowed(gCTX) devuelve «false», el EA simplemente indica «Trading OFF» y se cierra. Solo cuando se cumplen las condiciones temporales comprobamos que no haya ninguna posición abierta y que exista una señal de la media móvil exponencial (EMA). Si se cumplen ambas condiciones, pasamos la ejecución a gTradeMgr.ExecuteBuy

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
{
   if(InpDrawSessions)
      gSV.RefreshSessions(InpLookbackDays);
   
   if(!IsTradingAllowed(gCTX))
   {
      Comment("Pioneer EA: Trading OFF");
      return;
   }
   
   Comment("Pioneer EA: Trading ON | Positions: ", PositionsTotal());
   
   if(!HasOpenPosition() && EntrySignalDetected())
   {
      Print("=== SIGNAL: EMA Crossover Detected ===");
      gTradeMgr.ExecuteBuy(InpLotSize, 0, 0, "Pioneer Buy");
   }
}

Ejemplo de indicador que utiliza filtros de tiempo.

1. Descripción general del indicador: un complemento visual para el EA con filtro temporal

Comenzamos declarando un indicador de ventana gráfica con dos trazados: uno para señales alcistas y otro para señales bajistas. Estas propiedades le indican a MetaTrader 5 que estamos dibujando flechas directamente sobre el precio, y no en una subventana separada. 

// ============================================================================
// Author: Clemence Benjamin
// File: TimeFilteredSignal.mq5
// Purpose: Time-filtered RSI signal + alerts indicator
// Notes : Uses large Wingdings arrows for visual clarity
//         Signals only appear when IsTradingAllowed(iCTX) == true
// ============================================================================

#property strict
#property indicator_chart_window

// We use two plots: one for bullish arrows, one for bearish arrows
#property indicator_plots   2
#property indicator_buffers 2

// --- Plot 1: Bullish RSI signal (up arrow) ---
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLime
#property indicator_width1  2
#property indicator_label1  "TF_RSI_Bull"

// --- Plot 2: Bearish RSI signal (down arrow) ---
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_width2  2
#property indicator_label2  "TF_RSI_Bear"

2. Reutilización de la infraestructura de tiempo: sesiones y filtros, no reinventados.

Aquí incluimos los mismos componentes básicos que en el EA: SessionVisualizer.mqh y TimeFilters.mqh. Ese es el patrón clave: una política de tiempo centralizada, muchos consumidores (EAs/Asesores Expertos, indicadores y paneles de control). También declaramos las entradas para el dibujo de la sesión y la configuración de RSI, además del comportamiento de las alertas. Podemos activar o desactivar las sesiones, ajustar los niveles de RSI y elegir el nivel de ruido o silencio de las notificaciones.

#include <SessionVisualizer.mqh>
#include <TimeFilters.mqh>

// --------------------------- Inputs -----------------------------------------
// Visual session context (indicator-side)
input bool   InpDrawSessions_i    = true;
input int    InpGMTOffsetHours_i  = 0;
input int    InpLookbackDays_i    = 5;

// RSI configuration
input int    InpRSIPeriod         = 14;
input int    InpRSIOverbought     = 70;
input int    InpRSIOversold       = 30;

// Alert behavior
input bool   InpAlertOnCross      = true;      // enable popup alert on RSI cross
input bool   InpSendPush          = false;     // send mobile push
input bool   InpSendEmail         = false;     // send email
input string InpAlertPrefix       = "TF-RSI";  // prefix tag in messages

3. Contexto compartido y búferes: un único cerebro temporal y dos flechas

El indicador utiliza el mismo CTimeFilterContext que hemos diseñado anteriormente, y lo orienta hacia su propia instancia de CSessionVisualizer. De este modo, la implementación se mantiene coherente con la EA: ambas se basan en el mismo concepto de «tiempo permitido». Asignamos dos búferes: BuffUp para las flechas alcistas y BuffDn para las flechas bajistas. Esta distinción permite interpretar el gráfico al instante: flecha verde hacia arriba: evento alcista filtrado; flecha roja hacia abajo: evento bajista filtrado.

// --------------------------- Globals ----------------------------------------
CSessionVisualizer  iSV("TFI_SESS_");
CTimeFilterContext  iCTX;

// Two buffers: one for bullish arrows, one for bearish arrows
double BuffUp[];
double BuffDn[];

int      rsiHandle            = INVALID_HANDLE;
datetime gLastRSIAlertBarTime = 0;   // avoid duplicate alerts per bar

4. Función auxiliar de alertas: un mensaje, muchos canales

En lugar de dispersar las llamadas a Alert() por todo el bucle, centralizamos la lógica de notificación en FireRSIAlert. De este modo, el código de las señales se mantiene limpio y las mejoras (cambios de prefijo, formato, datos adicionales) resultan muy sencillas. Este patrón es escalable: hoy son mensajes del RSI; mañana pueden ser «Se ha detectado un grupo de flujo de órdenes» o «Se ha confirmado un cambio de régimen personalizado» sin necesidad de reestructurar todo el indicador.

// --------------------------- Alert helper -----------------------------------
void FireRSIAlert(const string direction, const double rsiValue, const datetime barTime)
{
   string timeStr = TimeToString(barTime, TIME_DATE|TIME_SECONDS);

   string msg = StringFormat("%s | %s | %s | RSI=%.2f | Time=%s (inside allowed window)",
                             InpAlertPrefix,
                             _Symbol,
                             direction,
                             rsiValue,
                             timeStr);

   Alert(msg);

   if(InpSendPush)
      SendNotification(msg);

   if(InpSendEmail)
      SendMail(InpAlertPrefix + " " + _Symbol, msg);

   Print("TimeFilteredSignal: ", msg);
}

5. OnInit – Asignación de flechas a los búferes y del tiempo a las sesiones

En OnInit, realizamos el cableado estructural:

  • Asignamos los búferes a los dos plots.
  • Selecciona los códigos de flecha de Wingdings (233 para arriba, 234 para abajo).
  • Asigna el visualizador al contexto y aplica la diferencia horaria con respecto a GMT.
  • Opcionalmente, se pueden dibujar sesiones.
  • Crea el identificador RSI e inicializa los búferes.

 El indicador debería fallar rápidamente si no se puede crear un recurso básico (RSI, información del símbolo).

// --------------------------- OnInit -----------------------------------------
int OnInit()
{
   // Bind buffers
   SetIndexBuffer(0, BuffUp, INDICATOR_DATA);
   SetIndexBuffer(1, BuffDn, INDICATOR_DATA);

   // Use large Wingdings arrows:
   // 233 = up arrow, 234 = down arrow
   PlotIndexSetInteger(0, PLOT_ARROW, 233); // Bullish arrow up
   PlotIndexSetInteger(1, PLOT_ARROW, 234); // Bearish arrow down

   // Time filter context
   iCTX.AttachVisualizer(iSV);
   iCTX.SetGMTOffset(InpGMTOffsetHours_i);

   if(InpDrawSessions_i)
      iSV.RefreshSessions(InpLookbackDays_i);

   // Create RSI handle
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
   {
      Print("TimeFilteredSignal: Failed to create RSI handle. Error = ", GetLastError());
      return(INIT_FAILED);
   }

   // Initialize buffers as empty
   ArrayInitialize(BuffUp, EMPTY_VALUE);
   ArrayInitialize(BuffDn, EMPTY_VALUE);

   return(INIT_SUCCEEDED);
}

6. OnCalculate — Cruces del RSI con disciplina temporal y flechas

Aquí es donde el indicador hace honor a su nombre. Iteramos a través de las barras, pero la lógica es intencionalmente simple y está organizada por capas:

  1. Las flechas están desactivadas por defecto (EMPTY_VALUE las oculta).
  2. Opcionalmente, actualice los elementos visuales de la sesión cuando lleguen nuevas barras.
  3. Obtén los valores RSI a través de CopyBuffer.
Para cada barra:

  1. Compruebe IsTradingAllowed(iCTX); si los filtros de tiempo lo rechazan, omita este paso.
  2. Detectar cruces RSI:
    • Tendencia alcista: salir de la zona sobrevendida hacia arriba → dibujar una flecha verde hacia arriba debajo de la barra.
    • Tendencia bajista: dejando zona sobrecomprada a la baja → dibujar una flecha roja hacia abajo encima de la barra.

Solo para la última barra, active las alertas una vez usando gLastRSIAlertBarTime.

Las señales no son solo "cruces del RSI"; son "cruces del RSI que respetan nuestros horarios de negociación profesionales, sesiones y bloques de eventos (futuros)".

// --------------------------- OnCalculate ------------------------------------
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total <= 2 || rsiHandle == INVALID_HANDLE)
      return(rates_total);

   // Refresh session drawings when new bars appear
   if(InpDrawSessions_i && rates_total != prev_calculated)
      iSV.RefreshSessions(InpLookbackDays_i);

   // Prepare RSI buffer
   static double rsiBuffer[];
   ArrayResize(rsiBuffer, rates_total);

   int copied = CopyBuffer(rsiHandle, 0, 0, rates_total, rsiBuffer);
   if(copied <= 0)
      return(prev_calculated > 0 ? prev_calculated : rates_total);

   // Recalc from prev-1 so last bar updates smoothly
   int start = (prev_calculated > 1 ? prev_calculated - 1 : 1);

   for(int i = start; i < rates_total; ++i)
   {
      // Default: hide both arrows on this bar
      BuffUp[i] = EMPTY_VALUE;
      BuffDn[i] = EMPTY_VALUE;

      // Respect global time filters (same logic as EA)
      if(!IsTradingAllowed(iCTX))
         continue;

      double rsi_prev = rsiBuffer[i - 1];
      double rsi_curr = rsiBuffer[i];

      bool bullCross =
         (rsi_prev < InpRSIOversold && rsi_curr >= InpRSIOversold);

      bool bearCross =
         (rsi_prev > InpRSIOverbought && rsi_curr <= InpRSIOverbought);

      // Bullish RSI recovery: big green up arrow below bar
      if(bullCross)
      {
         BuffUp[i] = low[i] - (_Point * 5);

         if(InpAlertOnCross && i == rates_total - 1 && time[i] != gLastRSIAlertBarTime)
         {
            FireRSIAlert("RSI cross UP from oversold", rsi_curr, time[i]);
            gLastRSIAlertBarTime = time[i];
         }
      }

      // Bearish RSI rejection: big red down arrow above bar
      if(bearCross)
      {
         BuffDn[i] = high[i] + (_Point * 5);

         if(InpAlertOnCross && i == rates_total - 1 && time[i] != gLastRSIAlertBarTime)
         {
            FireRSIAlert("RSI cross DOWN from overbought", rsi_curr, time[i]);
            gLastRSIAlertBarTime = time[i];
         }
      }
   }

   return(rates_total);
}

Escalabilidad: Más allá del RSI

El RSI es solo un marcador de posición. La estructura que construimos admite cualquier motor de señales prácticamente sin modificaciones:

Reemplace el bloque RSI con:

  • Clústeres de cruces de medias móviles.
  • Condiciones de ruptura (máximo/mínimo de la sesión, desviaciones del VWAP).
  • Patrones de velas (pin bars, engulfing) solo durante la sesión de Londres.
  • Regímenes de volatilidad (filtros ATR).
  • Métricas de libro de órdenes/proxy, resultados de aprendizaje automático, lo que sea.

Conserva todo lo demás:

  • IsTradingAllowed(iCTX) como la puerta de tiempo universal.
  • Flechas (o marcadores) en el gráfico.
  • Alertas opcionales a través de FireRSIAlert (cambiar el nombre a FireSignalAlert genérico).

En este ejemplo, utilizamos cruces RSI simplemente porque son fáciles de reconocer. El verdadero valor no reside en la elección del oscilador, sino en el marco de trabajo: cualquier señal puede enrutarse a través de la misma capa TimeFilter. Tú defines cuándo se le permite hablar a tu estrategia y luego especificas qué debería decir.



Pruebas

Tras compilar correctamente los archivos fuente, ejecutamos tanto el EA TimeFilteredEA como el indicador TimeFilteredSignal en el Probador de estrategias para validar el comportamiento general del marco de trabajo. La ejecución de la prueba fue notablemente lenta —algo comprensible dado que se ejecutaban simultáneamente filtros de tiempo en capas, renderizado de sesiones y lógica de señales—, pero se completó de forma fiable y produjo resultados claros e interpretables. A continuación se muestran las capturas de pantalla que ilustran cómo las operaciones y las flechas basadas en el RSI solo se activaron dentro de los intervalos de tiempo permitidos, tal como estaba previsto.

Probador de estrategias

Figura 1: Prueba de TimeFilteredEA en el Probador de estrategias


TimeFilteredSignal

Figura 2: Prueba del indicador «TimeFilteredSignal»




Conclusión

Es totalmente posible controlar cuándo se permite que operen nuestros sistemas de negociación mediante la implementación de una lógica de filtrado temporal estructurada en MQL5, como el marco desarrollado en este proyecto. En lugar de dispersar condiciones en sentencias if aleatorias, utilizamos abstracciones claras, contexto compartido y utilidades conscientes de la sesión para que el comportamiento fuera predecible y reutilizable. Al basar nuestra lógica en puntos de referencia claros (sesiones de mercado, intervalos horarios y, opcionalmente, ventanas del calendario económico), podemos definir ventanas de negociación y señalización precisas con un mínimo esfuerzo.

Una vez establecidos esas ventanas temporales, comienza la verdadera creatividad en lo que respecta a la señalización. Ahora, cualquier lógica estratégica (RSI, medias móviles, rupturas de estructura, filtros de volatilidad o conceptos de flujo de órdenes) puede integrarse en un cronograma disciplinado en lugar de estar activa las 24 horas del día, los 5 días de la semana. Este enfoque reduce directamente el ruido causado por la monitorización continua y sin tener en cuenta el contexto, y nos ayuda a centrarnos en mejorar la calidad de la ejecución donde más importa. En la tabla resumen que aparece a continuación, he resaltado las lecciones clave de este debate, y los códigos fuente completos se adjuntan debajo del artículo para que puedas estudiarlos, modificarlos y ampliar esta base con tus propias ideas.


Conclusiones clave:

Conclusiones clave Descripción
1. Centralizar la lógica de filtrado de tiempo La implementación de una capa TimeFilter dedicada (en lugar de condiciones if dispersas) facilita la reutilización de las mismas reglas de sesión, reloj y eventos en múltiples EA e indicadores, lo que garantiza un comportamiento coherente y un mantenimiento más sencillo.
2. Utilice objetos de contexto en lugar de caos global El patrón CTimeFilterContext muestra cómo agrupar la configuración (desplazamiento GMT, enlace al visualizador) en un único objeto, reduciendo las dependencias globales y haciendo que su código sea más modular, comprobable y escalable.
3. Separar la lógica de señales de la lógica de ejecución. Al ubicar la ejecución de operaciones dentro de CTradeManager y la detección de señales en funciones/indicadores dedicados, separamos claramente "qué activa" las órdenes de "cómo se envían", lo que mejora la claridad, la capacidad de depuración y la reutilización.
4. Diseño orientado a la retroalimentación visual y al diagnóstico Las señales de flecha del SessionVisualizer y del sistema Wingdings proporcionan una confirmación visual inmediata de que los filtros de tiempo y las señales se comportan como se espera, lo cual es esencial para depurar lógica compleja y generar confianza en el sistema por parte de los operadores.
5. Construye marcos de señalización extensibles El uso del RSI como módulo complementario dentro de un marco con filtrado temporal demuestra un diseño escalable: cualquier método futuro (acción del precio, cruces de medias móviles, filtros de volatilidad, modelos personalizados) puede integrarse detrás de la misma puerta IsTradingAllowed() con cambios mínimos en el código.

Archivos adjuntos:

Archivo fuente Versión Descripción
SessionVisualizer.mqh 1.02 Biblioteca modular de mapeo y representación gráfica de sesiones que muestra las sesiones clave de negociación de Forex en el gráfico y expone referencias de ventanas de tiempo reutilizables para herramientas de nivel superior.
TimeFilters.mqh 1.00 Marco central de filtrado de tiempo que proporciona comprobaciones basadas en el reloj, la sesión y los eventos (extensibles) a través de una interfaz unificada IsTradingAllowed() y un objeto de contexto compartido.
TimeFilteredEA.mq5 1.0 Ejemplo de Asesor Experto que utiliza los Filtros de Tiempo y el Visualizador de Sesión para ejecutar entradas basadas en EMA solo dentro de los intervalos de tiempo permitidos, con una gestión de operaciones estructurada.
TimeFilteredSignal.mq5 1.0 Indicador gráfico que traza grandes flechas Wingdings y genera alertas basadas en RSI estrictamente dentro de las ventanas de tiempo filtradas definidas, sirviendo como una capa de señal visual y extensible.

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

Archivos adjuntos |
TimeFilters.mqh (12.16 KB)
TimeFilteredEA.mq5 (12.07 KB)
Del básico al intermedio: Colas, listas y árboles (VII) Del básico al intermedio: Colas, listas y árboles (VII)
En este artículo mostraremos y explicaremos con claridad cómo se elimina un nodo de un árbol. Este proceso suele confundir a los principiantes más que ayudarlos a comprender cómo se lleva a cabo y por qué debe realizarse de una forma determinada.
Reimaginando las estrategias clásicas en MQL5 (Parte 13): Llevando nuestra estrategia de cruce a nuevas dimensiones (Parte 2) Reimaginando las estrategias clásicas en MQL5 (Parte 13): Llevando nuestra estrategia de cruce a nuevas dimensiones (Parte 2)
Únase a nosotros en nuestra discusión mientras buscamos mejoras adicionales para nuestra estrategia de cruce de medias móviles con el fin de reducir el retraso en nuestra estrategia de negociación a niveles más confiables, aprovechando nuestras habilidades en ciencia de datos. Es un hecho ampliamente estudiado que aumentar la dimensionalidad de los conjuntos de datos puede, en ocasiones, mejorar el rendimiento de los modelos de aprendizaje automático. Demostraremos qué significa esto en la práctica para usted como trader y le mostraremos cómo puede aprovechar este poderoso principio utilizando su terminal MetaTrader 5.
Simulación de mercado: Position View (XVIII) Simulación de mercado: Position View (XVIII)
En este artículo mostré, de la forma más didáctica posible, cómo puedes modificar y desarrollar un código capaz de cumplir determinados objetivos alterando lo mínimo posible un código ya existente. Añadiremos un indicador de volumen y, al mismo tiempo, impediremos que el usuario u operador elimine los objetos que se están creando por el indicador de posición.
Simulación de mercado: Position View (XVII) Simulación de mercado: Position View (XVII)
En el artículo anterior hicimos que el indicador mostrara el resultado financiero. Sin embargo, no a todos les gusta utilizar este modo de visualización. El motivo puede variar de un operador a otro, aunque en algunos casos me parece bastante razonable y justificable. Adaptar el código para ofrecer esta posibilidad no es, ni mucho menos, una de las tareas más complejas. En realidad, se trata de algo bastante sencillo. En este artículo veremos cómo hacerlo.