English
preview
Разработка динамического мультивалютного советника (Часть 8): Ротация капитала в зависимости от времени суток

Разработка динамического мультивалютного советника (Часть 8): Ротация капитала в зависимости от времени суток

MetaTrader 5Примеры |
25 0
Hlomohang John Borotho
Hlomohang John Borotho

Содержание

  1. Введение
  2. Обзор и понимание системы
  3. Приступим
  4. Тестирование на исторических данных
  5. Заключение


Введение

Большинство автоматизированных систем рассматривают каждый торговый час как равнозначный. Они выделяют одинаковый риск и на торговлю в узких диапазонах азиатской сессии, и для лондонских пробойных движений, и для волатильных расширений Нью-Йорка. Такой универсальный подход игнорирует фундаментальную рыночную реальность: у каждой торговой сессии свой характер, свой профиль волатильности и свои характеристики статистического преимущества. Трейдеры нередко видят, как депозиты проседают из-за низковероятных сетапов вне основных часов торговли. Они также упускают движения в ключевые часы, потому что капитал уже связан более ранними убыточными позициями. В результате капитал используется неэффективно, риск распределяется неравномерно, а доходность в течение торгового дня снижается.

Подход к ротации капитала по торговым сессиям решает эту проблему за счет динамического распределения рискового капитала между сессиями на основе их уникального статистического преимущества. Вместо того чтобы торговать по одной и той же схеме весь день, система смещает фокус в зависимости от сессии. Система выделяет меньшую долю азиатским рынкам, движущимся в узком диапазоне, увеличивает экспозицию во время лондонских пробоев и отдает наибольшую часть импульсным движениям Нью-Йорка. Каждая сессия торгует только своими исторически наиболее сильными инструментами (USDJPY и XAUUSD в азиатскую сессию, GBPUSD в лондонскую, а XAUUSD и USDZAR в нью-йоркскую), используя характерную для нее логику пробоя с подтверждением волатильности.

Подход отслеживает использование риска на дневном уровне и на уровне сессий, автоматически сбрасывается в начале каждого нового торгового дня и сохраняет прибыльные позиции при смене сессий, закрывая только убыточные. Такой ротационный подход обеспечивает использование капитала там, где вероятность успеха максимальна, и именно тогда, когда это наиболее важно.


Обзор и понимание системы

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

В этой системе распределение капитала строится по иерархической модели бюджетирования риска. Сначала трейдер задает DailyCapitalPercent (например, 30% от баланса счета), который определяет максимальную совокупную экспозицию по риску на весь торговый день. Затем этот дневной бюджет распределяется между активными сессиями либо поровну (например, 30% ÷ 3 сессии = 10% на сессию), либо вручную (например, Азия 7%, Лондон 8%, Нью-Йорк 15% от дневного бюджета).

Когда появляется сигнал, система рассчитывает остаток риска по сессии (максимум минус уже использованный риск). Затем размер позиции рассчитывается делением остатка риска на произведение стоп-лосса в пипсах и стоимости пипса. Например, если у лондонской сессии остается бюджет 240 долларов, а стоп-лосс составляет 30 пипсов при стоимости 1 доллар за пипс, размер позиции будет 0,08 лота. Это гарантирует, что каждая сделка будет соблюдать лимиты риска как на уровне сессии, так и в рамках дня. При этом размер лота будет автоматически уменьшаться по мере открытия сделок и расходования рискового бюджета сессии независимо от их результата.

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

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


Приступим

//+------------------------------------------------------------------+
//|                                             ToD Cap Rotation.mq5 |
//|                        Git, copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ru/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "Git, copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/johnhlomohang/"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

CTrade trade;
CPositionInfo posInfo;

//--- Input parameters
input string   Symbols                = "XAUUSD,GBPUSD,USDJPY,USDZAR"; // Symbols to monitor

//--- Session activation
input bool     UseAsianSession        = true;
input bool     UseLondonSession       = true;
input bool     UseNewYorkSession      = true;

//--- Session times (broker time, 24h format)
input int      AsianStart             = 0;
input int      AsianEnd               = 8;
input int      LondonStart            = 8;
input int      LondonEnd              = 16;
input int      NYStart                = 13;
input int      NYEnd                  = 22;

//--- Symbols active per session
input string   AsianSymbols           = "USDJPY,XAUUSD";
input string   LondonSymbols          = "GBPUSD";
input string   NYSymbols              = "XAUUSD,USDZAR";

//--- Session Range Parameters
input int      SessionRangeBars       = 100;       // Bars to look back for session high/low
input bool     RequireSessionFormation = true;     // Wait for session to form before trading
input int      SessionFormationMinutes = 60;       // Minutes after session start to start trading

//--- Volatility Stop Parameters
input bool     UseVolatilityStop      = true;      // Use ATR for volatility filter
input int      ATRPeriod              = 14;        // Period for ATR
input double   ATRMultiplier          = 1.5;       // ATR multiplier for volatility stop

// ============================================================
// CAPITAL ALLOCATION SETTINGS
// ============================================================
input double   DailyCapitalPercent    = 30.0;      // Total daily capital to risk (30%)

input bool     UseEqualSplit          = true;      // true = Equal split, false = Manual allocation

//--- Manual allocation (only used if UseEqualSplit = false)
input double   AsianAllocationPercent = 7.0;       // Asian session allocation (% of daily)
input double   LondonAllocationPercent = 8.0;      // London session allocation (% of daily)
input double   NYAllocationPercent    = 15.0;      // NY session allocation (% of daily)

//--- Breakout parameters
input double   StopLossATRMultiplier  = 1.5;       // SL = ATR * multiplier
input double   TakeProfitRR           = 2.0;       // Risk:Reward ratio
input bool     UseDynamicTP           = true;      // Use RR for TP instead of fixed

//--- Trade execution
input int      StopLoss_Pips_Fallback = 30;        // Fallback SL if ATR not available
input int      TakeProfit_Pips_Fallback = 60;      // Fallback TP if not using RR
input int      MagicNumber            = 123456;
input int      Slippage               = 30;

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

Кроме того, мы строим систему распределения капитала, которая распределяет между сессиями фиксированную долю дневного риска по счету. Это можно делать либо поровну, либо вручную (например, 7%, 8%, 15%), обеспечивая контролируемый уровень риска в течение всего дня. Наконец, мы определяем логику исполнения с динамическими настройками стоп-лосса и тейк-профита, основанными на риске. Мы также добавляем резервные значения для повышения отказоустойчивости, а также такие важные параметры, как проскальзывание и уникальное магическое число. В результате получается гибкая торговая основа, учитывающая специфику сессий и обеспечивающая качественный риск-менеджмент.

//+------------------------------------------------------------------+
//| Enumerations and structures                                      |
//+------------------------------------------------------------------+
enum SessionType
  {
   SESSION_ASIAN,
   SESSION_LONDON,
   SESSION_NEWYORK,
   SESSION_OFF
  };

struct SymbolData
  {
   string            name;
   double            sessionHigh;
   double            sessionLow;
   double            upperVolatilityStop;
   double            lowerVolatilityStop;
   bool              highBreakoutTriggered;
   bool              lowBreakoutTriggered;
   datetime          sessionStartTime;
   datetime          lastTradeTime;
   double            currentATR;
  };

struct SessionInfo
  {
   SessionType       type;
   double            allocationPercent;
   double            usedRisk;
   double            maxRisk;
   string            activeSymbols;
   datetime          startTime;
   datetime          endTime;
   bool              isActive;
  };

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int         m_numSymbols;
SymbolData  m_symbols[];
SessionInfo m_currentSession;
SessionInfo m_asianSession;
SessionInfo m_londonSession;
SessionInfo m_nySession;
datetime    m_lastBarTime;
SessionType m_previousSession;
double      m_dailyRiskUsed;
double      m_dailyMaxRisk;
datetime    m_lastResetDate;
int         m_atrHandle[];
bool        m_dailyResetPrinted;
double      m_dailyNetProfit;
datetime    m_lastProfitUpdate;
bool        m_allocationValid;

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

Затем мы объявляем глобальные переменные, которые связывают все в единую рабочую систему. Массивы m_symbols[] и m_atrHandle[] позволяют эффективно масштабировать решение на несколько инструментов, а объекты сессий m_asianSession, m_londonSession и m_nySession поддерживают независимое состояние для каждого торгового окна. Переменные m_dailyRiskUsed, m_dailyMaxRisk и m_dailyNetProfit отвечают за отслеживание общего риска и измерение результатов торговли. Кроме того, служебные переменные m_previousSession, m_lastResetDate и m_allocationValid обеспечивают плавные переходы между сессиями, корректные ежедневные сбросы и последовательную логику распределения.

//+------------------------------------------------------------------+
//| Validate allocation inputs                                       |
//+------------------------------------------------------------------+
bool ValidateAllocation()
  {
   m_allocationValid = true;

   if(!UseEqualSplit)
     {
      double totalPercent = 0;
      int activeSessions = 0;

      if(UseAsianSession)
        {
         if(AsianAllocationPercent < 0 || AsianAllocationPercent > 100)
           {
            Print("ERROR: Asian allocation must be between 0 and 100. Got: ", AsianAllocationPercent);
            m_allocationValid = false;
           }
         totalPercent += AsianAllocationPercent;
         activeSessions++;
        }

      if(UseLondonSession)
        {
         if(LondonAllocationPercent < 0 || LondonAllocationPercent > 100)
           {
            Print("ERROR: London allocation must be between 0 and 100. Got: ", LondonAllocationPercent);
            m_allocationValid = false;
           }
         totalPercent += LondonAllocationPercent;
         activeSessions++;
        }

      if(UseNewYorkSession)
        {
         if(NYAllocationPercent < 0 || NYAllocationPercent > 100)
           {
            Print("ERROR: NY allocation must be between 0 and 100. Got: ", NYAllocationPercent);
            m_allocationValid = false;
           }
         totalPercent += NYAllocationPercent;
         activeSessions++;
        }

      //--- Check if total exceeds 100%
      if(totalPercent > 100.01) // Small tolerance for floating point
        {
         Print("ERROR: Total session allocation (", totalPercent, "%) exceeds 100% of daily capital!");
         m_allocationValid = false;
        }

      //--- Check if total is significantly less than 100%
      if(activeSessions > 0 && totalPercent < 99.99)
        {
         Print("WARNING: Total session allocation (", totalPercent, "%) is less than 100% of daily capital. Remaining ",
               100 - totalPercent, "% will not be used.");
        }

      if(m_allocationValid)
         Print("Allocation validation passed. Total: ", totalPercent, "%");
     }
   else
     {
      Print("Using equal split allocation mode");
     }

   return m_allocationValid;
  }

//+------------------------------------------------------------------+
//| Calculate session allocation based on user settings              |
//+------------------------------------------------------------------+
double CalculateSessionAllocation(SessionType session)
  {
   if(UseEqualSplit)
     {
      int activeSessions = 0;
      if(UseAsianSession)
         activeSessions++;
      if(UseLondonSession)
         activeSessions++;
      if(UseNewYorkSession)
         activeSessions++;

      if(activeSessions == 0)
         return 0;

      return 100.0 / activeSessions;
     }
   else
     {
      switch(session)
        {
         case SESSION_ASIAN:
            return AsianAllocationPercent;
         case SESSION_LONDON:
            return LondonAllocationPercent;
         case SESSION_NEWYORK:
            return NYAllocationPercent;
         default:
            return 0;
        }
     }
  }

Первая функция проверяет, что заданное пользователем распределение капитала по сессиям корректно и внутренне согласовано. Когда включено ручное распределение, мы проверяем каждую активную сессию, чтобы убедиться, что ее процент лежит в допустимом диапазоне (0-100). По мере прохода по включенным сессиям мы суммируем общий объем распределения и отслеживаем, сколько сессий активно. Это позволяет выявлять критические проблемы, например распределение свыше 100% дневного капитала, которое разрушает модель риска, или существенно ниже 100%, из-за чего часть капитала остается неиспользованной. Ошибки сразу делают конфигурацию недопустимой, а предупреждения указывают на неэффективность. Если все корректно, мы подтверждаем успешное прохождение проверки, чтобы советник работал на основе надежной и предсказуемой схемы распределения капитала.

Вторая функция преобразует эти проверенные настройки в фактические значения распределения, используемые при исполнении. Если включен режим равного распределения, мы динамически делим 100% выделенного риска между всеми активными сессиями, обеспечивая разумное и простое распределение без ручного ввода. Если выбран ручной режим, мы напрямую возвращаем заранее заданное распределение для каждой сессии в зависимости от ее типа. Такое разделение между проверкой и расчетом делает систему понятной и устойчивой, позволяя безопасно переключаться между гибким ручным управлением и автоматическим распределением при сохранении последовательной логики назначения капитала.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Validate allocation settings first
   if(!ValidateAllocation())
     {
      Print("FATAL: Allocation validation failed! EA will not start.");
      return(INIT_FAILED);
     }

   //--- Build symbol list
   string list = Symbols;
   if(StringFind(list, _Symbol) < 0)
      list = list + "," + _Symbol;

   string parts[];
   int num = StringSplit(list, ',', parts);
   if(num <= 0)
     {
      Print("No symbols specified!");
      return(INIT_FAILED);
     }

   m_numSymbols = num;
   ArrayResize(m_symbols, m_numSymbols);
   ArrayResize(m_atrHandle, m_numSymbols);

   for(int i = 0; i < m_numSymbols; i++)
     {
      string sym = parts[i];
      StringTrimRight(sym);

      if(!SymbolSelect(sym, true))
        {
         Print("Failed to select symbol: ", sym);
         return(INIT_FAILED);
        }

      m_symbols[i].name = sym;
      m_symbols[i].sessionHigh = 0;
      m_symbols[i].sessionLow = 0;
      m_symbols[i].upperVolatilityStop = 0;
      m_symbols[i].lowerVolatilityStop = 0;
      m_symbols[i].highBreakoutTriggered = false;
      m_symbols[i].lowBreakoutTriggered = false;
      m_symbols[i].sessionStartTime = 0;
      m_symbols[i].lastTradeTime = 0;
      m_symbols[i].currentATR = 0;

      //--- Create ATR handle for each symbol
      m_atrHandle[i] = iATR(sym, PERIOD_M15, ATRPeriod);
      if(m_atrHandle[i] == INVALID_HANDLE)
        {
         Print("Failed to create ATR handle for ", sym);
         return(INIT_FAILED);
        }
     }

   //--- Get account balance
   double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);

   //--- Initialize daily risk
   m_dailyMaxRisk = accountBalance * (DailyCapitalPercent / 100.0);
   m_dailyRiskUsed = 0;
   m_lastResetDate = 0;
   m_dailyResetPrinted = false;
   m_dailyNetProfit = 0;
   m_lastProfitUpdate = 0;

   //--- Calculate session allocations
   double asianAllocPct = CalculateSessionAllocation(SESSION_ASIAN);
   double londonAllocPct = CalculateSessionAllocation(SESSION_LONDON);
   double nyAllocPct = CalculateSessionAllocation(SESSION_NEWYORK);

   //--- Setup session configurations with calculated allocations
   m_asianSession.type = SESSION_ASIAN;
   m_asianSession.allocationPercent = asianAllocPct;
   m_asianSession.maxRisk = m_dailyMaxRisk * (asianAllocPct / 100.0);
   m_asianSession.usedRisk = 0;
   m_asianSession.activeSymbols = AsianSymbols;
   m_asianSession.startTime = 0;
   m_asianSession.endTime = 0;
   m_asianSession.isActive = false;

   m_londonSession.type = SESSION_LONDON;
   m_londonSession.allocationPercent = londonAllocPct;
   m_londonSession.maxRisk = m_dailyMaxRisk * (londonAllocPct / 100.0);
   m_londonSession.usedRisk = 0;
   m_londonSession.activeSymbols = LondonSymbols;
   m_londonSession.startTime = 0;
   m_londonSession.endTime = 0;
   m_londonSession.isActive = false;

   m_nySession.type = SESSION_NEWYORK;
   m_nySession.allocationPercent = nyAllocPct;
   m_nySession.maxRisk = m_dailyMaxRisk * (nyAllocPct / 100.0);
   m_nySession.usedRisk = 0;
   m_nySession.activeSymbols = NYSymbols;
   m_nySession.startTime = 0;
   m_nySession.endTime = 0;
   m_nySession.isActive = false;

   m_currentSession.type = SESSION_OFF;
   m_currentSession.maxRisk = 0;
   m_currentSession.usedRisk = 0;
   m_currentSession.isActive = false;

   m_previousSession = SESSION_OFF;
   m_lastBarTime = 0;

   trade.SetExpertMagicNumber(MagicNumber);

   //--- Print initialization summary
   Print("═══════════════════════════════════════════");
   Print("SESSION BASED CAPITAL ALLOC EA Initialized");
   Print("═══════════════════════════════════════════");
   Print("Account Balance: ", DoubleToString(accountBalance, 2));
   Print("Daily Capital: ", DoubleToString(m_dailyMaxRisk, 2), " (", DailyCapitalPercent, "% of balance)");
   Print("Allocation Mode: ", UseEqualSplit ? "EQUAL SPLIT" : "MANUAL");
   Print("───────────────────────────────────────────");
   if(UseAsianSession)
      Print("Asian Session: ", DoubleToString(asianAllocPct, 1), "% (", DoubleToString(m_asianSession.maxRisk, 2), ")");
   if(UseLondonSession)
      Print("London Session: ", DoubleToString(londonAllocPct, 1), "% (", DoubleToString(m_londonSession.maxRisk, 2), ")");
   if(UseNewYorkSession)
      Print("NY Session: ", DoubleToString(nyAllocPct, 1), "% (", DoubleToString(m_nySession.maxRisk, 2), ")");
   Print("═══════════════════════════════════════════");

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   for(int i = 0; i < m_numSymbols; i++)
     {
      if(m_atrHandle[i] != INVALID_HANDLE)
         IndicatorRelease(m_atrHandle[i]);
     }
   ObjectsDeleteAll(0, "Session_");
   Comment("");
  }

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

Далее мы формируем базовую систему управления капиталом, рассчитывая дневной риск на основе баланса счета и заданного процента. На этой основе мы получаем распределение по сессиям – либо поровну, либо вручную – и назначаем каждой сессии ее максимально допустимый риск. После этого выполняется полная настройка структуры каждой сессии, включая процент распределения, активные символы и начальное состояние. Мы также сбрасываем переменные отслеживания, такие как использованный риск, прибыль и временные маркеры, чтобы обеспечить чистый старт. Наконец, мы устанавливаем магическое число советника и выводим подробную сводку инициализации, которая дает четкое представление о балансе, дневном капитале и распределении по сессиям. Функция деинициализации дополняет этот процесс: она освобождает ресурсы индикатора и очищает объекты графика, обеспечивая эффективное использование памяти и аккуратное завершение работы.

//+------------------------------------------------------------------+
//| Tick function                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Update daily net profit
   UpdateDailyNetProfit();

   //--- Reset daily risk at start of new day (ONCE per day)
   ResetDailyRisk();

   //--- Update ATR values for all symbols
   UpdateATRValues();

   //--- Session handling
   SessionType currentSessionType = GetCurrentSession();
   UpdateCurrentSession(currentSessionType);

   //--- Display session info on chart
   DisplaySessionInfo();

   //--- Draw session objects on chart
   DrawSessionObjects();

   //--- Detect session change and reset session state
   if(currentSessionType != m_previousSession)
     {
      OnSessionChange(currentSessionType);
      m_previousSession = currentSessionType;
     }

   if(m_currentSession.type == SESSION_OFF)
      return;

   //--- Update session ranges for all symbols
   for(int i = 0; i < m_numSymbols; i++)
     {
      UpdateSessionRange(i);
     }

   //--- Check for breakouts on all active symbols
   for(int i = 0; i < m_numSymbols; i++)
     {
      string sym = m_symbols[i].name;

      //--- Session activation check
      if(!IsSymbolActive(sym, m_currentSession.type))
         continue;

      //--- Check for breakouts using enhanced logic
      CheckForBreakouts(i);
     }
  }

//+------------------------------------------------------------------+
//| Update daily net profit from historical deals                    |
//+------------------------------------------------------------------+
void UpdateDailyNetProfit()
  {
   //--- Update every minute or when there's a new deal
   datetime now = TimeCurrent();
   if(now - m_lastProfitUpdate < 60 && m_lastProfitUpdate > 0)
      return;

   m_lastProfitUpdate = now;
   m_dailyNetProfit = GetDailyNetProfit();
  }

//+------------------------------------------------------------------+
//| Get daily net profit                                             |
//+------------------------------------------------------------------+
double GetDailyNetProfit()
  {
   double profit = 0.0;

   datetime todayStart;
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);

   dt.hour = 0;
   dt.min  = 0;
   dt.sec  = 0;

   todayStart = StructToTime(dt);

   if(!HistorySelect(todayStart, TimeCurrent()))
     {
      return 0;
     }

   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      ulong ticket = HistoryDealGetTicket(i);
      if(ticket == 0)
         continue;

      if(HistoryDealGetInteger(ticket, DEAL_MAGIC) != MagicNumber)
         continue;

      double dealProfit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
      double dealCommission = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      double dealSwap = HistoryDealGetDouble(ticket, DEAL_SWAP);

      profit += dealProfit + dealCommission + dealSwap;
     }

   return profit;
  }

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

Как только контекст сессии готов, мы переходим к логике исполнения. Мы фиксируем смену сессии и при необходимости сбрасываем состояние. Это предотвращает наложение торговых периодов друг на друга. Если сессия неактивна, выходим сразу. Для активных сессий мы проходим по всем отслеживаемым символам. Мы обновляем их диапазоны сессий и фильтруем их по правилам соответствующей сессии. Затем мы проверяем пробои по расширенной логике. Это позволяет искать точки входа в контролируемом режиме. В то же время UpdateDailyNetProfit и GetDailyNetProfit отслеживают результат в реальном времени. Они просматривают историю сделок с начала дня и фильтруют ее по магическому числу. Учитываются прибыль, комиссия и своп. Это гарантирует, что каждое решение будет приниматься на основе как структуры рынка, так и текущей экспозиции по риску.

//+------------------------------------------------------------------+
//| Reset daily risk at start of new trading day (ONCE per day)      |
//+------------------------------------------------------------------+
void ResetDailyRisk()
  {
   datetime now = TimeCurrent();
   MqlDateTime dt;
   TimeToStruct(now, dt);

   //--- Set to midnight of current day for comparison
   MqlDateTime todayDt;
   TimeToStruct(now, todayDt);
   todayDt.hour = 0;
   todayDt.min = 0;
   todayDt.sec = 0;
   datetime todayStart = StructToTime(todayDt);

   //--- Check if we need to reset (new day detected)
   if(m_lastResetDate == 0)
     {
      m_lastResetDate = todayStart;
      m_dailyResetPrinted = false;
      return;
     }

   //--- If last reset date is before today's start, we need to reset
   if(m_lastResetDate < todayStart)
     {
      double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);

      //--- Reset daily tracking
      m_dailyRiskUsed = 0;
      m_dailyMaxRisk = accountBalance * (DailyCapitalPercent / 100.0);

      //--- Recalculate session allocations based on current balance
      double asianAllocPct = CalculateSessionAllocation(SESSION_ASIAN);
      double londonAllocPct = CalculateSessionAllocation(SESSION_LONDON);
      double nyAllocPct = CalculateSessionAllocation(SESSION_NEWYORK);

      //--- Reset session risks with updated values
      m_asianSession.maxRisk = m_dailyMaxRisk * (asianAllocPct / 100.0);
      m_asianSession.usedRisk = 0;
      m_asianSession.allocationPercent = asianAllocPct;

      m_londonSession.maxRisk = m_dailyMaxRisk * (londonAllocPct / 100.0);
      m_londonSession.usedRisk = 0;
      m_londonSession.allocationPercent = londonAllocPct;

      m_nySession.maxRisk = m_dailyMaxRisk * (nyAllocPct / 100.0);
      m_nySession.usedRisk = 0;
      m_nySession.allocationPercent = nyAllocPct;

      //--- Update current session max risk if active
      if(m_currentSession.type != SESSION_OFF)
        {
         switch(m_currentSession.type)
           {
            case SESSION_ASIAN:
               m_currentSession.maxRisk = m_asianSession.maxRisk;
               break;
            case SESSION_LONDON:
               m_currentSession.maxRisk = m_londonSession.maxRisk;
               break;
            case SESSION_NEWYORK:
               m_currentSession.maxRisk = m_nySession.maxRisk;
               break;
           }
        }

      m_lastResetDate = todayStart;
      m_dailyResetPrinted = false;

      //--- Print reset message only once
      if(!m_dailyResetPrinted)
        {
         Print("═══════════════════════════════════════════");
         Print("        DAILY RISK RESET - NEW DAY         ");
         Print("═══════════════════════════════════════════");
         Print("Account Balance: ", DoubleToString(accountBalance, 2));
         Print("New Daily Max Risk: ", DoubleToString(m_dailyMaxRisk, 2), " (", DailyCapitalPercent, "% of balance)");
         Print("Allocation Mode: ", UseEqualSplit ? "EQUAL SPLIT" : "MANUAL");
         Print("Asian Session Max: ", DoubleToString(m_asianSession.maxRisk, 2), " (", DoubleToString(asianAllocPct, 1), "%)");
         Print("London Session Max: ", DoubleToString(m_londonSession.maxRisk, 2), " (", DoubleToString(londonAllocPct, 1), "%)");
         Print("NY Session Max: ", DoubleToString(m_nySession.maxRisk, 2), " (", DoubleToString(nyAllocPct, 1), "%)");
         Print("═══════════════════════════════════════════");
         m_dailyResetPrinted = true;
        }
     }
  }

//+------------------------------------------------------------------+
//| Update risk used after trade execution                           |
//+------------------------------------------------------------------+
void UpdateRiskUsed(int index, double lot, int slPips, double pipValue)
  {
   double tradeRisk = lot * slPips * pipValue;

   //--- Update session risk
   m_currentSession.usedRisk += tradeRisk;

   //--- Update session specific risk based on session type
   switch(m_currentSession.type)
     {
      case SESSION_ASIAN:
         m_asianSession.usedRisk += tradeRisk;
         break;
      case SESSION_LONDON:
         m_londonSession.usedRisk += tradeRisk;
         break;
      case SESSION_NEWYORK:
         m_nySession.usedRisk += tradeRisk;
         break;
     }

   //--- Update daily risk
   m_dailyRiskUsed += tradeRisk;

   Print("═══════════════════════════════════════════");
   Print("RISK UPDATED AFTER TRADE:");
   Print("Trade Risk: ", DoubleToString(tradeRisk, 2));
   Print("Session Risk Used: ", DoubleToString(m_currentSession.usedRisk, 2), "/", DoubleToString(m_currentSession.maxRisk, 2));
   Print("Daily Risk Used: ", DoubleToString(m_dailyRiskUsed, 2), "/", DoubleToString(m_dailyMaxRisk, 2));
   Print("═══════════════════════════════════════════");
  }

Функция ResetDailyRisk поддерживает согласованность нашей системы управления рисками с текущим торговым днем. Сначала мы преобразуем текущее время в структурированный формат и определяем точное начало дня – полночь. Так мы получаем четкую точку отсчета для сравнения. Если советник запущен впервые, мы просто инициализируем дату сброса и выходим. В противном случае мы сравниваем временные метки и проверяем, начался ли новый день. Когда обнаруживается новый день, мы сбрасываем все переменные дневного учета. Мы пересчитываем максимальный дневной риск на основе текущего баланса счета и заданного процента. После этого мы динамически перераспределяем этот риск между азиатской, лондонской и нью-йоркской сессиями. Для каждой сессии обновляются лимиты, а счетчик использованного риска обнуляется, что запускает новый дневной цикл распределения.

Кроме того, мы сразу обновляем параметры текущей активной сессии в соответствии с новыми лимитами риска. Это обеспечивает согласованность логики исполнения с новыми дневными границами. Чтобы сохранить прозрачность, мы выводим подробную сводку сброса, но только один раз в день, чтобы не засорять журнал. Параллельно функция UpdateRiskUsed отслеживает риск в реальном времени после каждой сделки. Она рассчитывает точный риск по сделке, используя размер лота, стоп-лосс в пипсах и стоимость пипса. Затем этот риск учитывается в активной сессии, в счетчике соответствующей сессии и в общем дневном использовании. Так мы поддерживаем единое представление о риске на всех уровнях. Вывод в журнал дает четкое представление о том, сколько риска уже израсходовано относительно допустимых лимитов.

//+------------------------------------------------------------------+
//| Update ATR values for all symbols                                |
//+------------------------------------------------------------------+
void UpdateATRValues()
  {
   for(int i = 0; i < m_numSymbols; i++)
     {
      double atr[1];
      if(CopyBuffer(m_atrHandle[i], 0, 0, 1, atr) > 0)
        {
         m_symbols[i].currentATR = atr[0];
        }
     }
  }

//+------------------------------------------------------------------+
//| Enhanced breakout checking logic                                 |
//+------------------------------------------------------------------+
void CheckForBreakouts(int index)
  {
   string sym = m_symbols[index].name;

   //--- Get current bid/ask
   double bid = SymbolInfoDouble(sym, SYMBOL_BID);
   double ask = SymbolInfoDouble(sym, SYMBOL_ASK);

   //--- Check if session has formed enough (optional)
   if(RequireSessionFormation)
     {
      datetime now = TimeCurrent();
      int minutesSinceStart = (int)((now - m_symbols[index].sessionStartTime) / 60);
      if(minutesSinceStart < SessionFormationMinutes)
         return;
     }

   //--- Calculate volatility stops
   if(UseVolatilityStop && m_symbols[index].currentATR > 0)
     {
      m_symbols[index].upperVolatilityStop = m_symbols[index].sessionHigh + (m_symbols[index].currentATR * ATRMultiplier);
      m_symbols[index].lowerVolatilityStop = m_symbols[index].sessionLow - (m_symbols[index].currentATR * ATRMultiplier);
     }
   else
     {
      //--- Fallback: use session range * 0.5 as volatility stop
      double range = m_symbols[index].sessionHigh - m_symbols[index].sessionLow;
      m_symbols[index].upperVolatilityStop = m_symbols[index].sessionHigh + (range * 0.5);
      m_symbols[index].lowerVolatilityStop = m_symbols[index].sessionLow - (range * 0.5);
     }

   //--- Check for HIGH breakout (price breaks above session high)
   if(!m_symbols[index].highBreakoutTriggered && ask >= m_symbols[index].sessionHigh)
     {
      m_symbols[index].highBreakoutTriggered = true;
      m_symbols[index].lastTradeTime = TimeCurrent();

      //--- Check if price has cleared volatility stop (genuine breakout)
      if(UseVolatilityStop && ask >= m_symbols[index].upperVolatilityStop)
        {
         Print("GENUINE HIGH breakout on ", sym, " - GOING LONG");
         ExecuteBreakoutTrade(index, ORDER_TYPE_BUY, "Genuine High Breakout - Long");
        }
      else
        {
         Print("FALSE HIGH breakout on ", sym, " - FADING SHORT");
         ExecuteBreakoutTrade(index, ORDER_TYPE_SELL, "False High Breakout - Fade Short");
        }
     }

   //--- Check for LOW breakout (price breaks below session low)
   if(!m_symbols[index].lowBreakoutTriggered && bid <= m_symbols[index].sessionLow)
     {
      m_symbols[index].lowBreakoutTriggered = true;
      m_symbols[index].lastTradeTime = TimeCurrent();

      //--- Check if price has cleared volatility stop (genuine breakout)
      if(UseVolatilityStop && bid <= m_symbols[index].lowerVolatilityStop)
        {
         Print("GENUINE LOW breakout on ", sym, " - GOING SHORT");
         ExecuteBreakoutTrade(index, ORDER_TYPE_SELL, "Genuine Low Breakout - Short");
        }
      else
        {
         Print("FALSE LOW breakout on ", sym, " - FADING LONG");
         ExecuteBreakoutTrade(index, ORDER_TYPE_BUY, "False Low Breakout - Fade Long");
        }
     }
  }

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

Функция CheckForBreakouts развивает эту логику, применяя структурированную торговую логику с учетом контекста. Мы начинаем с получения текущих цен Bid и Ask для символа. Если для сессии требуется формирование диапазона, мы проверяем, прошло ли достаточно времени, прежде чем разрешать сделки. Затем мы рассчитываем стопы по волатильности с использованием ATR, если он доступен, либо при необходимости переходим к методу на основе диапазона. После установки уровней мы отслеживаем, не пробивает ли цена максимум сессии вверх или минимум сессии вниз. Когда происходит пробой, мы классифицируем его как истинный или ложный в зависимости от того, преодолевает ли цена уровень стопа по волатильности. Истинные пробои дают сигнал на сделки по импульсу, а ложные – на сделки в противоположном направлении.

//+------------------------------------------------------------------+
//| Execute breakout trade                                           |
//+------------------------------------------------------------------+
void ExecuteBreakoutTrade(int index, ENUM_ORDER_TYPE direction, string reason)
  {
   string sym = m_symbols[index].name;

   //--- Check if already have a position
   if(PositionSelect(sym))
     {
      Print("Already have position on ", sym, " - skipping");
      return;
     }

   //--- Check if we still have risk budget for this session
   if(m_currentSession.usedRisk >= m_currentSession.maxRisk)
     {
      Print("Session risk limit reached. Used: ", m_currentSession.usedRisk, " Max: ", m_currentSession.maxRisk);
      return;
     }

   //--- Calculate remaining risk for this trade
   double remainingRisk = m_currentSession.maxRisk - m_currentSession.usedRisk;

   //--- Calculate stop loss in pips
   int slPips = GetStopLossPips(index);
   if(slPips <= 0)
      slPips = StopLoss_Pips_Fallback;

   //--- Calculate lot size based on available risk
   double pipValue = GetPipValue(sym);
   if(pipValue <= 0)
      return;

   double lot = remainingRisk / (slPips * pipValue);
   lot = NormalizeDouble(lot, 2);
   lot = MathMax(lot, SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN));
   lot = MathMin(lot, SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX));

   if(lot <= 0)
     {
      Print("Lot size too small for ", sym);
      return;
     }

   //--- Calculate SL and TP prices
   double price, slPrice, tpPrice;
   double pipSize = GetPipSize(sym);

   if(direction == ORDER_TYPE_BUY)
     {
      price = SymbolInfoDouble(sym, SYMBOL_ASK);
      slPrice = price - slPips * pipSize;

      if(UseDynamicTP)
         tpPrice = price + (slPips * TakeProfitRR) * pipSize;
      else
         tpPrice = price + TakeProfit_Pips_Fallback * pipSize;

      Print("═══════════════════════════════════════════");
      Print("BUY on ", sym, " - ", reason);
      Print("Lot: ", lot, " SL: ", slPips, "pips TP: ", (UseDynamicTP ? TakeProfitRR * slPips : TakeProfit_Pips_Fallback), "pips");
      Print("═══════════════════════════════════════════");

      if(trade.Buy(lot, sym, price, slPrice, tpPrice, "Session Breakout - " + reason))
        {
         Print("BUY executed successfully");
         UpdateRiskUsed(index, lot, slPips, pipValue);
        }
      else
         Print("BUY failed. Error: ", GetLastError());
     }
   else
      if(direction == ORDER_TYPE_SELL)
        {
         price = SymbolInfoDouble(sym, SYMBOL_BID);
         slPrice = price + slPips * pipSize;

         if(UseDynamicTP)
            tpPrice = price - (slPips * TakeProfitRR) * pipSize;
         else
            tpPrice = price - TakeProfit_Pips_Fallback * pipSize;

         Print("═══════════════════════════════════════════");
         Print("SELL on ", sym, " - ", reason);
         Print("Lot: ", lot, " SL: ", slPips, "pips TP: ", (UseDynamicTP ? TakeProfitRR * slPips : TakeProfit_Pips_Fallback), "pips");
         Print("═══════════════════════════════════════════");

         if(trade.Sell(lot, sym, price, slPrice, tpPrice, "Session Breakout - " + reason))
           {
            Print("SELL executed successfully");
            UpdateRiskUsed(index, lot, slPips, pipValue);
           }
         else
            Print("SELL failed. Error: ", GetLastError());
        }
  }

//+------------------------------------------------------------------+
//| Get stop loss in pips based on ATR or range                      |
//+------------------------------------------------------------------+
int GetStopLossPips(int index)
  {
   double pipSize = GetPipSize(m_symbols[index].name);

   if(UseVolatilityStop && m_symbols[index].currentATR > 0)
     {
      int slPips = (int)((m_symbols[index].currentATR * StopLossATRMultiplier) / pipSize);
      if(slPips >= 10)
         return slPips;
     }

   //--- Fallback to range-based SL
   double range = m_symbols[index].sessionHigh - m_symbols[index].sessionLow;
   if(range > 0 && pipSize > 0)
     {
      int slPips = (int)((range * 0.5) / pipSize);
      if(slPips >= 10)
         return slPips;
     }

   return StopLoss_Pips_Fallback;
  }

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

После расчета размера позиции мы определяем цену входа, а также уровни стоп-лосса и тейк-профита. Для покупок используется цена Ask, а для продаж – цена Bid. Тейк-профит может быть динамическим, на основе соотношения риск/прибыль, либо фиксированным – с использованием резервного значения. Затем перед исполнением сделки мы выводим ее подробную сводку. Если сделка открыта успешно, мы сразу обновляем объем использованного риска, чтобы учесть новую экспозицию. Если открыть сделку не удалось, мы записываем ошибку в журнал для отладки. Дополняет эту логику функция GetStopLossPips, которая определяет адаптивный стоп-лосс. Сначала она пытается использовать оценку волатильности по ATR. Если такой вариант недоступен или некорректен, используется метод на основе диапазона сессии. Если оба варианта не срабатывают, возвращается значение по умолчанию.

//+------------------------------------------------------------------+
//| Update session range for a symbol                                |
//+------------------------------------------------------------------+
void UpdateSessionRange(int index)
  {
   string sym = m_symbols[index].name;

   double highs[], lows[];
   ArrayResize(highs, 0);
   ArrayResize(lows, 0);
   ArraySetAsSeries(highs, true);
   ArraySetAsSeries(lows, true);

   //--- Get high/low data for the range period
   if(CopyHigh(sym, PERIOD_M15, 1, SessionRangeBars, highs) <= 0)
      return;

   if(CopyLow(sym, PERIOD_M15, 1, SessionRangeBars, lows) <= 0)
      return;

   //--- Find highest high and lowest low
   double maxH = highs[0];
   double minL = lows[0];

   for(int i = 1; i < ArraySize(highs); i++)
     {
      if(highs[i] > maxH)
         maxH = highs[i];
      if(lows[i] < minL)
         minL = lows[i];
     }

   //--- Only update if not already triggered (preserve breakout state)
   if(!m_symbols[index].highBreakoutTriggered)
      m_symbols[index].sessionHigh = maxH;

   if(!m_symbols[index].lowBreakoutTriggered)
      m_symbols[index].sessionLow = minL;
  }

//+------------------------------------------------------------------+
//| Handle session change                                            |
//+------------------------------------------------------------------+
void OnSessionChange(SessionType newSession)
  {
   Print("═══════════════════════════════════════════");
   Print("Session changed to: ", EnumToString(newSession));
   Print("═══════════════════════════════════════════");

   //--- Reset breakout triggers for all symbols for the new session
   for(int i = 0; i < m_numSymbols; i++)
     {
      m_symbols[i].highBreakoutTriggered = false;
      m_symbols[i].lowBreakoutTriggered = false;
      m_symbols[i].sessionStartTime = TimeCurrent();
     }

   //--- Close losing positions from previous session
   CloseLosingPositionsFromPreviousSession();
  }

Функция UpdateSessionRange отслеживает текущие ценовые границы сессии для каждого символа. Мы получаем данные о недавних максимумах и минимумах на таймфрейме M15 за заданное количество баров. Эти массивы обрабатываются как таймсерии, поэтому самые свежие данные всегда находятся в начале. Затем мы перебираем эти значения, чтобы найти в заданном диапазоне наибольший максимум и наименьший минимум. Так мы получаем диапазон активной сессии. Чтобы сохранить целостность торговой логики, мы обновляем эти уровни только в том случае, если пробой еще не был зафиксирован. Это исключает смещение ключевых уровней после того, как пробой уже произошел.

Функция OnSessionChange обрабатывает переходы между торговыми сессиями. Когда начинается новая сессия, мы записываем эту смену в журнал для наглядности. Затем мы сбрасываем все флаги пробоя по всем символам, чтобы подготовиться к новым возможностям. Каждому символу также присваивается новое время начала сессии, которое затем используется в условиях, связанных со временем. Кроме того, мы снижаем риск, закрывая убыточные позиции, перенесенные из предыдущей сессии. Это гарантирует, что каждая сессия будет начинаться в чистом и контролируемом состоянии – и структурно, и с финансовой точки зрения.

//+------------------------------------------------------------------+
//| Update current session info                                      |
//+------------------------------------------------------------------+
void UpdateCurrentSession(SessionType sessionType)
  {
   datetime now = TimeCurrent();
   MqlDateTime dt;
   TimeToStruct(now, dt);

   switch(sessionType)
     {
      case SESSION_ASIAN:
         m_currentSession = m_asianSession;
         dt.hour = AsianStart;
         dt.min = 0;
         dt.sec = 0;
         m_currentSession.startTime = StructToTime(dt);
         dt.hour = AsianEnd;
         m_currentSession.endTime = StructToTime(dt);
         m_currentSession.isActive = true;
         break;
      case SESSION_LONDON:
         m_currentSession = m_londonSession;
         dt.hour = LondonStart;
         dt.min = 0;
         dt.sec = 0;
         m_currentSession.startTime = StructToTime(dt);
         dt.hour = LondonEnd;
         m_currentSession.endTime = StructToTime(dt);
         m_currentSession.isActive = true;
         break;
      case SESSION_NEWYORK:
         m_currentSession = m_nySession;
         dt.hour = NYStart;
         dt.min = 0;
         dt.sec = 0;
         m_currentSession.startTime = StructToTime(dt);
         dt.hour = NYEnd;
         m_currentSession.endTime = StructToTime(dt);
         m_currentSession.isActive = true;
         break;
      default:
         m_currentSession.type = SESSION_OFF;
         m_currentSession.maxRisk = 0;
         m_currentSession.usedRisk = 0;
         m_currentSession.isActive = false;
         break;
     }
  }

//+------------------------------------------------------------------+
//| Session detection                                                |
//+------------------------------------------------------------------+
SessionType GetCurrentSession()
  {
   datetime now = TimeCurrent();
   MqlDateTime dt;
   TimeToStruct(now, dt);
   int hour = dt.hour;

   if(UseAsianSession && hour >= AsianStart && hour < AsianEnd)
      return SESSION_ASIAN;
   if(UseLondonSession && hour >= LondonStart && hour < LondonEnd)
      return SESSION_LONDON;
   if(UseNewYorkSession && hour >= NYStart && hour < NYEnd)
      return SESSION_NEWYORK;
   return SESSION_OFF;
  }

Функции UpdateCurrentSession и GetCurrentSession работают совместно, чтобы поддерживать соответствие советника активной торговой сессии. Сначала GetCurrentSession проверяет текущий час сервера и определяет, попадает ли он в азиатскую, лондонскую или нью-йоркскую сессию на основе заданных пользователем временных диапазонов и включенных флагов. Функция возвращает соответствующий тип сессии либо SESSION_OFF, если ни одна сессия не активна.

Затем UpdateCurrentSession использует этот результат и загружает соответствующую структуру сессии в m_currentSession. Функция задает точное время начала и окончания этой сессии, корректируя текущую дату в соответствии с настроенными часами сессии. При этом сессия помечается как активная, а все связанные параметры, например лимиты риска, назначаются корректно. Если ни одна сессия не активна, советник переводит состояние сессии в неактивное с нулевой экспозицией по риску, чтобы сделки не открывались вне заданных торговых интервалов.

//+------------------------------------------------------------------+
//| Check if symbol is active during current session                 |
//+------------------------------------------------------------------+
bool IsSymbolActive(string symbol, SessionType session)
  {
   string activeList = "";
   switch(session)
     {
      case SESSION_ASIAN:
         activeList = AsianSymbols;
         break;
      case SESSION_LONDON:
         activeList = LondonSymbols;
         break;
      case SESSION_NEWYORK:
         activeList = NYSymbols;
         break;
      default:
         return false;
     }

   string parts[];
   int count = StringSplit(activeList, ',', parts);
   for(int i = 0; i < count; i++)
     {
      string sym = parts[i];
      StringTrimRight(sym);
      if(sym == symbol)
         return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| Display session info on chart                                    |
//+------------------------------------------------------------------+
void DisplaySessionInfo()
  {
   string sessionName = "";
   double allocPercent = 0;
   double usedRisk = 0;
   double maxRisk = 0;

   if(m_currentSession.type == SESSION_OFF)
     {
      Comment("NO ACTIVE SESSION");
      return;
     }

   switch(m_currentSession.type)
     {
      case SESSION_ASIAN:
         sessionName = "ASIAN";
         allocPercent = m_asianSession.allocationPercent;
         break;
      case SESSION_LONDON:
         sessionName = "LONDON";
         allocPercent = m_londonSession.allocationPercent;
         break;
      case SESSION_NEWYORK:
         sessionName = "NEW YORK";
         allocPercent = m_nySession.allocationPercent;
         break;
     }

   usedRisk = m_currentSession.usedRisk;
   maxRisk  = m_currentSession.maxRisk;

   double sessionPct = (maxRisk > 0) ? (usedRisk / maxRisk) * 100 : 0;
   double sessionAccountPct = (AccountInfoDouble(ACCOUNT_BALANCE) > 0) ? (usedRisk / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0;

   double dailyPct = (m_dailyMaxRisk > 0) ? (m_dailyRiskUsed / m_dailyMaxRisk) * 100 : 0;
   double dailyAccountPct = (AccountInfoDouble(ACCOUNT_BALANCE) > 0) ? (m_dailyRiskUsed / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0;

   string profitSign = (m_dailyNetProfit >= 0) ? "+" : "";

   string info = "";
   info += "═══════════════════════════════════════════\n";
   info += "   SESSION BASED CAPITAL ALLOCATION EA\n";
   info += "═══════════════════════════════════════════\n";
   info += "Session: " + sessionName + "\n";
   info += "Mode: " + (UseEqualSplit ? "EQUAL SPLIT" : "MANUAL") + "\n";
   info += "───────────────────────────────────────────\n";
   info += "Daily Net PnL: " + profitSign + DoubleToString(m_dailyNetProfit, 2) + "\n";
   info += "Daily Budget: " + DoubleToString(DailyCapitalPercent, 1) + "%\n";
   info += "Used: " + DoubleToString(dailyPct, 1) + "% of daily\n";
   info += "Exposure: " + DoubleToString(dailyAccountPct, 2) + "% account\n";
   info += "───────────────────────────────────────────\n";
   info += "Session Budget: " + DoubleToString(allocPercent, 1) + "%\n";
   info += "Used: " + DoubleToString(sessionPct, 1) + "% of session\n";
   info += "Exposure: " + DoubleToString(sessionAccountPct, 2) + "% account\n";
   info += "───────────────────────────────────────────\n";
   info += "Symbols: " + m_currentSession.activeSymbols + "\n";
   info += "═══════════════════════════════════════════";

   Comment(info);
  }

Функция IsSymbolActive определяет, какие символы разрешено использовать для торговли в рамках конкретной сессии. Мы начинаем с выбора соответствующего списка символов на основе типа текущей сессии. У каждой сессии есть свой предопределенный список символов, сохраненный в виде строки, разделенной запятыми. Затем мы разбиваем эту строку на отдельные символы и перебираем их. Перед сравнением мы удаляем лишние пробелы, чтобы избежать несоответствий. Если заданный символ совпадает с какой-либо записью в списке, функция возвращает true. В противном случае функция возвращает false, и для торговли учитываются только символы, относящиеся к текущей сессии.

Функция DisplaySessionInfo показывает прямо на графике наглядную картину состояния советника в реальном времени. Если ни одна сессия не активна, мы просто выводим сообщение и выходим. В противном случае мы определяем текущую сессию и получаем данные о распределении риска и его использовании. Мы рассчитываем ключевые показатели, такие как процент использованного риска, экспозиция счета и дневной результат. Мы также форматируем дневную прибыль с указанием знака для наглядности. Затем вся эта информация собирается в наглядный информационный блок, где отображаются параметры сессии, использование риска и активные символы. Это помогает во время работы советника просто и наглядно контролировать и результативность, и риск.


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

Тестирование на исторических данных проводилось в течение примерно двух месяцев – с 20 ноября 2025 года по 30 января 2026 года – при следующих настройках:

Теперь взглянем на кривую капитала и результаты тестирования на исторических данных:


Заключение

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

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

На практике после внедрения этой системы вы сможете:

  • различать истинные пробои, сопровождаемые расширением волатильности, и ложные тени, которые лишь временно расширяют диапазон;
  • динамически распределять рисковый капитал между азиатской, лондонской и нью-йоркской сессиями с учетом их уникальных поведенческих профилей;
  • автоматически корректировать размер лота по мере расходования бюджетов риска сессий;
  • сохранять прибыльные позиции при переходе между сессиями и закрывать убыточные, если они больше не соответствуют логике текущей сессии;
  • привязывать каждую сделку к структурным уровням конкретной сессии, а не к произвольным фиксированным стопам.
По сути, этот подход переводит вашу торговую систему от модели "одна стратегия на весь день" к контекстно-зависимой ротации капитала. Каждое действие подкреплено логикой пробоя, соответствующей сессии, подтверждением волатильности и дисциплинированным распределением риска. Это повышает точность входов и качество управления внутридневным риском. Кроме того, это создает надежную и расширяемую основу, которую можно встроить в любую модель исполнения. Независимо от того, используете ли вы возврат к среднему, импульсную торговлю или анализ потока ордеров, распределение капитала остается в основе принятия решений.

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

Прикрепленные файлы |
Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (модуль временной согласованности) Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (модуль временной согласованности)
В статье продолжается адаптация фреймворка CogDriver к финансовым временным рядам. Основное внимание уделено модулю временной согласованности TCM, который связывает текущее рыночное состояние с памятью ранее сохранённых запросов (Query). Разбираются ранжируемая память, расчёт оценки (Score) через Flash-Attention, обновление слотов памяти средствами OpenCL и построение слоя CNeuronCogDriverRankTCM в MQL5, что даёт готовый контур временной согласованности для последующих торговых моделей.
Рыночные секреты Ларри Уильямса (Часть 9): Торговые паттерны для получения прибыли Рыночные секреты Ларри Уильямса (Часть 9): Торговые паттерны для получения прибыли
Эмпирическое исследование краткосрочных торговых паттернов Ларри Уильямса, показывающее, как классические сетапы можно автоматизировать в MQL5, протестировать на реальных рыночных данных и оценить с точки зрения устойчивости, прибыльности и практической ценности для торговли.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Алгоритм оптимизации шимпанзе: от ChOA к BChimp Алгоритм оптимизации шимпанзе: от ChOA к BChimp
Алгоритм оптимизации шимпанзе (ChOA) подражает групповой охоте приматов с разделением ролей, а его бинарная ветвь BChimp переносит эту механику в задачи отбора признаков. Реализуем непрерывное ядро в C_AO, по пути находим и исправляем унаследованный дефект коэффициента — незаметный за бинаризацией, но разрушающий поиск в непрерывной области. Аннотация даёт готовую реализацию и практические выводы о качестве и устойчивости поиска.