Разработка динамического мультивалютного советника (Часть 8): Ротация капитала в зависимости от времени суток
Содержание
Введение
Большинство автоматизированных систем рассматривают каждый торговый час как равнозначный. Они выделяют одинаковый риск и на торговлю в узких диапазонах азиатской сессии, и для лондонских пробойных движений, и для волатильных расширений Нью-Йорка. Такой универсальный подход игнорирует фундаментальную рыночную реальность: у каждой торговой сессии свой характер, свой профиль волатильности и свои характеристики статистического преимущества. Трейдеры нередко видят, как депозиты проседают из-за низковероятных сетапов вне основных часов торговли. Они также упускают движения в ключевые часы, потому что капитал уже связан более ранними убыточными позициями. В результате капитал используется неэффективно, риск распределяется неравномерно, а доходность в течение торгового дня снижается.
Подход к ротации капитала по торговым сессиям решает эту проблему за счет динамического распределения рискового капитала между сессиями на основе их уникального статистического преимущества. Вместо того чтобы торговать по одной и той же схеме весь день, система смещает фокус в зависимости от сессии. Система выделяет меньшую долю азиатским рынкам, движущимся в узком диапазоне, увеличивает экспозицию во время лондонских пробоев и отдает наибольшую часть импульсным движениям Нью-Йорка. Каждая сессия торгует только своими исторически наиболее сильными инструментами (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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (модуль временной согласованности)
Рыночные секреты Ларри Уильямса (Часть 9): Торговые паттерны для получения прибыли
Алгоритм оптимизации шимпанзе: от ChOA к BChimp
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования