English Русский Deutsch 日本語
preview
De novato a experto: Implementación de estrategias de Fibonacci en el trading posterior al NFP

De novato a experto: Implementación de estrategias de Fibonacci en el trading posterior al NFP

MetaTrader 5Ejemplos |
28 0
Clemence Benjamin
Clemence Benjamin

Contenido


Introducción

Perderte el momento exacto en el que se produce un anuncio económico importante —como el NFP (Non-Farm Payroll)— suele provocar FOMO (miedo a perderse algo). Esto suele ocurrir porque los operadores se precipitan para intentar capturar el movimiento incluso cuando este ya ha terminado. En la mayoría de los casos, el repunte inicial dura menos de un minuto y provoca un movimiento de muchos pips en la dirección favorecida por el mercado. Entrar tarde en este tipo de situaciones es extremadamente arriesgado, ya que el impulso inicial ya ha terminado y el precio puede dar un giro rápido, lo que puede provocar pérdidas importantes.

Los operadores profesionales, sin embargo, no se lanzan a por estas subidas bruscas. En cambio, o bien se preparan con antelación para entrar antes y de forma más segura, o bien esperan pacientemente a que el mercado retroceda y ofrezca nuevas oportunidades a niveles de entrada bien calculados. Su ventaja radica en comprender los principios de los retrocesos de Fibonacci y en dominar la disciplina de esperar hasta que se den las condiciones adecuadas.

En este debate, abordaremos los retos que plantea operar tras los picos bruscos provocados por las noticias, aplicando los principios de Fibonacci de forma algorítmica. Para sentar las bases, en la siguiente sección se presentará brevemente el concepto de Fibonacci a quienes no estén familiarizados con el concepto, antes de pasar al diseño y la implementación de la estrategia.

Una breve introducción al retroceso de Fibonacci

Popularizada por el matemático italiano Leonardo de Pisa (más conocido como Fibonacci) hace muchos siglos, la sucesión de Fibonacci se ha convertido en una de las herramientas más influyentes del análisis técnico moderno. En matemáticas, la sucesión es sencilla: cada número es la suma de los dos números anteriores (1, 1, 2, 3, 5, 8, 13, …). Sin embargo, su presencia en la naturaleza, la arquitectura e incluso el comportamiento humano ha fascinado a los estudiosos durante generaciones.

En los mercados financieros, la sucesión de Fibonacci se aplica a través de los niveles de retroceso de Fibonacci, un método que utilizan los operadores para identificar posibles zonas de reversión o de continuación durante las correcciones del mercado. Los ratios de retroceso más utilizados son el 23,6 %, el 38,2 %, el 50 %, el 61,8 % y el 78,6 %, todos ellos derivados de las relaciones que se dan en la sucesión de Fibonacci. Estos niveles actúan como puntos de referencia psicológicos en los que los operadores anticipan las reacciones de los precios, ya sea una pausa, un rebote o un cambio de tendencia.

El principio en el que se basa el retroceso es que los mercados nunca se mueven en línea recta. Tras un fuerte movimiento alcista o bajista, los precios suelen retroceder antes de retomar su tendencia. El retroceso de Fibonacci ofrece un marco para medir la profundidad probable de dichos retrocesos. Por ejemplo:

  • Un retroceso del 38,2 % suele indicar un retroceso leve en una tendencia fuerte.
  • El retroceso del 50 % (aunque no sea un número de Fibonacci propiamente dicho) se utiliza ampliamente como punto medio de corrección.
  • El retroceso del 61,8 %, conocido como la «proporción áurea», se considera el nivel más crítico, en el que suelen producirse importantes cambios de tendencia.

En la práctica del trading, los niveles de Fibonacci rara vez se utilizan de forma aislada. Su eficacia aumenta cuando se combinan con el price action, los niveles de soporte y resistencia, las medias móviles o el comportamiento del mercado impulsado por acontecimientos —como los retrocesos que se producen poco después de picos de noticias importantes—.

En esta estrategia, nos centramos en la negociación tras la publicación de los datos del NFP, donde el retroceso de Fibonacci ayuda a filtrar el ruido del repunte inicial y guía a los operadores hacia puntos de entrada estructurados y con mayor probabilidad de éxito. En lugar de dejarse llevar por el FOMO, el operador aprende a «esperar al retroceso» y a entrar en el mercado a niveles más sostenibles. En la plataforma de la comunidad MQL5 hay una gran cantidad de información sobre Fibonacci disponible para quienes deseen profundizar en el tema.

Accesibilidad en la terminal MetaTrader 5

En la interfaz predeterminada de MetaTrader 5, la barra de herramientas de gráficos tiene prioridad y está disponible de inmediato: los objetos analíticos más utilizados vienen precargados y se pueden arrastrar directamente al gráfico. Las herramientas de dibujo (como el objeto «Retroceso de Fibonacci») tienen vértices arrastrables que puedes colocar en los puntos de giro para visualizar los niveles de precios. Los traders utilizan estos objetos tanto para ver cómo reaccionó el precio ante esos niveles en el pasado como para proyectar las posibles zonas de reacción en futuros retrocesos.

Consejos prácticos:

  • Arrastra la herramienta hasta un máximo y un mínimo de oscilación bien definidos (o al revés) para fijar el retroceso a puntos de referencia significativos.
  • Utiliza los controladores de vértices del objeto para ajustar con precisión la ubicación, de modo que los niveles queden alineados con los cierres de las velas o con las mechas, según la regla que prefieras.
  • Guarda las plantillas o perfiles que utilices con más frecuencia para poder acceder al instante a tus ajustes de Fibonacci y a tu diseño de gráfico preferidos en todos los gráficos.

Véase la figura 1 a continuación, en la que se muestra el retroceso de Fibonacci trazado en el gráfico.

Cómo trazar la herramienta de retroceso de Fibonacci en la terminal MetaTrader 5

Figura 1. Cómo trazar la herramienta de retroceso de Fibonacci en la terminal MetaTrader 5

Estudio conceptual basado en el anuncio de las nóminas no agrícolas del 5 de septiembre de 2025:

Un análisis manual de la evolución del precio tras el anuncio de las nóminas no agrícolas (NFP) del 5 de septiembre de 2025 respalda la idea de que los niveles de retroceso de Fibonacci pueden servir de guía significativa para las entradas tras el evento. Las ilustraciones adjuntas utilizan la herramienta de retroceso de Fibonacci para destacar cómo el precio puso a prueba y respetó niveles específicos tras la subida inicial, lo que ayuda a distinguir las zonas de reentrada de alta probabilidad del ruido.

Estas observaciones nos permiten diseñar normas más rigurosas para la colocación de órdenes y la gestión de riesgos, basadas en las bandas de retroceso y en los precios de referencia más cercanos (línea de base previa al repunte y valor extremo del repunte). Para mayor claridad, he analizado tres gráficos de pares de divisas que incluyen el dólar estadounidense, ya que los pares con el USD suelen presentar movimientos más marcados y constantes en torno a las principales publicaciones económicas de EE. UU.

Nuestro estudio se basa en los datos más recientes de la NFP disponibles en el momento de redactar este artículo. Para el estudio de caso, analizamos tres pares centrados en el USD —GBP/USD, USD/JPY y EUR/USD— antes de plasmar la idea en un algoritmo. A continuación se muestran capturas de pantalla de gráficos que ilustran el rendimiento de la técnica en cada ejemplo.

EUR/USD tras el NFP del 5 de septiembre de 2025

Figura 2. EURUSD, M5, evolución del precio tras el anuncio de las nóminas no agrícolas (NFP)

En la figura 2 (EUR/USD, M5), el precio respeta claramente las leyes del retroceso. Los marcadores A y B indican la línea de base previa al pico y el valor máximo del pico provocado por el anuncio de las nóminas no agrícolas (NFP). Se trazó el retroceso de Fibonacci desde A (línea de base) hasta B (máximo del repunte) para la primera vela de M5 tras la publicación. Posteriormente, el precio retrocedió para poner a prueba las bandas de Fibonacci, y se aprecia claramente que se respetan los niveles de retroceso: en este ejemplo, se alcanzó el nivel del 50 % y este actuó como un punto de reacción significativo. El precio retrocedió para poner a prueba el retroceso de Fibonacci del 61,8 % e incluso superó ligeramente ese nivel, aunque no llegó a retroceder hasta la línea de base (100 %). Tras esa ligera perforación del nivel, se recuperó el impulso y el mercado alcanzó un nuevo máximo que superó el valor máximo del repunte inicial, lo que confirmó la continuación de la tendencia alcista.

Figura 2: GBPUSD, M5, evolución del precio tras el anuncio de las nóminas no agrícolas (NFP).

Figura 3. GBPUSD, M5, evolución del precio tras el anuncio de las NFP.

El par EUR/USD presenta una fuerte correlación con el GBP/USD, y este último mostró una reacción alcista similar —aunque no idéntica— ante el anuncio. La figura 3 (arriba) muestra el par GBP/USD con la misma superposición de niveles de Fibonacci utilizada en la figura 2, lo que ilustra cómo reaccionó el precio ante las noticias sobre ese par y refuerza la consistencia del comportamiento entre pares.

USDJPY, M5

Figura 4. USDJPY, M5, evolución del precio tras el anuncio de las NFP

La figura 4 (USD/JPY) muestra una clara reacción bajista tras la publicación de las nóminas no agrícolas (NFP). Tras la caída inicial (puntos A→B), el precio retrocedió hasta situarse aproximadamente en el nivel de retroceso de Fibonacci del 50 % antes de reanudar su descenso y alcanzar nuevos mínimos. En conjunto, los tres gráficos (EUR/USD, GBP/USD y USD/JPY) muestran una probabilidad constante de que el precio retroceda hasta las zonas de Fibonacci tras un repunte provocado por las noticias, incluso cuando la dirección difiere entre los distintos pares.

Implicaciones y enfoque general (antes de la programación)

Teniendo en cuenta los tres ejemplos (EUR/USD, GBP/USD y USD/JPY), podemos definir un plan conciso y de alto nivel para convertir la idea en un algoritmo:

Definiciones
  • P_base: la línea de base previa al repunte (precio en el que se originó el movimiento rápido).
  • P_spike: el valor extremo del pico (el precio más alto en el caso de un pico alcista y el más bajo en el caso de un pico bajista).
  • Dirección del pico: alcista si P_spike > P_base (pico al alza); bajista si P_spike < P_base (pico a la baja).
Lógica de entrada (en ambos sentidos)
1. Traza los niveles de retroceso de Fibonacci entre P_base y P_spike tras la primera vela de referencia completada tras el evento. 2. Espera a que se produzca un retroceso estructurado hasta la zona objetivo (por ejemplo, entre el 38,2 % y el 61,8 %) con la confirmación que hayas elegido (cierre de la vela, rechazo de la mecha, normalización del volumen por tick, etc.). 3. En el caso de un repunte alcista (el precio se disparó al alza y luego retrocedió):
  • Entrada: abrir una posición larga cuando el precio retroceda hasta la zona de Fibonacci elegida y se observe la confirmación.
  • Take Profit (TP): TP principal en P_spike (el extremo del pico). Los objetivos de precio secundarios o ampliados pueden basarse en extensiones de Fibonacci o múltiplos del ATR.
  • Stop Loss (SL): coloca el SL en el nivel P_base (la base del pico o el origen del movimiento rápido) o justo por debajo de este. Añade un pequeño margen (por ejemplo, el spread + X pips) para evitar que el microruido te haga salir de la posición.
4. En el caso de un repunte bajista (el precio se desplomó y luego se recuperó):
  • Entrada: abrir una posición corta cuando el precio retroceda hasta la zona de Fibonacci elegida y se observe la confirmación.
  • TP: TP primario en P_spike (el mínimo del pico descendente).
  • SL: Coloca el SL en P_base (el punto de origen del movimiento bajista rápido) o justo por encima de este, con un pequeño margen de seguridad.
¿Por qué esta asignación?
  • Utilizar P_spike como TP permite alinear el objetivo de la operación con el extremo demostrable más reciente: lo que intentamos es aprovechar la nueva prueba del extremo del pico (el «retorno al pico»).
  • Utilizar P_base como SL supone emplear el punto de origen del movimiento rápido como condición lógica de invalidación: si el precio vuelve a pasar más allá del punto de origen, la hipótesis de reentrada tras el pico queda invalidada.

Medidas prácticas y controles de riesgos

Aplicaremos un margen de seguridad al SL de una pequeña cantidad fija (por ejemplo, unos pocos pips o el spread multiplicado por 1,5) y calcularemos el tamaño del lote a partir del porcentaje de riesgo de la cuenta multiplicado por la distancia (SL — precio de entrada) para mantener el riesgo absoluto en un nivel bajo (objetivo: 0,5 %). Si P_base está demasiado lejos, ofreceremos un SL alternativo más ajustado (el siguiente nivel de Fibonacci o ATR(5m) × factor) como parámetro seleccionable. El EA admitirá salidas parciales en P_spike, el trailing stop sobre la posición restante, la cancelación automática de las órdenes limitadas caducadas tras M minutos y una opción para activar o desactivar la confirmación de pares correlacionados.

Elaboración de estrategias con Fibonacci

Figura 5. Resumen visual de la estrategia de reentrada de Fibonacci tras el NFP

La figura 5 (arriba) es una representación conceptual de la estrategia. Para una configuración alcista, coloca el stop-loss justo por debajo del punto A (la base del pico) y el take-profit por encima del punto B (el extremo del pico); para una configuración bajista, coloca el stop-loss por encima de A y el take-profit por debajo de B (ejemplo típico: figura 4, USDJPY). Añade un pequeño margen al stop-loss (por ejemplo, unos pocos pips o el spread multiplicado por 1,5) para reducir saltos del stop-loss por ruido.

Casos extremos

Si el precio retrocede por completo hasta el 100 % (es decir, vuelve a P_base) y luego continúa más allá de ese nivel, plantéate descartar la hipótesis original: el retroceso ha concluido y es posible que el movimiento se esté invirtiendo.

Si la dirección del repunte no está clara o la amplitud de la oscilación es inferior al número mínimo de barras/pips, cancela la operación (hay demasiado ruido).


Implementación de la estrategia

Para simplificar el proceso de desarrollo, he implementado un encabezado de clase específico en MetaEditor 5 encargado de gestionar las marcas de tiempo de los anuncios de las nóminas no agrícolas (NFP) y de convertirlas correctamente entre la hora del Este (ET), el UTC y la hora del servidor del bróker. Este encabezado centraliza la gestión del horario de verano, el cálculo del desfase horario y las utilidades de conversión, de modo que los demás módulos no dependan de la zona horaria.

A partir de ese encabezado, he desarrollado un asesor experto que:

  • Detecta la primera vela M5 que cierra tras la publicación de los datos del NFP,
  • Dibuja un retroceso de Fibonacci anclado a esa vela,
  • Coloca órdenes pendientes en los niveles del 38,2 %, el 50,0 % y el 61,8 %.
  • Aplica rigurosos controles a los brókers (distancias mínimas de stop, normalización, vencimiento) y
  • Gestiona el ciclo de vida de los objetos de los gráficos y las órdenes pendientes (eliminación al ejecutarse o al caducar).

En los dos pasos siguientes, ofreceremos explicaciones detalladas y adaptadas a los desarrolladores sobre el código: primero, el archivo de cabecera NFPTimeManager (decisiones de diseño, API, lógica del horario de verano y de las zonas horarias); después, el EA (flujo de datos, gestión de órdenes y comprobaciones de seguridad). Estas explicaciones mostrarán cómo se diseñó, implementó y probó el sistema en MQL5.

Paso 1: Encabezado de NFPTimeManager

1.1. Encabezado y metadatos del archivo

Este archivo comienza con un bloque de encabezado estándar y los atributos MQL5 #property. El encabezado indica quién es el autor y la versión, de modo que otros desarrolladores (o tú mismo en el futuro) puedan comprender de inmediato la finalidad y el origen del archivo. Estas propiedades permiten que el sistema de compilación de MQL5 muestre los metadatos en MetaEditor 5.

//+------------------------------------------------------------------+ 
//|                                               NFPTimeManager.mqh  |
//|                        Copyright 2025, Clemence Benjamin  |
//|                                       https://www.mql5.com/en/users/billionaire2024/seller|
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.0"

1.2. Declaración de la clase y campos privados de zona horaria

Incorporamos todas las funciones relacionadas con NFP en una clase CNFPTimeManager. Dos campos privados de tipo entero registran la zona horaria opcional del operador y la zona horaria del bróker o del servidor; el hecho de mantener estos campos internos hace que las funciones de conversión sean deterministas y evita que la lógica de las zonas horarias quede dispersa por todo el código.

class CNFPTimeManager
{
private:
   int      m_traderTimeZone; // Trader time zone offset from UTC (hours) - optional
   int      m_serverTimeZone; // Server time zone offset from UTC (hours)

1.3. GetETOffset — cálculo de la diferencia horaria con respecto a la hora del Este, teniendo en cuenta el horario de verano

Los datos del NFP se publican a una hora fija según la hora local de la costa este (08:30 ET). GetETOffset determina si una fecha determinada corresponde a la hora estándar del Este (UTC−5) o a la hora de verano del Este (UTC−4). Utiliza el sistema estadounidense Normas del horario de verano (del segundo domingo de marzo al primer domingo de noviembre) y devuelve la diferencia horaria con respecto al UTC en forma de número entero. Esto garantiza que los cálculos del calendario sean correctos en todas las temporadas.

   // returns ET offset (-4 for EDT, -5 for EST) for a given datetime (trader time context)
   int GetETOffset(datetime time)
   {
      MqlDateTime dt;
      TimeToStruct(time, dt);
      int year = dt.year;

      // DST: 2nd Sunday of March to 1st Sunday of November (US rules)
      datetime dst_start = GetNthDayOfWeek(year, 3, 0, 2); // 2nd Sunday of March
      datetime dst_end   = GetNthDayOfWeek(year, 11, 0, 1); // 1st Sunday of November
      // Note: We interpret the DST boundaries as local US/Eastern dates at 02:00 local time,
      // but for our purpose using whole-day thresholds is acceptable (NFP at 8:30 ET).
      if(time >= dst_start && time < dst_end)
         return -4; // EDT
      return -5;    // EST
   }

1.4. GetNthDayOfWeek — cálculos matemáticos fiables para el calendario mensual

Las operaciones aritméticas relacionadas con el calendario, como «el segundo domingo de marzo», pueden dar lugar a errores con sorprendente facilidad. GetNthDayOfWeek genera la fecha del día 1 del mes, determina el día de la semana correspondiente a esa fecha y calcula el desplazamiento hasta el N-ésimo día de la semana solicitado. La función devuelve una fecha y hora exactas correspondientes al evento solicitado y se reutiliza en los cálculos de marcas de tiempo del horario de verano (DST) y del NFP.

   // correctly compute the N-th day_of_week (0=Sunday..6=Saturday) in given month/year
   datetime GetNthDayOfWeek(int year, int month, int day_of_week, int n)
   {
      MqlDateTime dt;
      // build date for the 1st of month
      dt.year = year;
      dt.mon  = month;
      dt.day  = 1;
      dt.hour = 0;
      dt.min  = 0;
      dt.sec  = 0;
      datetime first = StructToTime(dt);
      MqlDateTime dt2;
      TimeToStruct(first, dt2);
      int dow_first = dt2.day_of_week; // 0..6
      int delta = (day_of_week - dow_first + 7) % 7;
      int days_to_nth = delta + (n - 1) * 7;
      return first + days_to_nth * 86400;
   }

1.5. Constructores y métodos para establecer la zona horaria

Un constructor breve inicializa los campos de zona horaria a cero. Hay dos métodos públicos que permiten al código que los invoca establecer explícitamente la zona horaria del operador o la del servidor, en caso de que no se desee la detección automática o si es necesario anularla para realizar pruebas o en servidores remotos.

public:
   CNFPTimeManager()
   {
      m_traderTimeZone = 0;
      m_serverTimeZone = 0;
   }

   // set trader timezone if you need to convert between trader/local and server
   void SetTraderTimeZone(int timezone_hours)
   {
      m_traderTimeZone = timezone_hours;
   }

   void SetServerTimeZone(int timezone_hours)
   {
      m_serverTimeZone = timezone_hours;
   }

1.6. DetectServerTimeZone y GetServerTime: funciones auxiliares relacionadas con el servidor

DetectServerTimeZone es una función que compara TimeCurrent() (hora del servidor) con TimeLocal() (hora del equipo local) y almacena la diferencia horaria en forma de número entero. GetServerTime simplemente envuelve la función TimeCurrent() para que otros módulos puedan recurrir a una única fuente para obtener la marca de tiempo actual del servidor.

   // attempt to auto-detect server timezone (difference between server and local)
   int DetectServerTimeZone()
   {
      datetime local = TimeLocal();
      datetime server = TimeCurrent();
      int offset_seconds = (int)(server - local);
      int offset_hours = offset_seconds / 3600;
      m_serverTimeZone = offset_hours;
      return offset_hours;
   }

   datetime GetServerTime()
   {
      return TimeCurrent();
   }

1.7. GetNFPTimestampTrader — Hora real del NFP en hora del Este (ET)

Este método devuelve una fecha y hora que representa el evento NFP a las 08:30 ET del primer viernes de un mes y año determinados. La función opera en términos de «hora del operador» (hora real de la costa este) y se convertirá a la hora del servidor por separado. Esta separación de responsabilidades hace que la función sea sencilla y predecible.

   // NFP candidate time in trader (ET) zone: first Friday of month at 08:30 ET
   datetime GetNFPTimestampTrader(int year, int month)
   {
      // first Friday (5) of the month
      datetime first_friday = GetNthDayOfWeek(year, month, 5, 1);
      MqlDateTime dt;
      TimeToStruct(first_friday, dt);
      dt.hour = 8; dt.min = 30; dt.sec = 0;
      return StructToTime(dt); // this is in server timescale semantics but represents the ET timestamp
   }

1.8. TraderETtoServer — convierte la marca de tiempo ET a la hora del servidor

Esta es la función de conversión clave: toma una marca de tiempo en hora real de la costa este (ET) (por ejemplo, 05/09/2025 a las 08:30 ET), calcula si en ese momento la costa este (ET) estaba en horario de verano, la convierte a UTC y, a continuación, la adapta a la zona horaria configurada del servidor. El resultado es el momento absoluto en tiempo de servidor al que el EA debe ajustar la programación y la indexación de barras.

   // Convert a trader ET timestamp to server absolute time using ET offset and server tz
   datetime TraderETtoServer(datetime nfp_trader_time)
   {
      int et_offset = GetETOffset(nfp_trader_time); // UTC offset for ET at that date (-4/-5)
      // nfp_trader_time is interpreted in ET (i.e., wall-clock ET). To get UTC we add -ET offset:
      // UTC = ET - ET_offset_hours
      // Server local = UTC + server_tz
      int server_tz = m_serverTimeZone;
      datetime nfp_server_time = nfp_trader_time - et_offset * 3600 + server_tz * 3600;
      return nfp_server_time;
   }

1.9. GetNextNFPServerTime y GetLastNFPServerTime — búsqueda hacia adelante/hacia atrás

Estas herramientas buscan hacia adelante (la siguiente) o hacia atrás (la más reciente) una marca de tiempo del servidor NFP, analizando un periodo de hasta 13 meses. Convierten la hora del candidato ET a la hora del servidor y la comparan con TimeCurrent(). Las funciones son seguras en los casos extremos en torno a los cambios de año, ya que incrementan o disminuyen correctamente el mes y el año.

   // Return the next NFP server datetime strictly greater than now (search up to 13 months)
   datetime GetNextNFPServerTime()
   {
      datetime server_time = GetServerTime();
      MqlDateTime sd; TimeToStruct(server_time, sd);
      int year = sd.year, month = sd.mon;
      for(int i=0;i<13;i++)
      {
         datetime nfp_trader = GetNFPTimestampTrader(year, month);
         datetime nfp_server = TraderETtoServer(nfp_trader);
         if(nfp_server > server_time) return(nfp_server);
         month++; if(month>12){ month=1; year++; }
      }
      return(0);
   }

   // Return most recent NFP server datetime <= now (search backwards)
   datetime GetLastNFPServerTime()
   {
      datetime server_time = GetServerTime();
      MqlDateTime sd; TimeToStruct(server_time, sd);
      int year = sd.year, month = sd.mon;
      for(int i=0;i<13;i++)
      {
         datetime nfp_trader = GetNFPTimestampTrader(year, month);
         datetime nfp_server = TraderETtoServer(nfp_trader);
         if(nfp_server <= server_time) return(nfp_server);
         month--; if(month<1){ month=12; year--; }
      }
      return(0);
   }

1.10. IsNFPEventActive — comprobación rápida en la ventana de actividades

Una sencilla función auxiliar booleana comprueba si el NFP más reciente se encuentra dentro de un intervalo de tiempo configurable (± una hora por defecto). Resulta útil cuando se desea que el código se comporte de forma diferente durante el periodo NFP (por ejemplo, para suspender la negociación o registrar eventos).

   // Detect if an NFP event is currently within ±window_seconds of server time (default ±3600s)
   bool IsNFPEventActive(int window_seconds = 3600)
   {
      datetime server_time = GetServerTime();
      datetime last_nfp = GetLastNFPServerTime();
      if(last_nfp == 0) return(false);
      if(MathAbs((long)(server_time - last_nfp)) <= window_seconds) return(true);
      return(false);
   }

1.11. FirstM5OpenAfterNFP — asigna la hora del evento a la apertura del M5

La lógica de negociación reacciona al cierre de la primera vela de M5 tras la publicación de las cifras del NFP. Esta utilidad devuelve la hora de apertura del M5 en la que se produce el evento (su hora de cierre es la primera hora de cierre posterior a la marca de tiempo). Utilizamos un sencillo cálculo aritmético (marca de tiempo / 300 * 300) para que sea rápido y no dé lugar a ambigüedades.

   // Utility: compute the M5 open time for the bar that contains the NFP or the first bar after it.
   // Returns the M5 open time for the bar whose close is the first > nfp_server_time.
   datetime FirstM5OpenAfterNFP(datetime nfp_server_time)
   {
      if(nfp_server_time <= 0) return(0);
      // PeriodSeconds for M5 = 300
      int period = PERIOD_M5;
      int psec = 60 * 5;
      // floor open time:
      long open_floor = (long)(nfp_server_time / psec) * psec;
      // if the event falls exactly at an open boundary, then the bar with open=open_floor is the one that includes event
      // its close = open_floor + psec -> that is the first close after event
      return (datetime)open_floor;
   }

1.12. GetM5BarIndexByOpenTime — convierte la hora de apertura en un índice de barras

Por último, esta función envuelve a iBarShift para devolver el índice de la barra de la serie M5 (con base en 0 a partir de la barra actual) correspondiente a una hora de apertura M5 determinada. Devuelve -1 si no se encuentra ninguna coincidencia exacta. Esta función resulta útil cuando el EA necesita llamar a CopyRates o acceder a las barras históricas.

   // Convenience: compute the M5 bar index (iBarShift) for an M5 open time (exact match)
   int GetM5BarIndexByOpenTime(string symbol, datetime m5_open_time)
   {
      if(m5_open_time <= 0) return(-1);
      // prefer exact match - returns index where rates[].time == m5_open_time
      int idx = iBarShift(symbol, PERIOD_M5, m5_open_time, true);
      return idx; // -1 if not found
   }
};

Paso 2: EA «Fibonacci Trader» tras la publicación de los datos del NFP

2.1. Encabezado del archivo, archivos de inclusión y entradas 

Este bloque superior declara los metadatos del EA e importa el ayudante de operaciones y el encabezado NFPTimeManager. Todas las líneas de entrada corresponden a los controles que pones a disposición del operador: colores, nombres, si el EA coloca órdenes automáticamente, el tamaño del lote, los márgenes para el SL/TP, el nivel de rigor y la duración del Fibonacci. Mantén estos valores sencillos durante las pruebas en demo y ajústalos por símbolo más adelante.

//+------------------------------------------------------------------+
//|             Post-NFP Fibonacci Trader EA                        |
//|  Post-NFP fib + pending orders with robust SL/TP buffer & object |
//|  lifecycle: destroy object when any order fills OR after expiry  |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>
#include <NFPTimeManager.mqh>

// --- visual / fib inputs
input color  InpFiboColor = clrDodgerBlue;
input string InpPrefix = "FIBO_NFP_";
input bool   InvertFiboOrientation = true;
input bool   InpRayRight = true;

// Trading inputs
input double InpLots = 0.01;
input int    InpSlippage = 10;
input ulong  InpMagic = 123456;
input int    InpOrderExpirationMinutes = 0;
input bool   InpAutoPlaceOrders = true;
input bool   InpRequireStrictSLAt100 = false; // strict mode
input double InpSLBufferPoints = 20.0; // SL moved away from 100% by this many points
input double InpTPBufferPoints = 20.0; // TP moved away from 0% by this many points
input int    InpFibExpiryHours = 3;    // how many hours before fib auto-deletes

2.2. Estado global: lugar donde se almacenan los datos de ejecución

Estas variables globales almacenan el estado en tiempo real que el EA necesita entre llamadas: los puntos de referencia de Fibonacci, si la barra original era alcista, las órdenes pendientes registradas, el nombre del objeto Fibonacci actual y su hora de creación (para el vencimiento), los niveles fijos de Fibonacci y el registro de datos de M5. Son declaradas por el desarrollador y se actualizan en tiempo de ejecución.

// --- internal
CTrade trade;
CNFPTimeManager nfpManager;

double g_price0 = 0.0;
double g_price100 = 0.0;
bool   g_fibBullish = false;

ulong g_pendingTickets[];                  // tracked pending tickets
string g_currentFibName = "";              // current fib object name
datetime g_fibCreateTime = 0;              // server time when fib created

static const double S_levels[] = {38.2, 50.0, 61.8};

datetime lastM5Open = 0;
datetime lastProcessedNFP = 0;

2.3. OnInit() — configuración inicial y enlace al encabezado NFP

Cuando se activa el EA, OnInit detecta la zona horaria del servidor mediante el encabezado (para que las conversiones del servidor ET sean correctas), configura los valores predeterminados de CTrade (número mágico y deslizamiento), borra cualquier memoria previa de la matriz de tickets y registra la hora actual de apertura del M5. Esto permite que el EA detecte el próximo cierre del M5 tras la publicación de las cifras del NFP.

int OnInit()
{
   // initialize NFP manager tz
   int detected = nfpManager.DetectServerTimeZone();
   PrintFormat("EA: Detected server timezone UTC%+d", detected);

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);

   ArrayFree(g_pendingTickets);

   // initialize last M5 open time
   MqlRates r[];
   if(CopyRates(_Symbol, PERIOD_M5, 0, 1, r) == 1) lastM5Open = r[0].time;
   else lastM5Open = 0;

   Print("EA: Initialized v1.50 — monitoring NFP, will create fib on first M5 close after NFP.");

   return(INIT_SUCCEEDED);
}

2.4. OnDeinit() — apagado y limpieza

OnDeinit garantiza que el EA no deje órdenes pendientes ni objetos de gráfico huérfanos al desactivarse. Cancela cualquier orden pendiente con seguimiento, elimina el nivel de Fibonacci actual (si lo hay) y libera la matriz de órdenes. Se trata de un paso administrativo importante para garantizar la seguridad tanto durante las pruebas como en el uso real.

void OnDeinit(const int reason)
{
   // cleanup: cancel tracked pending orders and delete fib
   CancelExistingPendingOrders();
   DeleteCurrentFibIfExists();
   ArrayFree(g_pendingTickets);
}

2.5. OnTradeTransaction() — detectar ejecuciones y reaccionar de inmediato

Esta función de devolución de llamada detecta los eventos del sistema de operaciones. Cuando se añade una operación (una ejecución), el EA comprueba si la orden ejecutada coincide con alguna de las órdenes pendientes que tiene registradas. En caso afirmativo, elimina el fib y cancela las órdenes pendientes restantes (y borra su lista de órdenes). Además, elimina los tickets del seguimiento si se borran desde fuera del sistema. Esto garantiza que la representación gráfica y las órdenes pendientes registradas se mantengan sincronizadas con las operaciones reales.

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
   // We care about deals added (fills) and order deletions possibly made externally
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
   {
      ulong orderTicket = trans.order;
      if(orderTicket == 0) return;

      for(int i = ArraySize(g_pendingTickets)-1; i >= 0; i--)
      {
         if(g_pendingTickets[i] == orderTicket)
         {
            PrintFormat("EA: Detected fill for tracked order %I64u -> destroying fib and cancelling remaining pending orders.", orderTicket);
            ArrayRemove(g_pendingTickets, i);
            CancelExistingPendingOrders();
            DeleteCurrentFibIfExists();
            ArrayFree(g_pendingTickets);
            return;
         }
      }
   }
   else if(trans.type == TRADE_TRANSACTION_ORDER_DELETE)
   {
      ulong orderTicket = trans.order;
      if(orderTicket == 0) return;
      for(int i = ArraySize(g_pendingTickets)-1; i>=0; i--)
      {
         if(g_pendingTickets[i] == orderTicket)
         {
            ArrayRemove(g_pendingTickets, i);
            PrintFormat("EA: Tracked order %I64u was deleted externally — removed from internal list.", orderTicket);
            break;
         }
      }
   }
}

2.6. OnTick() — lógica de «heartbeat»: comprobación de caducidad y activación por el cierre de M5

OnTick se ejecuta con cada tick del mercado. Realiza dos comprobaciones fundamentales: (A) si el fib actual existe y su plazo de vigencia (por ejemplo, 3 horas) ha vencido, lo elimina y cancela las órdenes pendientes; (B) detecta cuándo se abre una nueva barra M5 (la barra M5 anterior se ha cerrado). En ese evento de cierre de M5, solicita al encabezado la última hora del servidor NFP, comprueba si el M5 cerrado es el primer M5 tras el NFP y, en caso afirmativo, ejecuta las rutinas de Fibonacci y de colocación de órdenes. Este diseño garantiza que el fib se fije a una vela M5 completa tras la publicación de los datos del NFP.

void OnTick()
{
   // 1) Check for fib expiry by time
   if(StringLen(g_currentFibName) > 0 && g_fibCreateTime > 0)
   {
      datetime now = TimeCurrent();
      if(now >= (datetime)(g_fibCreateTime + InpFibExpiryHours * 3600))
      {
         PrintFormat("EA: Fib '%s' expired after %d hours -> deleting object and cancelling pending orders.",
                     g_currentFibName, InpFibExpiryHours);
         CancelExistingPendingOrders();
         DeleteCurrentFibIfExists();
      }
   }

   // 2) Detect M5 new bar open (previous M5 bar closed)
   MqlRates m5rt[];
   if(CopyRates(_Symbol, PERIOD_M5, 0, 1, m5rt) != 1) return;
   datetime currentM5Open = m5rt[0].time;
   if(currentM5Open == lastM5Open) return;

   // previous M5 open is the bar that just closed
   datetime prevOpen = lastM5Open;
   lastM5Open = currentM5Open;

   // find last NFP server time (the release to react to)
   datetime lastNFP = nfpManager.GetLastNFPServerTime();
   if(lastNFP == 0) return;

   // if already processed this NFP skip
   if(lastProcessedNFP == lastNFP) return;

   // compute bar open floor (M5) that contains the event
   int psec = 60*5;
   long floorOpen = (long)(lastNFP / psec) * psec;
   datetime firstM5OpenAfterNFP = (datetime)floorOpen;

   // we want prevOpen == firstM5OpenAfterNFP (i.e. the bar that closed is that target)
   if(prevOpen != firstM5OpenAfterNFP) return;

   // find M5 index of that open time
   int m5Index = iBarShift(_Symbol, PERIOD_M5, prevOpen, true);
   if(m5Index < 0)
   {
      PrintFormat("EA: Could not find M5 index for open time %s", TimeToString(prevOpen, TIME_DATE|TIME_MINUTES));
      return;
   }

   // update fib using that M5 bar and create pending orders
   if(!UpdateFibFromM5Bar(m5Index)) { Print("EA: UpdateFibFromM5Bar failed."); return; }
   if(InpAutoPlaceOrders) PlaceOrRefreshPendingOrders();

   // mark processed
   lastProcessedNFP = lastNFP;
   PrintFormat("EA: Processed NFP at %s (server time). Used M5 open %s as anchor.",
               TimeToString(lastNFP, TIME_DATE|TIME_MINUTES), TimeToString(prevOpen, TIME_DATE|TIME_MINUTES));
}

2.7. UpdateFibFromM5Bar() — lee la barra M5, calcula los puntos de referencia y traza la secuencia de Fibonacci

Esta función lee la barra M5 seleccionada con CopyRates, determina si esa vela fue alcista o bajista (cierre frente a apertura), calcula los puntos de referencia del 0 % y del 100 % según tu configuración, los invierte opcionalmente según tus preferencias visuales, los almacena para operar, elimina los objetos prefijo antiguos y crea un OBJ_FIBO con un nombre único que abarca exactamente esa vela. Registra la hora de creación para la lógica de caducidad.

bool UpdateFibFromM5Bar(int m5Index)
{
   if(m5Index < 0) return(false);

   MqlRates r[];
   int copied = CopyRates(_Symbol, PERIOD_M5, m5Index, 1, r);
   if(copied != 1)
   {
      PrintFormat("EA: CopyRates for M5 index %d failed (copied=%d).", m5Index, copied);
      return(false);
   }

   double bar_high = r[0].high;
   double bar_low  = r[0].low;
   double bar_open = r[0].open;
   double bar_close= r[0].close;
   datetime bar_time = r[0].time;

   bool bullish = (bar_close > bar_open);
   g_fibBullish = bullish;

   // user's mapping:
   double price0 = bullish ? bar_high : bar_low;   // 0% anchor
   double price100= bullish ? bar_low  : bar_high; // 100% anchor

   if(InvertFiboOrientation) { double t = price0; price0 = price100; price100 = t; }

   // store anchors for trading
   g_price0 = price0;
   g_price100 = price100;

   // delete any existing prefix objects to avoid clutter
   int tot = ObjectsTotal(0);
   for(int i = tot-1; i >= 0; i--)
   {
      string nm = ObjectName(0, i);
      if(StringFind(nm, InpPrefix) == 0) ObjectDelete(0, nm);
   }

   // create a unique name for this fib
   g_currentFibName = InpPrefix + _Symbol + "_" + IntegerToString((int)bar_time);
   bool created = ObjectCreate(0, g_currentFibName, OBJ_FIBO, 0, bar_time, price0, (datetime)(bar_time + PeriodSeconds(PERIOD_M5)), price100);
   if(!created)
   {
      PrintFormat("EA: Failed to create Fibo '%s' (err=%d)", g_currentFibName, GetLastError());
      g_currentFibName = "";
      return(false);
   }

   ObjectSetInteger(0, g_currentFibName, OBJPROP_COLOR, InpFiboColor);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_RAY_RIGHT, InpRayRight ? 1 : 0);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_SELECTABLE, true);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_HIDDEN, false);

   string desc = bullish ? "Fibo anchored to bullish M5 bar" : "Fibo anchored to bearish M5 bar";
   if(InvertFiboOrientation) desc += " (inverted)";
   if(InpRayRight) desc += " (ray-right)";
   ObjectSetString(0, g_currentFibName, OBJPROP_TEXT, desc);

   g_fibCreateTime = TimeCurrent();

   PrintFormat("EA: Drew Fibo '%s' on M5 index %d (open=%s) high=%.5f low=%.5f",
               g_currentFibName, m5Index, TimeToString(bar_time, TIME_DATE|TIME_MINUTES), bar_high, bar_low);

   return(true);
}

2.8. DeleteCurrentFibIfExists() — un único lugar para eliminar el fib

Una pequeña herramienta que elimina el objeto «fib» actual, si existe, y borra el estado del EA relacionado con él. Centralizar la eliminación reduce la duplicación y evita errores sutiles en los que el nombre no se borra.

void DeleteCurrentFibIfExists()
{
   if(StringLen(g_currentFibName) == 0) return;
   if(ObjectFind(0, g_currentFibName) >= 0)
   {
      ObjectDelete(0, g_currentFibName);
      PrintFormat("EA: Deleted fib object '%s'.", g_currentFibName);
   }
   g_currentFibName = "";
   g_fibCreateTime = 0;
}

2.9. CancelExistingPendingOrders() — estrategia de seguimiento y limpieza

El EA realiza un seguimiento de los tickets que crea en g_pendingTickets. Esta rutina recorre esa lista y llama a trade.OrderDelete para cada orden. Utilizar las órdenes con seguimiento es más seguro que eliminarlas por símbolo o comentario, ya que solo afecta a las órdenes creadas durante la ejecución actual del EA (a menos que más adelante se añada la opción de persistencia).

void CancelExistingPendingOrders()
{
   if(ArraySize(g_pendingTickets) == 0) return;

   for(int i = ArraySize(g_pendingTickets)-1; i >= 0; i--)
   {
      ulong ticket = g_pendingTickets[i];
      if(ticket == 0) { ArrayRemove(g_pendingTickets, i); continue; }
      bool del = trade.OrderDelete(ticket);
      if(!del) PrintFormat("EA: Failed to delete pending ticket %I64u (err=%d)", ticket, GetLastError());
      else PrintFormat("EA: Deleted pending ticket %I64u", ticket);
      ArrayRemove(g_pendingTickets, i);
   }
}

2.10. PlaceOrRefreshPendingOrders() — cálculo de entradas, SL/TP y realización de órdenes (el núcleo de negociación)

Este es el núcleo de la lógica de trading. Para cada nivel de Fibonacci (38,2; 50; 61,8):

  • Calcula el precio de entrada mediante interpolación lineal entre g_price0 y g_price100.
  • Calcula el SL y el TP deseados, fijados en el 100 % y el 0 %, respectivamente, y luego los desplaza desde el punto de entrada una distancia igual a InpSLBufferPoints / InpTPBufferPoints, dependiendo de si la barra M5 original era alcista o bajista. (Esto garantiza que el SL esté por debajo del 100 % y el TP por encima del 0 % en los casos alcistas, y al revés en los bajistas.)
  • Normaliza los precios al número de dígitos del símbolo.
  • Consulta los límites del bróker (SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT) y garantiza que se respeten las distancias mínimas de los órdenes stop y pendientes. Si es necesario, ajusta el SL/TP (o omite el nivel cuando el modo estricto está activado).
  • Decide, para cada nivel, si es adecuado un BUY_LIMIT (entrada por debajo del precio de mercado) o un SELL_LIMIT (entrada por encima del precio de mercado), realiza la orden pendiente mediante CTrade con el tipo de vencimiento/tiempo si está configurado, registra si se ha realizado con éxito o ha fallado, y almacena el ticket para su posterior gestión.

Este enfoque reduce al mínimo los rechazos de los brókers y garantiza que las relaciones entre precios sean lógicamente correctas.

void PlaceOrRefreshPendingOrders()
{
   // clear previously tracked tickets first
   CancelExistingPendingOrders();

   if(g_price0 == 0.0 || g_price100 == 0.0)
   {
      Print("EA: anchors not set - skipping placement");
      return;
   }

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

   long stops_level = 0;
   if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL, stops_level)) stops_level = 0;
   double min_stop_distance = (double)stops_level * point;
   if(min_stop_distance < point) min_stop_distance = point;
   double min_pending_distance = min_stop_distance;

   // compute buffer values in price units (points * point)
   double sl_buffer_price = InpSLBufferPoints * point;
   double tp_buffer_price = InpTPBufferPoints * point;

   ENUM_ORDER_TYPE_TIME place_time_type = (InpOrderExpirationMinutes > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC;
   datetime place_expiration = (InpOrderExpirationMinutes > 0) ? (TimeCurrent() + InpOrderExpirationMinutes * 60) : 0;

   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   int levelsCount = ArraySize(S_levels);

   for(int i=0;i<levelsCount;i++)
   {
      double pct = S_levels[i] / 100.0;
      double entry = g_price0 + pct * (g_price100 - g_price0);
      // base desired SL and TP anchored at 100% and 0%
      double sl_desired = g_price100;
      double tp_desired = g_price0;

      // Apply buffers depending on the original fib bullish/bearish orientation
      if(g_fibBullish)
      {
         sl_desired = (g_price100 - sl_buffer_price);
         tp_desired = (g_price0 + tp_buffer_price);
      }
      else
      {
         sl_desired = (g_price100 + sl_buffer_price);
         tp_desired = (g_price0 - tp_buffer_price);
      }

      // normalize values
      entry = NormalizeDouble(entry, digits);
      sl_desired = NormalizeDouble(sl_desired, digits);
      tp_desired = NormalizeDouble(tp_desired, digits);

      // decide pending type by comparing entry price with market and min_pending_distance
      bool wantBuy = false, wantSell = false;
      if(entry <= (ask - min_pending_distance)) wantBuy = true;
      else if(entry >= (bid + min_pending_distance)) wantSell = true;
      else
      {
         PrintFormat("EA: Skipping level %.2f: entry(%.5f) too close to market (Ask=%.5f Bid=%.5f) min_pending_dist=%.5f",
                     S_levels[i], entry, ask, bid, min_pending_distance);
         continue;
      }

      ENUM_ORDER_TYPE otype = (wantBuy ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT);
      bool adjusted = false;

      // Validate/adjust stops unless strict mode requested
      if(otype == ORDER_TYPE_BUY_LIMIT)
      {
         // For BUY_LIMIT we need sl < entry < tp
         if(!(sl_desired < entry - min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because SL would be adjusted (desired SL %.5f not < entry-%.5f).",
                           S_levels[i], sl_desired, (entry - min_stop_distance));
               continue;
            }
            double old = sl_desired;
            sl_desired = NormalizeDouble(entry - min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted SL BUY level %.2f old=%.5f -> new=%.5f", S_levels[i], old, sl_desired);
         }
         if(!(tp_desired > entry + min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because TP would be adjusted (desired TP %.5f not > entry+%.5f).",
                           S_levels[i], tp_desired, (entry + min_stop_distance));
               continue;
            }
            double old = tp_desired;
            tp_desired = NormalizeDouble(entry + min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted TP BUY level %.2f old=%.5f -> new=%.5f", S_levels[i], old, tp_desired);
         }
      }
      else // SELL_LIMIT
      {
         // For SELL_LIMIT we need tp < entry < sl
         if(!(sl_desired > entry + min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because SL would be adjusted (desired SL %.5f not > entry+%.5f).",
                           S_levels[i], sl_desired, (entry + min_stop_distance));
               continue;
            }
            double old = sl_desired;
            sl_desired = NormalizeDouble(entry + min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted SL SELL level %.2f old=%.5f -> new=%.5f", S_levels[i], old, sl_desired);
         }
         if(!(tp_desired < entry - min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because TP would be adjusted (desired TP %.5f not < entry-%.5f).",
                           S_levels[i], tp_desired, (entry - min_stop_distance));
               continue;
            }
            double old = tp_desired;
            tp_desired = NormalizeDouble(entry - min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted TP SELL level %.2f old=%.5f -> new=%.5f", S_levels[i], old, tp_desired);
         }
      }

      // final relation sanity
      bool valid = true;
      if(otype == ORDER_TYPE_BUY_LIMIT) valid = (sl_desired < entry && tp_desired > entry);
      else valid = (sl_desired > entry && tp_desired < entry);
      if(!valid)
      {
         PrintFormat("EA: Skipping level %.2f due to invalid final SL/TP relation entry=%.5f SL=%.5f TP=%.5f",
                     S_levels[i], entry, sl_desired, tp_desired);
         continue;
      }

      // build order comment
      string comment = InpPrefix + DoubleToString(S_levels[i], 2);

      // Place pending with proper signature (includes time type and expiration)
      bool placed = false;
      if(otype == ORDER_TYPE_BUY_LIMIT)
         placed = trade.BuyLimit(InpLots, entry, _Symbol, sl_desired, tp_desired, place_time_type, place_expiration, comment);
      else
         placed = trade.SellLimit(InpLots, entry, _Symbol, sl_desired, tp_desired, place_time_type, place_expiration, comment);

      if(!placed)
      {
         int err = GetLastError();
         PrintFormat("EA: Failed to place %s at %.5f (lvl %.2f) err=%d ret=%d",
                     (otype==ORDER_TYPE_BUY_LIMIT ? "BUY_LIMIT":"SELL_LIMIT"),
                     entry, S_levels[i], err, (int)trade.ResultRetcode());
      }
      else
      {
         ulong ticket = trade.ResultOrder();
         PrintFormat("EA: Placed %s ticket=%I64u lvl=%.2f entry=%.5f SL=%.5f TP=%.5f adjusted=%s",
                     (otype==ORDER_TYPE_BUY_LIMIT ? "BUY_LIMIT":"SELL_LIMIT"),
                     ticket, S_levels[i], entry, sl_desired, tp_desired, (adjusted ? "yes":"no"));

         // track ticket for cleanup and fill detection
         ArrayResize(g_pendingTickets, ArraySize(g_pendingTickets) + 1);
         g_pendingTickets[ArraySize(g_pendingTickets)-1] = ticket;
      }
   } // levels loop
}

2.11. Cómo utiliza el EA el encabezado NFPTimeManager: puntos de llamada sencillos

El encabezado se encarga de las tareas más complejas relacionadas con la fecha y la hora: el EA llama a DetectServerTimeZone() en OnInit() para configurar la conversión, y a GetLastNFPServerTime() en cada cierre de M5 para obtener la hora del servidor en el momento del NFP más reciente. A partir de esa marca de tiempo, el EA calcula la primera barra M5 que se abre tras la publicación de los datos del NFP y reacciona cuando esa sesión de M5 se cierra. De este modo, la lógica principal del EA se mantiene clara y es independiente de la zona horaria.

// example header calls in OnInit/OnTick
int detected = nfpManager.DetectServerTimeZone();
datetime lastNFP = nfpManager.GetLastNFPServerTime();


Resultados

Gracias a la clase NFPTimeManager, el EA identifica de forma fiable la publicación de las nóminas no agrícolas (NFP) incluso en datos históricos, lo que nos permite ejecutar y validar la estrategia en el Strategy Tester tras una compilación satisfactoria. A continuación se muestran unas imágenes animadas que ilustran el proceso de prueba en el Strategy Tester.

metatester64_nDEhinFAuB

Figura 6. 7 de junio de 2024, anuncio de las nóminas no agrícolas (NFP)

En la figura 6, durante el anuncio de las nóminas no agrícolas (NFP) del 7 de junio de 2024, el EA detectó correctamente el día del evento y sincronizó adecuadamente la hora de publicación de las NFP con la hora local del servidor, tal y como se confirma en el Strategy Tester. La herramienta de retroceso de Fibonacci se trazó y se aplicó tal y como estaba prevista. Sin embargo, las órdenes pendientes no se activaron porque el retroceso fue demasiado superficial y no llegó a los niveles de entrada.

Otro problema que se observó fue la colocación incorrecta de los valores de stop-loss y take-profit en las órdenes pendientes, que no se ajustaban plenamente a las especificaciones previstas. Esto se puede corregir bien en el código, bien ajustando los parámetros de entrada.

A pesar de estas limitaciones, el resultado más importante es que el EA fue capaz de detectar el evento, mapear las zonas horarias con precisión y automatizar la colocación de los niveles de Fibonacci. Estos logros constituyen una base sólida, y se podrán introducir nuevas mejoras en futuras versiones. A continuación se muestra otra imagen del evento del 1 de noviembre de 2024.

 metatester64_lfPG2lDG5m

Figura 7. 1 de noviembre de 2024, anuncio de las nóminas no agrícolas (NFP)



Conclusión

La herramienta de retroceso de Fibonacci puede aplicarse de forma algorítmica para operar en los eventos relacionados con las nóminas no agrícolas (NFP) mediante un enfoque estructurado y basado en reglas. En lugar de intentar aprovechar el repunte inicial, este método opera en el mercado unos minutos después de la publicación, durante la fase de corrección natural. En teoría, se puede obtener como beneficio entre el 30 % y el 60 % de la subida de precios inicial de cinco minutos, incluso si un operador se ha perdido el primer movimiento. Esto es posible gracias a las proporciones de Fibonacci, que establecen el alcance total del repunte como el 100 % y proporcionan niveles de retroceso precisos para la apertura de posiciones.

Entre ellos, el nivel de Fibonacci del 38,2 % suele ofrecer la mayor probabilidad de generar una ganancia comparable al 38,2 % de la magnitud del repunte inicial. Sin embargo, es importante señalar que el mercado no siempre se comporta así, y ninguna configuración está garantizada. Nuestras pruebas han arrojado resultados prometedores en múltiples situaciones, aunque este enfoque debe considerarse una probabilidad estructurada, no una certeza.

En este estudio, no hemos llevado a cabo un análisis exhaustivo del rendimiento del EA, sino que nos hemos centrado en demostrar el concepto mediante simulaciones prácticas en el Strategy Tester. El prototipo tiene margen para ser perfeccionado y optimizado, ya sea mediante una gestión mejorada de las órdenes, una validación estadística o la adaptación a otros pares de divisas y a contextos de trading no vinculados a noticias.

Igualmente valiosas fueron las lecciones aprendidas al desarrollar la estrategia: empezar desde cero, aplicar la modularización para mantener el código limpio y reutilizable, y anticipar futuras ampliaciones. Se trata de una base sobre la que seguir construyendo y que, con un mayor desarrollo, puede convertirse en una herramienta de negociación más potente y flexible.

Te invitamos a que nos des tu opinión y tus comentarios en la sección de comentarios que aparece a continuación. Además, le rogamos que consulte los documentos originales adjuntos y revise nuestra tabla resumen de las principales conclusiones para obtener ideas prácticas.


Lecciones clave

Lección clave Descripción:
Modularizar la lógica Divide la funcionalidad en unidades pequeñas y específicas (clases o funciones). Ejemplo: un NFPTimeManager específico para la lógica de hora y horario de verano permite que el núcleo de EA siga siendo legible y reutilizable.
Gestionar las zonas horarias y el horario de verano de forma explícita Convertir las horas de los eventos (ET) a la hora del servidor utilizando reglas deterministas. Los errores en este punto provocarán una desalineación de los anclajes en las pruebas históricas y en las ejecuciones en producción, por lo que conviene centralizar y probar esta lógica.
Anclaje a barras cerradas Basa siempre los cálculos en las velas completadas (por ejemplo, la primera vela de 5 minutos que cerró tras la publicación de las nóminas no agrícolas (NFP)). Esto evita la inestabilidad derivada del recálculo intrabárico y hace que los resultados sean reproducibles en el Strategy Tester.
Respeta los límites del bróker Consulta los campos SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT y SYMBOL_DIGITS en tiempo de ejecución y comprueba las distancias de SL/TP y de las órdenes pendientes para evitar que se rechacen las órdenes.
Normaliza los precios y utiliza la precisión correcta Normaliza siempre el punto de entrada, el SL y el TP a los dígitos del símbolo. Los pequeños errores de redondeo pueden provocar fácilmente que el bróker rechace la operación.
Diseñar comportamientos de respaldo seguros Ofrecer modos «estricto» y «adaptativo»: o bien omitir los niveles que requieran cambios en los stop (modo «estricto»), o bien ajustar automáticamente los stop para cumplir con las normas del bróker (modo «adaptativo»). Registra ambas decisiones.
Utiliza API robustas para la colocación de órdenes Utiliza los métodos auxiliares de CTrade con las firmas correctas (incluido el tipo de caducidad o de tiempo) para evitar modificaciones posteriores y reducir la complejidad en la gestión de errores.
Realiza un seguimiento y gestiona tus propios tickets Mantén una lista interna de los tickets creados para que el asesor experto pueda cancelar o supervisar de forma fiable únicamente las órdenes que él mismo haya creado (evita eliminar órdenes de usuarios que no tengan relación con ellos).
Ciclo de vida de los objetos y orden en la interfaz de usuario Asigna a los objetos del gráfico nombres breves y únicos (por ejemplo, fib_SYMBOL_YYMMDDhhmm), guarda la información detallada en OBJPROP_TEXT y elimina los objetos cuando las órdenes se ejecuten o caduquen.
Utiliza OnTradeTransaction para las ejecuciones Detecta ejecuciones y eliminaciones externas mediante eventos de transacciones bursátiles para reaccionar de inmediato (limpieza, registro) y evitar retrasos derivados del sondeo.
Registro detallado para la trazabilidad Registra cada acción clave (crear, ajustar, omitir, colocar, fallar, eliminar). Los registros claros son esenciales para la depuración, las pruebas retrospectivas y los fines normativos y de auditoría.
Prueba primero en el Probador de estrategias y en una cuenta demo Comprueba la lógica con fechas históricas de publicación del NFP en el Probador de estrategias (de forma determinista) y después ejecútalo en una cuenta demo. Utiliza anclajes determinísticos (barras cerradas) para que las pruebas sean reproducibles.



Archivos adjuntos

Nombre del archivo Versión Descripción
NFPTimeManager.mqh
1.0 Clase de utilidades de tiempo compatible con el horario de verano (DST) que centraliza todos los cálculos de marcas de tiempo relacionados con las nóminas no agrícolas (NFP) y las conversiones entre la hora del Este (ET), el UTC y la hora del bróker o del servidor. Entre sus funciones principales se incluyen la detección de la zona horaria del servidor, el cálculo de la hora real del NFP (el primer viernes a las 08:30 ET), la conversión a la hora del servidor ET, la obtención de la marca de tiempo más reciente o la siguiente del servidor NFP, y herramientas de apoyo para relacionar los momentos clave del NFP con los horarios de apertura del M5 y los índices de barras. Diseñado para su reutilización y para las pruebas unitarias, de modo que el EA siga siendo independiente de la zona horaria.
Post-NFP Fibonacci Trader EA.mq5
1.0 Asesor experto completo que utiliza el NFPTimeManager para detectar el primer cierre de una vela M5 tras la publicación de los datos del NFP, traza un retroceso de Fibonacci basado en esa vela y coloca órdenes pendientes en los niveles de retroceso del 38,2 %, el 50 % y el 61,8 %. Entre sus características se incluyen los búferes SL/TP, la gestión de stops estricta frente a la adaptativa, la validación de los límites del bróker (SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT, dígitos), el seguimiento de órdenes, la limpieza automática (al ejecutarse la orden o tras un plazo de caducidad configurable) y el registro detallado para garantizar la trazabilidad. Configurable mediante parámetros de entrada; probado en el Probador de estrategias y en una cuenta demo.

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

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Del básico al intermedio: Colas, listas y árboles (I) Del básico al intermedio: Colas, listas y árboles (I)
En este artículo comenzaremos a explorar una pequeña serie de conceptos de suma importancia para quienes realmente desean aprender a programar correctamente. Como al principio puede resultar muy complicado, aunque se base en elementos sencillos, lo veremos poco a poco. Entonces, aquí comenzaremos a ver qué son las colas de datos.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Dominar los Fair Value Gaps: formación, lógica y automatización del trading con Breakers y Market Structure Shifts (MSS) Dominar los Fair Value Gaps: formación, lógica y automatización del trading con Breakers y Market Structure Shifts (MSS)
Este artículo tiene como objetivo exponer y explicar los Fair Value Gaps, la lógica que subyace a su formación y aparición, y su aplicación al trading automatizado mediante Breakers y cambios en la estructura del mercado.