English Deutsch 日本語
preview
Разработка пробойной торговой системы на основе волатильности

Разработка пробойной торговой системы на основе волатильности

MetaTrader 5Трейдинг |
87 0
Hlomohang John Borotho
Hlomohang John Borotho

Введение

Многие традиционные пробойные системы сталкиваются с такими проблемами, как частые ложные сигналы, когда цена на короткое время пробивает уровень поддержки или сопротивления, а затем разворачивается обратно в существующий диапазон. Эти ложные пробои часто происходят в условиях низкой волатильности или в периоды рыночного шума, что приводит к срабатыванию стоп-лоссов, снижению прибыльности и разочарованию трейдеров. Без учета меняющихся рыночных условий статичные пробойные стратегии могут испытывать трудности с адаптацией, что делает их ненадежными на разных этапах движения рынка.

Пробойная система, основанная на волатильности, решает эту проблему с помощью фильтров волатильности, таких как средний истинный диапазон (ATR, Average True Range), для измерения силы рынка и динамической корректировки условий пробоя. Требуя, чтобы цена вышла за пределы как диапазона колебаний, так и порога волатильности, система помогает отличать подлинные пробои от рыночного шума. Это обеспечивает заключение сделок только при наличии достаточного импульса, что повышает точность, улучшает управление рисками и укрепляет общую стабильность результатов.



Планирование и логика

Пробой на покупку при необъяснимой волатильности:

Пробой на покупку при объяснимой волатильности:

Пробой на продажу при необъяснимой волатильности:

Пробой на продажу при объяснимой волатильности:

Логика принятия решений при пробоях




Начало работы

//+------------------------------------------------------------------+
//|                                          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>

Как обычно, начнём с включения необходимых для торговли библиотек на языке MQL5: #include <Trade\Trade.mqh> и #include <Trade\PositionInfo.mqh>. Первая из них предоставляет доступ к классу CTrade, который позволяет советнику исполнять, изменять и закрывать торговые ордера, а вторая — к классу CPositionInfo, позволяющему советнику получать подробную информацию об активных позициях, такую как данные о типе ордера, размере лота, уровнях стоп-лосса и тейк-профита.

//+------------------------------------------------------------------+
//|                         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

Здесь мы определяем входные параметры, которые трейдер может настроить при использовании советника. Первая группа параметров, «Настройки диапазона» (Range Settings), задает временной интервал для формирования ценового диапазона: RangeStartTime указывает, когда начинается диапазон (в минутах от полуночи), RangeDuration определяет продолжительность диапазона, а RangeCloseTime определяет, когда сессия будет закрыта. Эти настройки позволяют советнику выстраивать логику пробоя в зависимости от конкретных торговых часов, что особенно полезно для установления целей активных торговых сессий.

Вторая и третья группы настраивают правила торговли и волатильности системы. Настройки торговли позволяют контролировать управление рисками и торговлей, например, параметром RiskPerTrade для определения размера позиции, множителями для стоп-лосса и тейк-профита в зависимости от размера диапазона, а также логикой трейлинг-стопа (BreakEvenAtPips, TrailStartAtPips и TrailStepPips). Между тем, в настройках волатильности используется индикатор ATR с заданными пользователем значениями ATRPeriod и ATRMultiplier для расчета стоп-уровней на основе волатильности. Это обеспечивает динамическую адаптацию пробойных сделок к меняющимся рыночным условиям, снижая вероятность ложных сигналов.

//+------------------------------------------------------------------+
//| 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;

В этом разделе кода объявляются глобальные переменные, которые будут использоваться везде в советнике. Уникальное магическое число советника (ExpertMagicNumber) позволяет идентифицировать сделки, открытые этим советником, чтобы их можно было отличить от сделок, совершенных вручную, или сделок, открытых другими советниками. Объект CTrade TradeManager отвечает за исполнение сделок и управление ими, а объект CPositionInfo PositionInfo получает подробную информацию об открытых позициях. Переменная MqlTick CurrentTick содержит самую последнюю котировку цены (цена покупки, цена продажи и время), гарантируя, что торговая логика всегда будет основана на данных рынка в режиме реального времени.

Структура торговой сессии систематизирует такую ключевую информацию, относящуюся к сессии, как время ее начала и окончания, максимальные и минимальные цены за сессию, а также отметки, позволяющие отслеживать, произошел ли уже пробой. Экземпляр этой структуры, CurrentSession, объявляется для хранения данных о торговой сессии за текущий торговый день. Кроме того, настроены переменные для управления волатильностью: UpperVolatilityStop и LowerVolatilityStop устанавливают динамические пороговые значения на основе ATR, а ATRHandle хранит дескриптор индикатора, который необходим для доступа к значениям ATR. В совокупности эти переменные обеспечивают основу для обнаружения пробоев и исполнения сделок.

//+------------------------------------------------------------------+
//| 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;
}

Функция OnInit() исполняется при запуске советника и подготавливает систему к торговле. Во-первых, она проверяет входные параметры, чтобы убедиться, что уровень риска находится в допустимом диапазоне; в противном случае советник остановится и выдаст ошибку. Затем она инициализирует индикатор ATR, создавая хэндл для расчетов волатильности, и если это не удается, советник завершает работу. Далее, TradeManager получает уникальный ExpertMagicNumber, чтобы сделки можно было корректно отслеживать, и, наконец, советник вызывает CalculateTradingSession() для настройки исходной торговой сессии, после чего возвращает статус успешного завершения.

//+------------------------------------------------------------------+
//| 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);
}

Функция OnDeinit() запускается при удалении или остановке советника. Эта функция выпускает индикатор ATR, чтобы освободить ресурсы, и удаляет с графика все графические объекты, чтобы исключить появление лишних отрисовок.

//+------------------------------------------------------------------+
//| 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();
   }
}

Функция CheckForNewSession() гарантирует, что новая торговая сессия будет рассчитываться в начале каждого нового дня. Она преобразует текущее время тика и время последнего расчета сессии в структуры дат, а затем сравнивает день, месяц и год. Если текущая дата отличается от даты последней зарегистрированной торговой сессии или если торговая сессия еще не была начата, вызывается функция CalculateTradingSession() для сброса и подготовки новой торговой сессии.

//+------------------------------------------------------------------+
//| 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();
}

Функция CalculateTradingSession() настраивает дневную торговую сессию, сбрасывая все связанные с сессией переменные и рассчитывая правильное время начала, окончания и закрытия на основе введенных пользователем данных. Она обеспечивает соответствие торговых сессий суточным циклам, пропуская выходные дни, поэтому торговля будет осуществляться только в рабочие дни рынка. Эта функция также сбрасывает флаги пробоя, обновляет дату расчета сессии и вызывает функцию DrawSessionObjects() для визуального отображения границ и уровней сессии на графике.

//+------------------------------------------------------------------+
//| 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();
   }
}

Функция UpdateTradingSession() непрерывно обновляет данные текущей сессии по мере поступления новых ценовых тиков. Она активирует сессию, когда текущее время находится в пределах периода сессии, а затем отслеживает максимальную цену предложения и минимальную цену покупки для обновления максимальной и минимальной цен сессии. Наконец, она вызывает метод DrawSessionObjects(), чтобы визуально отобразить эти обновленные уровни на графике.

//+------------------------------------------------------------------+
//| 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();
}

Функция CalculateVolatilityStops() вычисляет динамические верхний и нижний стоп-уровни на основе волатильности рынка с использованием индикатора ATR. Сначала она получает последнее значение ATR, проверяет его во избежание ошибок и при необходимости устанавливает безопасное значение по умолчанию. Затем функция вычисляет верхний и нижний уровни волатильности, прибавляя или вычитая значения ATR (масштабированные с помощью ATRMultiplier) из максимальной и минимальной цен торговой сессии, и вызывает функцию DrawVolatilityStops() для отображения этих уровней на графике.

//+------------------------------------------------------------------+
//| 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");
      }
   }
}

Здесь функция CheckForBreakouts() отслеживает движение цены, чтобы выявить потенциальные возможности для пробоя после того, как торговая сессия сформировалась и стала активной. Он проверяет, пересекает ли цена максимум или минимум сессии, и гарантирует, что каждый пробой срабатывает только один раз за сессию. Сделки исполняются в зависимости от того, превышает ли цена соответствующий стоп-уровень по волатильности: подтвержденный пробой запускает сделку в направлении этого пробоя, а ложный пробой, когда цена не преодолевает порог волатильности, запускает сделку на снижение в противоположном направлении.

//+------------------------------------------------------------------+
//| 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());
   }
}

Функция ExecuteTrade() отвечает за открытие сделки с надлежащим управлением рисками. Сначала она рассчитывает размер позиции с помощью функции CalculatePositionSize(), затем определяет соответствующие уровни стоп-лосса и тейк-профита с помощью функции CalculateStopLevels(). После проверки этих уровней с помощью функции ValidateStopLevels(), функция исполняет ордер на покупку или продажу, используя объект TradeManager, и регистрирует сообщение об ошибке, если сделка не удалась.

//+------------------------------------------------------------------+
//| 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;
}

Эта функция определяет объем сделок в соответствии с остатками средств на счете, заданным пользователем процентом риска и текущими рыночными условиями. Она извлекает относящиеся к конкретному символу данные, такие как размер тика, значение тика и размер точки, а затем вычисляет расстояние до стоп-уровня, используя индикатор ATR, масштабированный с помощью StopLossMultiplier. Используя рассчитанный уровень риска, функция вычисляет размер лота, нормализует его в соответствии с разрешенным брокером шагом и проверяет, соответствует ли он минимальным и максимальным ограничениям по объему, прежде чем вернуть окончательный размер позиции.

//+------------------------------------------------------------------+
//| 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;
}

Функция CalculateStopLevels() определяет уровни стоп-лосса и тейк-профита для сделки на основе диапазона текущей торговой сессии. Для ордеров на покупку стоп-лосс устанавливается ниже текущей цены bid, а тейк-профит — выше текущей цены ask, масштабированных заданными пользователем множителями; для ордеров на продажу логика обратная. Оба уровня нормализованы с учетом десятичной точности инструмента для обеспечения точности исполнения.

Функция ValidateStopLevels() обеспечивает соответствие рассчитанных уровней стоп-лосса и тейк-профита минимальным требованиям брокера к минимальному расстоянию до стоп-уровней. Она проверяет, превышает ли расстояние между ценой входа и стоп-уровнями значение SYMBOL_TRADE_STOPS_LEVEL, умноженное на размер точки, что предотвращает недействительные ордера или ордера со слишком узким диапазоном. Если стоп-уровни расположены слишком близко, функция возвращает false и выводит сообщение об ошибке, гарантируя исполнение только действительных сделок.

//+------------------------------------------------------------------+
//| 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);
}

Функция ManageTrailingStops() обеспечивает динамическое управление активными сделками по мере их перехода в стадию получения прибыли. Она перебирает все позиции, фильтрует их по символу и магическому числу, отбирая позиции, принадлежащие советнику, а затем рассчитывает прибыль в пипсах. В зависимости от условий, она может перемещать стоп-лосс в точку безубыточности после достижения определенного порога прибыли, или применить трейлинг-стоп для фиксации прибыли по мере дальнейшего развития сделки в пользу трейдера. Это помогает снизить риски и защитить прибыль без необходимости вмешиваться, действуя вручную.

Функция DrawSessionObjects() визуально отображает на графике текущую торговую сессию. Она создает горизонтальные линии для максимальной и минимальной цен сессии, а также вертикальные линии для начала, конца и завершения сессии, причем каждая из этих линий имеет свой собственный цвет и стиль. Это позволяет трейдерам легко визуально подтверждать непосредственно на графике границы торговых сессий и ключевые уровни.

Наконец, функция DrawVolatilityStops() отображает на графике рассчитанные стоп-уровни, основанные на волатильности. Зеленая пунктирная линия обозначает верхний стоп-уровень волатильности, а красная пунктирная линия — нижний. Эти линии обеспечивают четкие визуальные ориентиры для подтверждения пробоя и обнаружения ложных пробоев, улучшая процесс принятия решений за счет согласования ценового движения с динамикой волатильности.



Результаты тестирования на истории

Тестирование на истории оценивалось на таймфрейме 1H на протяжении примерно 2-месячного периода тестирования (с 7 июля 2025 года по 8 сентября 2025 года) со следующими настройками:


Заключение

Подытоживая все вышеописанное: мы разработали пробойную систему на основе волатильности, объединив определение диапазона торговой сессии, анализ волатильности с использованием ATR и структурированное управление сделками. Система начинает работу с определения ежедневных торговых сессий, отслеживания максимумов и минимумов сессий, а также расчета стоп-уровней волатильности для различения подлинных и ложных пробоев. Затем система включает в себя логику обнаружения пробоев, совершает сделки с динамическим размещением стоп-лосса и тейк-профита, а также применяет надежное управление рисками посредством определения размера позиции, корректировки точки безубыточности и использования трейлинг-стопов. Кроме того, для обеспечения наглядности торговых условий непосредственно на графике были интегрированы такие визуальные элементы, как маркеры сессий и стоп-линии волатильности.

В заключение надо отметить, что эта система помогает трейдерам избежать распространенных ошибок традиционных пробойных стратегий, таких как открытие сделок на ложных движениях без учета волатильности рынка. Используя стоп-уровни волатильности на основе ATR, она обеспечивает соответствие сделок в большей степени реальной рыночной динамике, чем произвольным уровням. Добавление автоматизированных средств контроля рисков и визуализации графиков еще больше совершенствуют процесс принятия решений, предоставляя трейдерам дисциплинированную, прозрачную и более надежную систему пробойной торговли, которая может адаптироваться к различным условиям рынка.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19459

Прикрепленные файлы |
Алгоритм извлечения торговых правил из паттернов в MQL5 Алгоритм извлечения торговых правил из паттернов в MQL5
Статья показывает, как формализовать интуитивно замеченные ценовые паттерны и превратить их в статистически проверенные торговые сигналы. Советник кодирует последовательности баров в бинарные строки U/D и для каждого паттерна вычисляет пять независимых метрик: поддержку, уверенность, лифт, хи-квадрат и байесовскую вероятность. Позиция открывается только тогда, когда текущий паттерн совпадает с историческим правилом и все фильтры пройдены — динамический лот масштабируется по силе сигнала, стоп и тейк рассчитываются через дневной ATR.
Возможности Мастера MQL5, которые вам нужно знать (Часть 65): Использование паттернов FrAMA и индекса силы Возможности Мастера MQL5, которые вам нужно знать (Часть 65): Использование паттернов FrAMA и индекса силы
Фрактальная адаптивная скользящая средняя (FrAMA) и осциллятор индекса силы (Force Index Oscillator) — еще одна пара индикаторов, которые можно использовать совместно в советнике на языке MQL5. Эти два индикатора в некоторой степени дополняют друг друга, поскольку FrAMA — это индикатор следования за трендом, а индекс силы — это осциллятор, основанный на объеме. Как всегда, мы используем Мастер MQL5 для быстрого изучения любого потенциала этих двух инструментов.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Моделирование рынка (Часть 18): Первые шаги на SQL (I) Моделирование рынка (Часть 18): Первые шаги на SQL (I)
Неважно, какую программу SQL мы будем использовать: MySQL, SQL Server, SQLite, OpenSQL или другую. У всех есть что-то общее, а этот общий элемент — язык SQL. Даже если мы не собираемся использовать WorkBench, можно манипулировать или работать с базой данных непосредственно в MetaEditor или через MQL5 для выполнения действий в MetaTrader 5, но для этого вам понадобятся знания SQL. Итак, здесь мы выучим, как минимум, основы.