Automatización·

Cómo agregar un filtro de noticias a tu Expert Advisor de MT4

MetaTrader 4 no ofrece un calendario económico a los Expert Advisors. Esta guía te enseña a agregarlo en diez minutos: una API de calendario gratuita, un archivo include y una función que llamas antes de cada entrada. Código MQL4 completo, sin clave de API.

Un Expert Advisor que sigue operando en plena publicación de las nóminas no agrícolas (NFP) o de una decisión de tasas de interés ya no aplica su estrategia: está lanzando una moneda al aire, y con el spread bien abierto. Un filtro de noticias lo evita. Suspende las entradas nuevas durante unos minutos antes y después de las publicaciones que mueven el mercado, y el resto del tiempo deja que la estrategia haga su trabajo.

MetaTrader 5 trae un calendario integrado. MetaTrader 4, no, así que el EA tiene que conseguir las noticias por su cuenta. Esta guía usa la API gratuita del calendario económico de ShowMyTrades: sin cuenta ni clave de API, con una sola URL que autorizar en el terminal y un archivo include que puedes pegar en cualquier EA.

🧭 Qué vas a construir

cada 15 minutos            en cada tick
┌────────────────────┐     ┌──────────────────────────────────┐
│ descarga noticias  │ ──► │ ¿hay una publicación dentro de   │
│ de los próximos    │     │ [-30 min, +30 min] desde ahora?  │
│ 2 días (CSV, una   │     │   sí → ninguna entrada nueva     │
│ sola solicitud)    │     │   no → se ejecuta la estrategia  │
└────────────────────┘     └──────────────────────────────────┘
DecisiónValor predeterminado en el códigoPor qué
Qué divisasEUR,USDLas dos divisas del par con el que operas. Si lo dejas vacío, entran las nueve principales
Qué impactohighhigh,medium para un filtro más prudente
Pausa antes30 minutosAntes del dato los spreads se amplían y la liquidez se reduce
Pausa después30 minutosAquí se concentran la primera reacción y el giro que suele venir después
Intervalo de descarga15 minutosEl calendario cambia pocas veces al día y, de todos modos, la API guarda los datos en caché durante 15 minutos

🔑 Paso 1: autoriza la dirección en MetaTrader

WebRequest(), la función de MQL4 que descarga datos de internet, solo se comunica con las direcciones que hayas aprobado. En el terminal abre Herramientas → Opciones → Asesores Expertos (Tools → Options → Expert Advisors), marca Permitir WebRequest para las siguientes URL (Allow WebRequest for listed URL) y agrega:

https://showmytrades.com

Si te saltas este paso, la descarga devuelve -1 con el error 4060. Cuando eso ocurre, el archivo include deja un aviso en la pestaña Expertos (Experts), así que no te quedarás preguntándote por qué el filtro no dice nada.

📄 Paso 2: guarda el archivo include

Guarda el código siguiente como SMT_NewsFilter.mqh en la carpeta MQL4\Include de tu terminal; la encuentras en Archivo → Abrir carpeta de datos (File → Open Data Folder). Es el mismo archivo que aparece en la referencia de la API, y además compila tal cual en MQL5.

SMT_NewsFilter.mqh
//+------------------------------------------------------------------+
//| SMT_NewsFilter.mqh - filtro de noticias para MT4 y MT5           |
//| Datos: https://showmytrades.com/api/public/calendar/v1 (gratis)  |
//| Docs: https://showmytrades.com/docs/api/economic-calendar        |
//| Configuración: Herramientas > Opciones > Asesores Expertos >     |
//|        Permitir WebRequest (Allow WebRequest) para               |
//|        https://showmytrades.com                                  |
//+------------------------------------------------------------------+
#property strict

input string SMT_Currencies     = "EUR,USD"; // Noticias: divisas (separadas por comas, vacío = todas)
input string SMT_Impact         = "high";    // Noticias: impacto (high | high,medium | all)
input int    SMT_MinutesBefore  = 30;        // Noticias: pausa antes de una publicación (minutos)
input int    SMT_MinutesAfter   = 30;        // Noticias: pausa después de una publicación (minutos)
input int    SMT_RefreshMinutes = 15;        // Noticias: intervalo de descarga (minutos, mín. 15)

datetime smt_eventTime[];
string   smt_eventTitle[];
datetime smt_nextDownload = 0;
long     smt_serverTime   = 0;     // hora UTC del servidor (segundos Unix) en la última descarga
uint     smt_syncTick     = 0;     // GetTickCount() en ese mismo momento
bool     smt_clockSynced  = false;

//--- la hora UTC real: el reloj del servidor más los milisegundos transcurridos desde la
//--- descarga, contados con GetTickCount(), que no sabe de zonas horarias, horario de
//--- verano ni ajustes del reloj del equipo (la resta en uint sobrevive a su desbordamiento).
//--- Antes de la primera descarga se usa TimeGMT() como respaldo. Nunca TimeCurrent(): el
//--- desfase GMT del bróker es desconocido y salta con el horario de verano.
datetime SMT_Now()
{
   if(!smt_clockSynced) return TimeGMT();
   uint elapsedMs = GetTickCount() - smt_syncTick;
   return (datetime)(smt_serverTime + elapsedMs / 1000);
}

//--- cuánto se aparta el reloj del bróker de UTC, en segundos (7200 = GMT+2). Útil por sí
//--- solo: la mayoría de los EA nunca lo conoce. TimeCurrent() es la hora del último tick,
//--- así que léelo con el mercado abierto, no durante el fin de semana.
int SMT_BrokerGmtOffsetSeconds()
{
   return (int)(TimeCurrent() - SMT_Now());
}

//--- todo lo que hay después de la n-ésima coma (el título del CSV, que puede contener comas)
string SMT_AfterComma(const string line, const int n)
{
   int pos = -1;
   for(int i = 0; i < n; i++)
   {
      pos = StringFind(line, ",", pos + 1);
      if(pos < 0) return "";
   }
   string rest = StringSubstr(line, pos + 1);
   StringReplace(rest, "\"\"", "\"");
   if(StringLen(rest) >= 2 && StringGetCharacter(rest, 0) == '"')
      rest = StringSubstr(rest, 1, StringLen(rest) - 2);
   return rest;
}

//--- descarga los próximos 2 días (la ventana también cubre las últimas 6 horas)
bool SMT_Download()
{
   string url = "https://showmytrades.com/api/public/calendar/v1?format=csv&days=2&impact=" + SMT_Impact;
   if(StringLen(SMT_Currencies) > 0) url += "&currency=" + SMT_Currencies;

   char   post[], result[];
   string headers;
   ResetLastError();
   int status = WebRequest("GET", url, "", NULL, 10000, post, 0, result, headers);
   if(status != 200)
   {
      Print("[SMT] calendar download failed: HTTP ", status, ", error ", GetLastError(),
            ". Is https://showmytrades.com in Tools > Options > Expert Advisors > Allow WebRequest?");
      smt_nextDownload = TimeCurrent() + 60; // reintenta en un minuto
      return false;
   }

   string body = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
   string lines[];
   int n = StringSplit(body, '\n', lines);

   ArrayResize(smt_eventTime, 0);
   ArrayResize(smt_eventTitle, 0);
   for(int i = 1; i < n; i++) // la línea 0 es el encabezado
   {
      // última línea "# server_time=1760702400 server_time_iso=...": el reloj UTC del servidor
      if(StringFind(lines[i], "# server_time=") == 0)
      {
         long serverTime = StringToInteger(StringSubstr(lines[i], 14, 10));
         if(serverTime > 0)
         {
            smt_serverTime  = serverTime;
            smt_syncTick    = GetTickCount();
            smt_clockSynced = true;
         }
         continue;
      }
      string f[];
      if(StringSplit(lines[i], ',', f) < 8) continue;
      int k = ArraySize(smt_eventTime);
      ArrayResize(smt_eventTime, k + 1);
      ArrayResize(smt_eventTitle, k + 1);
      smt_eventTime[k]  = (datetime)StringToInteger(f[0]);      // segundos Unix, UTC
      smt_eventTitle[k] = f[2] + " " + f[3] + ": " + SMT_AfterComma(lines[i], 7);
   }
   smt_nextDownload = TimeCurrent() + MathMax(SMT_RefreshMinutes, 15) * 60;
   Print("[SMT] calendar: ", ArraySize(smt_eventTime), " events loaded. UTC now ",
         TimeToString(SMT_Now(), TIME_DATE|TIME_MINUTES), ", broker clock is GMT",
         (SMT_BrokerGmtOffsetSeconds() >= 0 ? "+" : ""), DoubleToString(SMT_BrokerGmtOffsetSeconds() / 3600.0, 1));
   return true;
}

//--- true mientras haya una publicación dentro de la ventana de pausa; reason indica cuál
bool SMT_IsNewsTime(string &reason)
{
   reason = "";
   if(MQLInfoInteger(MQL_TESTER)) return false;   // sin red en el probador de estrategias
   if(TimeCurrent() >= smt_nextDownload) SMT_Download();

   datetime now = SMT_Now();
   for(int i = 0; i < ArraySize(smt_eventTime); i++)
   {
      if(now >= smt_eventTime[i] - SMT_MinutesBefore * 60 && now <= smt_eventTime[i] + SMT_MinutesAfter * 60)
      {
         reason = smt_eventTitle[i];
         return true;
      }
   }
   return false;
}

Lo que hace, por orden: construye la solicitud a partir de los parámetros de entrada, descarga el CSV, guarda una marca de tiempo y una etiqueta por evento, y responde a la única pregunta que le importa al EA: ¿hay ahora mismo una publicación dentro de la ventana?

🔌 Paso 3: llámala antes de cada entrada

En tu EA, incluye el archivo y pregunta antes de abrir una posición:

YourEA.mq4
#include <SMT_NewsFilter.mqh>

void OnTick()
{
   string news;
   if(SMT_IsNewsTime(news))
   {
      Comment("News filter active: ", news);
      return;                       // sin entradas nuevas durante la ventana de noticias
   }
   Comment("");
   // ... tu lógica de entrada ...
}

Los cinco parámetros de entrada (SMT_Currencies, SMT_Impact y los tres valores en minutos) aparecen en la ventana de parámetros del EA como cualquier otra opción, así que cada gráfico puede tener los suyos.

Vuelve a compilar, coloca el EA en el gráfico y mira la pestaña Expertos: la primera línea debería ser [SMT] calendar: N events loaded. En un día tranquilo N puede ser cero, y eso es lo correcto, no un error.

⏱️ Horas, relojes y el probador de estrategias

Hay tres detalles que deciden si el filtro salta en el minuto correcto.

La API habla en UTC y te dice qué hora ests es tiempo Unix en segundos, en UTC. Cada descarga termina con el reloj del propio servidor (# server_time=…), y SMT_Now() lo mantiene en marcha con el contador de ticks del equipo, que no sabe de zonas horarias ni de horario de verano. Nada depende de la zona horaria del bróker, del horario de verano de ningún país ni de que el VPS tenga bien configurada la zona
Nunca TimeCurrent()La hora del bróker va desfasada respecto a UTC en una cantidad que el EA no puede conocer y que cambia dos veces al año. SMT_BrokerGmtOffsetSeconds() te mide ese desfase, algo útil por sí solo; el filtro nunca depende de él
El probador de estrategias (Strategy Tester) no tiene internetAhí WebRequest() está desactivada, así que en los backtests la función responde «no hay noticias». Para probar un filtro de noticias sobre datos históricos hace falta un archivo local con los eventos pasados, y eso ya es otro proyecto

🎛️ Cómo ajustarlo

  • Posiciones abiertas. El código solo bloquea las entradas nuevas. Cerrar, ajustar los stops o ampliarlos antes de una publicación es una decisión de estrategia; el mismo SMT_IsNewsTime() te dice cuándo hacerlo.
  • Solo una divisa del par. Pon SMT_Currencies en USD si solo te interesan las noticias del dólar.
  • También impacto medio. Con SMT_Impact = "high,medium" los eventos se multiplican más o menos por tres. Va bien para los scalpers y suele ser demasiado para los sistemas de swing.
  • Días festivos. impact=all incluye los días festivos de los mercados como eventos de día completo. Casi ningún filtro los quiere, y con el valor predeterminado quedan fuera.

🔍 Cómo comprobar los datos

El mismo calendario también es una página que puedes consultar: calendario económico. Cuando el EA indique que se detuvo por «USD high: Nonfarm Payrolls», ahí verás el evento con su previsión y, después de la publicación, el dato publicado (columna «Actual»).

❓ Preguntas frecuentes

¿Hace falta una cuenta de ShowMyTrades? No. La API del calendario es gratuita y abierta, sin clave. Si quieres la página pública verificada, haz seguimiento de tu cuenta en ShowMyTrades, pero el filtro de noticias no depende de ello.

¿Cuánto tráfico genera? Una solicitud cada 15 minutos por gráfico, de unos pocos kilobytes cada una. Diez EA en un mismo terminal suman 40 solicitudes por hora. El límite es de 600 por minuto por dirección IP.

¿Por qué no leer directamente Forex Factory? Se puede, y muchos EA lo hacen. Su archivo semanal admite dos descargas cada cinco minutos por dirección IP, algo que no aguanta un VPS con muchos terminales, y no trae los valores actual. El endpoint de ShowMyTrades filtra por divisa e impacto en el servidor, devuelve unos pocos kilobytes y está pensado para consultarse a intervalos regulares.

El reloj o la zona horaria de mi VPS están mal. ¿El filtro salta igualmente en el minuto correcto? Sí. Con cada descarga, el servidor le dice al EA qué hora es en UTC, y el EA mantiene ese reloj en marcha por su cuenta. Que el VPS tenga la zona equivocada o vaya unos minutos desfasado no cambia nada. Ni siquiera el cambio al horario de verano en el VPS afecta: el EA mide el tiempo transcurrido con el contador de ticks, no con el reloj.

¿Qué pasa si la descarga falla? El filtro conserva el último calendario que cargó y vuelve a intentarlo al cabo de un minuto. Si nunca llegó a cargar ninguno (arranque en limpio, sin internet), responde «no hay noticias» y el EA opera. Si no es lo que quieres, comprueba ArraySize(smt_eventTime) == 0 y decide qué hacer.

¿El mismo archivo funciona en MT5? Sí, sin cambios. MT5 además tiene su propio calendario; usa el que mejor te venga.

🔗 Artículos relacionados


El filtro cabe en una página de código y una URL. Si ya haces seguimiento de tu EA en ShowMyTrades, las operaciones que evita en torno a las noticias aparecerán como el drawdown que no tuvo.