Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 28): Herramienta de ruptura del rango de apertura
Contenido
- Introducción
- Rango de apertura
- Resumen de la estrategia
- Desglose de los componentes del EA
- Backtesting y resultados
- Conclusión
Introducción
El 6 de mayo de 2010, los mercados bursátiles de EE. UU. sufrieron el famoso «Flash Crash», un colapso vertiginoso en el que una orden de venta masiva, impulsada por algoritmos, sobre los futuros del E-Mini S&P 500 desencadenó liquidaciones frenéticas de alta frecuencia y llevó al Dow a desplomarse casi 1.000 puntos en cuestión de minutos, antes de recuperarse con la misma rapidez. Este episodio dramático puso de manifiesto una vulnerabilidad crítica: reaccionar ante una ruptura de precios no confirmada, por muy evidente que sea, puede atrapar a los operadores cuando la liquidez desaparece y el mercado se da la vuelta con fuerza.
Una ruptura se produce cuando el precio supera de forma decisiva un límite claramente definido, como el máximo o el mínimo del intervalo de apertura de la sesión, lo que indica que la oferta o la demanda han superado al lado opuesto. Es fundamental saber distinguir las rupturas auténticas, ya que estas suelen marcar el inicio de tendencias sostenidas, mientras que las rupturas falsas, si se actúa sobre ellas de forma prematura, provocan pérdidas por oscilaciones bruscas.
La estrategia «Opening Range Breakout» (ORB) aborda esta cuestión determinando en primer lugar los límites superior e inferior de ese intervalo de apertura (configurable para cualquier duración) y resaltando dicho rango en el gráfico mediante un rectángulo. Estos límites sirven tanto como referencias visuales como niveles precisos de activación de la ruptura. Solo cuando el precio rompe el rectángulo, retrocede hacia ese nivel y luego vuelve a romperlo, la estrategia confirma la entrada, filtrando el ruido y aislando los movimientos impulsados por la convicción real del mercado.
En este artículo, descubrirás una implementación profesional de ORB mediante un asesor experto de MQL5, que incluye:
- Captura de rango encapsulada que realiza un seguimiento y muestra los máximos y mínimos de apertura sin sobrecargar el bucle principal,
- Niveles de stop y objetivos ajustados a la volatilidad mediante un módulo ATR reutilizable,
- Lógica de confirmación del retest que distingue las rupturas reales de los picos transitorios,
- Visualización en gráficos con etiquetas dinámicas en el panel de control y objetos gráficos,
- Una máquina de estados clara en OnTick() para coordinar cada paso y restablecerse correctamente al cerrar la sesión.
Rango de apertura
El «rango de apertura» se refiere a los niveles de precio máximo y mínimo alcanzados durante los primeros minutos de una sesión bursátil, normalmente los primeros 15 o 30 minutos. Este periodo refleja la reacción inmediata del mercado ante las noticias de la noche anterior, los datos económicos y la actividad de los inversores institucionales, y suele caracterizarse por un elevado volumen de operaciones y una gran volatilidad. Los traders utilizan los límites del rango como puntos de referencia; las rupturas al alza o a la baja pueden indicar un posible impulso en esa dirección. Estos niveles ayudan a planificar operaciones con puntos de entrada, stop-loss y objetivos definidos, lo que convierte al rango de apertura en una herramienta práctica para la identificación temprana de tendencias y para estrategias intradía estructuradas.
Conocer y operar de forma eficaz con el rango de apertura ofrece varias ventajas en los mercados intradía:
| Ventaja | Descripción |
|---|---|
| Detección temprana de tendencias | Una ruptura del rango inicial de 15-30 minutos indica un posible impulso, ayudando a los operadores a alinearse con los movimientos intradía. |
| Reglas de trading claras | Con límites precisos de máximo y mínimo, obtienes puntos de entrada y niveles de stop objetivos, lo que reduce las conjeturas. |
| Gestión de riesgos integrada | Los stops pueden colocarse justo dentro del límite opuesto, definiendo así de antemano la relación riesgo/beneficio. |
| Eficiencia en el uso del tiempo y coherencia | Las operaciones limitadas al horario de la mañana suponen menos tiempo frente a la pantalla, lo que hace que el sistema sea repetible. |
| Aplicabilidad en distintos mercados | Funciona con acciones, divisas, futuros y distintos marcos temporales, lo que ofrece una mayor versatilidad al trader. |
| Ventaja de volatilidad | Aprovecha la liquidez y los estrechos spreads que se observan en los movimientos de principios de sesión. |
| Flexibilidad estratégica | Admite estrategias de ruptura, reversión y scalping dentro de un marco unificado. |
Utilizamos dos métodos principales para calcular el rango de apertura.
Método 1: Rango de dos velas
Este método se basa en tan solo dos barras. En primer lugar, tomemos la última vela de la sesión anterior; su máximo y su mínimo definen los extremos del cierre de ayer. A continuación, use la vela de apertura de la sesión actual; su máximo y su mínimo capturan el impulso inicial de la sesión. El rango de apertura es simplemente la distancia entre el máximo más alto y el mínimo más bajo de esas dos barras. Este cálculo compacto le proporciona una visión inmediata del impulso inicial y ayuda a identificar posibles niveles de ruptura para el día.

Primero, calcule la fecha y hora exactas en que comienza su sesión de negociación (por ejemplo, las 09:30 hora local del servidor). Puede codificarlo directamente o calcularlo mediante SymbolInfoSessionTrade.
// Example: use SymbolInfoSessionTrade to get today's session start MqlDateTime nowStruct; TimeToStruct(TimeCurrent(), nowStruct); datetime from, to; SymbolInfoSessionTrade(_Symbol, (ENUM_DAY_OF_WEEK)nowStruct.day_of_week, SessionIndex, from, to); // 'from' now holds seconds since midnight; convert to full datetime: datetime todayStart = (TimeCurrent() - TimeCurrent()%86400) + from;
Encuentre el índice de la primera barra M1 en o después de todayStart, luego tome la barra siguiente (índice + 1) como la barra final de ayer.
// Find bar numbers int firstBarToday = iBarShift(_Symbol, PERIOD_M1, todayStart, false); int lastBarYesterday = firstBarToday + 1;Utiliza las funciones de series integradas para obtener los valores máximos y mínimos de cada índice de barras.
double highYesterday = iHigh(_Symbol, PERIOD_M1, lastBarYesterday); double lowYesterday = iLow (_Symbol, PERIOD_M1, lastBarYesterday); double highToday = iHigh(_Symbol, PERIOD_M1, firstBarToday); double lowToday = iLow (_Symbol, PERIOD_M1, firstBarToday);Toma el máximo de los dos máximos y el mínimo de los dos mínimos para formar el rango de apertura y, a continuación, calcula su amplitud.
double openingHigh = MathMax(highYesterday, highToday); double openingLow = MathMin (lowYesterday, lowToday); double openingSize = openingHigh - openingLow; PrintFormat("Opening Range → High: %.5f Low: %.5f Size: %.5f", openingHigh, openingLow, openingSize);
Método 2
En lugar de utilizar velas específicas, este método define el rango de apertura mediante los precios más altos y más bajos alcanzados durante los primeros minutos después de la apertura del mercado. Al hacer un seguimiento de esos primeros extremos, se capta el sentimiento y el impulso iniciales del día en tiempo real. Estos niveles máximos y mínimos de apertura sirven como umbrales de ruptura; una vez que el precio los supera, indica una posible tendencia para el resto de la sesión.

Primero, calcule la marca de tiempo exacta en la que comienza la sesión de negociación actual (por ejemplo, 09:30). Utilizamos SymbolInfoSessionTrade para obtener los segundos «desde» y convertirlos en una fecha y hora completas.
// Calculate the session’s opening datetime MqlDateTime nowStruct; TimeToStruct(TimeCurrent(), nowStruct); datetime fromSec, toSec; SymbolInfoSessionTrade(_Symbol, (ENUM_DAY_OF_WEEK)nowStruct.day_of_week, SessionIndex, fromSec, toSec); // Build a full datetime from today’s midnight plus the session offset datetime sessionStart = (TimeCurrent() - TimeCurrent() % 86400) + fromSec;
A continuación, recorra cada barra de minuto en los primeros RangeMinutes después de sessionStart, registrando el máximo más alto y el mínimo más bajo observados hasta el momento.
double orHigh = -DBL_MAX; double orLow = DBL_MAX; // Find the bar index at or just after sessionStart int startIdx = iBarShift(_Symbol, TF, sessionStart, false); // Loop through the first RangeMinutes bars (M1 timeframe) for(int i = startIdx; i >= startIdx - (RangeMinutes - 1); i--) { if(i < 0) break; // safety check double h = iHigh(_Symbol, TF, i); double l = iLow (_Symbol, TF, i); orHigh = MathMax(orHigh, h); orLow = MathMin(orLow, l); }
Por último, tienes «orHigh» y «orLow». Puedes calcular el tamaño del rango y comparar el precio actual (Bid) con estos niveles para generar señales.
double openingSize = orHigh - orLow; PrintFormat("Opening Range (Method 2): High=%.5f Low=%.5f Size=%.5f", orHigh, orLow, openingSize); // Breakout detection double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(bid > orHigh) Alert("Breakout Long at price " + DoubleToString(bid, _Digits)); else if(bid < orLow) Alert("Breakout Short at price " + DoubleToString(bid, _Digits));
Resumen de la estrategia
Este EA automatiza una estrategia de ruptura del rango de apertura identificando, en primer lugar, los límites máximo y mínimo del periodo de apertura del mercado. Al inicio de cada sesión, registra el precio de apertura y, a continuación, actualiza continuamente el máximo y el mínimo a lo largo de los siguientes RangeMinutes (por ejemplo, 15 minutos). Una vez que se cierra esa ventana, dibuja líneas horizontales y un rectángulo en el gráfico para marcar visualmente la zona de «rango» en la que se produjo la volatilidad inicial. Un filtro ATR (que utiliza un período y un multiplicador ATR configurables) garantiza que las señales de negociación solo se activen cuando la volatilidad haya alcanzado un nivel significativo.
Una vez definido el rango, el EA espera a que se produzca una ruptura en dos pasos: en primer lugar, el precio debe cerrar fuera del rango; a continuación, debe retroceder hasta el límite y volver a cerrar más allá de él (la «retest»). Cuando se produce esa confirmación de la retest, el EA traza una flecha hacia arriba o hacia abajo en la barra de entrada, calcula los niveles de stop-loss y take-profit basándose en el ATR y activa alertas (en pantalla, por correo electrónico y/o mediante notificaciones push) para anunciar la señal. Un panel de control integrado en el gráfico se actualiza continuamente con el estado actual del EA, el valor del ATR y la amplitud del rango, y todo se restablece por completo en la siguiente sesión o a medianoche.
Este EA automatiza una estrategia de ruptura del rango de apertura identificando, en primer lugar, los límites máximo y mínimo del periodo de apertura del mercado. Al inicio de cada sesión, registra el precio de apertura y, a continuación, actualiza continuamente el máximo y el mínimo a lo largo de los siguientes RangeMinutes (por ejemplo, 15 minutos). Una vez que se cierra esa ventana, dibuja líneas horizontales y un rectángulo en el gráfico para marcar visualmente la zona de «rango» en la que se produjo la volatilidad inicial. Un filtro ATR (que utiliza un período y un multiplicador ATR configurables) garantiza que las señales de negociación solo se activen cuando la volatilidad haya alcanzado un nivel significativo.
Una vez definido el rango, el EA espera a que se produzca una ruptura en dos pasos: en primer lugar, el precio debe cerrar fuera del rango; a continuación, debe retroceder hasta el límite y volver a cerrar más allá de él (la «retest»). Cuando se produce esa confirmación de la retest, el EA traza una flecha hacia arriba o hacia abajo en la barra de entrada, calcula los niveles de stop-loss y take-profit basándose en el ATR y activa alertas (en pantalla, por correo electrónico y/o mediante notificaciones push) para anunciar la señal. Un panel de control integrado en el gráfico se actualiza continuamente con el estado actual del EA, el valor del ATR y la amplitud del rango, y todo se restablece por completo en la siguiente sesión o a medianoche.
Desglose de los componentes del EA
1. Encabezado y propiedades del archivo
En la parte superior, indicamos nuestros derechos de autor, el enlace y la versión mediante #property. No se trata solo de una fórmula estándar; la directiva #property strict indica al compilador que aplique las normas modernas de MQL5 (sin conversiones implícitas, prototipos de funciones obligatorios, etc.), lo que ayuda a detectar errores sutiles en tiempo de compilación. Al configurar el enlace #property, cualquier persona que revise el EA en MetaEditor puede hacer clic para acceder a nuestro perfil de la comunidad MQL5, lo que refuerza el control de versiones y la autoría. También puedes usar #property description para añadir anotaciones propias, que aparecerán en el cuadro de diálogo de propiedades del EA, lo que facilita a los lectores documentar su configuración sin necesidad de código adicional para la interfaz de usuario.
//+------------------------------------------------------------------+ //| ORB Breakout EA| //| Copyright 2025, MetaQuotes Ltd.| //| https://www.mql5.com/en/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property description "Opening‑Range Breakout with ATR confirmation" #property strict
2. Parámetros de entrada
Agrupamos los ajustes que el usuario puede modificar bajo comentarios claros: índice de sesión y longitud del rango, ajustes del filtro ATR, códigos y colores de las flechas, y opciones de activación/desactivación de alertas. En MQL5, las variables de entrada aparecen automáticamente en el cuadro de diálogo «Propiedades» del EA, con los tipos adecuados e incluso las enumeraciones (como ENUM_TIMEFRAMES para los marcos temporales) se muestran como menús desplegables, por lo que no es necesario crear manualmente tu propia interfaz gráfica de usuario. Ten en cuenta que, al elegir la variable de entrada `int ArrowUpCode`, permitimos a los usuarios seleccionar cualquier código de carácter de Wingdings; sin embargo, si quisiéramos imponer una lista concreta, podríamos definir nuestra propia enumeración `ArrowSymbol { UP=233, DOWN=234 }` para restringir las opciones en tiempo de compilación. Los campos de entrada también admiten el modo de solo lectura si deseas impedir que se editen sobre la marcha.
//--- session & range input int SessionIndex = 0; // 0 = first session input int RangeMinutes = 15; // minutes to capture range //--- ATR filter input ENUM_TIMEFRAMES TF = PERIOD_M1; // ATR timeframe input int ATRPeriod = 14; // ATR look‑back input double ATRMultiplier = 1.5; // ATR * multiplier //--- arrows & alerts input int ArrowUpCode = 233; // wingdings ↑ input int ArrowDnCode = 234; // wingdings ↓ input color ArrowUpColor = clrLime; // long arrow input color ArrowDnColor = clrRed; // short arrow input bool SendEmailAlert = false; // email on signal input bool PushNotify = false; // push on signal input string EmailSubject = "ORB Signal"; // mail subject
3. Clase RangeCapture
Al encapsular la lógica del rango de apertura en su propia clase CRangeCapture, mantenemos ordenada nuestra función principal OnTick() y resaltamos la estructura orientada a objetos en MQL5. Almacenamos la hora de inicio y los precios máximos y mínimos a lo largo del tiempo, mediante los métodos Init(), Update() e IsDefined(). Recuerda que las clases de MQL5 no se inicializan automáticamente, por lo que es obligatorio llamar explícitamente a la función Init(). Además, al mantener hi y lo como valores de tipo «double», aprovechamos la precisión de coma flotante de 64 bits de MQL5, algo imprescindible para los precios del mercado de divisas. Si lo deseas, podrías ampliar esta clase para registrar los valores máximos y mínimos de cada tick en un búfer interno, lo que ilustraría cómo las clases pueden evolucionar sin alterar la interfaz original.
class CRangeCapture { private: datetime startTime; double hi, lo; public: void Init(datetime t, double price) { startTime = t; hi = lo = price; } void Update(double price, datetime now) { if(now < startTime + RangeMinutes*60) { hi = MathMax(hi, price); lo = MathMin(lo, price); } } bool IsDefined(datetime now) const { return(now >= startTime + RangeMinutes*60); } double High() const { return hi; } double Low() const { return lo; } }; static CRangeCapture g_range;
4. Clase ATRModule
Aquí envolvemos el identificador del indicador ATR integrado (iATR) dentro de la clase CATRModule. En Init() creamos el identificador, comprobando si es INVALID_HANDLE para evitar problemas relacionados con los símbolos o los marcos temporales. El método Value() llama a CopyBuffer solo una vez por tick, lo cual es mucho más eficiente que llamar repetidamente a iATR dentro de OnTick(). Al reutilizar el handle, evitamos fugas de memoria y reducimos la sobrecarga. Cuando llamas a IndicatorRelease() en Release(), liberas ese identificador; si no lo haces, podrías acabar con una lista de indicadores lenta y saturada en la pestaña «Indicadores» de la Terminal. Este patrón se adapta perfectamente si se integran los indicadores adicionales (EMA, Bandas de Bollinger) en sus propios módulos.
class CATRModule { private: int handle; public: bool Init() { handle = iATR(_Symbol, TF, ATRPeriod); return(handle != INVALID_HANDLE); } double Value() const { double buf[]; if(handle != INVALID_HANDLE && CopyBuffer(handle, 0, 0, 1, buf) == 1) return buf[0] * ATRMultiplier; return 0.0; } void Release() { if(handle != INVALID_HANDLE) IndicatorRelease(handle); } }; static CATRModule g_atr;
5. Clase de lógica de retest
La clase CRetestSignal implementa nuestro patrón «ruptura, retest y nueva ruptura» mediante tres variables booleanas sencillas. Mantenemos breakLong, breakShort y retested como variables internas, y solo exponemos los métodos Reset(), OnBreak() y CheckRetest(). Esto oculta los detalles de implementación de OnTick(), de modo que los lectores se centren en el «qué» en lugar del «cómo». Fíjate en cómo pasamos la última barra de MqlRates por referencia, lo que nos ahorra la sobrecarga de la copia. Si alguna vez necesitas trabajar con varios marcos temporales, podrías ampliar la función CheckRetest() para que acepte una matriz de MqlRates o un parámetro de intervalo de tiempo, lo que demostraría la flexibilidad de la sobrecarga de métodos en MQL5.
class CRetestSignal { private: bool breakLong, breakShort, retested; public: void Reset() { breakLong = breakShort = retested = false; } void OnBreak(double close, double h, double l) { breakLong = (close > h); breakShort = (close < l); retested = false; } bool CheckRetest(const MqlRates &r, bool &isLong) { if(breakLong) { if(!retested && r.low <= g_range.High()) { retested = true; isLong = true; return false; } if(retested && r.close > g_range.High()) { isLong = true; return true; } } else if(breakShort) { if(!retested && r.high >= g_range.Low()) { retested = true; isLong = false; return false; } if(retested && r.close < g_range.Low()) { isLong = false; return true; } } return false; } }; static CRetestSignal g_retest;
6. Panel de control en el gráfico
Para ofrecer información en tiempo real, CDashboard crea un sencillo OBJ_LABEL en la esquina superior izquierda. Como ves, las funciones Init(), Update() y Delete() gestionan el ciclo de vida de esta etiqueta; este mismo patrón se aplica a OBJ_TEXT, OBJ_RECTANGLE o cualquier otro tipo de objeto. La etiqueta se actualiza en cada tick, pero como establecer el mismo texto repetidamente no supone un gran esfuerzo, el rendimiento se mantiene alto. Si quisieras optimizar aún más el código, podrías almacenar en caché la última cadena y llamar a ObjectSetString() solo cuando esta cambie realmente, lo que demuestra cómo pequeños ajustes en la gestión de objetos pueden aportar pequeñas mejoras de rendimiento en los EA que consumen muchos ticks.
class CDashboard { private: string name; public: void Init() { name = "ORB_Info"; if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 30); } } void Update(const string &txt) { ObjectSetString(0, name, OBJPROP_TEXT, txt); } void Delete() { if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); } }; static CDashboard g_dash;
7. Interfaz de operaciones y notificaciones
Aunque aquí no enviamos órdenes, incluimos un objeto estático CTrade para mostrar cómo se integrarían las operaciones reales más adelante. Y lo que es más importante, cuando se confirma una señal, dibujamos una flecha (OBJ_ARROW) utilizando los códigos y colores de Wingdings elegidos por el usuario, y a continuación activamos las funciones Alert(), SendNotification() y SendMail(). Esta tríada muestra el sistema integrado de eventos y mensajería de MQL5, sin necesidad de bibliotecas externas. Incluso puedes combinar PlaySound() o EventChartCustom() con los webhooks, lo que permite a los lectores apreciar la amplia variedad de canales de comunicación integrados.
static CTrade trade; // ... inside your retest-confirmation block: string msg = StringFormat("%s Signal @%.5f SL=%.5f TP=%.5f", isLong ? "LONG" : "SHORT", r[0].close, sl, tp); Alert(msg); if(PushNotify) SendNotification(msg); if(SendEmailAlert) SendMail(EmailSubject, msg); // draw arrow on chart: string arrowName = "ORB_Arrow_" + IntegerToString((int)r[0].time); ObjectCreate(0, arrowName, OBJ_ARROW, 0, r[0].time, isLong ? r[0].low - 5 * _Point : r[0].high + 5 * _Point); ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, isLong ? ArrowUpCode : ArrowDnCode); ObjectSetInteger(0, arrowName, OBJPROP_COLOR, isLong ? ArrowUpColor : ArrowDnColor);
8. OnInit() y OnDeinit()
En OnInit() inicializamos nuestro módulo ATR y nuestro panel de control, devolviendo INIT_FAILED si surge algún problema; este patrón garantiza que el EA nunca se ejecute en un estado parcialmente defectuoso. En OnDeinit() liberamos de forma responsable el manejador del indicador ATR y eliminamos todos los objetos de gráficos, evitando así que queden indicadores u objetos huérfanos cuando se recargue o se elimine el EA. Esta práctica es fundamental en el código de producción: combina siempre cada identificador iXXX con IndicatorRelease(), y cada ObjectCreate() con ObjectDelete() en OnDeinit() o con una lógica condicional en tu rutina de reinicio.
int OnInit() { if(!g_atr.Init()) return INIT_FAILED; g_dash.Init(); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { g_atr.Release(); ObjectDelete(0, "ORB_High"); ObjectDelete(0, "ORB_Low"); ObjectDelete(0, "ORB_Range"); g_dash.Delete(); }
9. Máquina de estados OnTick()
El núcleo del EA es la función OnTick(). Utilizamos `static bool inited` y `static int state`, de modo que persisten entre llamadas; así es como MQL5 logra memoria por EA sin variables globales ni almacenamiento externo. En primer lugar, calculamos la hora de inicio de la sesión y la reiniciamos una vez al día, lo que refleja un patrón habitual en la lógica intradía. A continuación, una estructura switch(state) recorre limpiamente nuestras cuatro fases (Capture, Break, Retest y Done). Dado que MQL5 no admite «yields» al estilo de las corrutinas, el uso de una máquina de estados es la forma más habitual de dividir las estrategias de varios pasos en un procesamiento por tick. Además, permite que cada rama sea pequeña y fácil de probar.
void OnTick() { static bool inited = false; static int state = 0; static datetime sessionStart; if(!inited) { // compute sessionStart, init g_range & g_retest, Alert inited = true; state = 0; } // fetch latest MqlRates r[0] and bid/ask switch(state) { case 0: // Capture g_range.Update(bid, TimeCurrent()); if(g_range.IsDefined(TimeCurrent())) state = 1; break; case 1: // Break if(r[0].close > g_range.High() || r[0].close < g_range.Low()) { g_retest.OnBreak(r[0].close, g_range.High(), g_range.Low()); state = 2; } break; case 2: // Retest { bool isLong; if(g_retest.CheckRetest(r[0], isLong)) state = 3; } break; case 3: // Done break; } }
10. Reinicio a medianoche
Por último, comparamos «today = now - now%86400» con una fecha y hora estáticas, «lastDay». En cuanto cambia la fecha, eliminamos los objetos antiguos y restablecemos nuestras banderas, sin necesidad de recurrir a trucos complicados del tipo «if hour==0». Este patrón funciona para cualquier EA basado en rutinas diarias: barras cero, rutinas de pivotes diarios, informes al cierre de la jornada, etc. Animar a los lectores a adoptar esta expresión les ayuda a evitar sutiles errores de «off-by-one» cuando los relojes pasan de la medianoche o cuando se produce el cambio de hora.
// at the end of OnTick(): static datetime lastDay = 0; datetime today = TimeCurrent() - TimeCurrent() % 86400; if(today != lastDay) { lastDay = today; inited = false; state = 0; ObjectDelete(0, "ORB_High"); ObjectDelete(0, "ORB_Low"); ObjectDelete(0, "ORB_Range"); }
Backtesting y resultados
El backtesting es el proceso de aplicar tu estrategia de trading, tal y como está programada en tu EA, a datos históricos del mercado para ver qué resultados habría obtenido en el pasado. Esto te permite:
- Comprueba que tus reglas den resultados lógicos
- Mide indicadores clave como la tasa de ganancias, la caída máxima y el factor de rentabilidad
- Optimiza los parámetros de entrada (por ejemplo, la longitud del rango o el multiplicador del ATR) para encontrar las combinaciones que, según los datos históricos, han dado mejores resultados.
En MetaTrader 5, el Probador de estrategias envía cada tick histórico a la función OnTick(), por lo que:
- OnInit() se encarga de la configuración inicial (identificador ATR, objetos de gráfico, estado inicial)
- OnTick() controla la captura de rangos, la lógica de ruptura, el retest, el trazado de flechas, las alertas y las actualizaciones del panel de control en cada tick simulado.
- OnDeinit() se encarga de limpiar todo tras la prueba
En otras palabras, el Probador utiliza la misma ruta de código que se emplea en un gráfico en tiempo real para realizar el backtesting.

El GIF animado anterior muestra el EA «Opening Range Breakout» ejecutándose en un gráfico M15 del EURUSD durante una prueba retrospectiva. En el panel principal, se puede ver un rectángulo sombreado que marca el rango de apertura, delimitado por dos líneas horizontales que indican el máximo y el mínimo de ese intervalo inicial. Este marco visual indica de inmediato dónde el EA espera que se produzca una acción del precio significativa.
Cada flecha verde del gráfico indica una entrada en posición larga confirmada. La lógica «ruptura → retest → nueva ruptura» del EA garantiza que el precio deba, en primer lugar, superar el límite superior, luego volver a tocarlo y, por último, cerrar de nuevo por encima de él antes de que aparezca una flecha. Esta confirmación en dos pasos elimina las falsas rupturas y solo emite señales cuando existe un impulso real.
Debajo del gráfico, el panel «Diario» registra todos los eventos críticos: inicio de la sesión, definición del rango, detección de ruptura y señal final, junto con los valores exactos de entrada, stop-loss y take-profit. Estas entradas del registro reflejan las transiciones de estado internas del EA, lo que aporta transparencia sobre cómo y cuándo se toman las decisiones.
Este ejemplo muestra cómo la estrategia ORB capta la volatilidad al inicio de la sesión, confirma las rupturas reales y genera señales consistentes y repetibles en el marco temporal M15 del EURUSD.

También he probado este EA en el par AUD/USD utilizando el marco temporal M5 para obtener una visión más clara de la acción del precio intradía. Consulta el gráfico anterior.
Conclusión
El EA «Opening Range Breakout» que hemos desarrollado en MQL5 transforma el sencillo concepto del corredor de precios al inicio de la sesión en una estrategia disciplinada y automatizada. Al modularizar cada componente —la captura de rangos, los stops basados en la volatilidad, la validación del retest y la información visual en el gráfico—, hemos creado un código que es a la vez fácil de mantener y lo suficientemente rápido como para su implementación en producción.
Este enfoque aborda dos retos fundamentales del mercado intradía: distinguir el impulso real de los picos pasajeros y evaluar el riesgo de acuerdo con las condiciones del mercado en tiempo real. La secuencia de confirmación «ruptura → retest → nueva ruptura» descarta las señales falsas, mientras que los niveles de stop-loss y take-profit derivados del ATR se adaptan dinámicamente a la evolución de la volatilidad. De este modo, el EA ofrece señales coherentes y de gran certeza, precisamente el tipo de entradas que resisten los mercados turbulentos y sacan partido de la evolución real de los precios.
Ahora es el momento de darle tu toque personal. Compila el EA en MetaEditor, realiza pruebas retrospectivas con distintos símbolos y sesiones, y prueba diferentes parámetros, como la duración del rango y el multiplicador del ATR. Observa cómo el rectángulo, las líneas y las flechas cobran vida en tu gráfico, y sigue las entradas del Diario para ver cómo se desarrolla cada decisión. Con este marco ORB a tu disposición, estarás preparado para convertir las fluctuaciones de la apertura diaria en operaciones repetibles que te den confianza. Con ello, podrás lograr entradas más precisas y una gestión del riesgo más inteligente.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18486
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Simulación de mercado: Position View (XII)
Redes neuronales en el trading: Modelo multidimensional de extremo a extremo para la previsión de series temporales (Componentes principales)
Del básico al intermedio: Colas, listas y árboles (V)
Introducción a MQL5 (Parte 24): Creación de un asesor experto (EA) que opere a partir de objetos del gráfico
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso