CURSO DE TRADING ALGORITMICO

Estas son las funciones mas importantes de un Expert Advisor (EA) en MQL5

Aprende la estructura exacta de un Expert Advisor en MQL5: OnInit, OnTick, OnDeinit y OnTester con código funcional, gráficas y ejercicios prácticos para MetaTrade

Marzo 2026
21 min lectura

Plataforma: MetaTrader 5 + MetaEditor Referencia: MQL5 Documentation Official Prerequisito: Episodio 1 — Primer robot funcionando

En el Episodio 1 viste un EA abrir su primera operación. Funcionó, pero probablemente no entendiste cada línea. Eso cambia aquí. Vamos a diseccionar un Expert Advisor completo: sus cuatro funciones de evento, los tipos de variables, el sistema de handles para indicadores y el flujo exacto de ejecución tick a tick.

No es una clase de programación abstracta. Cada concepto va acompañado del código que lo implementa y del gráfico que lo explica. Al final de este artículo tendrás el mapa mental completo de cómo MT5 corre tu robot — y eso cambia la forma en que escribes código.

4
Funciones de evento principales
OnInit · OnTick · OnDeinit · OnTester
3
Tipos de programas en MQL5
Expert Advisor · Indicador · Script
~1ms
Frecuencia de OnTick en cuenta activa
Depende del broker y del activo
1
Solo una instancia de OnTick se ejecuta a la vez
MQL5 es single-threaded por EA

01 La estructura general de un EA

Un Expert Advisor en MQL5 es un archivo .mq5 que MetaEditor compila a bytecode .ex5. Ese bytecode lo ejecuta la máquina virtual de MT5 en respuesta a eventos: un nuevo tick, la inicialización del EA, el cierre de la plataforma, etc. No hay un bucle while(true) — el modelo es reactivo, no imperativo.

Flujo de ejecución de un Expert Advisor
01
OnInit()
Se ejecuta una sola vez cuando el EA se adjunta al gráfico o cuando MT5 se inicia. Aquí inicializas handles de indicadores, validas parámetros de entrada y reservas memoria. Si retorna un valor distinto de INIT_SUCCEEDED, el EA no arranca.
Retorna: int INIT_SUCCEEDED = 0
02
OnTick()
Se ejecuta en cada nuevo tick que llega del broker. Es el corazón del EA: aquí calculas indicadores, evalúas señales de entrada/salida y envías órdenes. Se puede llamar cientos de veces por minuto en pares líquidos. Nunca bloquees esta función con bucles infinitos.
Retorna: void Más llamada del EA
03
OnDeinit()
Se ejecuta cuando el EA se elimina del gráfico, se cambia de símbolo/temporalidad, o MT5 se cierra. Su función es liberar recursos: handles de indicadores con IndicatorRelease(), archivos abiertos, objetos gráficos.
Retorna: void Parámetro: const int reason
04
OnTester()
Opcional. Solo se ejecuta al finalizar un backtest en el Strategy Tester. Retorna un double que puede usarse como criterio de optimización personalizado. Lo veremos en el Episodio 10.
Retorna: double Solo en backtesting
¿Cuántas veces se llama cada función? — sesión de 8 horas en USD/JPY M5
OnTick domina absolutamente · OnInit y OnDeinit son eventos únicos · OnTester solo en backtest
MQL5 Docs · TN Medición

02 OnInit() — El constructor del robot

OnInit() es la primera función que ejecuta MT5 cuando adjuntas el EA a un gráfico. Se llama exactamente una vez. Su trabajo es preparar todo lo que el EA necesitará durante su vida: crear handles de indicadores, validar parámetros, calcular constantes. Si algo falla aquí, retornas INIT_FAILED y el EA no arranca — lo cual es exactamente lo que quieres.

¿Qué pasa dentro de OnInit()? — Tareas por frecuencia de aparición en EAs
Análisis de 200+ EAs open-source en MQL5.com · % que incluyen esa tarea en OnInit
MQL5 Code Base
Regla práctica: si una operación es costosa (crear un indicador tarda ~12ms) o solo necesita ejecutarse una vez, va en OnInit(). Todo lo que va en OnTick() se repite miles de veces — ponlo solo si es imprescindible ahí.
EJERCICIO PRÁCTICO OnInit() — Validar parámetros y crear handles

Copia este código en MetaEditor, compila y adjunta al gráfico EURUSD M5. Observa el panel de Expertos: verás los mensajes de inicialización. Luego cambia InpMAPeriod a 0 y recompila — el EA rechazará el parámetro inválido.

MQL5 Ejercicio 1 — OnInit() con validación completa
#property strict

// ── Parámetros configurables desde MT5 ────────────────────────────
input int    InpMAPeriod = 20;   // Período MA
input double InpLots     = 0.10; // Lotaje

// ── Variable global para el handle ────────────────────────────────
int g_maHandle;

int OnInit() {
   // 1. Validar parámetros ANTES de crear handles
   if(InpMAPeriod < 2) {
      Alert("ERROR: InpMAPeriod debe ser >= 2. Valor recibido: ", InpMAPeriod);
      return(INIT_PARAMETERS_INCORRECT); // EA no arranca
   }
   if(InpLots <= 0) {
      Alert("ERROR: Lotaje debe ser positivo");
      return(INIT_PARAMETERS_INCORRECT);
   }

   // 2. Crear el handle del indicador
   g_maHandle = iMA(_Symbol, _Period, InpMAPeriod, 0, MODE_SMA, PRICE_CLOSE);

   // 3. Verificar que el handle es válido
   if(g_maHandle == INVALID_HANDLE) {
      Print("ERROR al crear handle MA. Código: ", GetLastError());
      return(INIT_FAILED);
   }

   // 4. Log de confirmación — visible en Herramientas > Diario de Expertos
   Print("EA iniciado correctamente en ", _Symbol, " ", EnumToString(_Period));
   Print("MA(20) handle: ", g_maHandle);

   return(INIT_SUCCEEDED); // EA arranca
}

03 OnTick() — El corazón que late con el mercado

OnTick() se ejecuta cada vez que llega un nuevo precio del broker. En USD/JPY durante la sesión de Londres puede llamarse más de 2,000 veces por minuto. Es donde vive toda la lógica de tu estrategia: leer precios, evaluar indicadores, decidir si entrar o salir. Lo que pongas aquí se repite constantemente — cada milisegundo cuenta.

Ticks por minuto por par y sesión — OnTick() se llama cada vez
Cuenta demo · promedio por sesión · mayor volumen = más ticks = más llamadas a OnTick
TN Data · ICMarkets demo
Concepto clave — nueva vela vs. nuevo tick: OnTick se dispara en cada cambio de precio. Si quieres ejecutar lógica solo cuando se cierra una vela (como la mayoría de estrategias de indicadores), necesitas detectarlo con IsNewBar(). Sin ese control, estarás evaluando señales en barras incompletas.
EJERCICIO PRÁCTICO OnTick() — Detectar nueva vela y leer indicadores

Este es el patrón que usarás en el 90% de tus EAs. Copia, compila y observa el panel de Expertos: verás un mensaje cada vez que se cierre una vela con el valor actual de la MA.

MQL5 Ejercicio 2 — OnTick() con detección de nueva vela
// Variable global para detectar nueva vela
datetime g_lastBarTime = 0;

void OnTick() {
   // ── PASO 1: Detectar nueva vela ──────────────────────────────
   datetime currentBarTime = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);

   bool isNewBar = (currentBarTime != g_lastBarTime);
   if(!isNewBar) return; // Salir si no es nueva vela — sin hacer nada
   g_lastBarTime = currentBarTime;

   // ── PASO 2: Esperar suficientes barras ───────────────────────
   if(Bars(_Symbol, _Period) < InpMAPeriod + 10) {
      Print("Esperando datos suficientes...");
      return;
   }

   // ── PASO 3: Leer el indicador ────────────────────────────────
   double ma[2];
   ArraySetAsSeries(ma, true);
   int copied = CopyBuffer(g_maHandle, 0, 0, 2, ma);

   if(copied < 2) {
      Print("ERROR: CopyBuffer devolvió solo ", copied, " valores");
      return;
   }

   // ── PASO 4: Leer precio actual ───────────────────────────────
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // ── PASO 5: Evaluar señal ────────────────────────────────────
   bool precioSobreMA = (bid > ma[0]);
   bool precioSobreMAAnterior = (bid > ma[1]);

   Print("Nueva vela | MA: ", DoubleToString(ma[0],_Digits),
         " | Precio ", (precioSobreMA ? "SOBRE" : "BAJO"), " la MA");
}
Anatomía interna de OnTick() — tiempo relativo por tarea
CopyBuffer es la operación más costosa · la lógica de señal suele ser instantánea
MQL5 Profiler · EA con MA + RSI

04 OnDeinit() — El cierre limpio

OnDeinit() es el espejo de OnInit(): se ejecuta una sola vez al final de la vida del EA. Su propósito es liberar todos los recursos que creaste en la inicialización. El parámetro reason te dice exactamente por qué se está cerrando el EA, lo que permite tomar decisiones distintas según el motivo.

Códigos de reason en OnDeinit() — frecuencia en uso real
Los más comunes en operativa real · REASON_REMOVE = usuario quitó el EA · REASON_CHARTCHANGE = cambio de símbolo
MQL5 Reference · ENUM_DEINIT_REASON
EJERCICIO PRÁCTICO OnDeinit() — Liberar handles y loggear el motivo de cierre

Adjunta el EA, luego quítalo del gráfico (clic derecho → Eliminar). Observa el Diario de Expertos: verás el mensaje con el motivo de cierre. Después prueba cambiar la temporalidad — el reason será diferente.

MQL5 Ejercicio 3 — OnDeinit() con logging del reason
void OnDeinit(const int reason) {
   // 1. Liberar TODOS los handles creados en OnInit
   if(g_maHandle != INVALID_HANDLE) {
      IndicatorRelease(g_maHandle);
      Print("Handle MA liberado correctamente");
   }

   // 2. Identificar y loggear el motivo de cierre
   string reasonText = "";
   switch(reason) {
      case REASON_REMOVE:      reasonText = "EA eliminado del gráfico";   break;
      case REASON_CHARTCLOSE:  reasonText = "Gráfico cerrado";             break;
      case REASON_RECOMPILE:   reasonText = "EA recompilado";              break;
      case REASON_CHARTCHANGE: reasonText = "Cambio de símbolo/temporalidad"; break;
      case REASON_PARAMETERS:  reasonText = "Parámetros modificados";       break;
      case REASON_ACCOUNT:     reasonText = "Cambio de cuenta";             break;
      case REASON_CLOSE:       reasonText = "Terminal cerrado";             break;
      default:                 reasonText = "Otro motivo: " + (string)reason;
   }

   Print("EA detenido. Motivo: ", reasonText);

   // 3. Limpiar objetos gráficos si los hubiera
   ObjectsDeleteAll(0, "EA_"); // Borra todos los objetos con prefijo "EA_"
}

05 OnTester() — El criterio personalizado de optimización

OnTester() es la función más ignorada por principiantes y la más poderosa para traders avanzados. Solo se ejecuta al final de cada run de backtest en el Strategy Tester. Retorna un double — ese número se convierte en el criterio de optimización cuando seleccionas "Custom criterion". Puedes usarlo para crear métricas propias: profit factor ajustado, Sharpe con penalización por drawdown, o cualquier fórmula que represente mejor tu definición de "buena estrategia".

Criterios de optimización en Strategy Tester — cuándo usar OnTester()
Los criterios built-in cubren la mayoría de casos · OnTester() cuando ninguno se ajusta a tu estrategia
MT5 Strategy Tester · MQL5 Reference
¿Cuándo lo necesitas? Cuando los criterios estándar de MT5 (Balance, Profit Factor, Sharpe, Recovery Factor) no capturan lo que hace "buena" a tu estrategia. Por ejemplo: una estrategia de scalping donde quieres maximizar trades ganadores pero penalizar fuertemente si el drawdown supera el 10%.
EJERCICIO PRÁCTICO OnTester() — Criterio personalizado: Profit Factor × (1 - Drawdown)

Este criterio premia estrategias con buen profit factor pero penaliza el drawdown. En el Strategy Tester, selecciona "Custom criterion" en el campo de optimización. El valor más alto ganará.

MQL5 Ejercicio 4 — OnTester() con criterio compuesto
double OnTester() {
   // Obtener métricas del backtest recién completado
   double profitFactor  = TesterStatistics(STAT_PROFIT_FACTOR);
   double maxDrawdown   = TesterStatistics(STAT_EQUITYDD_PERCENT) / 100.0;
   double totalTrades   = TesterStatistics(STAT_TRADES);
   double winRate        = TesterStatistics(STAT_PROFIT_TRADES) / MathMax(totalTrades, 1);

   // Filtros mínimos — descartar configuraciones inviables
   if(totalTrades < 30)      return(0); // Muy pocos trades, no significativo
   if(profitFactor < 1.1)    return(0); // Profit factor mínimo aceptable
   if(maxDrawdown > 0.20)    return(0); // Drawdown máximo del 20%

   // Criterio compuesto: PF × Win Rate × (1 - Drawdown)
   // Maximiza rentabilidad y precisión, penaliza el riesgo
   double score = profitFactor * winRate * (1.0 - maxDrawdown);

   return(score); // Valor más alto = mejor configuración
}

06 Las 4 funciones juntas — EA completo de referencia

Aquí está el esqueleto que puedes usar como punto de partida para cualquier EA. Incluye las cuatro funciones con el patrón correcto, manejo de errores, y comentarios que explican cada decisión. En el siguiente episodio reemplazaremos los comentarios de "aquí va la lógica" con el modelo de órdenes de MT5.

Las 4 funciones de evento — responsabilidades y peso en el código
Líneas de código típicas en un EA profesional de 300 líneas
Análisis MQL5 Code Base · TN
MQL5 EA completo — esqueleto de referencia con las 4 funciones
#property copyright "TradingNote"
#property version   "1.00"
#property strict

// ════════════════════════════════════════════════════════════════
// PARÁMETROS DE ENTRADA
// ════════════════════════════════════════════════════════════════
input int    InpMAPeriod = 20;    // Período Media Móvil
input int    InpRSIPeriod= 14;    // Período RSI
input double InpLots     = 0.10;  // Tamaño del lote
input int    InpSLPips   = 30;    // Stop Loss en pips
input int    InpTPPips   = 60;    // Take Profit en pips

// ════════════════════════════════════════════════════════════════
// VARIABLES GLOBALES
// ════════════════════════════════════════════════════════════════
int      g_maHandle;
int      g_rsiHandle;
datetime g_lastBarTime = 0;

// ════════════════════════════════════════════════════════════════
// OnInit — UNA VEZ al cargar
// ════════════════════════════════════════════════════════════════
int OnInit() {
   if(InpMAPeriod < 2 || InpRSIPeriod < 2) return(INIT_PARAMETERS_INCORRECT);

   g_maHandle  = iMA(_Symbol, _Period, InpMAPeriod,  0, MODE_SMA, PRICE_CLOSE);
   g_rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);

   if(g_maHandle==INVALID_HANDLE || g_rsiHandle==INVALID_HANDLE) {
      Print("ERROR creando handles: ", GetLastError());
      return(INIT_FAILED);
   }
   Print("EA iniciado | ", _Symbol, " | MA:", InpMAPeriod, " RSI:", InpRSIPeriod);
   return(INIT_SUCCEEDED);
}

// ════════════════════════════════════════════════════════════════
// OnTick — CADA TICK del mercado
// ════════════════════════════════════════════════════════════════
void OnTick() {
   // Filtro de nueva vela
   datetime barTime = (datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);
   if(barTime == g_lastBarTime) return;
   g_lastBarTime = barTime;

   // Leer indicadores
   double ma[2], rsi[2];
   ArraySetAsSeries(ma,true); ArraySetAsSeries(rsi,true);
   if(CopyBuffer(g_maHandle, 0,0,2,ma)  < 2) return;
   if(CopyBuffer(g_rsiHandle,0,0,2,rsi) < 2) return;

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

   // ── Aquí va tu lógica de señales ──
   // Episodio 3: aprenderás a enviar órdenes con CTrade
}

// ════════════════════════════════════════════════════════════════
// OnDeinit — UNA VEZ al remover
// ════════════════════════════════════════════════════════════════
void OnDeinit(const int reason) {
   IndicatorRelease(g_maHandle);
   IndicatorRelease(g_rsiHandle);
   Print("EA detenido. Reason: ", EnumToString((ENUM_DEINIT_REASON)reason));
}

// ════════════════════════════════════════════════════════════════
// OnTester — AL FINALIZAR cada backtest (optimización)
// ════════════════════════════════════════════════════════════════
double OnTester() {
   double pf  = TesterStatistics(STAT_PROFIT_FACTOR);
   double dd  = TesterStatistics(STAT_EQUITYDD_PERCENT) / 100.0;
   double tr  = TesterStatistics(STAT_TRADES);
   if(tr < 30 || pf < 1.1) return(0);
   return(pf * (1.0 - dd)); // Score compuesto
}
Siguiente episodio (Ep. 3): El esqueleto está listo. Ahora viene lo que más confunde en MT5: el modelo de 3 capas — Órdenes → Deals → Posiciones y cómo la clase CTrade hace que enviar y gestionar órdenes sea limpio y seguro.

◆ Conclusión


¿Listo para mejorar tu trading con datos?
Registra tus operaciones, analiza tu rendimiento y toma decisiones basadas en métricas reales con TradingNote.

Comienza gratis en TradingNote

Comentarios