Desarrollo de un sistema de ruptura basado en la volatilidad
Introducción
Muchos sistemas tradicionales de ruptura se enfrentan a problemas como las señales falsas frecuentes, que se producen cuando el precio supera brevemente un nivel de soporte o resistencia para luego volver al interior del rango. Estas falsas rupturas suelen producirse en entornos de baja volatilidad o en momentos de ruido en el mercado, lo que provoca que se activen los stop-loss, se reduzca la rentabilidad y aumente la frustración de los traders. Si no se consideran las condiciones cambiantes del mercado, las estrategias estáticas de ruptura pueden tener dificultades para adaptarse, lo que las hace poco fiables en las distintas fases del mercado.
Un sistema de ruptura basado en la volatilidad aborda esta cuestión incorporando filtros de volatilidad, como el rango verdadero medio (ATR), para medir la fuerza del mercado y ajustar las condiciones de ruptura de forma dinámica. Exigiendo que el precio supere tanto el límite del rango como un umbral de volatilidad, el sistema ayuda a distinguir las rupturas auténticas del ruido del mercado. Esto garantiza que las operaciones solo se realicen cuando exista un impulso suficiente, lo que mejora la precisión, la gestión del riesgo y la consistencia general de los resultados.
Planificación y lógica
Ruptura alcista sin confirmar por volatilidad:

Ruptura alcista confirmada por volatilidad:

Ruptura bajista sin confirmar por volatilidad:

Ruptura bajista confirmada por volatilidad:




Lógica de decisión de rupturas

Primeros pasos
//+------------------------------------------------------------------+ //| Volatility Breakout.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #include <Trade\Trade.mqh> #include <Trade\PositionInfo.mqh>
Como de costumbre, empezaremos incluyendo las bibliotecas de trading de MQL5 necesarias: #include <Trade\Trade.mqh> y #include <Trade\PositionInfo.mqh>. La primera ofrece acceso a la clase CTrade, que permite al EA ejecutar, modificar y cerrar órdenes de negociación, mientras que la segunda proporciona la clase CPositionInfo, lo que permite al EA obtener información detallada sobre las posiciones activas, como el tipo de orden, el tamaño del lote y los niveles de stop loss y take profit.
//+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input group "--------------- Range Settings ---------------" input int RangeStartTime = 600; // Range start time (minutes from midnight) input int RangeDuration = 120; // Range duration in minutes input int RangeCloseTime = 1200; // Range close time (minutes from midnight) input group "--------------- Trading Settings ---------------" input double RiskPerTrade = 1.0; // Risk percentage per trade input double StopLossMultiplier = 1.5; // Stop loss multiplier (range-based) input double TakeProfitMultiplier = 2.0; // Take profit multiplier (range-based) input bool UseTrailingStop = true; // Enable trailing stops input int BreakEvenAtPips = 250; // Move to breakeven at this profit (pips) input int TrailStartAtPips = 500; // Start trailing at this profit (pips) input int TrailStepPips = 100; // Trail by this many pips input group "--------------- Volatility Settings ---------------" input int ATRPeriod = 14; // ATR period for volatility calculation input double ATRMultiplier = 2.0; // ATR multiplier for volatility stops
Aquí definimos los parámetros de entrada que un trader puede personalizar al utilizar el asesor experto. El primer grupo, «Configuración del rango», especifica el intervalo de tiempo para establecer un rango de precios: «RangeStartTime» marca el inicio del rango (en minutos desde la medianoche), «RangeDuration» determina la duración del rango y «RangeCloseTime» define cuándo finaliza la sesión. Estos ajustes permiten al EA estructurar su lógica de ruptura en función de horarios de mercado específicos, lo cual resulta especialmente útil para centrarse en las sesiones de negociación activas.
El segundo y el tercer grupo configuran las reglas de negociación y volatilidad del sistema. Los parámetros de negociación controlan la gestión del riesgo y de las operaciones, como «RiskPerTrade» para el cálculo del tamaño de la posición, los multiplicadores para el stop loss y el take profit en función del tamaño del rango, y la lógica del trailing stop («BreakEvenAtPips», «TrailStartAtPips» y «TrailStepPips»). Por su parte, los ajustes de volatilidad utilizan el indicador ATR (rango verdadero medio), con los parámetros ATRPeriod y ATRMultiplier definidos por el usuario, para calcular los niveles de stop basados en la volatilidad. Esto garantiza que las operaciones de ruptura se adapten dinámicamente a las condiciones cambiantes del mercado, lo cual reduce la probabilidad de señales falsas.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ long ExpertMagicNumber = 20250908; CTrade TradeManager; CPositionInfo PositionInfo; MqlTick CurrentTick; // Range structure struct TradingSession { datetime SessionStart; datetime SessionEnd; datetime SessionClose; double SessionHigh; double SessionLow; bool IsActive; bool HighBreakoutTriggered; bool LowBreakoutTriggered; datetime LastCalculationDate; TradingSession() : SessionStart(0), SessionEnd(0), SessionClose(0), SessionHigh(0), SessionLow(DBL_MAX), IsActive(false), HighBreakoutTriggered(false), LowBreakoutTriggered(false), LastCalculationDate(0) {}; }; TradingSession CurrentSession; // Volatility stops double UpperVolatilityStop = 0.0; double LowerVolatilityStop = 0.0; int ATRHandle = INVALID_HANDLE;
En esta sección del código se declaran las variables globales que se utilizarán a lo largo de todo el asesor experto. El «ExpertMagicNumber» identifica de forma única las operaciones abiertas por este EA, de modo que puedan distinguirse de las operaciones manuales o de otros EA. El objeto CTrade TradeManager se encarga de ejecutar y gestionar las operaciones, mientras que CPositionInfo PositionInfo recupera información sobre las posiciones abiertas. La variable CurrentTick, de tipo MqlTick, contiene la cotización más reciente (precio bid (compra), precio ask (venta) y time (hora)), lo que garantiza que la lógica de negociación se base siempre en datos de mercado en tiempo real.
La estructura de la sesión de negociación organiza la información clave relacionada con la sesión, como las horas de inicio y fin, los precios máximos y mínimos durante la sesión, y las banderas que permiten comprobar si ya se ha producido una ruptura. Se declara una instancia de esta estructura, «CurrentSession», para almacenar los datos de la sesión correspondientes a la jornada de negociación actual. Además, se configuran variables para la gestión de la volatilidad: «UpperVolatilityStop» y «LowerVolatilityStop» marcan stops dinámicos basados en el ATR, mientras que «ATRHandle» almacena el identificador del indicador necesario para acceder a los valores del ATR. En conjunto, estas variables constituyen la base para la detección de rupturas y la ejecución de operaciones.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Validate input parameters if(RiskPerTrade <= 0 || RiskPerTrade > 5) { Alert("Risk per trade must be between 0.1 and 5.0"); return INIT_PARAMETERS_INCORRECT; } // Initialize ATR indicator ATRHandle = iATR(_Symbol, _Period, ATRPeriod); if(ATRHandle == INVALID_HANDLE) { Alert("Failed to create ATR indicator"); return INIT_FAILED; } TradeManager.SetExpertMagicNumber(ExpertMagicNumber); // Calculate initial session CalculateTradingSession(); return INIT_SUCCEEDED; }
La función OnInit() se ejecuta al iniciarse el asesor experto y prepara el sistema para operar. En primer lugar, comprueba los parámetros de entrada para asegurarse de que la configuración de riesgo se encuentra dentro de un rango aceptable; de lo contrario, el EA detendrá la inicialización con un error. Luego inicializa el indicador ATR creando un identificador para los cálculos de volatilidad y, si esto falla, el EA se cierra. A continuación, se asigna al TradeManager el número único «ExpertMagicNumber» para que las operaciones se puedan seguir correctamente y, por último, el EA llama a la función CalculateTradingSession() para configurar la sesión de negociación inicial antes de devolver un estado de éxito.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up indicators if(ATRHandle != INVALID_HANDLE) { IndicatorRelease(ATRHandle); } // Delete all graphical objects ObjectsDeleteAll(0, -1, -1); }
La función OnDeinit() se ejecuta cuando se elimina o se detiene el EA. Esta libera el indicador ATR para liberar recursos y elimina todos los objetos gráficos del gráfico para garantizar que no queden dibujos residuales.
//+------------------------------------------------------------------+ //| Check if we need to calculate a new session for the day | //+------------------------------------------------------------------+ void CheckForNewSession() { MqlDateTime currentTime; TimeToStruct(CurrentTick.time, currentTime); MqlDateTime lastCalcTime; TimeToStruct(CurrentSession.LastCalculationDate, lastCalcTime); // Check if we're in a new day or if session hasn't been calculated yet if (currentTime.day != lastCalcTime.day || currentTime.mon != lastCalcTime.mon || currentTime.year != lastCalcTime.year || CurrentSession.LastCalculationDate == 0) { CalculateTradingSession(); } }
La función CheckForNewSession() garantiza que se calcule una nueva sesión de negociación al inicio de cada nuevo día. Convierte la hora del tick actual y la hora del último cálculo de la sesión en estructuras de fecha y, a continuación, compara el día, el mes y el año. Si la fecha actual difiere de la fecha de la última sesión registrada, o si aún no se ha establecido ninguna sesión, llama a la función CalculateTradingSession() para restablecer y preparar una nueva sesión.
//+------------------------------------------------------------------+ //| Calculate trading session for the day | //+------------------------------------------------------------------+ void CalculateTradingSession() { // Reset session variables CurrentSession.SessionStart = 0; CurrentSession.SessionEnd = 0; CurrentSession.SessionClose = 0; CurrentSession.SessionHigh = 0; CurrentSession.SessionLow = DBL_MAX; CurrentSession.IsActive = false; CurrentSession.HighBreakoutTriggered = false; CurrentSession.LowBreakoutTriggered = false; // Calculate session times int timeCycle = 86400; // Seconds in a day CurrentSession.SessionStart = (CurrentTick.time - (CurrentTick.time % timeCycle)) + RangeStartTime * 60; // Adjust for weekends for(int i = 0; i < 8; i++) { MqlDateTime tmp; TimeToStruct(CurrentSession.SessionStart, tmp); int dayOfWeek = tmp.day_of_week; if(CurrentTick.time >= CurrentSession.SessionStart || dayOfWeek == 0 || dayOfWeek == 6) { CurrentSession.SessionStart += timeCycle; } } // Calculate session end time CurrentSession.SessionEnd = CurrentSession.SessionStart + (RangeDuration * 60); for(int i = 0; i < 2; i++) { MqlDateTime tmp; TimeToStruct(CurrentSession.SessionEnd, tmp); int dayOfWeek = tmp.day_of_week; if(dayOfWeek == 0 || dayOfWeek == 6) { CurrentSession.SessionEnd += timeCycle; } } // Calculate session close time CurrentSession.SessionClose = (CurrentSession.SessionEnd - (CurrentSession.SessionEnd % timeCycle)) + RangeCloseTime * 60; for(int i = 0; i < 3; i++) { MqlDateTime tmp; TimeToStruct(CurrentSession.SessionClose, tmp); int dayOfWeek = tmp.day_of_week; if(CurrentSession.SessionClose <= CurrentSession.SessionEnd || dayOfWeek == 0 || dayOfWeek == 6) { CurrentSession.SessionClose += timeCycle; } } // Set last calculation date CurrentSession.LastCalculationDate = CurrentTick.time; // Draw session objects DrawSessionObjects(); }
La función CalculateTradingSession() configura la sesión de negociación diaria restableciendo todas las variables relacionadas con la sesión y calculando las horas correctas de inicio, fin y cierre en función de los parámetros introducidos por el usuario. Garantiza que las sesiones se ajusten a los ciclos diarios, omitiendo los fines de semana, de modo que las operaciones solo se realicen en días hábiles de mercado. La función también restablece las banderas de estado de ruptura, actualiza la fecha de cálculo de la sesión y llama a la función DrawSessionObjects() para mostrar los límites y los niveles de la sesión en el gráfico.
//+------------------------------------------------------------------+ //| Update trading session with current price data | //+------------------------------------------------------------------+ void UpdateTradingSession() { if(CurrentTick.time >= CurrentSession.SessionStart && CurrentTick.time < CurrentSession.SessionEnd) { CurrentSession.IsActive = true; // Update session high if(CurrentTick.ask > CurrentSession.SessionHigh) { CurrentSession.SessionHigh = CurrentTick.ask; } // Update session low if(CurrentTick.bid < CurrentSession.SessionLow) { CurrentSession.SessionLow = CurrentTick.bid; } // Draw session on chart DrawSessionObjects(); } }
La función UpdateTradingSession() actualiza continuamente los datos de la sesión actual a medida que se reciben nuevos ticks de precios. Activa la sesión cuando la hora actual se encuentra dentro del periodo de la sesión y, a continuación, realiza un seguimiento del precio ask (venta) más alto y del precio bid (compra) más bajo para actualizar el máximo y el mínimo de la sesión. Por último, llama a la función DrawSessionObjects() para reflejar visualmente estos niveles actualizados en el gráfico.
//+------------------------------------------------------------------+ //| Calculate volatility stops using ATR | //+------------------------------------------------------------------+ void CalculateVolatilityStops() { double atrValue[1]; if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0) { Print("Failed to get ATR value"); return; } // Validate ATR value to prevent "inf" errors if(atrValue[0] <= 0 || !MathIsValidNumber(atrValue[0]) || atrValue[0] > 1000) { Print("Invalid ATR value: ", atrValue[0], ", using default"); atrValue[0] = 10 * SymbolInfoDouble(_Symbol, SYMBOL_POINT); } // Calculate volatility stops UpperVolatilityStop = CurrentSession.SessionHigh + (atrValue[0] * ATRMultiplier); LowerVolatilityStop = CurrentSession.SessionLow - (atrValue[0] * ATRMultiplier); // Draw volatility stops on chart DrawVolatilityStops(); }
La función CalculateVolatilityStops() calcula los stops dinámicos de volatilidad superior e inferior en función de la volatilidad del mercado utilizando el indicador ATR. En primer lugar, recupera el último valor de ATR, lo valida para evitar errores y, si es necesario, utiliza un valor seguro como alternativa. A continuación, la función calcula los stops de volatilidad superior e inferior sumando el ATR (multiplicado por ATRMultiplier) al máximo de la sesión y restándolo del mínimo, y llama a la función DrawVolatilityStops() para mostrar estos niveles en el gráfico.
//+------------------------------------------------------------------+ //| Check for breakouts and execute trades | //+------------------------------------------------------------------+ void CheckForBreakouts() { // Only check after session formation period if(CurrentTick.time < CurrentSession.SessionEnd || !CurrentSession.IsActive) { return; } // Check for high breakout if(!CurrentSession.HighBreakoutTriggered && CurrentTick.ask >= CurrentSession.SessionHigh) { CurrentSession.HighBreakoutTriggered = true; // Check if price has cleared volatility stop if(CurrentTick.ask >= UpperVolatilityStop) { // Regular breakout - execute buy ExecuteTrade(ORDER_TYPE_BUY, "Session High Breakout"); } else { // False breakout - execute sell (fade) ExecuteTrade(ORDER_TYPE_SELL, "False Breakout - Fade High"); } } // Check for low breakout if(!CurrentSession.LowBreakoutTriggered && CurrentTick.bid <= CurrentSession.SessionLow) { CurrentSession.LowBreakoutTriggered = true; // Check if price has cleared volatility stop if(CurrentTick.bid <= LowerVolatilityStop) { // Regular breakout - execute sell ExecuteTrade(ORDER_TYPE_SELL, "Session Low Breakout"); } else { // False breakout - execute buy (fade) ExecuteTrade(ORDER_TYPE_BUY, "False Breakout - Fade Low"); } } }
En este caso, la función CheckForBreakouts() supervisa la evolución de los precios para identificar posibles oportunidades de ruptura una vez que la sesión se ha formado y está activa. Comprueba si el precio supera el máximo de la sesión o cae por debajo del mínimo y garantiza que cada ruptura solo se active una vez por sesión. Las operaciones se ejecutan en función de si el precio rebasa el umbral de volatilidad correspondiente: una ruptura confirmada desencadena una operación en la dirección de la ruptura, mientras que una falsa ruptura —en la que el precio no rebasa el umbral de volatilidad— desencadena una operación en la dirección opuesta a la ruptura.
//+------------------------------------------------------------------+ //| Execute trade with proper risk management | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment) { // Calculate position size based on risk double lotSize = CalculatePositionSize(); if(lotSize <= 0) { Print("Failed to calculate position size"); return; } // Calculate stop loss and take profit double stopLoss = 0.0; double takeProfit = 0.0; CalculateStopLevels(orderType, stopLoss, takeProfit); // Validate stop levels if(!ValidateStopLevels(orderType, stopLoss, takeProfit)) { Print("Invalid stop levels - trade not executed"); return; } // Execute trade if(orderType == ORDER_TYPE_BUY) { TradeManager.Buy(lotSize, _Symbol, CurrentTick.ask, stopLoss, takeProfit, comment); } else { TradeManager.Sell(lotSize, _Symbol, CurrentTick.bid, stopLoss, takeProfit, comment); } // Check result if(TradeManager.ResultRetcode() != TRADE_RETCODE_DONE) { Print("Trade execution failed: ", TradeManager.ResultRetcodeDescription()); } }
La función ExecuteTrade() se encarga de abrir una operación con una gestión adecuada del riesgo. En primer lugar, calcula el tamaño de la posición mediante la función CalculatePositionSize(); a continuación, determina los niveles adecuados de stop loss y take profit mediante la función CalculateStopLevels(). Tras validar estos niveles con ValidateStopLevels(), la función ejecuta una orden de compra o venta mediante el objeto TradeManager y registra un mensaje de error si la operación falla.
//+------------------------------------------------------------------+ //| Calculate position size based on risk percentage | //+------------------------------------------------------------------+ double CalculatePositionSize() { double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(accountBalance <= 0 || tickSize <= 0 || tickValue <= 0 || point <= 0) { return 0.0; } // Get ATR value for stop loss distance double atrValue[1]; if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0) { return 0.0; } // Validate ATR value if(atrValue[0] <= 0 || !MathIsValidNumber(atrValue[0])) { return 0.0; } // Calculate stop distance in points double stopDistance = atrValue[0] * StopLossMultiplier / point; // Calculate risk amount double riskAmount = accountBalance * (RiskPerTrade / 100.0); // Calculate position size double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double lotSize = (riskAmount / (stopDistance * tickValue)) * tickSize; // Normalize lot size lotSize = MathFloor(lotSize / lotStep) * lotStep; // Apply min/max limits double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); lotSize = MathMax(minLot, MathMin(maxLot, lotSize)); return lotSize; }
Esta función determina el volumen de operación adecuado en función del saldo de la cuenta, el porcentaje de riesgo definido por el usuario y las condiciones actuales del mercado. Recupera datos específicos del instrumento, como el tamaño del tick, el valor del tick y el tamaño del punto, y a continuación calcula la distancia del stop utilizando el valor del indicador ATR multiplicado por StopLossMultiplier. A partir del importe de riesgo calculado, determina el tamaño del lote, lo ajusta al incremento de volumen permitido por el bróker y se asegura de que se encuentre dentro de los límites de volumen mínimo y máximo antes de devolver el tamaño definitivo de la posición.
//+------------------------------------------------------------------+ //| Calculate stop loss and take profit levels | //+------------------------------------------------------------------+ void CalculateStopLevels(ENUM_ORDER_TYPE orderType, double &stopLoss, double &takeProfit) { // Use range-based stops double rangeSize = CurrentSession.SessionHigh - CurrentSession.SessionLow; if(orderType == ORDER_TYPE_BUY) { stopLoss = CurrentTick.bid - (rangeSize * StopLossMultiplier / 100.0); takeProfit = CurrentTick.ask + (rangeSize * TakeProfitMultiplier / 100.0); } else { stopLoss = CurrentTick.ask + (rangeSize * StopLossMultiplier / 100.0); takeProfit = CurrentTick.bid - (rangeSize * TakeProfitMultiplier / 100.0); } // Normalize prices stopLoss = NormalizeDouble(stopLoss, _Digits); takeProfit = NormalizeDouble(takeProfit, _Digits); } //+------------------------------------------------------------------+ //| Validate stop loss and take profit levels | //+------------------------------------------------------------------+ bool ValidateStopLevels(ENUM_ORDER_TYPE orderType, double stopLoss, double takeProfit) { // Check if stop levels are valid double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double minStopDistance = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point; if(orderType == ORDER_TYPE_BUY) { if(CurrentTick.ask - stopLoss < minStopDistance) { Print("Stop loss too close for buy order"); return false; } if(takeProfit - CurrentTick.ask < minStopDistance) { Print("Take profit too close for buy order"); return false; } } else { if(stopLoss - CurrentTick.bid < minStopDistance) { Print("Stop loss too close for sell order"); return false; } if(CurrentTick.bid - takeProfit < minStopDistance) { Print("Take profit too close for sell order"); return false; } } return true; }
La función CalculateStopLevels() determina los niveles de stop loss y take profit de una operación en función del rango de la sesión actual. En el caso de las órdenes de compra, el stop loss se establece por debajo del precio bid (compra) actual y el take profit por encima del precio ask (venta) actual, con distancias ajustadas mediante multiplicadores definidos por el usuario; en el caso de las órdenes de venta, la lógica se invierte. Ambos niveles se normalizan según la precisión decimal del instrumento para garantizar la exactitud en la ejecución.
La función ValidateStopLevels() garantiza que los niveles calculados de stop loss y take profit cumplan con los requisitos de distancia mínima del bróker. Comprueba que la distancia entre el precio de entrada y los niveles de stop sea mayor que el valor de SYMBOL_TRADE_STOPS_LEVEL multiplicado por el tamaño del punto, lo que evita órdenes no válidas o con niveles demasiado próximos al precio de entrada. Si los niveles de stop están demasiado cerca del precio de entrada, la función devuelve false y muestra un mensaje de error, lo que garantiza que solo se ejecuten operaciones válidas.
//+------------------------------------------------------------------+ //| Manage trailing stops for open positions | //+------------------------------------------------------------------+ void ManageTrailingStops() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == ExpertMagicNumber) { ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentStop = PositionGetDouble(POSITION_SL); double currentProfit = PositionGetDouble(POSITION_PROFIT); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); // Calculate profit in pips double profitInPips = 0; if(positionType == POSITION_TYPE_BUY) { profitInPips = (CurrentTick.bid - openPrice) / point; } else { profitInPips = (openPrice - CurrentTick.ask) / point; } // Check if we should move to breakeven if(profitInPips >= BreakEvenAtPips && currentStop != openPrice) { if(positionType == POSITION_TYPE_BUY) { TradeManager.PositionModify(ticket, openPrice, PositionGetDouble(POSITION_TP)); } else { TradeManager.PositionModify(ticket, openPrice, PositionGetDouble(POSITION_TP)); } } // Check if we should start trailing if(profitInPips >= TrailStartAtPips) { double newStop = 0; if(positionType == POSITION_TYPE_BUY) { newStop = CurrentTick.bid - (TrailStepPips * point); // Only move stop if it's higher than current if(newStop > currentStop || currentStop == 0) { TradeManager.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)); } } else { newStop = CurrentTick.ask + (TrailStepPips * point); // Only move stop if it's lower than current if(newStop < currentStop || currentStop == 0) { TradeManager.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)); } } } } } } //+------------------------------------------------------------------+ //| Draw session objects on chart | //+------------------------------------------------------------------+ void DrawSessionObjects() { // Draw session high ObjectDelete(0, "SessionHigh"); ObjectCreate(0, "SessionHigh", OBJ_HLINE, 0, 0, CurrentSession.SessionHigh); ObjectSetInteger(0, "SessionHigh", OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, "SessionHigh", OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, "SessionHigh", OBJPROP_WIDTH, 2); // Draw session low ObjectDelete(0, "SessionLow"); ObjectCreate(0, "SessionLow", OBJ_HLINE, 0, 0, CurrentSession.SessionLow); ObjectSetInteger(0, "SessionLow", OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, "SessionLow", OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, "SessionLow", OBJPROP_WIDTH, 2); // Draw session start time ObjectDelete(0, "SessionStart"); ObjectCreate(0, "SessionStart", OBJ_VLINE, 0, CurrentSession.SessionStart, 0); ObjectSetInteger(0, "SessionStart", OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, "SessionStart", OBJPROP_WIDTH, 2); // Draw session end time ObjectDelete(0, "SessionEnd"); ObjectCreate(0, "SessionEnd", OBJ_VLINE, 0, CurrentSession.SessionEnd, 0); ObjectSetInteger(0, "SessionEnd", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "SessionEnd", OBJPROP_WIDTH, 2); // Draw session close time ObjectDelete(0, "SessionClose"); ObjectCreate(0, "SessionClose", OBJ_VLINE, 0, CurrentSession.SessionClose, 0); ObjectSetInteger(0, "SessionClose", OBJPROP_COLOR, clrDarkRed); ObjectSetInteger(0, "SessionClose", OBJPROP_WIDTH, 2); } //+------------------------------------------------------------------+ //| Draw volatility stops on chart | //+------------------------------------------------------------------+ void DrawVolatilityStops() { // Draw upper volatility stop ObjectDelete(0, "VolatilityStopUpper"); ObjectCreate(0, "VolatilityStopUpper", OBJ_HLINE, 0, 0, UpperVolatilityStop); ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_WIDTH, 1); // Draw lower volatility stop ObjectDelete(0, "VolatilityStopLower"); ObjectCreate(0, "VolatilityStopLower", OBJ_HLINE, 0, 0, LowerVolatilityStop); ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_WIDTH, 1); }
La función ManageTrailingStops() garantiza que las operaciones activas se gestionen de forma dinámica a medida que pasan a generar beneficios. Recorre todas las posiciones, filtra aquellas que pertenecen al EA por símbolo y número mágico, y a continuación calcula las ganancias en pips. En función de las condiciones, puede desplazar el stop loss hasta el punto de equilibrio una vez que se alcance un umbral de beneficios determinado, o bien aplicar un trailing stop para asegurar los beneficios a medida que la operación evoluciona a favor del trader. Esto contribuye a reducir el riesgo y a proteger los beneficios sin necesidad de intervención manual.
La función DrawSessionObjects() representa visualmente la sesión de negociación actual en el gráfico. Crea líneas horizontales que indican el máximo y el mínimo de la sesión, y líneas verticales que marcan el inicio, el final y el cierre de la sesión, cada una con colores y estilos distintos. Esto permite a los traders confirmar visualmente y con facilidad los límites de la sesión y los niveles clave directamente en su gráfico.
Por último, la función DrawVolatilityStops() traza en el gráfico los stops de volatilidad calculados. Una línea punteada verde representa el umbral de volatilidad superior, mientras que una línea punteada roja representa el inferior. Estas líneas ofrecen puntos de referencia visuales claros para validar las rupturas y detectar las falsas rupturas, lo que mejora la toma de decisiones al alinear la evolución del precio con la dinámica de la volatilidad.
Resultados de la prueba con datos históricos
La prueba con datos históricos se realizó en el marco temporal de una hora (1H), durante un periodo de aproximadamente dos meses (del 7 de julio de 2025 al 8 de septiembre de 2025), con los siguientes parámetros:



Conclusión
En resumen, hoy hemos desarrollado un sistema de ruptura basado en la volatilidad que combina la detección del rango de la sesión, el análisis de la volatilidad mediante el ATR y una gestión estructurada de las operaciones. El sistema comienza identificando las sesiones de negociación diarias, registrando los máximos y mínimos de cada sesión y calculando los stops de volatilidad para distinguir entre rupturas reales y falsas. A continuación, incorpora una lógica de detección de rupturas, ejecuta operaciones con la colocación dinámica de stop loss y take profit, y aplica una sólida gestión del riesgo mediante el cálculo del tamaño de las posiciones, ajustes del stop loss al punto de equilibrio y trailing stops. También se han integrado elementos visuales, como los marcadores de sesión y las líneas de los stops de volatilidad, para ofrecer una visión clara de las condiciones de negociación directamente en el gráfico.
En conclusión, este sistema ayuda a los traders a evitar los errores habituales de las estrategias tradicionales de ruptura, como entrar en operaciones basándose en movimientos falsos sin tener en cuenta la volatilidad del mercado. Aprovechando los stops de volatilidad basados en el ATR, se garantiza que las operaciones se ajusten a la dinámica real del mercado, en lugar de a niveles arbitrarios. La incorporación de controles de riesgo automatizados y de elementos visuales en el gráfico mejora aún más la toma de decisiones y proporciona a los traders un marco disciplinado, transparente y más fiable para operar con rupturas, capaz de adaptarse a las distintas condiciones del mercado.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19459
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.
Desarrollo de estrategias de trading con los osciladores Parafrac y Parafrac V2: análisis del rendimiento de una única entrada
Ecuación simbólica de previsión del precio con SymPy
Modificación del algoritmo de optimización de Dingo — Dingo Optimization Algorithm M (DOAm)
Simplificación de bases de datos en MQL5 (Parte 2): Uso de la metaprogramación para crear entidades
- 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