English Русский Deutsch 日本語
preview
Desarrollo de un sistema de ruptura basado en la volatilidad

Desarrollo de un sistema de ruptura basado en la volatilidad

MetaTrader 5 — Trading |
144 0
Hlomohang John Borotho
Hlomohang John Borotho

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

Archivos adjuntos |
Desarrollo de estrategias de trading con los osciladores Parafrac y Parafrac V2: análisis del rendimiento de una única entrada Desarrollo de estrategias de trading con los osciladores Parafrac y Parafrac V2: análisis del rendimiento de una única entrada
Este artículo presenta el oscilador ParaFrac y su modelo V2 como herramientas de negociación. En él se describen tres estrategias de negociación desarrolladas a partir de estos indicadores. Cada estrategia se sometió a pruebas y se optimizó para identificar sus puntos fuertes y débiles. El análisis comparativo puso de manifiesto las diferencias de rendimiento entre el oscilador Parafrac original y su modelo V2.
Ecuación simbólica de previsión del precio con SymPy Ecuación simbólica de previsión del precio con SymPy
El artículo describe un enfoque interesante del trading algorítmico basado en ecuaciones matemáticas simbólicas, en lugar de las tradicionales «cajas negras» del aprendizaje automático. El autor muestra cómo transformar redes neuronales opacas en ecuaciones matemáticas legibles utilizando la biblioteca SymPy y la regresión polinómica, lo que permite comprender plenamente la lógica que subyace a las decisiones de trading. Este enfoque combina la potencia computacional del aprendizaje automático con la transparencia de los métodos clásicos, lo que permite a los traders analizar, ajustar y adaptar los modelos en tiempo real.
Modificación del algoritmo de optimización de Dingo — Dingo Optimization Algorithm M (DOAm) Modificación del algoritmo de optimización de Dingo — Dingo Optimization Algorithm M (DOAm)
La modificación del algoritmo de Dingo propuesta por el autor en este artículo ha elevado considerablemente el listón en la búsqueda del mejor de los mejores algoritmos de optimización. ¿Es posible obtener resultados aún mejores?
Simplificación de bases de datos en MQL5 (Parte 2): Uso de la metaprogramación para crear entidades Simplificación de bases de datos en MQL5 (Parte 2): Uso de la metaprogramación para crear entidades
Hoy hemos analizado el uso avanzado de #define para la metaprogramación en MQL5, creando entidades que representan tablas y metadatos de columnas (tipo, clave primaria, autoincremento, posibilidad de valores nulos, etc.). Además, hemos centralizado estas definiciones en el archivo TickORM.mqh, automatizando la generación de clases de metadatos y allanando el camino para una manipulación eficiente de los datos por parte del ORM, sin necesidad de escribir SQL manualmente.