Automatización de estrategias de trading en MQL5 (Parte 24): Sistema de ruptura de la sesión de Londres con gestión del riesgo y stops dinámicos
Introducción
En nuestro artículo anterior (Parte 23), mejoramos el Sistema de Recuperación de Zonas para el trading de tendencias con envolventes en MetaQuotes Language 5 (MQL5) con stops dinámicos y operaciones con varias cestas, con el fin de mejorar la protección de los beneficios y la gestión de las señales. En la Parte 24, desarrollamos un sistema de ruptura de la sesión de Londres que identifica los rangos previos a la sesión, coloca órdenes pendientes e incorpora herramientas de gestión de riesgos, entre las que se incluyen ratios de riesgo-rentabilidad, límites de drawdown y un panel de control para la supervisión en tiempo real. Trataremos los siguientes temas:
- Comprender la estrategia de ruptura de la sesión de Londres
- Implementación en MQL5
- Backtesting
- Conclusión
Al final, tendrás un programa completo de ruptura en MQL5 con controles de riesgo avanzados, listo para ser probado y perfeccionado. ¡Comencemos!
Comprender la estrategia de ruptura de la sesión de Londres
La estrategia de ruptura de la sesión de Londres aprovecha el aumento de la volatilidad que se produce durante la apertura del mercado de Londres, identificando el rango de precios que se forma en las horas previas a la apertura de Londres y colocando órdenes pendientes para aprovechar las rupturas de dicho rango. Esta estrategia es importante porque la sesión de Londres suele caracterizarse por una elevada liquidez y fuertes fluctuaciones de precios, lo que ofrece oportunidades fiables de obtener beneficios; sin embargo, requiere una gestión cuidadosa del riesgo para evitar falsas rupturas y drawdowns.
Lo conseguiremos calculando los máximos y mínimos previos a la sesión de Londres para establecer órdenes stop de compra y venta con desplazamientos, incorporando ratios de riesgo-rentabilidad para las órdenes de cierre de take profit, stops dinámicos para asegurar las ganancias, y límites en las posiciones abiertas y en la reducción máxima (drawdown) diaria con el fin de proteger el capital. Tenemos previsto utilizar un panel de control para la monitorización en tiempo real y las comprobaciones específicas de cada sesión, con el fin de garantizar que las operaciones se realicen únicamente dentro de rangos definidos, lo que hará que el sistema sea adaptable a las diferentes condiciones del mercado. En resumen, esta es una representación del sistema que queremos lograr.

Implementación en MQL5
Para crear el programa en MQL5, abre el MetaEditor, ve al Navegador, busca la carpeta «Indicators», haz clic en la pestaña «Nuevo» y sigue las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, empezaremos por declarar algunas entradas y estructuras que harán que el programa sea más dinámico.
//+------------------------------------------------------------------+ //| London Breakout EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations //--- Enumerations enum ENUM_TRADE_TYPE { //--- Enumeration for trade types TRADE_ALL, // All Trades (Buy and Sell) TRADE_BUY_ONLY, // Buy Trades Only TRADE_SELL_ONLY // Sell Trades Only }; //--- Input parameters sinput group "General EA Settings" input double inpTradeLotsize = 0.01; // Lotsize input ENUM_TRADE_TYPE TradeType = TRADE_ALL; // Trade Type Selection sinput int MagicNumber = 12345; // Magic Number input double RRRatio = 1.0; // Risk to Reward Ratio input int StopLossPoints = 500; // Stop loss in points input int OrderOffsetPoints = 10; // Points offset for Orders input bool DeleteOppositeOrder = true; // Delete opposite order when one is activated? input bool UseTrailing = false; // Use Trailing Stop? input int TrailingPoints = 50; // Trailing Points (distance) input int MinProfitPoints = 100; // Minimum Profit Points to start trailing sinput group "London Session Settings" input int LondonStartHour = 9; // London Start Hour input int LondonStartMinute = 0; // London Start Minute input int LondonEndHour = 8; // London End Hour input int LondonEndMinute = 0; // London End Minute input int MinRangePoints = 100; // Min Pre-London Range in points input int MaxRangePoints = 300; // Max Pre-London Range in points sinput group "Risk Management" input int MaxOpenTrades = 2; // Maximum simultaneous open trades input double MaxDailyDrawdownPercent = 5.0; // Max daily drawdown % to stop trading //--- Structures struct PositionInfo { //--- Structure for position information ulong ticket; // Position ticket double openPrice; // Entry price double londonRange; // Pre-London range in points for this position datetime sessionID; // Session identifier (day) bool trailingActive; // Trailing active flag };
Comenzamos a implementar nuestro nuestro sistema de ruptura de la sesión de Londres incluyendo la biblioteca «<Trade\Trade.mqh>» y definiendo las enumeraciones clave, las entradas y una estructura para el seguimiento de posiciones. Incluimos «<Trade\Trade.mqh>» para acceder a la clase CTrade y poder realizar operaciones de trading, como colocar órdenes y modificar posiciones. Definimos la enumeración «ENUM_TRADE_TYPE» con las opciones «TRADE_ALL» para operaciones tanto de compra como de venta, «TRADE_BUY_ONLY» para operaciones de compra únicamente y «TRADE_SELL_ONLY» para operaciones de venta únicamente, lo que nos permite restringir las direcciones de las operaciones.
A continuación, configuramos los parámetros de entrada en grupos: en «General EA Settings», «inpTradeLotsize» en 0,01 para el tamaño del lote, «TradeType» utilizando la enumeración con el valor predeterminado «TRADE_ALL», «MagicNumber» en 12345 para identificar las operaciones del EA, «RRRatio» en 1,0 para la relación riesgo-beneficio, «StopLossPoints» en 500 para la distancia del stop-loss, «OrderOffsetPoints» en 10 para los desplazamientos de entrada, «DeleteOppositeOrder» en «true» para eliminar las órdenes pendientes opuestas, «UseTrailing» con valor «false» para controlar el trailing stop, «TrailingPoints» en 50 para la distancia del stop dinámico y «MinProfitPoints» en 100 para activar el stop dinámico.
En «London Session Settings», «LondonStartHour» en 9 y «LondonStartMinute» en 0 para el inicio de la sesión; «LondonEndHour» en 8 y «LondonEndMinute» en 0 para el final; «MinRangePoints» en 100 y «MaxRangePoints» en 300 para la validación del rango previo a Londres. En la sección «Risk Management», «MaxOpenTrades» se ha establecido en 2 para limitar el número de posiciones simultáneas, y «MaxDailyDrawdownPercent» en 5,0 para detener el trading en caso de una caída excesiva. Definimos la estructura «PositionInfo» para realizar un seguimiento de las posiciones abiertas, con «ticket» para el número de operación, «openPrice» para el precio de entrada, «londonRange» para el rango previo a la apertura de Londres, «sessionID» para el identificador del día y «trailingActive» como un valor booleano que indica el estado del trailing stop. Al compilar, obtenemos el siguiente resultado.

Una vez configuradas las entradas de esta forma estructurada, ya podemos definir algunas variables globales adicionales que utilizaremos a lo largo del código.
//--- Global variables CTrade obj_Trade; //--- Trade object double PreLondonHigh = 0.0; //--- Pre-London session high double PreLondonLow = 0.0; //--- Pre-London session low datetime PreLondonHighTime = 0; //--- Time of Pre-London high datetime PreLondonLowTime = 0; //--- Time of Pre-London low ulong buyOrderTicket = 0; //--- Buy stop order ticket ulong sellOrderTicket = 0; //--- Sell stop order ticket bool panelVisible = true; //--- Panel visibility flag double LondonRangePoints = 0.0; //--- Current session's Pre-London range PositionInfo positionList[]; //--- Array to store position info datetime lastCheckedDay = 0; //--- Last checked day bool noTradeToday = false; //--- Flag to prevent trading today bool sessionChecksDone = false; //--- Flag for session checks completion datetime analysisTime = 0; //--- Time for London analysis double dailyDrawdown = 0.0; //--- Current daily drawdown bool isTrailing = false; //--- Global flag for any trailing active const int PreLondonStartHour = 3; //--- Fixed Pre-London Start Hour const int PreLondonStartMinute = 0; //--- Fixed Pre-London Start Minute
Aquí definimos las variables globales de nuestro programa: «obj_Trade» como CTrade para las operaciones, «PreLondonHigh» y «PreLondonLow» como valores de tipo dobles (double) para los rangos, «PreLondonHighTime» y «PreLondonLowTime» como datetimes para los tiempos, «buyOrderTicket» y «sellOrderTicket» como enteros largos sin signo (ulong) para las órdenes, «panelVisible» como «true» para el panel, «LondonRangePoints» como 0,0 para el rango actual, «positionList» como una matriz de «PositionInfo» para las posiciones, «lastCheckedDay» como 0 para el seguimiento diario, «noTradeToday» y «sessionChecksDone» como false para los indicadores de trading, «analysisTime» como 0 para el tiempo de la sesión, «dailyDrawdown» como 0,0 para el riesgo, «isTrailing» como false para el trailing, y las constantes «PreLondonStartHour» como 3 y «PreLondonStartMinute» como 0.
Una vez hecho esto, pasaremos a crear el panel, que es el paso más sencillo, y luego avanzaremos hacia la lógica de trading más compleja. Empecemos por las funciones de creación necesarias.
//+------------------------------------------------------------------+ //| Create a rectangle label for the panel background | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set x-size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set y-size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create a text label for panel elements | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 10, string font = "Arial") { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Aquí implementamos funciones de utilidad para crear los elementos del panel de control Interfaz de usuario (UI) utilizando etiquetas rectangulares para los fondos y etiquetas de texto para la visualización. Comenzamos con la función "createRecLabel" para generar etiquetas rectangulares para los fondos de los paneles, tomando algunos parámetros. Restablecemos los errores con la función ResetLastError y creamos el objeto con ObjectCreate como OBJ_RECTANGLE_LABEL, registrando los errores con Print y devolviendo «false» si la operación no se realiza correctamente. Establecemos las propiedades utilizando ObjectSetInteger para OBJPROP_XDISTANCE y de la misma forma para el resto de propiedades enteras necesarias; a continuación, volvemos a dibujar el gráfico con ChartRedraw y devolvemos «true».
A continuación, creamos la función «createLabel» para las etiquetas de texto del panel, que toma como parámetros «objName», «xD», «yD», «txt», «clrTxt», «fontSize» y «font». Restablecemos los errores con «ResetLastError» y creamos el objeto con «ObjectCreate» como OBJ_LABEL, registrando los fallos con «Print» y devolviendo «false» si no se ha realizado correctamente. Establecemos las propiedades mediante la función «ObjectSetInteger», igual que hicimos con la función de la etiqueta del rectángulo, pero en este caso utilizamos además la función ObjectSetString para las propiedades «OBJPROP_TEXT» y OBJPROP_FONT, tras lo cual volvemos a dibujar y devolvemos «true». Estas funciones nos permitirán crear un panel de control dinámico para supervisar los datos de las sesiones y el estado del programa. Ahora podemos utilizar las funciones para crear y actualizar el panel.
string panelPrefix = "LondonPanel_"; //--- Prefix for panel objects //+------------------------------------------------------------------+ //| Create the information panel | //+------------------------------------------------------------------+ void CreatePanel() { createRecLabel(panelPrefix + "Background", 10, 10, 270, 200, clrMidnightBlue, 1, clrSilver); //--- Create background createLabel(panelPrefix + "Title", 20, 15, "London Breakout Control Center", clrGold, 12); //--- Create title createLabel(panelPrefix + "RangePoints", 20, 40, "Range (points): ", clrWhite, 10); //--- Create range label createLabel(panelPrefix + "HighPrice", 20, 60, "High Price: ", clrWhite); //--- Create high price label createLabel(panelPrefix + "LowPrice", 20, 80, "Low Price: ", clrWhite); //--- Create low price label createLabel(panelPrefix + "BuyLevel", 20, 100, "Buy Level: ", clrWhite); //--- Create buy level label createLabel(panelPrefix + "SellLevel", 20, 120, "Sell Level: ", clrWhite); //--- Create sell level label createLabel(panelPrefix + "AccountBalance", 20, 140, "Balance: ", clrWhite); //--- Create balance label createLabel(panelPrefix + "AccountEquity", 20, 160, "Equity: ", clrWhite); //--- Create equity label createLabel(panelPrefix + "CurrentDrawdown", 20, 180, "Drawdown (%): ", clrWhite); //--- Create drawdown label createRecLabel(panelPrefix + "Hide", 250, 10, 30, 22, clrCrimson, 1, clrNONE); //--- Create hide button createLabel(panelPrefix + "HideText", 258, 12, CharToString(251), clrWhite, 13, "Wingdings"); //--- Create hide text ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_SELECTABLE, true); //--- Make hide selectable ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_STATE, true); //--- Set hide state } //+------------------------------------------------------------------+ //| Update panel with current data | //+------------------------------------------------------------------+ void UpdatePanel() { string rangeText = "Range (points): " + (LondonRangePoints > 0 ? DoubleToString(LondonRangePoints, 0) : "Calculating..."); //--- Format range text ObjectSetString(0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); //--- Update range text string highText = "High Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh, _Digits) : "N/A"); //--- Format high text ObjectSetString(0, panelPrefix + "HighPrice", OBJPROP_TEXT, highText); //--- Update high text string lowText = "Low Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow, _Digits) : "N/A"); //--- Format low text ObjectSetString(0, panelPrefix + "LowPrice", OBJPROP_TEXT, lowText); //--- Update low text string buyText = "Buy Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh + OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format buy text ObjectSetString(0, panelPrefix + "BuyLevel", OBJPROP_TEXT, buyText); //--- Update buy text string sellText = "Sell Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow - OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format sell text ObjectSetString(0, panelPrefix + "SellLevel", OBJPROP_TEXT, sellText); //--- Update sell text string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Format balance text ObjectSetString(0, panelPrefix + "AccountBalance", OBJPROP_TEXT, balanceText); //--- Update balance text string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Format equity text ObjectSetString(0, panelPrefix + "AccountEquity", OBJPROP_TEXT, equityText); //--- Update equity text string ddText = "Drawdown (%): " + DoubleToString(dailyDrawdown, 2); //--- Format drawdown text ObjectSetString(0, panelPrefix + "CurrentDrawdown", OBJPROP_TEXT, ddText); //--- Update drawdown text ObjectSetInteger(0, panelPrefix + "CurrentDrawdown", OBJPROP_COLOR, dailyDrawdown > MaxDailyDrawdownPercent / 2 ? clrYellow : clrWhite); //--- Set drawdown color }
Aquí definimos la cadena «panelPrefix» como «LondonPanel_» para añadirla como prefijo a todos los nombres de los objetos del panel, lo que garantiza una identificación ordenada del panel de control. Creamos la función «CreatePanel» para generar la interfaz de usuario del panel de información. Llamamos a «createRecLabel» con «panelPrefix + «Background»» para crear el fondo del panel en la posición 10,10, con un tamaño de 270x200, un fondo de color «clrMidnightBlue», un ancho de 1 y un borde plateado. Utilizamos «createLabel» para añadir el título «London Breakout Control Center» en la posición 20,15, con color dorado y tamaño 12, así como etiquetas para el rango, el precio máximo, el precio mínimo, el nivel de compra, el nivel de venta, el saldo, el patrimonio neto y la reducción máxima (drawdown) en sus respectivas posiciones, con color blanco y tamaño 10.
Para el botón «Ocultar», llamamos a «createRecLabel» para «panelPrefix + “Hide”» en la posición 250,10, con un tamaño de 30x22 y un fondo de color «clrCrimson», y «createLabel» para «panelPrefix + “HideText” con CharToString(251) de Wingdings en la posición 258,12, con color «clrWhite» y tamaño 13. Establecemos OBJPROP_SELECTABLE y «OBJPROP_STATE» en «true» para el botón de ocultar mediante ObjectSetInteger, con el fin de que sea interactivo. La elección del código Wingdings a utilizar depende de tu estilo estético. Aquí tienes una lista de códigos entre los que puedes elegir.

A continuación, implementamos la función «UpdatePanel» para actualizar el panel con los datos actuales. Formateamos «rangeText» con «LondonRangePoints» utilizando DoubleToString o «Calculando...» si el valor es cero, y actualizamos el texto «panelPrefix + «RangePoints»» con la función ObjectSetString. Del mismo modo, formateamos y actualizamos los textos correspondientes al precio máximo, el precio mínimo, el nivel de compra (añadiendo «OrderOffsetPoints * _Point» a «PreLondonHigh»), el nivel de venta (restando «OrderOffsetPoints * _Point» de «PreLondonLow»), el saldo con AccountInfoDouble (ACCOUNT_BALANCE)), el patrimonio neto con «AccountInfoDouble(ACCOUNT_EQUITY)» y la reducción máxima (drawdown) con «dailyDrawdown».
Establecemos el color de la caída con ObjectSetInteger en amarillo si la caída diaria supera el «MaxDailyDrawdownPercent / 2»; en caso contrario, en blanco. Para que las funciones sean utilizables, las llamamos en la función de inicialización de la siguiente manera.
//+------------------------------------------------------------------+ //| Initialize EA | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number ArrayFree(positionList); //--- Free position list CreatePanel(); //--- Create panel panelVisible = true; //--- Set panel visible return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Deinitialize EA | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ArrayFree(positionList); //--- Free position list }
En el controlador de eventos OnInit, establecemos el número mágico utilizando el objeto «trade», utilizamos la función ArrayFree para liberar la lista de posiciones, llamamos a la función «CreatePanel» para crear el panel y establecemos el indicador de visibilidad del panel en «true» tras su creación. A continuación, en el controlador de eventos OnDeinit, utilizamos la función ObjectsDeleteAll para eliminar todos los objetos con el prefijo especificado y liberar el array de la lista de posiciones, ya que ya no lo necesitamos. Al compilar, obtenemos el siguiente resultado.

Ahora que hemos creado el panel, vamos a darle vida haciendo que el botón para ocultar sea interactivo, de modo que el panel desaparezca al hacer clic en él. Lo conseguiremos en el controlador de eventos OnChartEvent.
//+------------------------------------------------------------------+ //| Handle chart events (e.g., panel close) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK && sparam == panelPrefix + "Hide") { //--- Check hide click panelVisible = false; //--- Set panel hidden ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ChartRedraw(0); //--- Redraw chart } }
En el controlador de eventos OnChartEvent, comprobamos si el identificador del evento corresponde a un clic sobre un objeto y si dicho objeto es el botón «Ocultar» o «Cancelar»; a continuación, establecemos el indicador de visibilidad en «false». A continuación, eliminamos los objetos del panel y volvemos a dibujar el gráfico para que los cambios surtan efecto. Tras la compilación, obtenemos el siguiente resultado.

En la visualización podemos ver que el panel ya está listo. Vamos a actualizarlo para que inicialice todas las partes. Necesitaremos algunas funciones para establecer los rangos diarios y calcular las características de reducción máxima.
//+------------------------------------------------------------------+ //| Check if it's a new trading day | //+------------------------------------------------------------------+ bool IsNewDay(datetime currentBarTime) { MqlDateTime barTime; //--- Bar time structure TimeToStruct(currentBarTime, barTime); //--- Convert time datetime currentDay = StringToTime(StringFormat("%04d.%02d.%02d", barTime.year, barTime.mon, barTime.day)); //--- Get current day if (currentDay != lastCheckedDay) { //--- Check new day lastCheckedDay = currentDay; //--- Update last day sessionChecksDone = false; //--- Reset checks noTradeToday = false; //--- Reset no trade buyOrderTicket = 0; //--- Reset buy ticket sellOrderTicket = 0; //--- Reset sell ticket LondonRangePoints = 0.0; //--- Reset range return true; //--- Return new day } return false; //--- Return not new day } //+------------------------------------------------------------------+ //| Update daily drawdown | //+------------------------------------------------------------------+ void UpdateDailyDrawdown() { static double maxEquity = 0.0; //--- Max equity tracker double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity if (equity > maxEquity) maxEquity = equity; //--- Update max equity dailyDrawdown = (maxEquity - equity) / maxEquity * 100; //--- Calculate drawdown if (dailyDrawdown >= MaxDailyDrawdownPercent) noTradeToday = true; //--- Set no trade if exceeded }
Aquí implementamos la función «IsNewDay» para comprobar si es un nuevo día de trading. Creamos una estructura MqlDateTime denominada «barTime» y convertimos «currentBarTime» a dicha estructura mediante la función TimeToStruct. Calculamos «currentDay» utilizando StringToTime y StringFormat a partir de «barTime.year», «barTime.mon» y «barTime.day». Si «currentDay» difiere de «lastCheckedDay», actualizamos «lastCheckedDay», restablecemos «sessionChecksDone» y «noTradeToday» a «false», ponemos «buyOrderTicket» y «sellOrderTicket» a 0, establecemos «LondonRangePoints» en 0,0 y devolvemos «true»; de lo contrario, devolvemos «false». Esta función garantiza el restablecimiento diario de los datos para el análisis de sesiones y las alertas de trading.
A continuación, implementamos la función «UpdateDailyDrawdown» para supervisar el riesgo diario. Utilizamos una variable estática «maxEquity», inicializada en 0,0, para hacer un seguimiento del valor máximo del patrimonio neto. Obtenemos el «patrimonio neto» con AccountInfoDouble utilizando «ACCOUNT_EQUITY», actualizamos «maxEquity» si el «capital» es superior y calculamos el «dailyDrawdown» como el porcentaje de caída respecto a «maxEquity». Si «dailyDrawdown» alcanza o supera el «MaxDailyDrawdownPercent», establecemos «noTradeToday» en «true» para detener el trading, lo que proporciona una protección esencial frente a los drawdowns. Para que las funciones sean utilizables, las llamamos en el controlador de eventos OnTick para rellenar el panel con los datos actualizados.
//+------------------------------------------------------------------+ //| Main tick handler | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time IsNewDay(currentBarTime); //--- Check new day UpdatePanel(); //--- Update panel UpdateDailyDrawdown(); //--- Update drawdown }
Aquí simplemente llamamos a la función que detecta un nuevo día para establecer los parámetros del rango, actualizar el panel y los niveles de caída diaria, respectivamente. Cuando ejecutamos ahora el programa, obtenemos el siguiente resultado.

En la visualización podemos ver que el panel ya contiene datos recientes y muestra el estado. Pasemos ahora a la lógica más compleja que supone definir los intervalos de sesión. Primero comprobemos las condiciones de trading y, si se cumplen, realicemos las órdenes; ya nos ocuparemos de gestionar las posiciones más adelante. Así pues, necesitaremos algunas funciones que nos permitan, en primer lugar, definir el rango, visualizar los rangos y, a continuación, colocar órdenes. Esta es la lógica que seguimos para conseguirlo.
//+------------------------------------------------------------------+ //| Fixed lot size | //+------------------------------------------------------------------+ double CalculateLotSize(double entryPrice, double stopLossPrice) { return NormalizeDouble(inpTradeLotsize, 2); //--- Normalize lot size } //+------------------------------------------------------------------+ //| Calculate session range (high-low) in points | //+------------------------------------------------------------------+ double GetRange(datetime startTime, datetime endTime, double &highVal, double &lowVal, datetime &highTime, datetime &lowTime) { int startBar = iBarShift(_Symbol, _Period, startTime, true); //--- Get start bar int endBar = iBarShift(_Symbol, _Period, endTime, true); //--- Get end bar if (startBar == -1 || endBar == -1 || startBar < endBar) return -1; //--- Invalid bars int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + 1, endBar); //--- Get highest bar int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + 1, endBar); //--- Get lowest bar highVal = iHigh(_Symbol, _Period, highestBar); //--- Set high value lowVal = iLow(_Symbol, _Period, lowestBar); //--- Set low value highTime = iTime(_Symbol, _Period, highestBar); //--- Set high time lowTime = iTime(_Symbol, _Period, lowestBar); //--- Set low time return (highVal - lowVal) / _Point; //--- Return range in points } //+------------------------------------------------------------------+ //| Place pending buy/sell stop orders | //+------------------------------------------------------------------+ void PlacePendingOrders(double preLondonHigh, double preLondonLow, datetime sessionID) { double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; //--- Calculate buy price double sellPrice = preLondonLow - OrderOffsetPoints * _Point; //--- Calculate sell price double slPoints = StopLossPoints; //--- Set SL points double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL double tpPoints = slPoints * RRRatio; //--- Calculate TP points double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { //--- Check buy trade obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket } if (TradeType == TRADE_ALL || TradeType == TRADE_SELL_ONLY) { //--- Check sell trade obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket } } //+------------------------------------------------------------------+ //| Draw session ranges on the chart | //+------------------------------------------------------------------+ void DrawSessionRanges(datetime preLondonStart, datetime londonEnd) { string sessionID = "Sess_" + IntegerToString(lastCheckedDay); //--- Session ID string preRectName = "PreRect_" + sessionID; //--- Rectangle name ObjectCreate(0, preRectName, OBJ_RECTANGLE, 0, PreLondonHighTime, PreLondonHigh, PreLondonLowTime, PreLondonLow); //--- Create rectangle ObjectSetInteger(0, preRectName, OBJPROP_COLOR, clrTeal); //--- Set color ObjectSetInteger(0, preRectName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, preRectName, OBJPROP_BACK, true); //--- Set background string preTopLineName = "PreTopLine_" + sessionID; //--- Top line name ObjectCreate(0, preTopLineName, OBJ_TREND, 0, preLondonStart, PreLondonHigh, londonEnd, PreLondonHigh); //--- Create top line ObjectSetInteger(0, preTopLineName, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, preTopLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preTopLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preTopLineName, OBJPROP_BACK, true); //--- Set background string preBotLineName = "PreBottomLine_" + sessionID; //--- Bottom line name ObjectCreate(0, preBotLineName, OBJ_TREND, 0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); //--- Create bottom line ObjectSetInteger(0, preBotLineName, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(0, preBotLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preBotLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preBotLineName, OBJPROP_BACK, true); //--- Set background }
Para habilitar el cálculo del rango, la realización de órdenes y el trazado de gráficos, comenzamos con la función «CalculateLotSize» para calcular los tamaños de lote fijos, tomando «entryPrice» y «stopLossPrice» como parámetros (que no se utilizan para el cálculo de tamaños fijos). Devolvemos «inpTradeLotsize» normalizado a 2 decimales con NormalizeDouble, lo que garantiza que los tamaños de lote sean uniformes en todas las operaciones. Puedes utilizar otro número de decimales en función del tipo de cuenta que tengas.
A continuación, creamos la función «GetRange» para calcular el rango previo a la sesión de Londres. Obtenemos «startBar» y «endBar» mediante la función iBarShift utilizando «startTime» y «endTime», que devuelve -1 si los valores no son válidos o si «startBar» < «endBar». Encontramos «highestBar» con iHighest en MODE_HIGH y «lowestBar» con «iLowest» en MODE_LOW en el rango de barras. Establecemos «highVal» con «iHigh» en «highestBar», «lowVal» con «iLow» en «lowestBar», «highTime» con «iTime» en «highestBar» y «lowTime» con «iTime» en «lowestBar». Devolvemos el rango como el valor «(highVal-lowVal) /_Point».
A continuación, definimos la función «PlacePendingOrders» para configurar órdenes stop de compra y venta. Calculamos «buyPrice» como «preLondonHigh + OrderOffsetPoints * _Point» y «sellPrice» como «preLondonLow - OrderOffsetPoints * _Point». Establecemos «slPoints» como «StopLossPoints», «buySL» como «buyPrice - slPoints * _Point», «sellSL» como «sellPrice + slPoints * _Point», «tpPoints» como «slPoints * RRRatio», «buyTP» como «buyPrice + tpPoints * _Point» y «sellTP» como «sellPrice - tpPoints * _Point». Calculamos «lotSizeBuy» y «lotSizeSell» con «CalculateLotSize».
Si «TradeType» es «TRADE_ALL» o «TRADE_BUY_ONLY», colocamos una orden stop de compra con «obj_Trade.BuyStop» utilizando «lotSizeBuy», «buyPrice», «buySL», «buyTP» y la etiqueta «Buy Stop - Londres», y almacenamos el ticket en «buyOrderTicket» desde «ResultOrder». Del mismo modo, para vender si se indica «TRADE_ALL» o «TRADE_SELL_ONLY».
Por último, implementamos la función «DrawSessionRanges» para visualizar la sesión en el gráfico. Creamos «sessionID» como «Sess_» más «lastCheckedDay» con la función IntegerToString. Para el rectángulo «preRectName», que se denomina «PreRect_» más «sessionID», utilizamos ObjectCreate como OBJ_RECTANGLE desde «PreLondonHighTime», «PreLondonHigh» hasta «PreLondonLowTime», «PreLondonLow», estableciendo OBJPROP_COLOR en «clrTeal», «OBJPROP_FILL» en true y «OBJPROP_BACK» en true.
Para la línea superior «preTopLineName», que es «PreTopLine_» más «sessionID», creamos OBJ_TREND desde «preLondonStart», «PreLondonHigh» hasta «londonEnd», «PreLondonHigh», estableciendo «OBJPROP_COLOR» en «clrBlack», «OBJPROP_WIDTH» en 1, «OBJPROP_RAY_RIGHT» en false y «OBJPROP_BACK» en true. Del mismo modo, para la línea inferior «preBotLineName», se utilizará el formato «PreBottomLine_» más el «sessionID» desde «preLondonStart», «PreLondonLow» hasta «londonEnd» y «PreLondonLow», con color rojo. Ahora podemos definir una función para comprobar las condiciones de trading utilizando estas funciones.
//+------------------------------------------------------------------+ //| Check trading conditions and place orders | //+------------------------------------------------------------------+ void CheckTradingConditions(datetime currentTime) { MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert time datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); //--- Get today datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; //--- Pre-London start datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; //--- London start datetime londonEnd = today + LondonEndHour * 3600 + LondonEndMinute * 60; //--- London end analysisTime = londonStart; //--- Set analysis time if (currentTime < analysisTime) return; //--- Exit if before analysis double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); //--- Get range if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { //--- Check range limits noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges return; //--- Exit } LondonRangePoints = preLondonRange; //--- Set range points PlacePendingOrders(PreLondonHigh, PreLondonLow, today); //--- Place orders noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges }
Implementamos la función «CheckTradingConditions» para evaluar las condiciones de la sesión y realizar órdenes en nuestro sistema de ruptura de la sesión de Londres. Creamos una estructura MqlDateTime, «timeStruct», y convertimos la hora actual a dicha estructura mediante la función TimeToStruct. Calculamos «hoy» utilizando «StringToTime» y «StringFormat» a partir de «timeStruct.year», «timeStruct.mon» y «timeStruct.day». Establecemos «preLondonStart» como «hoy» más «PreLondonStartHour» y «PreLondonStartMinute» en segundos; «londonStart» como «hoy» más «LondonStartHour» y «LondonStartMinute»; y «londonEnd» como «hoy» más «LondonEndHour» y «LondonEndMinute». Asignamos «analysisTime» a «londonStart» y salimos si la hora actual es anterior a esa.
Obtenemos «preLondonRange» con la función «GetRange», pasando «preLondonStart», «currentTime» y las referencias para «PreLondonHigh», «PreLondonLow», «PreLondonHighTime» y «PreLondonLowTime». Si «preLondonRange» está por debajo de «MinRangePoints» o por encima de «MaxRangePoints», establecemos «noTradeToday» y «sessionChecksDone» en «true», llamamos a «DrawSessionRanges» con «preLondonStart» y «londonEnd», y salimos. De lo contrario, establecemos «LondonRangePoints» en «preLondonRange», llamamos a «PlacePendingOrders» con «PreLondonHigh», «PreLondonLow» y «today», establecemos «noTradeToday» y «sessionChecksDone» en «true», y llamamos a «DrawSessionRanges», asegurándonos de que las operaciones se realicen únicamente dentro de los rangos de sesión válidos. Ahora podemos llamar a la función en el controlador de eventos OnTick de las señales.
if (!noTradeToday && !sessionChecksDone) { //--- Check trading conditions CheckTradingConditions(TimeCurrent()); //--- Check conditions }
Si hoy no se han realizado operaciones y aún no se han llevado a cabo las comprobaciones de la sesión, llamamos a la función para comprobar las condiciones correspondientes a la hora actual. Al ejecutar el programa, obtenemos el siguiente resultado.

Podemos ver que establecimos los rangos y colocamos los órdenes pendientes. El rango es de 100 puntos, lo que cumple con nuestras condiciones de trading. Ahora podemos pasar a gestionar las operaciones, pero antes, revisemos y eliminemos las órdenes pendientes cuando se active alguna, y añadamos las posiciones a la lista de posiciones para su gestión.
//+------------------------------------------------------------------+ //| Delete opposite pending order when one is filled | //+------------------------------------------------------------------+ void CheckAndDeleteOppositeOrder() { if (!DeleteOppositeOrder || TradeType != TRADE_ALL) return; //--- Exit if not applicable bool buyOrderExists = false; //--- Buy exists flag bool sellOrderExists = false; //--- Sell exists flag for (int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders ulong orderTicket = OrderGetTicket(i); //--- Get ticket if (OrderSelect(orderTicket)) { //--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; //--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; //--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != 0) { //--- Check delete sell obj_Trade.OrderDelete(sellOrderTicket); //--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != 0) { //--- Check delete buy obj_Trade.OrderDelete(buyOrderTicket); //--- Delete buy order } } //+------------------------------------------------------------------+ //| Add position to tracking list when opened | //+------------------------------------------------------------------+ void AddPositionToList(ulong ticket, double openPrice, double londonRange, datetime sessionID) { if (londonRange <= 0) return; //--- Exit if invalid range int index = ArraySize(positionList); //--- Get current size ArrayResize(positionList, index + 1); //--- Resize array positionList[index].ticket = ticket; //--- Set ticket positionList[index].openPrice = openPrice; //--- Set open price positionList[index].londonRange = londonRange; //--- Set range positionList[index].sessionID = sessionID; //--- Set session ID positionList[index].trailingActive = false; //--- Set trailing inactive }
Empezamos con la función «CheckAndDeleteOppositeOrder» para eliminar la orden pendiente opuesta cuando se activa una de ellas. Salimos antes de tiempo si «DeleteOppositeOrder» es falso o si «TradeType» no es «TRADE_ALL». Inicializamos «buyOrderExists» y «sellOrderExists» como «false». Recorremos las órdenes en sentido inverso utilizando OrdersTotal y OrderGetTicket, y seleccionamos cada uno de ellos con «OrderSelect». Si la orden coincide con «_Symbol» y «MagicNumber» mediante «OrderGetString» y OrderGetInteger, establecemos «buyOrderExists» o «sellOrderExists» si el ticket coincide con «buyOrderTicket» o «sellOrderTicket».
Si la orden de compra ha desaparecido y la orden de venta sigue existiendo, eliminamos «sellOrderTicket» mediante «obj_Trade.OrderDelete»; del mismo modo, si la orden de venta ha desaparecido y la orden de compra sigue existiendo. Esta función garantizará que, una vez activada, solo se pueda operar en una dirección.
A continuación, creamos la función «AddPositionToList» para realizar un seguimiento de las posiciones abiertas. Salimos si «londonRange» es <= 0, ya que el rango aún no se ha establecido. Obtenemos el «índice» actual con ArraySize de «positionList», lo redimensionamos con ArrayResize para añadir una posición y establecemos «positionList[índice]. ticket», «openPrice», «londonRange», «sessionID» y «trailingActive» a «false», lo que ayuda a mantener una lista para gestionar los stops dinámicos y los datos específicos de cada sesión. Ahora podemos implementar esta lógica en el controlador de eventos «tick».
CheckAndDeleteOppositeOrder(); //--- Delete opposite order // Add untracked positions for (int i = 0; i < PositionsTotal(); i++) { //--- Iterate through positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check position bool tracked = false; //--- Tracked flag for (int j = 0; j < ArraySize(positionList); j++) { //--- Check list if (positionList[j].ticket == ticket) tracked = true; //--- Set tracked } if (!tracked) { //--- If not tracked double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); //--- Add to list } } }
Aquí llamamos a la función «CheckAndDeleteOppositeOrder» para gestionar las órdenes pendientes, asegurándonos de que, si se ejecuta una orden en una dirección, la de sentido contrario se elimine según el parámetro «DeleteOppositeOrder», evitando así operaciones conflictivas.
A continuación, añadimos las posiciones no registradas a la «positionList» para garantizar que todas las posiciones abiertas relevantes se supervisen a efectos de los stops dinámicos. Recorremos todas las posiciones con PositionsTotal y PositionGetTicket para obtener cada «ticket». Si PositionSelectByTicket se ejecuta correctamente y la posición coincide con _Symbol y «MagicNumber» mediante «PositionGetString» y «PositionGetInteger», establecemos el indicador «tracked» en «false» y comprobamos el array «positionList» con «ArraySize» y un bucle interno para ver si el «ticket» ya se encuentra en «positionList[j].ticket». Si no está «registrada», obtenemos «openPrice» con PositionGetDouble utilizando POSITION_PRICE_OPEN y llamamos a «AddPositionToList» con «ticket», «openPrice», «LondonRangePoints» y «lastCheckedDay». De este modo, se garantiza que todas las posiciones coincidentes se añadan a la lista para su gestión sin duplicados. Este es el resultado.

Dado que todo es perfecto hasta este punto, ahora podemos definir la función para gestionar estas posiciones de la siguiente manera.
//+------------------------------------------------------------------+ //| Remove position from tracking list when closed | //+------------------------------------------------------------------+ void RemovePositionFromList(ulong ticket) { for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through list if (positionList[i].ticket == ticket) { //--- Match ticket for (int j = i; j < ArraySize(positionList) - 1; j++) { //--- Shift elements positionList[j] = positionList[j + 1]; //--- Copy next } ArrayResize(positionList, ArraySize(positionList) - 1); //--- Resize array break; //--- Exit loop } } } //+------------------------------------------------------------------+ //| Manage trailing stops | //+------------------------------------------------------------------+ void ManagePositions() { if (PositionsTotal() == 0 || !UseTrailing) return; //--- Exit if no positions or no trailing isTrailing = false; //--- Reset trailing flag double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask double point = _Point; //--- Get point value for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through positions ulong ticket = positionList[i].ticket; //--- Get ticket if (!PositionSelectByTicket(ticket)) { //--- Select position RemovePositionFromList(ticket); //--- Remove if not selected continue; //--- Skip } if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip if not matching double openPrice = positionList[i].openPrice; //--- Get open price long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; //--- Get current price double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; //--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { //--- Check for trailing double newSL = 0.0; //--- New SL variable if (positionType == POSITION_TYPE_BUY) { //--- Buy position newSL = currentPrice - TrailingPoints * point; //--- Calculate new SL } else { //--- Sell position newSL = currentPrice + TrailingPoints * point; //--- Calculate new SL } double currentSL = PositionGetDouble(POSITION_SL); //--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { //--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { //--- Modify position positionList[i].trailingActive = true; //--- Set trailing active isTrailing = true; //--- Set global trailing } } } } }
Aquí implementamos funciones para eliminar las posiciones cerradas de la lista de seguimiento y gestionar los stops dinámicos. Comenzamos con la función "RemovePositionFromList" para limpiar el array "positionList" cuando se cierra una posición, tomando "ticket" como parámetro. Recorremos «positionList» con ArraySize, y si «positionList[i].ticket» coincide con «ticket», desplazamos los elementos siguientes mediante un bucle interno que copia «positionList[j + 1]» a «positionList[j]», a continuación redimensionamos el array con ArrayResize para reducir su tamaño en uno, y break el bucle. Esta función garantizará que la lista se mantenga actualizada, evitando comprobaciones innecesarias de las posiciones cerradas, especialmente cuando las ajustamos y las cerramos.
A continuación, creamos la función «ManagePositions» para gestionar los stops dinámicos de las posiciones abiertas. Salimos antes de tiempo si PositionsTotal es 0 o si «UseTrailing» es falso. Restablecemos «isTrailing» a «false», obtenemos «currentBid» y «currentAsk» con SymbolInfoDouble utilizando «SYMBOL_BID» y «SYMBOL_ASK», y «point» como «_Point». Recorremos «positionList» durante «ArraySize» veces, obtenemos «ticket» y lo seleccionamos con la función PositionSelectByTicket. Si la selección falla, llamamos a «RemovePositionFromList» y continuamos. Si la posición no coincide con «_Symbol» o «MagicNumber» mediante «PositionGetString» y «PositionGetInteger», pasamos de largo. Obtenemos «openPrice» de «positionList[i]», «positionType» mediante PositionGetInteger, «currentPrice» según el tipo y «profitPoints» como la diferencia dividida por punto.
Si «profitPoints» es igual o superior a «MinProfitPoints + TrailingPoints», calculamos «newSL» como «currentPrice - TrailingPoints * point» para las operaciones de compra, o sumando ese valor para las operaciones de venta. Obtenemos «currentSL» con PositionGetDouble, y si «newSL» es mejor que «currentSL» en al menos «point», modificamos la posición con «obj_Trade.PositionModify» utilizando el valor normalizado de «newSL» y el TP actual obtenido de «PositionGetDouble». Si la operación tiene éxito, establecemos «positionList[i].trailingActive» e «isTrailing» en «true», lo que nos permite ajustar dinámicamente los stops para asegurar las ganancias, al tiempo que dejamos que las operaciones ganadoras sigan su curso. Ahora, solo tienes que llamar a la función para gestionar las posiciones en cada tick. Tras la compilación, obtenemos el siguiente resultado.

En la imagen podemos ver que analizamos las condiciones de trading, realizamos órdenes y las gestionamos de acuerdo con la estrategia de trading, logrando así nuestros objetivos. Lo que queda por hacer es realizar un backtesting del programa, y eso se trata en la siguiente sección.
Backtesting
Tras realizar un exhaustivo análisis retrospectivo, hemos obtenido los siguientes resultados.
Gráfico de backtesting:

Informe de backtesting:

Conclusión
En conclusión, hemos desarrollado un sistema de ruptura de la sesión de Londres en MQL5 que analiza los rangos previos a la sesión para colocar órdenes pendientes con relaciones riesgo-beneficio personalizables, stops dinámicos y límites de operaciones múltiples, complementado con un panel de control para la supervisión en tiempo real de los rangos, los niveles y la reducción máxima (drawdown). A través de componentes modulares como la estructura «PositionInfo», este programa ofrece un enfoque riguroso para el trading de ruptura que puedes perfeccionar ajustando los horarios de las sesiones o los parámetros de riesgo.
Descargo de responsabilidad: Este artículo tiene fines exclusivamente educativos. Operar en los mercados financieros conlleva riesgos financieros significativos, y la volatilidad del mercado puede ocasionar pérdidas. Es fundamental realizar pruebas retrospectivas exhaustivas y una gestión de riesgos cuidadosa antes de implementar este programa en mercados reales.
Aprovechando los conceptos y la implementación presentados, podrás adaptar este sistema este sistema de ruptura a su estilo de trading, potenciando así sus estrategias algorítmicas. ¡Felices operaciones!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18867
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.
Utilizando redes neuronales en MetaTrader
Características del Wizard MQL5 que debe conocer (Parte 76): Uso de los patrones del Awesome Oscillator y los canales Envelopes con aprendizaje supervisado
Particularidades del trabajo con números del tipo double en MQL4
Aprendizaje automático y Data Science (Parte 46): Pronóstico de los mercados bursátiles con N-BEATS en Python
- 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
El segundo problema es que los niveles máximos y mínimos, así como los niveles de compra y venta del panel de control, no se actualizan.
Los niveles de rango máximo/mínimo se muestran claramente en el gráfico, por lo que supongo que los niveles de compra/venta también deberían aparecer en el gráfico y actualizarse en el panel de control, ya que se derivan directamente de los niveles de rango máximo/mínimo.
¿Qué sugerencias tenéis para que esto funcione correctamente?
Gracias de antemano.
En cuanto a tu segunda duda, el artículo lo explica, pero suponiendo que tu problema se deba a datos de prueba insuficientes y para darte una pista, cuando se está calculando el rango, siempre verás el estado «Calculando...» hasta que haya datos suficientes para establecer la sesión del rango de Londres o cualquier otra sesión que definas en los parámetros de entrada. Suponiendo que utilices la configuración predeterminada, con 3 horas previas a Londres, y que la hora que aparece en la captura de pantalla compartida sea el 13 de febrero, dos barras después de las 22:00, lo que equivale a 2 × 15 minutos = 30, lo que da como resultado las 22:30, está fuera del intervalo de tiempo de cálculo del rango; por lo tanto, los datos del panel deberían seguir siendo visibles, ya que el rango establecido anteriormente sigue vigente, a menos que aún no se haya encontrado la primera sesión, y se borrarán cuando se alcance el cálculo del rango a partir de medianoche. Véase a continuación:
Quizá te interese ver la lógica que se muestra a continuación para determinar el intervalo
y cómo se establece.
Observa la imagen siguiente; aunque desconocemos el año de tu prueba, tomaremos el 2025; si se trata de 2020, como en tu caso, no disponemos de datos de calidad para ese año, por lo que, en cualquier caso, utilizaremos 2025 y, por lo tanto, el cálculo del intervalo debería comenzar a medianoche.
En la imagen se puede ver que los datos de las 23:55 siguen intactos. Sin embargo, al llegar la medianoche, deberíamos reiniciar. Véase a continuación.
Se puede observar que hemos restablecido los datos a medianoche para el cálculo del intervalo. De hecho, una vez realizado el cálculo del rango, la visualización puede ayudarte a comprender qué ha ocurrido realmente. Por ejemplo, en tu caso, utilizando la configuración predeterminada, veremos las barras de rango desde las 03:00 hasta las 08:00, ya que eso es lo que hemos definido. Véase a continuación:
Espero que esto aclare las cosas de nuevo. Puedes ajustar todo según tu estilo de negociación. Para evitar los problemas a los que te enfrentas, es recomendable utilizar datos de prueba fiables. Gracias.
En cuanto a tu segunda duda, el artículo lo explica, pero suponiendo que tu problema se deba a datos de prueba insuficientes y para darte una pista, cuando se está calculando el rango, siempre verás el estado «Calculando...» hasta que haya datos suficientes para establecer la sesión del rango de Londres o cualquier otra sesión que definas en los parámetros de entrada. Suponiendo que utilices la configuración predeterminada, con 3 horas previas a Londres, y que la hora que aparece en la captura de pantalla compartida sea el 13 de febrero, dos barras después de las 22:00, lo que equivale a 2 × 15 minutos = 30, lo que da como resultado las 22:30, se encuentra fuera del periodo de cálculo del rango; por lo tanto, los datos del panel deberían seguir siendo visibles, ya que el rango establecido anteriormente sigue vigente, a menos que aún no se haya encontrado la primera sesión, y se borrarán cuando se alcance el cálculo del rango a partir de la medianoche. Véase a continuación:
Quizá te resulte útil consultar la lógica que se muestra a continuación para determinar el intervalo
y cómo se establece.
Fíjate en la imagen de abajo; aunque no sabemos en qué año se realizó la prueba, tomaremos como referencia el año 2025. Si fuera 2020, como en tu caso, no disponemos de datos fiables para ese año, así que, en cualquier caso, utilizaremos 2025 y, por lo tanto, el cálculo del intervalo debería comenzar a medianoche.
En la imagen se puede ver que los datos de las 23:55 siguen intactos. Sin embargo, al llegar la medianoche, deberíamos reiniciar. Véase a continuación.
Se puede observar que hemos reiniciado los datos a medianoche para el cálculo del otro intervalo. De hecho, una vez realizado el cálculo del intervalo, la visualización puede ayudarte a comprender qué ha ocurrido realmente. Por ejemplo, en tu caso, utilizando la configuración predeterminada, veremos las barras del intervalo de las 03:00 a las 08:00 horas, ya que eso es lo que hemos definido. Véase a continuación:
Espero que esto aclare las cosas de nuevo. Puedes ajustar todo según tu estilo de negociación. Para evitar los problemas a los que te enfrentas, es recomendable utilizar datos de prueba fiables. Gracias.
Muchas gracias por la respuesta tan completa.
Sí, leí el artículo y seguí los pasos para programar mi propia versión hasta que me encontré con los problemas que he descrito. Lo que observé fue que el panel no se actualizaba, ni siquiera durante los horarios predeterminados. Mi captura de pantalla pretendía mostrar que, aunque el recuadro se había dibujado en el gráfico y los datos se habían recopilado, el panel no se había actualizado. Además, no había mensajes de error en los registros relativos a precios o niveles no válidos.
He añadido mensajes de registro a mi versión; gracias a ellos, puedo ver que el panel no se actualiza cuando el rango es demasiado grande o demasiado pequeño; así que eso puede ser parte del motivo.
Volveré a comprobar la calidad de los datos de prueba. Y gracias por indicar en qué par has realizado las pruebas; sin duda haré los ajustes necesarios para los pares que yo haya elegido.
Muchas gracias por tu ayuda.
Muchas gracias por la respuesta tan completa.
Sí, leí el artículo y seguí las instrucciones para programar mi propia versión hasta que me encontré con los problemas que he descrito. Lo que observé fue que el panel no se actualizaba, ni siquiera en los momentos predeterminados. Mi captura de pantalla pretendía mostrar que, aunque el recuadro se había dibujado en el gráfico y se habían recopilado los datos, el panel no se había actualizado. Además, no había mensajes de error en los registros relativos a precios o niveles no válidos.
He añadido mensajes de registro a mi versión; por ellos puedo ver que el panel no se actualiza cuando el rango es demasiado grande o demasiado pequeño; así que eso puede ser parte del motivo.
Volveré a comprobar la calidad de los datos de prueba. Y gracias por indicar en qué par has realizado las pruebas; sin duda haré los ajustes necesarios para los pares que he elegido.
Muchas gracias por tu ayuda.
De nada. De nada.
Gracias por compartir tu código con nosotros.
Como yo mismo he escrito EA dependientes de la sesión, te puedo decir que el código solo funciona si tu bróker está siempre en la zona horaria GMT+1 y también utiliza el horario de verano británico.
En todos los demás casos, lamentablemente, tu hora de inicio no es la correcta. ¿Por qué? Porque la sesión de Londres comienza a las 8:00 h, hora del Reino Unido. En invierno son las 8:00 h GMT y en verano, las 7:00 h GMT.
La función TimeCurrent() no te devuelve tu hora local, sino siempre la hora del servidor de operaciones.