Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 39): Automatización de la detección de BOS y ChoCH en MQL5
Introducción
Le damos la bienvenida a la parte 39 de la serie Desarrollo de un kit de herramientas para el análisis de la acción del precio. En esta entrega, desarrollaremos un sistema práctico en MQL5 que ayudará a los operadores que trabajan con la acción del precio a leer el gráfico con mayor claridad, en lugar de quedarse mirando las velas y pasar por alto los cambios en la estructura. El objetivo es sencillo: convertir los pivotes fractales y las reglas de estructura en señales fiables, sin repintado, en las que usted pueda confiar tanto en el trading en directo como en las pruebas retrospectivas.
Para ello, utilizaremos los pivotes fractales como puntos de referencia locales fiables y detectaremos dos señales complementarias: ChoCH (Change of Character), que indica cuándo el mercado está perdiendo su sesgo previo —por ejemplo, cuando una tendencia alcista no logra alcanzar un máximo más alto o una tendencia bajista no logra alcanzar un mínimo más bajo— y BOS (Break of Structure), que confirma que el sesgo del mercado ha cambiado cuando el precio cierra de forma decisiva por encima de un máximo o mínimo anterior. Piense en ChoCH como una alerta temprana y en BOS como la confirmación.

Combinar fractales con ChoCH/BOS ofrece anclajes más limpios y sin repintado para el análisis, una advertencia más temprana de posibles reversiones y claridad en múltiples marcos temporales, lo que ayuda a filtrar el ruido en los gráficos inferiores. Esas mismas reglas son fáciles de automatizar, registrar y someter a pruebas retrospectivas, lo que las hace ideales para un EA.
El artículo de hoy abarca el diseño del algoritmo y la implementación completa en MQL5: el escaneo de fractales en barras cerradas, el almacenamiento de fractales seguro en memoria, el dibujado seguro de objetos persistentes y la gestión de eventos, con registro y alertas para cada evento BOS/ChoCH confirmado: BOS/ChoCH (de escritorio, móvil y sonora). Al final, contará con un detector listo para producción que podrá compilar, probar y desplegar.
Comenzaremos por la lógica de la estrategia; después pasaremos a la implementación en MQL5, las alertas, las opciones de registro y notificación, las pruebas y los resultados, y, por último, cerraremos con las conclusiones. Consulte el índice que aparece a continuación.
- Introducción
- Lógica de la estrategia
- Implementación en MQL5
- Opciones de alertas, registro y notificaciones
- Pruebas y resultados
- Conclusión
Lógica de la estrategia
En esta sección repasaremos la lógica fundamental de la estrategia en la que se basa el sistema que estamos desarrollando. La técnica se centra en el indicador fractal y en las señales de estructura obtenidas a partir de él. Por si se lo perdió en su momento, en un artículo anterior hablamos de una estrategia de tendencia basada en la ruptura de fractales; los fractales son muy versátiles: además de las rupturas, ofrecen puntos de anclaje fiables que sirven de ayuda en muchos tipos de análisis de la acción del precio. Aquí los utilizamos para detectar cambios de carácter (ChoCH) y rupturas de estructura (BOS).
Un pivote fractal es un punto de giro local que se forma cuando el máximo (o el mínimo) de una barra central es superior (o inferior) al de un número simétrico de barras adyacentes. En la práctica, con una longitud de ventana definida como g_length = 2*p + 1, la barra central se considera un fractal de máximo si su máximo es mayor o igual que todos los máximos de la ventana, y un fractal de mínimo si su mínimo es menor o igual que todos los mínimos de la ventana. Los fractales generan puntos de anclaje constantes y sin repintado, ya que su detección requiere la ventana completa de barras de confirmación.
Un Break of Structure (BOS) es una ruptura decisiva de la estructura reciente del mercado: el precio cierra por encima de un máximo de swing anterior (BOS alcista) o por debajo de un mínimo de swing anterior (BOS bajista). Un BOS indica que el impulso y el sesgo del mercado a corto y medio plazo han cambiado en la dirección de la ruptura; es la confirmación que suelen utilizar los operadores para negociar en la nueva dirección.
Un Change of Character (ChoCH) es una señal temprana y más suave de que el sesgo del mercado está cambiando. Entre los ejemplos típicos se incluyen no marcar un máximo más alto durante una tendencia alcista o no marcar un mínimo más bajo durante una tendencia bajista. El ChoCH debe interpretarse como una señal de alerta: a menudo precede a un BOS si la acción del precio posterior cobra convicción, y le permite prepararse (ajustar los stops, reducir la exposición o buscar entradas de reversión).
Es fundamental que, para evitar el repintado, todas las señales se evalúen usando únicamente barras cerradas. En la práctica, esto significa que comparamos los cierres de barras completadas (por ejemplo, prevClose frente a curClose) con los niveles fractales almacenados; nunca declaramos un BOS o un ChoCH en una barra sin confirmar que aún se está formando. El uso de lógica de barras cerradas garantiza que los eventos sean reproducibles y se puedan someter a pruebas retrospectivas.
El algoritmo es deliberadamente sencillo y determinista: detecta puntos de anclaje fractales fiables en barras cerradas, vigila los cruces en barras cerradas de esos puntos de anclaje, marca y dibuja cada ruptura solo una vez, y emite un único registro o alerta por cada evento confirmado. Este enfoque basado en barras cerradas evita el repintado y genera señales reproducibles y aptas para las pruebas retrospectivas.
- Flujo de alto nivel

- Espere a que aparezca una nueva barra cerrada (use la marca de tiempo de la barra cerrada como disparador).
- Detecte un fractal en el centro de una ventana simétrica de longitud g_length = 2*p + 1.
- Si se encuentra, guarde el fractal (tiempo, precio) y establezca marked = false.
- En cada nueva barra cerrada, compare prevClose y curClose con cada precio fractal almacenado para detectar cruces.
- Cuando se produzca un cruce (ruptura en barra cerrada), marque dicho fractal, trace una línea horizontal o de tendencia y una etiqueta anclada, y emita una única entrada de registro/alerta.
- Elimine de forma periódica los fractales antiguos para que los arrays mantengan un tamaño acotado.
Pseudocódigo principal (conciso):
if new_closed_bar(): ScanForFractals() // detect & append new fractal anchors PruneFractals(maxKeepBars) // remove very old anchors for each fractal in stored_fractals: if not fractal.marked and crossed(prevClose, curClose, fractal.price): DrawBreak(fractal) LabelAndLog(fractal) fractal.marked = true
Implementación en MQL5
Encabezado y metadatos
Al principio del archivo, unos comentarios concisos ofrecen metadatos esenciales, como el nombre del archivo, el autor, los derechos de autor y los enlaces de referencia. Estos datos resultan útiles para el control de versiones y como referencia futura a la hora de compartir o volver a consultar el archivo. Las directivas #property determinan el comportamiento de la compilación; en particular, #property strict aplica comprobaciones más estrictas de tipos y de la API, lo que ayuda a detectar errores sutiles en las primeras fases del desarrollo. La directiva #include <stdlib.mqh> importa una biblioteca auxiliar estándar que simplifica las tareas habituales de programación y mantiene el código principal más limpio y fácil de mantener. Si los lectores no disponen de esta biblioteca, deberán incorporarla o eliminar la directiva include para evitar errores de compilación.
//+------------------------------------------------------------------+ //| Fractal Reaction System.mq5| //| Copyright 2025, Christian Benjamin.| //| https://www.mql5.com/en/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Christian Benjamin." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict #include <stdlib.mqh>
Entradas de usuario
El bloque de entrada expone los parámetros de configuración que controlan el comportamiento del EA sin necesidad de recompilarlo. AutoDetectLength permite seleccionar automáticamente una longitud de fractal adecuada en función del marco temporal del gráfico, mientras que LengthInput permite especificarla manualmente. Tenga en cuenta que la detección de fractales requiere un tamaño de ventana impar (por ejemplo, 3, 5 o 7) para garantizar que haya una única barra central con barras vecinas simétricas. Las opciones de visualización, como ShowBull y ShowBear, y los ajustes de color (BullColor, BearColor) mejoran la claridad visual y agilizan la interpretación en múltiples gráficos.
Los parámetros HorizontalRightBars y HorizontalLeftExtend determinan cómo se extienden visualmente las líneas de tendencia hacia la derecha o hacia la izquierda, lo cual facilita la interpretación de la relevancia de los niveles. DebugMode habilita el registro de diagnóstico para el desarrollo y las pruebas, y MaxFractalHistoryBars limita el número de fractales históricos que se conservan, evitando así un crecimiento ilimitado de la memoria durante un funcionamiento prolongado.
// User-configurable inputs input bool AutoDetectLength = false; input int LengthInput = 5; input bool ShowBull = true; input color BullColor = clrLime; input bool ShowBear = true; input color BearColor = clrRed; input int HorizontalRightBars = 0; input int HorizontalLeftExtend = 3; input bool DebugMode = false; input int MaxFractalHistoryBars = 2000;
Variables globales y estructuras de datos
Las variables globales gestionan el estado en tiempo de ejecución y el almacenamiento persistente. g_chart_id almacena el identificador del gráfico, de modo que todos los objetos gráficos se asocien explícitamente al gráfico correcto. g_length y p_half representan la longitud de la ventana fractal y la mitad de su tamaño, respectivamente; estos valores se calculan una sola vez durante la inicialización y se reutilizan. ea_digits, ea_point y ea_point_pips normalizan la precisión de los precios entre diferentes brókeres y símbolos, lo que garantiza desplazamientos y colocación de etiquetas coherentes. El sistema almacena los fractales alcistas y bajistas en arrays paralelos (*_time[], *_price[], *_marked[]), en las que las marcas de tiempo y los precios identifican cada fractal, y el indicador «marked» evita el procesamiento duplicado. Por último, os_state encapsula el sesgo del mercado del sistema: 0 para el sesgo neutral, 1 para el alcista y -1 para el bajista.
// Internal globals long g_chart_id; int g_length; int p_half; int ea_digits; double ea_point, ea_point_pips; datetime bull_time[]; double bull_price[]; bool bull_marked[]; datetime bear_time[]; double bear_price[]; bool bear_marked[]; int os_state = 0; // 0: none, 1: bullish, -1: bearish
Initialization (OnInit)
La función OnInit() lleva a cabo la inicialización y validación de los parámetros de entrada. La rutina obtiene el ID del gráfico, determina la longitud del fractal (ya sea detectada automáticamente o introducida por el usuario), aplica un valor mínimo y se asegura de que la longitud sea impar. Esta calcula p_half y lee la precisión del símbolo para determinar los tamaños de punto y de pip, para trazar con precisión. Los arrays fractales se restablecen a un estado vacío. Cuando DebugMode está activado, la función muestra un mensaje de inicialización que contiene valores clave, lo que ayuda a confirmar que la configuración es correcta antes de continuar con la ejecución.
int OnInit() { g_chart_id = ChartID(); // Determine fractal length if(AutoDetectLength) { if(_Period <= PERIOD_H1) g_length = 5; else if(_Period <= PERIOD_H4) g_length = 7; else if(_Period <= PERIOD_D1) g_length = 9; else g_length = 11; } else { g_length = LengthInput; } // Ensure odd length >=3 if(g_length < 3) g_length = 5; if((g_length % 2) == 0) g_length++; p_half = g_length / 2; // Get symbol info ea_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); ea_point = Point(); ea_point_pips = ea_point; if(ea_digits == 3 || ea_digits == 5) ea_point_pips = Point() * 10.0; // Clear fractal arrays ArrayResize(bull_time,0); ArrayResize(bull_price,0); ArrayResize(bull_marked,0); ArrayResize(bear_time,0); ArrayResize(bear_price,0); ArrayResize(bear_marked,0); if(DebugMode) PrintFormat("EA INIT: AutoDetect=%s LengthInput=%d g_length=%d p_half=%d chart=%d", AutoDetectLength ? "true" : "false", LengthInput, g_length, p_half, g_chart_id); return(INIT_SUCCEEDED); }
Cleanup (OnDeinit)
La función OnDeinit() se encarga de la limpieza cuando el EA se elimina del gráfico. Invoca CleanupObjectsByPrefix() utilizando un prefijo coherente para eliminar todos los objetos gráficos creados por el EA (como líneas de tendencia y etiquetas). Esta práctica evita que los objetos huérfanos saturen el gráfico e interfieran en análisis o herramientas posteriores, lo cual es un aspecto importante a tener en cuenta en implementaciones profesionales y demostraciones.
void OnDeinit(const int reason) { CleanupObjectsByPrefix("CHB_"); }
Main loop (OnTick)
OnTick() implementa un modelo de «ejecución una vez por barra cerrada» para garantizar un comportamiento determinista. La función comprueba la marca de tiempo de la barra cerrada más reciente y no realiza ninguna otra operación si la barra no ha cambiado desde la última llamada. Cuando se detecta una nueva barra cerrada, OnTick() activa tres funciones principales: ScanForFractals(), para detectar fractales recién validados; PruneFractals(), para eliminar las entradas que superen el límite de historial configurado; y ProcessFractalCrosses(), para evaluar si el precio ha cruzado alguno de los niveles de fractales almacenados. Esta secuenciación preserva la eficiencia y garantiza que toda la detección y el procesamiento se realicen en barras cerradas, lo cual es importante para realizar pruebas retrospectivas reproducibles y lograr un comportamiento fiable en tiempo real.
void OnTick() { static datetime last_checked = 0; datetime t = iTime(_Symbol, _Period, 1); if(t == last_checked) return; last_checked = t; ScanForFractals(); PruneFractals(MaxFractalHistoryBars); ProcessFractalCrosses(); }
Fractal detection (ScanForFractals)
La función ScanForFractals() comprueba si hay suficientes datos históricos y, a continuación, analiza la barra central en el desplazamiento p_half, que representa el centro de la ventana fractal. Invoca las funciones IsFractalHighAtShift() e IsFractalLowAtShift() para determinar si la barra central cumple los requisitos para considerarse un fractal de máximo o de mínimo. Estas funciones auxiliares realizan comparaciones estrictas entre la barra central y las barras adyacentes. Cuando se identifica un fractal válido que aún no se ha registrado (prevención de duplicados mediante marca temporal), la rutina añade su marca temporal y su precio a los arrays correspondientes y lo marca como sin procesar para que pueda participar posteriormente en la detección de cruces.
void ScanForFractals() { int bars = iBars(_Symbol, _Period); if(bars <= g_length) return; int centerShift = p_half; if(centerShift >= bars) return; // High fractal detection if(IsFractalHighAtShift(centerShift)) { datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift); double p_fr = iHigh(_Symbol, _Period, centerShift); // Store if new bool exists = false; for(int i=0;i<ArraySize(bull_time);i++) if(bull_time[i]==t_fr) exists = true; if(!exists) { int n = ArraySize(bull_time); ArrayResize(bull_time, n+1); ArrayResize(bull_price, n+1); ArrayResize(bull_marked, n+1); bull_time[n] = t_fr; bull_price[n] = p_fr; bull_marked[n] = false; if(DebugMode) PrintFormat("FRAC_BULL DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr); } } // Low fractal detection if(IsFractalLowAtShift(centerShift)) { datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift); double p_fr = iLow(_Symbol, _Period, centerShift); // Store if new bool exists = false; for(int i=0;i<ArraySize(bear_time);i++) if(bear_time[i]==t_fr) exists = true; if(!exists) { int n = ArraySize(bear_time); ArrayResize(bear_time, n+1); ArrayResize(bear_price, n+1); ArrayResize(bear_marked, n+1); bear_time[n] = t_fr; bear_price[n] = p_fr; bear_marked[n] = false; if(DebugMode) PrintFormat("FRAC_BEAR DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr); } } }
Funciones auxiliares de validación de fractales
IsFractalHighAtShift() y IsFractalLowAtShift() aplican la definición de fractal iterando por la ventana simétrica alrededor de la barra central. Retornan «false» si alguna barra vecina invalida el predominio de la barra central o si no está disponible la ventana completa. Estas comprobaciones estrictas evitan declaraciones prematuras o espurias de fractales y protegen contra errores de índice al inicio de los datos históricos o después de cambios de marco temporal. En el caso de conjuntos de datos más grandes, considere copiar en bloque las series históricas con CopyHigh/CopyLow para reducir las llamadas repetidas a la API por barra y mejorar el rendimiento.
bool IsFractalHighAtShift(int shift) { int bars = iBars(_Symbol,_Period); int p = p_half; if(shift < 0 || shift >= bars) return false; double center = iHigh(_Symbol,_Period,shift); for(int k=-p; k<=p; k++) { if(k == 0) continue; int s = shift + k; if(s < 0 || s >= bars) return false; // incomplete window if(iHigh(_Symbol,_Period,s) > center) return false; } return true; }
Función auxiliar: comprobar fractal de mínimo
bool IsFractalLowAtShift(int shift) { int bars = iBars(_Symbol,_Period); int p = p_half; if(shift < 0 || shift >= bars) return false; double center = iLow(_Symbol,_Period,shift); for(int k=-p; k<=p; k++) { if(k == 0) continue; int s = shift + k; if(s < 0 || s >= bars) return false; if(iLow(_Symbol,_Period,s) < center) return false; } return true; }
Processing crosses (ProcessFractalCrosses)
La función ProcessFractalCrosses() convierte los niveles fractales almacenados en señales utilizables comprobando si el cierre confirmado más reciente ha cruzado algún precio fractal. La función utiliza un enfoque conservador basado en barras cerradas: compara prevClose (la barra cerrada anterior) y curClose (la barra cerrada más reciente) y aplica CrossedOver() o CrossedUnder() para determinar los cruces. Cuando se detecta un cruce en un fractal sin procesar, el EA le asigna una etiqueta de objeto única, clasifica el evento como ruptura de estructura (BOS) o cambio de carácter (ChoCH) según os_state, opcionalmente dibuja una línea de ruptura y una etiqueta, actualiza os_state, marca el fractal como procesado y emite alertas. Dicho procesamiento una sola vez por fractal hace que el comportamiento sea determinista y muy adecuado para las pruebas retrospectivas.
void ProcessFractalCrosses() { double prevClose = iClose(_Symbol, _Period, 2); double curClose = iClose(_Symbol, _Period, 1); datetime curTime = (datetime)iTime(_Symbol, _Period, 1); // Process bullish fractals for(int i=0; i<ArraySize(bull_time); i++) { if(bull_marked[i]) continue; double level = bull_price[i]; if(CrossedOver(prevClose, curClose, level)) { datetime fr_time = bull_time[i]; string tag = "CHB_BULL_" + IntegerToString((int)fr_time); bool isChoCH = (os_state == -1); string niceName = isChoCH ? "Bull ChoCH" : "Bull BOS"; if(ShowBull) { DrawBreak(tag, fr_time, level, curTime, true); CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level + 3*ea_point, BullColor); } os_state = 1; bull_marked[i] = true; string msg = StringFormat("%s detected: %s %s at %s price=%s", niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits)); EmitLogAlert(msg); } } // Process bearish fractals for(int i=0; i<ArraySize(bear_time); i++) { if(bear_marked[i]) continue; double level = bear_price[i]; if(CrossedUnder(prevClose, curClose, level)) { datetime fr_time = bear_time[i]; string tag = "CHB_BEAR_" + IntegerToString((int)fr_time); bool isChoCH = (os_state == 1); string niceName = isChoCH ? "Bear ChoCH" : "Bear BOS"; if(ShowBear) { DrawBreak(tag, fr_time, level, curTime, false); CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level - 3*ea_point, BearColor); } os_state = -1; bear_marked[i] = true; string msg = StringFormat("%s detected: %s %s at %s price=%s", niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits)); EmitLogAlert(msg); } } }
Alerting (EmitLogAlert)
La función EmitLogAlert() centraliza la lógica de notificación. Siempre escribe el mensaje en el registro de Experts para su auditoría y revisión. Opcionalmente, muestra una alerta emergente, envía una notificación push a un cliente móvil de MetaTrader configurado y reproduce un archivo de sonido. Este sistema de alertas multicanal garantiza que los operadores reciban notificaciones puntuales, independientemente de su contexto actual. Si se requieren notificaciones push, asegúrese de que el ID de MetaQuotes esté configurado en el terminal.
void EmitLogAlert(const string msg) { Print(msg); if(EnableAlerts) Alert(msg); if(EnableNotifications) SendNotification(msg); if(EnableSound && StringLen(AlertSoundFile) > 0) PlaySound(AlertSoundFile); }
Funciones de visualización
Las funciones DrawBreak(), CreateTrendLine() y CreateAnchoredLabel() gestionan los elementos gráficos del gráfico. DrawBreak() calcula los índices de las barras para los tiempos del fractal y de la ruptura mediante iBarShift(), extiende el lado más antiguo según HorizontalLeftExtend y, opcionalmente, desplaza el borde más reciente hacia el presente según HorizontalRightBars. La función CreateTrendLine() crea un objeto de tendencia anclado entre dos momentos y le aplica un estilo visual. La función CreateAnchoredLabel() coloca un texto descriptivo en una marca temporal y un precio, desplazado unos puntos para facilitar la lectura. Cada rutina de creación utiliza SafeDelete() para eliminar los objetos conflictivos con el mismo nombre antes de crear otros nuevos; esto garantiza que el gráfico se mantenga ordenado y coherente.
void DrawBreak(const string tag, datetime fract_time, double fract_price, datetime break_time, bool bullish) { int barFr = iBarShift(_Symbol, _Period, fract_time, false); int barBreak = iBarShift(_Symbol, _Period, break_time, false); int bars = iBars(_Symbol, _Period); if(barFr == -1 || barBreak == -1) return; int older_shift = MathMax(barFr, barBreak); int newer_shift = MathMin(barFr, barBreak); // Extend left older_shift = MathMin(older_shift + HorizontalLeftExtend, bars - 1); // Extend right towards current bar if(HorizontalRightBars > 0) newer_shift = MathMax(newer_shift - HorizontalRightBars, 0); // Swap if necessary if(older_shift < newer_shift) { int tmp = older_shift; older_shift = newer_shift; newer_shift = tmp; } datetime tLeft = (datetime)iTime(_Symbol, _Period, older_shift); datetime tRight = (datetime)iTime(_Symbol, _Period, newer_shift); string lineName = tag + "_line"; CreateTrendLine(lineName, tLeft, fract_price, tRight, (bullish ? BullColor : BearColor), false); } void CreateAnchoredLabel(const string name, const string txt, datetime when, double price, color col) { SafeDelete(name); if(ObjectCreate(g_chart_id, name, OBJ_TEXT, 0, when, price)) { ObjectSetString(g_chart_id, name, OBJPROP_TEXT, txt); ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col); ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false); ObjectMove(g_chart_id, name, 0, when, price); } } void CreateTrendLine(const string name, datetime tLeft, double price, datetime tRight, color col, bool dashed=false) { SafeDelete(name); if(ObjectCreate(g_chart_id, name, OBJ_TREND, 0, tLeft, price, tRight, price)) { ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col); ObjectSetInteger(g_chart_id, name, OBJPROP_WIDTH, 2); ObjectSetInteger(g_chart_id, name, OBJPROP_STYLE, dashed ? STYLE_DASH : STYLE_SOLID); ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false); ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false); } } void SafeDelete(const string name) { if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name); }
Funciones auxiliares de utilidad
Las funciones CrossedOver() y CrossedUnder() encapsulan las condiciones de cruce en barras cerradas (prevClose <= level && curClose > level, y la inversa).
bool CrossedOver(double prevClose, double curClose, double level) { return (prevClose <= level && curClose > level); } bool CrossedUnder(double prevClose, double curClose, double level) { return (prevClose >= level && curClose < level); }
La función TimeframeToString() convierte las constantes de marco temporal en cadenas legibles por humanos para que los mensajes del registro sean más claros.
string TimeframeToString(int period) { switch(period) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return IntegerToString(period); } }
La función CleanupObjectsByPrefix() elimina todos los objetos que comparten un prefijo; recorre la lista de objetos en sentido inverso para evitar errores de desplazamiento de índices durante la eliminación.
void CleanupObjectsByPrefix(const string prefix) { long total = ObjectsTotal(g_chart_id); for(int i=total-1; i>=0; i--) { string name = ObjectName(g_chart_id, i); if(StringLen(name) >= StringLen(prefix) && StringSubstr(name, 0, StringLen(prefix)) == prefix) ObjectDelete(g_chart_id, name); } }
Poda (PruneFractals)
La función PruneFractals() mantiene el rendimiento y la estabilidad de la memoria eliminando los fractales almacenados que son más antiguos que el umbral MaxFractalHistoryBars. La rutina compacta los arrays in situ utilizando un puntero de escritura y, a continuación, cambia su tamaño, evitando así asignaciones innecesarias. Tenga en cuenta que iBarShift() se invoca por cada fractal almacenado durante la poda, por lo que unos límites de almacenamiento muy elevados pueden aumentar el tiempo de procesamiento. Elegir un valor por defecto razonable (como 2000 barras) permite encontrar un equilibrio entre la cobertura histórica y la eficiencia en tiempo de ejecución.
void PruneFractals(int keepBars) { if(keepBars <=0) return; // Prune bullish fractals int nB = ArraySize(bull_time); if(nB > 0) { int write = 0; for(int i=0; i<nB; i++) { int sh = iBarShift(_Symbol, _Period, bull_time[i], false); if(sh != -1 && sh <= keepBars) { bull_time[write] = bull_time[i]; bull_price[write] = bull_price[i]; bull_marked[write] = bull_marked[i]; write++; } } if(write != nB) { ArrayResize(bull_time, write); ArrayResize(bull_price, write); ArrayResize(bull_marked, write); } } // Prune bearish fractals int nS = ArraySize(bear_time); if(nS > 0) { int write = 0; for(int i=0; i<nS; i++) { int sh = iBarShift(_Symbol, _Period, bear_time[i], false); if(sh != -1 && sh <= keepBars) { bear_time[write] = bear_time[i]; bear_price[write] = bear_price[i]; bear_marked[write] = bear_marked[i]; write++; } } if(write != nS) { ArrayResize(bear_time, write); ArrayResize(bear_price, write); ArrayResize(bear_marked, write); } } }
Este EA es un sistema modular y de nivel profesional que identifica fractales validados en barras cerradas, los almacena de forma eficiente, detecta cruces confirmados para generar señales BOS/ChoCH, muestra anotaciones claras en los gráficos y emite alertas multicanal. Cada módulo está diseñado para garantizar la claridad, la reproducibilidad y la facilidad de mantenimiento. En un entorno de producción o de formación, recomiende a los lectores que realicen pruebas en un entorno de demostración y que activen DebugMode durante los pasos de validación.
Opciones de alertas, registro y notificaciones
El EA ofrece tres canales de salida configurables para que no se pierda ningún evento: ventanas emergentes en el escritorio, notificaciones push en el móvil y sonido. Úselos juntos para obtener la máxima cobertura o por separado cuando desee un funcionamiento más silencioso.
Configuración:
- EnableAlerts — ventanas emergentes en el escritorio mediante Alert() (inmediatas, visibles mientras el terminal está en funcionamiento). Útil para la supervisión activa.
- EnableNotifications — notificaciones push en el móvil mediante SendNotification() (requiere introducir su ID de MetaQuotes en Herramientas - Opciones - Notificaciones y activar las notificaciones en la aplicación móvil MetaTrader).
- EnableSound — reproduce un sonido local del terminal mediante PlaySound(nombre_archivo); el archivo debe estar en la carpeta «Sounds» del terminal y el volumen de sonido del terminal no debe estar silenciado.
Formato recomendado para los mensajes de alerta (claro y fácil de analizar):
Bull BOS detected: EURUSD H1 at 2025.08.01 14:00 price=1.12345 fr_time=2025.08.01 12:00
Incluya el símbolo, el marco temporal, el tipo de evento, la hora de confirmación, el precio y, opcionalmente, la marca temporal del fractal para facilitar la conciliación.
Para activar las notificaciones, configure su ID de MetaQuotes en las opciones del terminal y asegúrese de que las notificaciones estén activadas en su dispositivo móvil.

Para las alertas sonoras, copie sus archivos WAV o MP3 en la carpeta «Sounds» del terminal y compruebe que la función PlaySound funciona correctamente a través de Opciones - Eventos. Para evitar el spam, utilice los arrays *_marked[] con el fin de evitar alertas duplicadas para el mismo fractal y plantéese implementar un breve periodo de espera (por ejemplo, de 30 a 120 segundos) por símbolo en condiciones de volatilidad del mercado. Además, con fines de auditoría y resolución de problemas, puede registrar los eventos añadiéndolos a un archivo CSV —utilizando FILE_COMMON o colocando el archivo en el directorio MQL5/Files del terminal— durante las pruebas, y asegúrese de cerrar el archivo inmediatamente después de escribir en él para evitar bloqueos de archivo.
Pruebas y resultados
En esta sección presento el rendimiento del sistema —tanto las pruebas retrospectivas como los resultados en directo—, que, en mi caso, se ajusta a los objetivos de diseño y se comporta según lo previsto. Mostraré la configuración y las métricas de las pruebas retrospectivas, la curva de equity y ejemplos de operaciones que ilustran cómo se comporta el detector en la práctica, así como un breve resumen del rendimiento en directo con capturas de pantalla en tiempo real y una conciliación con la prueba retrospectiva. Por último, resumiré las limitaciones y los siguientes pasos sugeridos para una validación más exhaustiva.
He realizado pruebas retrospectivas con el EURUSD y el Step Index, ambas en el marco temporal H1, y a continuación muestro los resultados. El GIF animado que aparece a continuación etiqueta y visualiza claramente los eventos ChoCH y BOS; las anotaciones son precisas y facilitan el seguimiento de la secuencia de aviso y confirmación.
- Step Index H1: pivote fractal anotado (punto de anclaje), ChoCH (HH fallido) y BOS alcista (confirmado al cierre).

- EURUSD H1: pivote fractal anotado (punto de anclaje), ChoCH (HH fallido) y BOS alcista (confirmado al cierre).

También realicé pruebas en directo para comprobar el rendimiento en tiempo real, y la herramienta funcionó exactamente como se esperaba. Las capturas de pantalla que se muestran a continuación ilustran cómo el EA detecta, etiqueta y registra eventos ChoCH y BOS en tiempo real, con alertas de escritorio y objetos del gráfico que aparecen inmediatamente en la barra cerrada confirmada. El comportamiento en condiciones reales coincidió en cuanto a la dirección con las señales de la prueba retrospectiva, y en los registros de ejecución solo se observaron los efectos habituales del mercado en directo (spread y slippage).
Demostración en directo — Volatility 75 (1s) Index M1: alertas de ChoCH en tiempo real y confirmaciones de BOS alcista.
Esta captura de pantalla en directo muestra cómo el sistema utiliza pivotes fractales para señalar alertas tempranas de ChoCH y, a continuación, confirmar los cambios de estructura con marcadores BOS sin repintado, lo que permite validar visualmente el comportamiento en tiempo real del detector.
Demostración en directo — Step Index M1: alertas de ChoCH en tiempo real y confirmaciones de BOS alcista
El EA señala primero una fase bajista con un ChoCH bajista y sucesivos niveles de BOS bajista; a continuación, indica un ChoCH alcista cuando el movimiento bajista fracasa y confirma una reversión alcista con múltiples señales de BOS alcista, lo que demuestra el comportamiento del sistema de advertencia temprana (ChoCH) y confirmación sin repintado (BOS) en tiempo real.
A continuación se muestran los registros de la pestaña «Expertos» de MetaTrader 5.
2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.40 2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73 2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73 2025.09.03 10 : 20 :58.894 Fractal Reaction System (Step Index,M1) Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6 2025.09.03 10 : 20 :58.894 Fractal Reaction System (Step Index,M1) Alert: Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6 2025.09.03 10 : 20 :58.896 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3447.86
Tanto en las pruebas retrospectivas (EURUSD y Step Index, H1) como en las pruebas en directo, el detector basado en fractales se comportó exactamente según lo previsto: los pivotes fractales proporcionaron puntos de anclaje estables y sin repintado, las advertencias ChoCH señalaron a tiempo la pérdida de sesgo y las confirmaciones de BOS indicaron de forma fiable los cambios estructurales. Las pruebas retrospectivas produjeron eventos de trading coherentes que coinciden con los GIF anotados (secuencias de advertencia-confirmación), y las capturas de pantalla en directo muestran cómo surge el mismo patrón en tiempo real, con solo los efectos esperados del mercado en directo (spread y slippage).
Conclusión
El Fractal Reaction System convierte pivotes fractales simples en señales fiables de estructura del mercado y sin repintado: ChoCH (cambio de carácter) como aviso temprano y BOS (ruptura de estructura) como confirmación. El EA que aquí se presenta es seguro en el uso de memoria, evalúa únicamente barras cerradas para garantizar la reproducibilidad, dibuja objetos de estructura persistentes en el gráfico, y registra y alerta de cada evento confirmado; un comportamiento que se mantiene constante tanto en las pruebas retrospectivas como en las pruebas en directo. Sus principales puntos fuertes son la transparencia (eventos auditables), la reproducibilidad (lógica de barras cerradas) y la practicidad (notificaciones de escritorio, móviles y sonoras, además de una reconciliación sencilla mediante los registros).
Esta herramienta es un detector de señales, no un gestor de operaciones completo: los factores de ejecución en directo, como el spread, el slippage y las ejecuciones, afectarán al rendimiento real, y las alertas de ChoCH están diseñadas para ser informativas, no prescriptivas. Antes de invertir capital, valide el EA con sus propios instrumentos y la configuración de su bróker, revise el archivo .set de prueba retrospectiva incluido y los registros de eventos, y considere añadir reglas de dimensionamiento de posiciones, filtros de marcos temporales superiores o un intervalo de espera que se adapte a su perfil de riesgo.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19365
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.
Potencie su operativa con Smart Money Concepts (SMC): OB, BOS y FVG
Creación de un sistema de trading profesional con Heikin Ashi (Parte 1): Desarrollo de un indicador personalizado
Arbitraje estadístico mediante acciones cointegradas (Parte 3): Configuración de la base de datos
Superando las limitaciones del aprendizaje automático (Parte 3): Una nueva perspectiva sobre el error irreducible
- 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
Hola,
no se ha encontrado el archivo «C:\Users\Administrator\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Include\stdlib_mq5.mqh» no se ha encontrado Fractal_Reaction_System.mq5
¿Dónde puedo conseguir el archivo?
Chris
Hola,
No se ha encontrado el archivo «C:\Users\Administrator\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Include\stdlib_mq5.mqh» no se ha encontrado Fractal_Reaction_System.mq5
¿Dónde puedo conseguir el archivo?
Chris
Parece un patrón de IA. Quizá se utilice para depurar el código. Los agentes de IA a veces introducen aquí sintaxis de C/C++ incompatible. El código se compila si se borra la línea.
Para que quede claro, la directiva «include» real en el código es:
#include <stdlib.mqh>Fíjate en que no hay ningún espacio con un guión bajo.