Автоматизация торговых стратегий на MQL5 (Часть 24): Система торговли на пробое лондонской сессии с риск-менеджментом и трейлинг-стопами
Введение
В предыдущей статье (Часть 23) мы усовершенствовали систему Zone Recovery для торговли по тренду с использованием индикатора Envelopes, написанную на MQL5, добавив трейлинг-стопы и несколько корзин для лучшей защиты прибыли и обработки сигналов. В части 24 мы разрабатываем систему на пробое лондонской сессии, которая определяет диапазоны до открытия сессии, размещает отложенные ордера и содержит инструменты управления рисками — соотношение риск/прибыль, ограничения просадки и панель управления для мониторинга в реальном времени. В статье рассмотрим следующие темы:
- Понимание стратегии пробоя лондонской сессии
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге вы получите полноценную программу на MQL5 с продвинутыми механизмами контроля рисков, готовую к тестированию и доработке. Приступим!
Понимание стратегии пробоя лондонской сессии
Стратегия пробоя лондонской сессии работает на повышенной волатильности при открытии лондонского рынка. Она определяет ценовой диапазон, сформированный в часы до открытия Лондона, и выставляет отложенные ордера на пробой этого диапазона. В лондонскую сессию часто наблюдаются высокая ликвидность и значительные ценовые движения, что дает неплохие возможности для получения прибыли. Однако стратегия требует тщательного управления рисками для защиты от ложных пробоев и просадок.
Для такой защиты мы будем рассчитывать максимум и минимум до лондонской сессии для установки ордеров buy stop и sell stop с отступами, добавляя соотношение риск/прибыль для тейк-профитов, трейлинг-стопы для фиксации прибыли и ограничения на количество открытых сделок и дневную просадку для защиты капитала. Также будем использовать панель управления для мониторинга в реальном времени и проверок сессии, чтобы сделки совершались только в заданных диапазонах, а сама система при этом будет адаптивной к различным рыночным условиям. Ниже представлена концепция системы, которую мы будем реализовывать.

Реализация средствами MQL5
Чтобы создать программу в MQL5, откройте MetaEditor, перейдите в Navigator, найдите папку Indicators, нажмите вкладку New и следуйте инструкциям для создания файла. После этого в среде разработки начнем с объявления входных параметров и структур.
//+------------------------------------------------------------------+ //| London Breakout EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations //--- Enumerations enum ENUM_TRADE_TYPE { //--- Enumeration for trade types TRADE_ALL, // All Trades (Buy and Sell) TRADE_BUY_ONLY, // Buy Trades Only TRADE_SELL_ONLY // Sell Trades Only }; //--- Input parameters sinput group "General EA Settings" input double inpTradeLotsize = 0.01; // Lotsize input ENUM_TRADE_TYPE TradeType = TRADE_ALL; // Trade Type Selection sinput int MagicNumber = 12345; // Magic Number input double RRRatio = 1.0; // Risk to Reward Ratio input int StopLossPoints = 500; // Stop loss in points input int OrderOffsetPoints = 10; // Points offset for Orders input bool DeleteOppositeOrder = true; // Delete opposite order when one is activated? input bool UseTrailing = false; // Use Trailing Stop? input int TrailingPoints = 50; // Trailing Points (distance) input int MinProfitPoints = 100; // Minimum Profit Points to start trailing sinput group "London Session Settings" input int LondonStartHour = 9; // London Start Hour input int LondonStartMinute = 0; // London Start Minute input int LondonEndHour = 8; // London End Hour input int LondonEndMinute = 0; // London End Minute input int MinRangePoints = 100; // Min Pre-London Range in points input int MaxRangePoints = 300; // Max Pre-London Range in points sinput group "Risk Management" input int MaxOpenTrades = 2; // Maximum simultaneous open trades input double MaxDailyDrawdownPercent = 5.0; // Max daily drawdown % to stop trading //--- Structures struct PositionInfo { //--- Structure for position information ulong ticket; // Position ticket double openPrice; // Entry price double londonRange; // Pre-London range in points for this position datetime sessionID; // Session identifier (day) bool trailingActive; // Trailing active flag };
Мы начинаем реализацию системы пробоя лондонской сессии с подключения библиотеки <Trade\Trade.mqh> и определения перечислений, входных параметров и структуры для отслеживания позиций. Библиотека <Trade\Trade.mqh> используется для доступа к классу CTrade, который позволяет выполнять торговые операции — выставлять ордера и изменять позиции. Определим перечисление ENUM_TRADE_TYPE с вариантами TRADE_ALL для торговли в обоих направлениях, TRADE_BUY_ONLY только для покупок и TRADE_SELL_ONLY только для продаж, что позволяет ограничивать направления торговли.
Затем задаем входные параметры по группам: в разделе общих настроек параметр inpTradeLotsize установлен на 0.01 для размера лота, TradeType использует перечисление со значением по умолчанию TRADE_ALL, MagicNumber равен 12345 для идентификации сделок советника, соотношения риск/прибыль RRRatio установлено на 1.0, расстояние стоп-лосса StopLossPoints равно 500, смещение входа OrderOffsetPoints = 10, DeleteOppositeOrder установлен в true для удаления противоположных отложенных ордеров, для использования трейлинг-стопа параметр UseTrailing установлен в false, расстояние TrailingPoints равно 50 и начало трейлинга MinProfitPoints равно 100.
В разделе London Session Settings задается начало сессии через параметры LondonStartHour = 9 и LondonStartMinute = 0, окончание сессии через LondonEndHour = 8 и LondonEndMinute = 0. Также для проверки диапазона используются параметры MinRangePoints = 100 и MaxRangePoints = 300. В разделе Risk Management параметр MaxOpenTrades установлен в значение 2 для ограничения количества одновременных позиций, а MaxDailyDrawdownPercent равен 5.0 для остановки торговли при чрезмерной просадке. Определяем структуру PositionInfo для отслеживания открытых сделок, где ticket — это тикет позиции, openPrice — цена открытия, londonRange — диапазон до лондонской сессии, sessionID — идентификатор дня, а trailingActive — логическое значение состояния трейлинга. После компиляции получаем следующий результат.

Теперь можно определить дополнительные глобальные переменные, которые будут использоваться в программе.
//--- Global variables CTrade obj_Trade; //--- Trade object double PreLondonHigh = 0.0; //--- Pre-London session high double PreLondonLow = 0.0; //--- Pre-London session low datetime PreLondonHighTime = 0; //--- Time of Pre-London high datetime PreLondonLowTime = 0; //--- Time of Pre-London low ulong buyOrderTicket = 0; //--- Buy stop order ticket ulong sellOrderTicket = 0; //--- Sell stop order ticket bool panelVisible = true; //--- Panel visibility flag double LondonRangePoints = 0.0; //--- Current session's Pre-London range PositionInfo positionList[]; //--- Array to store position info datetime lastCheckedDay = 0; //--- Last checked day bool noTradeToday = false; //--- Flag to prevent trading today bool sessionChecksDone = false; //--- Flag for session checks completion datetime analysisTime = 0; //--- Time for London analysis double dailyDrawdown = 0.0; //--- Current daily drawdown bool isTrailing = false; //--- Global flag for any trailing active const int PreLondonStartHour = 3; //--- Fixed Pre-London Start Hour const int PreLondonStartMinute = 0; //--- Fixed Pre-London Start Minute
Здесь мы объявляем глобальные переменные программы: obj_Trade как объект CTrade для торговли, PreLondonHigh и PreLondonLow типа double для диапазонов, PreLondonHighTime и PreLondonLowTime типа datetime для хранения времени формирования этих экстремумов, buyOrderTicket и sellOrderTicket типа ulong для ордеров, panelVisible со значением true для отображения панели, LondonRangePoints равный 0.0 для текущего диапазона, positionList как массив PositionInfo для позиций, lastCheckedDay равный 0 для ежедневного отслеживания, noTradeToday и sessionChecksDone со значением false как флаги торговли, analysisTime равный 0 для времени анализа, dailyDrawdown равный 0.0 для риска, isTrailing со значением false для трейлинга, а также константы PreLondonStartHour = 3 и PreLondonStartMinute - 0.
После этого приступим к созданию панели, что является самым простым этапом, а затем перейдем к более сложной торговой логике. Начнем с необходимых функций для создания панели.
//+------------------------------------------------------------------+ //| Create a rectangle label for the panel background | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set x-size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set y-size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create a text label for panel elements | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 10, string font = "Arial") { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Здесь мы реализуем вспомогательные функции для создания элементов пользовательского интерфейса панели управления с использованием прямоугольных меток для фона и текстовых меток для отображения. Сначала создаем функцию createRecLabel для создания прямоугольных меток фона панели, в которую передаем необходимые параметры. Сбрасываем ошибки с помощью функции ResetLastError и создаем с помощью ObjectCreate объект типа OBJ_RECTANGLE_LABEL, выводя ошибки через Print и возвращая false в случае неудачи. Свойства задаем с помощью ObjectSetInteger для OBJPROP_XDISTANCE и аналогично для остальных целочисленных свойств, затем выполняется перерисовка графика через ChartRedraw и возвращается true.
Далее создаем функцию createLabel для текстовых меток панели с параметрами objName, xD, yD, txt, clrTxt, fontSize и font. Сбрасываем ошибки с помощью ResetLastError и создаем через ObjectCreate объект типа OBJ_LABEL, при этом выводим ошибки через Print и возвращаем false в случае неудачи. Свойства задаются с помощью ObjectSetInteger аналогично функции прямоугольной метки, но дополнительно используется ObjectSetString для свойств OBJPROP_TEXT и OBJPROP_FONT, после чего выполняется перерисовка и возвращается true. Эти функции позволят создать динамическую панель управления для мониторинга данных сессии и состояния программы. Теперь можно использовать их для создания и обновления панели.
string panelPrefix = "LondonPanel_"; //--- Prefix for panel objects //+------------------------------------------------------------------+ //| Create the information panel | //+------------------------------------------------------------------+ void CreatePanel() { createRecLabel(panelPrefix + "Background", 10, 10, 270, 200, clrMidnightBlue, 1, clrSilver); //--- Create background createLabel(panelPrefix + "Title", 20, 15, "London Breakout Control Center", clrGold, 12); //--- Create title createLabel(panelPrefix + "RangePoints", 20, 40, "Range (points): ", clrWhite, 10); //--- Create range label createLabel(panelPrefix + "HighPrice", 20, 60, "High Price: ", clrWhite); //--- Create high price label createLabel(panelPrefix + "LowPrice", 20, 80, "Low Price: ", clrWhite); //--- Create low price label createLabel(panelPrefix + "BuyLevel", 20, 100, "Buy Level: ", clrWhite); //--- Create buy level label createLabel(panelPrefix + "SellLevel", 20, 120, "Sell Level: ", clrWhite); //--- Create sell level label createLabel(panelPrefix + "AccountBalance", 20, 140, "Balance: ", clrWhite); //--- Create balance label createLabel(panelPrefix + "AccountEquity", 20, 160, "Equity: ", clrWhite); //--- Create equity label createLabel(panelPrefix + "CurrentDrawdown", 20, 180, "Drawdown (%): ", clrWhite); //--- Create drawdown label createRecLabel(panelPrefix + "Hide", 250, 10, 30, 22, clrCrimson, 1, clrNONE); //--- Create hide button createLabel(panelPrefix + "HideText", 258, 12, CharToString(251), clrWhite, 13, "Wingdings"); //--- Create hide text ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_SELECTABLE, true); //--- Make hide selectable ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_STATE, true); //--- Set hide state } //+------------------------------------------------------------------+ //| Update panel with current data | //+------------------------------------------------------------------+ void UpdatePanel() { string rangeText = "Range (points): " + (LondonRangePoints > 0 ? DoubleToString(LondonRangePoints, 0) : "Calculating..."); //--- Format range text ObjectSetString(0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); //--- Update range text string highText = "High Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh, _Digits) : "N/A"); //--- Format high text ObjectSetString(0, panelPrefix + "HighPrice", OBJPROP_TEXT, highText); //--- Update high text string lowText = "Low Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow, _Digits) : "N/A"); //--- Format low text ObjectSetString(0, panelPrefix + "LowPrice", OBJPROP_TEXT, lowText); //--- Update low text string buyText = "Buy Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh + OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format buy text ObjectSetString(0, panelPrefix + "BuyLevel", OBJPROP_TEXT, buyText); //--- Update buy text string sellText = "Sell Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow - OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format sell text ObjectSetString(0, panelPrefix + "SellLevel", OBJPROP_TEXT, sellText); //--- Update sell text string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Format balance text ObjectSetString(0, panelPrefix + "AccountBalance", OBJPROP_TEXT, balanceText); //--- Update balance text string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Format equity text ObjectSetString(0, panelPrefix + "AccountEquity", OBJPROP_TEXT, equityText); //--- Update equity text string ddText = "Drawdown (%): " + DoubleToString(dailyDrawdown, 2); //--- Format drawdown text ObjectSetString(0, panelPrefix + "CurrentDrawdown", OBJPROP_TEXT, ddText); //--- Update drawdown text ObjectSetInteger(0, panelPrefix + "CurrentDrawdown", OBJPROP_COLOR, dailyDrawdown > MaxDailyDrawdownPercent / 2 ? clrYellow : clrWhite); //--- Set drawdown color }
Здесь мы определяем строку panelPrefix со значением LondonPanel_ для добавления префикса ко всем именам объектов панели, обеспечивая удобную идентификацию элементов панели управления. Создаем функцию CreatePanel для построения пользовательского интерфейса информационной панели. Вызываем createRecLabel для panelPrefix + Background, чтобы создать фон панели в позиции 10,10 размером 270x200 с цветом clrMidnightBlue, шириной 1 и серебряной рамкой. Затем используем createLabel для добавления заголовка London Breakout Control Center в позиции 20,15 золотым цветом и размером 12, а также меток для диапазона, максимальной цены, минимальной цены, уровня покупки, уровня продажи, баланса, средств (equity) и просадки в соответствующих позициях белым цветом и размером 10.
Для кнопки скрытия вызываем createRecLabel для panelPrefix + Hide в позиции 250,10 размером 30x22 с фоном clrCrimson и createLabel для panelPrefix + HideText с использованием CharToString(251) из набора Wingdings в позиции 258,12 с цветом clrWhite и размером 13. Мы устанавливаем свойства OBJPROP_SELECTABLE и OBJPROP_STATE в true с помощью ObjectSetInteger, чтобы сделать кнопку интерактивной. Выбор кода Wingdings зависит от ваших эстетических предпочтений. Ниже приведен список доступных кодов.

Далее реализуем функцию UpdatePanel для обновления панели текущими данными. Форматируем rangeText с использованиемLondonRangePoints через DoubleToString или выводим Calculating..., если значение равно нулю, и обновляем текст panelPrefix + RangePoints с помощью ObjectSetString. Аналогично форматируем и обновляем значения для максимальной цены, минимальной цены, уровня покупки (добавляя OrderOffsetPoints * _Point к PreLondonHigh), уровня продажи (вычитая OrderOffsetPoints * _Point из PreLondonLow), баланса через AccountInfoDouble(ACCOUNT_BALANCE), средств через AccountInfoDouble(ACCOUNT_EQUITY) и просадки через dailyDrawdown.
Цвет просадки задается через ObjectSetInteger: желтый, если дневная просадка превышает MaxDailyDrawdownPercent / 2, иначе белый. Чтобы сделать функции рабочими, мы вызываем их в функции инициализации следующим образом.
//+------------------------------------------------------------------+ //| Initialize EA | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number ArrayFree(positionList); //--- Free position list CreatePanel(); //--- Create panel panelVisible = true; //--- Set panel visible return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Deinitialize EA | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ArrayFree(positionList); //--- Free position list }
В обработчике события OnInit мы устанавливаем magic number через торговый объект, используем функцию ArrayFree для очистки списка позиций, вызываем функцию CreatePanel для создания панели и устанавливаем флаг видимости панели в true после ее создания. Затем в обработчике события OnDeinit мы используем функцию ObjectsDeleteAll для удаления всех объектов с указанным префиксом и освобождаем массив списка позиций, так как он больше не нужен. После компиляции получаем следующий результат.

Поскольку панель уже создана, добавим ей интерактивность, сделав кнопку закрытия активной — при нажатии панель будет удаляться. Это реализуем в обработчике события OnChartEvent.
//+------------------------------------------------------------------+ //| Handle chart events (e.g., panel close) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK && sparam == panelPrefix + "Hide") { //--- Check hide click panelVisible = false; //--- Set panel hidden ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ChartRedraw(0); //--- Redraw chart } }
В обработчике OnChartEvent мы проверяем, что идентификатор события соответствует клику по объекту и что объект является кнопкой скрытия или закрытия, после чего устанавливаем флаг видимости в false. Затем удаляем объекты панели и перерисовываем график, чтобы изменения вступили в силу. После компиляции получаем следующий результат.

На анимации видно, что панель полностью готова. Теперь обновим ее, чтобы она инициализировала все элементы. Для этого нам понадобятся функции для задания дневных диапазонов и расчета просадки.
//+------------------------------------------------------------------+ //| Check if it's a new trading day | //+------------------------------------------------------------------+ bool IsNewDay(datetime currentBarTime) { MqlDateTime barTime; //--- Bar time structure TimeToStruct(currentBarTime, barTime); //--- Convert time datetime currentDay = StringToTime(StringFormat("%04d.%02d.%02d", barTime.year, barTime.mon, barTime.day)); //--- Get current day if (currentDay != lastCheckedDay) { //--- Check new day lastCheckedDay = currentDay; //--- Update last day sessionChecksDone = false; //--- Reset checks noTradeToday = false; //--- Reset no trade buyOrderTicket = 0; //--- Reset buy ticket sellOrderTicket = 0; //--- Reset sell ticket LondonRangePoints = 0.0; //--- Reset range return true; //--- Return new day } return false; //--- Return not new day } //+------------------------------------------------------------------+ //| Update daily drawdown | //+------------------------------------------------------------------+ void UpdateDailyDrawdown() { static double maxEquity = 0.0; //--- Max equity tracker double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity if (equity > maxEquity) maxEquity = equity; //--- Update max equity dailyDrawdown = (maxEquity - equity) / maxEquity * 100; //--- Calculate drawdown if (dailyDrawdown >= MaxDailyDrawdownPercent) noTradeToday = true; //--- Set no trade if exceeded }
Здесь мы реализуем функцию IsNewDay для проверки наступления нового торгового дня. Создаем структуру MqlDateTime barTime и преобразуем в нее currentBarTime с помощью функции TimeToStruct. Вычисляем currentDay с использованием StringToTime и StringFormat на основе barTime.year, barTime.mon и barTime.day. Если currentDay отличается от lastCheckedDay, мы обновляем lastCheckedDay, сбрасываем sessionChecksDone и noTradeToday в false, обнуляем buyOrderTicket и sellOrderTicket, устанавливаем LondonRangePoints в 0.0 и возвращаем true; в противном случае возвращаем false. Эта функция обеспечивает ежедневный сброс параметров анализа сессии и торговых флагов.
Далее реализуем функцию UpdateDailyDrawdown для мониторинга дневного риска. Используем статическую переменную maxEquity, инициализированную значением 0.0, для отслеживания максимального значения средств (equity). Получаем текущее значение с помощью функции AccountInfoDouble с параметром ACCOUNT_EQUITY, обновляем maxEquity, если значение выше, и рассчитываем dailyDrawdown как процентное снижение от maxEquity. Если dailyDrawdown достигает или превышает MaxDailyDrawdownPercent, устанавливаем noTradeToday в true — это остановит торговлю, чтобы защитить от просадки. Чтобы использовать эти функции, вызываем их в обработчике OnTick для обновления данных панели.
//+------------------------------------------------------------------+ //| Main tick handler | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time IsNewDay(currentBarTime); //--- Check new day UpdatePanel(); //--- Update panel UpdateDailyDrawdown(); //--- Update drawdown }
Здесь мы просто вызываем функцию нового дня для установки параметров диапазона, обновления панели и уровня дневной просадки соответственно. При запуске программы получаем следующий результат.

На рисунке видно, что панель теперь заполнена актуальными данными и отображает текущее состояние. Перейдем к более сложной логике — определению диапазонов сессии. Сначала проверим торговые условия и разместим ордера при их выполнении, а затем займемся управлением позициями. Для этого нам понадобятся функции для определения диапазона, его визуализации и установки ордеров. Ниже приведена логика реализации.
//+------------------------------------------------------------------+ //| Fixed lot size | //+------------------------------------------------------------------+ double CalculateLotSize(double entryPrice, double stopLossPrice) { return NormalizeDouble(inpTradeLotsize, 2); //--- Normalize lot size } //+------------------------------------------------------------------+ //| Calculate session range (high-low) in points | //+------------------------------------------------------------------+ double GetRange(datetime startTime, datetime endTime, double &highVal, double &lowVal, datetime &highTime, datetime &lowTime) { int startBar = iBarShift(_Symbol, _Period, startTime, true); //--- Get start bar int endBar = iBarShift(_Symbol, _Period, endTime, true); //--- Get end bar if (startBar == -1 || endBar == -1 || startBar < endBar) return -1; //--- Invalid bars int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + 1, endBar); //--- Get highest bar int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + 1, endBar); //--- Get lowest bar highVal = iHigh(_Symbol, _Period, highestBar); //--- Set high value lowVal = iLow(_Symbol, _Period, lowestBar); //--- Set low value highTime = iTime(_Symbol, _Period, highestBar); //--- Set high time lowTime = iTime(_Symbol, _Period, lowestBar); //--- Set low time return (highVal - lowVal) / _Point; //--- Return range in points } //+------------------------------------------------------------------+ //| Place pending buy/sell stop orders | //+------------------------------------------------------------------+ void PlacePendingOrders(double preLondonHigh, double preLondonLow, datetime sessionID) { double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; //--- Calculate buy price double sellPrice = preLondonLow - OrderOffsetPoints * _Point; //--- Calculate sell price double slPoints = StopLossPoints; //--- Set SL points double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL double tpPoints = slPoints * RRRatio; //--- Calculate TP points double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { //--- Check buy trade obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket } if (TradeType == TRADE_ALL || TradeType == TRADE_SELL_ONLY) { //--- Check sell trade obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket } } //+------------------------------------------------------------------+ //| Draw session ranges on the chart | //+------------------------------------------------------------------+ void DrawSessionRanges(datetime preLondonStart, datetime londonEnd) { string sessionID = "Sess_" + IntegerToString(lastCheckedDay); //--- Session ID string preRectName = "PreRect_" + sessionID; //--- Rectangle name ObjectCreate(0, preRectName, OBJ_RECTANGLE, 0, PreLondonHighTime, PreLondonHigh, PreLondonLowTime, PreLondonLow); //--- Create rectangle ObjectSetInteger(0, preRectName, OBJPROP_COLOR, clrTeal); //--- Set color ObjectSetInteger(0, preRectName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, preRectName, OBJPROP_BACK, true); //--- Set background string preTopLineName = "PreTopLine_" + sessionID; //--- Top line name ObjectCreate(0, preTopLineName, OBJ_TREND, 0, preLondonStart, PreLondonHigh, londonEnd, PreLondonHigh); //--- Create top line ObjectSetInteger(0, preTopLineName, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, preTopLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preTopLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preTopLineName, OBJPROP_BACK, true); //--- Set background string preBotLineName = "PreBottomLine_" + sessionID; //--- Bottom line name ObjectCreate(0, preBotLineName, OBJ_TREND, 0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); //--- Create bottom line ObjectSetInteger(0, preBotLineName, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(0, preBotLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preBotLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preBotLineName, OBJPROP_BACK, true); //--- Set background }
Чтобы обеспечить расчет диапазона, установку ордеров и отрисовку на графике, начнем с функции CalculateLotSize, которая вычисляет фиксированный размер лота. Передаем ей параметры entryPrice и stopLossPrice (не используются при фиксированном размере). Функция возвращает значение inpTradeLotsize, нормализованное с помощью NormalizeDouble до 2 знаков, чтобы размер лота был одинаковый для всех сделок. Вы можете использовать другое количество знаков в зависимости от типа счета.
Далее создаем функцию GetRange для расчета диапазона до лондонской сессии. Получаем бары startBar и endBar с помощью функции iBarShift, используя startTime и endTime, и возвращаем -1, если значения некорректны или startBar < endBar. Определяем в заданном диапазоне баров максимальный бар highestBar с помощью функции iHighest по MODE_HIGH и минимальный бар lowestBar с помощью iLowest по MODE_LOW. Устанавливаем значения highVal через iHigh для highestBar, lowVal через iLow для lowestBar, highTime через iTime для highestBar и lowTime через iTime для lowestBar. Возвращаем диапазон как значение (highVal-lowVal) /_Point.
Затем определяем функцию PlacePendingOrders для установки отложенных ордеров buy stop и sell stop. Рассчитываем цену покупки buyPrice как preLondonHigh + OrderOffsetPoints * _Point, а цену продажи sellPrice — как preLondonLow - OrderOffsetPoints * _Point. Устанавливаем slPoints равным StopLossPoints, buySL как buyPrice - slPoints * _Point, sellSL как sellPrice + slPoints * _Point, tpPoints как slPoints * RRRatio, buyTP как buyPrice + tpPoints * _Point и sellTP как sellPrice - tpPoints * _Point. Вычисляем размеры лотов lotSizeBuy и lotSizeSell с помощью CalculateLotSize.
Если тип торговли TradeType равен TRADE_ALL или TRADE_BUY_ONLY, размещаем buy stop через функцию obj_Trade.BuyStop с параметрами lotSizeBuy, buyPrice, buySL, buyTP и меткой "Buy Stop - London", сохраняя тикет в buyOrderTicket через ResultOrder. Аналогично размещаем sell stop, если TradeType равен TRADE_ALL или TRADE_SELL_ONLY.
В завершение реализуем функцию DrawSessionRanges для визуализации сессии на графике. Создаем sessionID как Sess_ плюс lastCheckedDay с помощью функции IntegerToString. Для прямоугольника с названием preRectName = PreRect_ + sessionID используем ObjectCreate типа OBJ_RECTANGLE от PreLondonHighTime, PreLondonHigh до PreLondonLowTime, PreLondonLow, задаваем цвет OBJPROP_COLOR равным clrTeal, заливку OBJPROP_FILL равным true и размещение фоном OBJPROP_BACK равным true.
Для верхней линии с названием в preTopLineName, образованным как PreTopLine_ плюс sessionID, создаем объект OBJ_TREND от preLondonStart, PreLondonHigh до londonEnd, PreLondonHigh, устанавливая OBJPROP_COLOR в clrBlack, OBJPROP_WIDTH в 1, OBJPROP_RAY_RIGHT в false и OBJPROP_BACK в true. Аналогично создаем нижнюю линию preBotLineName как PreBottomLine_ плюс sessionID от preLondonStart, PreLondonLow до londonEnd, PreLondonLow с красным цветом. Теперь можно определить функцию проверки торговых условий с использованием этих функций.
//+------------------------------------------------------------------+ //| Check trading conditions and place orders | //+------------------------------------------------------------------+ void CheckTradingConditions(datetime currentTime) { MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert time datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); //--- Get today datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; //--- Pre-London start datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; //--- London start datetime londonEnd = today + LondonEndHour * 3600 + LondonEndMinute * 60; //--- London end analysisTime = londonStart; //--- Set analysis time if (currentTime < analysisTime) return; //--- Exit if before analysis double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); //--- Get range if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { //--- Check range limits noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges return; //--- Exit } LondonRangePoints = preLondonRange; //--- Set range points PlacePendingOrders(PreLondonHigh, PreLondonLow, today); //--- Place orders noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges }
Реализуем функцию CheckTradingConditions для оценки условий сессии и выставления ордеров в нашей системе пробоя лондонской сессии. Создаем структуру MqlDateTime timeStruct и преобразуем текущее время с помощью TimeToStruct. Вычисляем today через StringToTime и StringFormat на основе timeStruct.year, timeStruct.mon и timeStruct.day. Устанавливаем preLondonStart как today плюс PreLondonStartHour и PreLondonStartMinute в секундах, londonStart как today плюс LondonStartHour и LondonStartMinute, и londonEnd как today плюс LondonEndHour и LondonEndMinute. Присваиваем analysisTime значение londonStart и выходим, если текущее время меньше этого значения.
Получаем preLondonRange с помощью функции GetRange, передавая preLondonStart, currentTime и ссылки на PreLondonHigh, PreLondonLow, PreLondonHighTime и PreLondonLowTime. Если preLondonRange меньше MinRangePoints или больше MaxRangePoints, устанавливаем noTradeToday и sessionChecksDone в true, вызываем DrawSessionRanges с параметрами preLondonStart и londonEnd и выходим. В противном случае присваиваем LondonRangePoints значение preLondonRange, вызываем PlacePendingOrders с параметрами PreLondonHigh, PreLondonLow и today, устанавливаем noTradeToday и sessionChecksDone в true и вызываем DrawSessionRanges, обеспечивая выполнение сделок только при корректных диапазонах. Теперь можно вызвать эту функцию в обработчике OnTick для генерации сигналов.
if (!noTradeToday && !sessionChecksDone) { //--- Check trading conditions CheckTradingConditions(TimeCurrent()); //--- Check conditions }
Если на текущий день еще не было сделок и проверка сессии не выполнялась, вызываем функцию проверки условий для текущего времени. При запуске программы получаем следующий результат.

Мы видим, что диапазоны заданы и отложенные ордера установлены. Диапазон составляет 100 пунктов, что соответствует нашим торговым условиям. Теперь можно перейти к управлению сделками, но сначала добавим проверку и удаление противоположных отложенных ордеров при активации одного из них, а также добавим позиции в список для последующего управления.
//+------------------------------------------------------------------+ //| Delete opposite pending order when one is filled | //+------------------------------------------------------------------+ void CheckAndDeleteOppositeOrder() { if (!DeleteOppositeOrder || TradeType != TRADE_ALL) return; //--- Exit if not applicable bool buyOrderExists = false; //--- Buy exists flag bool sellOrderExists = false; //--- Sell exists flag for (int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders ulong orderTicket = OrderGetTicket(i); //--- Get ticket if (OrderSelect(orderTicket)) { //--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; //--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; //--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != 0) { //--- Check delete sell obj_Trade.OrderDelete(sellOrderTicket); //--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != 0) { //--- Check delete buy obj_Trade.OrderDelete(buyOrderTicket); //--- Delete buy order } } //+------------------------------------------------------------------+ //| Add position to tracking list when opened | //+------------------------------------------------------------------+ void AddPositionToList(ulong ticket, double openPrice, double londonRange, datetime sessionID) { if (londonRange <= 0) return; //--- Exit if invalid range int index = ArraySize(positionList); //--- Get current size ArrayResize(positionList, index + 1); //--- Resize array positionList[index].ticket = ticket; //--- Set ticket positionList[index].openPrice = openPrice; //--- Set open price positionList[index].londonRange = londonRange; //--- Set range positionList[index].sessionID = sessionID; //--- Set session ID positionList[index].trailingActive = false; //--- Set trailing inactive }
Начнем с функции CheckAndDeleteOppositeOrder для удаления противоположного отложенного ордера при активации одного из них. Сразу выходим, если DeleteOppositeOrder равен false или TradeType не равен TRADE_ALL. Инициализируем переменные buyOrderExists и sellOrderExists значением false. Перебираем ордера в обратном порядке с помощью OrdersTotal и OrderGetTicket, выбирая каждый через OrderSelect. Если ордер соответствует _Symbol и MagicNumber через OrderGetString и OrderGetInteger, устанавливаем buyOrderExists или sellOrderExists, если тикет совпадает с buyOrderTicket или sellOrderTicket.
Если buy-ордер отсутствует, а sell-ордер существует, удаляем sellOrderTicket через obj_Trade.OrderDelete; аналогично, если отсутствует sell-ордер, а buy-ордер существует. Эта функция гарантирует, что после активации останется только одно направление сделки.
Далее создаем функцию AddPositionToList для отслеживания открытых позиций. Выходим, если londonRange <= 0, так как диапазон еще не задан. Получаем текущий индекс через ArraySize массива positionList, увеличиваем размер массива с помощью ArrayResize, добавив новый элемент, и заполняем positionList[index]. ticket, openPrice, londonRange, sessionID и устанавливаем trailingActive в false. Это позволяет поддерживать список позиций для управления трейлинг-стопами и данными сессии. Теперь можно реализовать эту логику в обработчике события OnTick.
CheckAndDeleteOppositeOrder(); //--- Delete opposite order // Add untracked positions for (int i = 0; i < PositionsTotal(); i++) { //--- Iterate through positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check position bool tracked = false; //--- Tracked flag for (int j = 0; j < ArraySize(positionList); j++) { //--- Check list if (positionList[j].ticket == ticket) tracked = true; //--- Set tracked } if (!tracked) { //--- If not tracked double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); //--- Add to list } } }
Здесь мы вызываем функцию CheckAndDeleteOppositeOrder для управления отложенными ордерами, чтобы при исполнении ордера в одном направлении противоположный ордер удалялся в соответствии с параметром DeleteOppositeOrder и чтобы в итоге у нас не было конфликтующих сделок.
Далее мы добавляем неотслеживаемые позиции в массив positionList, чтобы контролировать все нужные открытые сделки для трейлинг-стопов. Мы перебираем все позиции с помощью PositionsTotal и PositionGetTicket, получая каждый ticket. Если PositionSelectByTicket выполняется успешно и позиция соответствует символу _Symbol и магическому числу MagicNumber через PositionGetString и PositionGetInteger, мы устанавливаем флаг tracked в false и проверяем массив positionList с помощью ArraySize и внутреннего цикла, чтобы определить, существует ли нужный тикет в positionList[j].ticket. Если позиция не отслеживается, получаем openPrice через PositionGetDouble с параметром POSITION_PRICE_OPEN и вызываем AddPositionToList с параметрами ticket, openPrice, LondonRangePoints и lastCheckedDay. Это гарантирует, что каждая подходящая позиция добавляется в список без дубликатов. Ниже представлен результат.

Поскольку на данном этапе всё работает корректно, можем определить функцию для управления позициями.
//+------------------------------------------------------------------+ //| Remove position from tracking list when closed | //+------------------------------------------------------------------+ void RemovePositionFromList(ulong ticket) { for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through list if (positionList[i].ticket == ticket) { //--- Match ticket for (int j = i; j < ArraySize(positionList) - 1; j++) { //--- Shift elements positionList[j] = positionList[j + 1]; //--- Copy next } ArrayResize(positionList, ArraySize(positionList) - 1); //--- Resize array break; //--- Exit loop } } } //+------------------------------------------------------------------+ //| Manage trailing stops | //+------------------------------------------------------------------+ void ManagePositions() { if (PositionsTotal() == 0 || !UseTrailing) return; //--- Exit if no positions or no trailing isTrailing = false; //--- Reset trailing flag double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask double point = _Point; //--- Get point value for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through positions ulong ticket = positionList[i].ticket; //--- Get ticket if (!PositionSelectByTicket(ticket)) { //--- Select position RemovePositionFromList(ticket); //--- Remove if not selected continue; //--- Skip } if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip if not matching double openPrice = positionList[i].openPrice; //--- Get open price long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; //--- Get current price double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; //--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { //--- Check for trailing double newSL = 0.0; //--- New SL variable if (positionType == POSITION_TYPE_BUY) { //--- Buy position newSL = currentPrice - TrailingPoints * point; //--- Calculate new SL } else { //--- Sell position newSL = currentPrice + TrailingPoints * point; //--- Calculate new SL } double currentSL = PositionGetDouble(POSITION_SL); //--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { //--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { //--- Modify position positionList[i].trailingActive = true; //--- Set trailing active isTrailing = true; //--- Set global trailing } } } } }
Здесь мы реализуем функции для удаления закрытых позиций из списка отслеживания и управления трейлинг-стопами. Начнем с функции RemovePositionFromList для очистки массива positionList при закрытии позиции, принимающей параметр ticket. Мы перебираем positionList с помощью ArraySize, и если positionList[i].ticket совпадает с ticket, сдвигаем последующие элементы с помощью внутреннего цикла, копируя positionList[j + 1] в positionList[j], затем уменьшаем размер массива с помощью ArrayResize и выходим из цикла. Эта функция обеспечивает актуальность списка и исключает лишние проверки закрытых позиций, особенно при использовании трейлинга и их закрытии.
Далее создаем функцию ManagePositions для обработки трейлинг-стопов открытых сделок. Сразу выходим, если PositionsTotal равно 0 или UseTrailing равен false. Сбрасываем isTrailing в false, получаем currentBid и currentAsk через SymbolInfoDouble с параметрами SYMBOL_BID и SYMBOL_ASK, а также значение point как _Point. Перебираем positionList с помощью ArraySize, получаем ticket и выбираем позицию через PositionSelectByTicket. Если выбрать не удалось, вызываем RemovePositionFromList и продолжаем цикл. Если позиция не соответствует символу _Symbol или магическому числу MagicNumber через PositionGetString и PositionGetInteger, пропускаем ее. Получаем openPrice из positionList[i], тип позиции positionType через PositionGetInteger, текущую цену currentPrice в зависимости от типа позиции и рассчитываем profitPoints как разницу, деленную на point.
Если profitPoints больше или равен MinProfitPoints + TrailingPoints, вычисляем новый уровень стоп-лосса newSL как currentPrice - TrailingPoints * point для покупок или currentPrice + TrailingPoints * point для продаж. Получаем текущий стоп-лосс currentSL через PositionGetDouble, и если новый стоп-лосс newSL лучше текущего значения как минимум на point, модифицируем позицию через obj_Trade.PositionModify, используя нормализованное значение newSL и текущий тейк-профит из PositionGetDouble. При успешном выполнении устанавливаем positionList[i].trailingActive и isTrailing в true, что позволяет динамически подтягивать стоп-лосс, фиксируя прибыль и одновременно давая прибыльным сделкам развиваться. Теперь остается лишь вызывать эту функцию на каждом тике для управления позициями. После компиляции получаем следующий результат.

На рисунке видно, что мы проверяем торговые условия, размещаем ордера и управляем ими в соответствии с торговой стратегией. Остается только протестировать программу. Для этого перейдем в новый раздел.
Тестирование на истории
После тщательного тестирования на истории получены следующие результаты.
График тестирования:

Отчет тестирования:

Заключение
Итак, мы разработали систему для торговли на пробое лондонской сессии на MQL5. Она анализирует диапазоны до открытия сессии и устанавливает отложенные ордера с настраиваемым соотношением риск/прибыль, трейлинг-стопами и ограничениями на количество сделок. Также в ней мы реализовали панель управления для мониторинга диапазонов, уровней и просадки в реальном времени. Благодаря модульным компонентам, включая структуру PositionInfo, данная программа предлагает эффективный подход к торговле на пробой, который можно дополнительно оптимизировать, изменяя параметры сессии или риска.
Внимание: все содержание настоящей статьи предназначено только для целей обучения и не предназначена для других целей. Торговля связана со значительными финансовыми рисками, а рыночная волатильность может привести к убыткам. Перед использованием программы на реальном счете необходимы тщательное тестирование и грамотное управление рисками.
Вы можете свободно использовать представленные концепции и реализацию, чтобы адаптировать систему под свой стиль торговли или усилить собственные алгоритмические стратегии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18867
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Торговые инструменты на MQL5 (Часть 13): Создание ценовой панели на базе Canvas с панелями графика и статистики
Нейросети в трейдинге: Адаптивное масштабирование представлений (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Вторая проблема заключается в том, что уровни максимума/минимума и уровни покупки/продажи в панели управления не обновляются.
Уровни высокого / низкого диапазона четко отображаются на графике, поэтому я предполагаю, что уровни покупки / продажи также должны отображаться на графике и обновляться в панели управления, так как они являются производными непосредственно от уровней высокого / низкого диапазона.
Какие у вас есть предложения, чтобы заставить это работать правильно?
Заранее спасибо.
Что касается второй проблемы, то в статье это объясняется, но если предположить, что ваша проблема связана с плохими тестовыми данными и дать подсказку, когда диапазон находится в расчете, вы всегда будете видеть статус "Calculating...", пока не будет достаточно данных для установки сессии диапазона Лондона или любой сессии, которую вы определите во входных данных. Предполагая, что вы используете настройки по умолчанию, с предлондонским временем 3, и ваше время из общего скриншота 13 февраля, 2 бара после 22:00, что составляет 2*15 минут = 30, следовательно, давая 22:30, находится вне времени расчета диапазона, поэтому данные на панели должны быть видны, так как предыдущий установленный диапазон все еще в игре, если первая сессия еще не найдена, и будет очищен, когда расчет диапазона будет достигнут с полуночи. См. ниже:
Вам может понадобиться увидеть приведенную ниже логику поиска диапазона
И как он устанавливается.
На изображении ниже, хотя мы не знаем год вашего тестирования, мы возьмем 2025, если это 2020, как в вашем случае, у нас нет качественных данных для этого, поэтому в любом случае мы используем 2025 и, таким образом, расчет диапазона должен начаться в полночь.
Из изображения видно, что данные в 23:55 все еще нетронуты. Однако, когда наступит полночь, мы должны сбросить данные. Смотрите ниже.
Видно, что мы сбросили данные в полночь для другого расчета диапазона. На самом деле, когда расчет диапазона завершен, визуализация может помочь вам понять, что произошло на самом деле. Например, в вашем случае при использовании настроек по умолчанию мы увидим столбики raneg с 03:00 до 08:00, потому что именно это мы и определили. См. ниже:
Надеюсь, это еще раз прояснит ситуацию. Вы можете настроить все в соответствии с вашим стилем торговли. Чтобы избежать проблем, с которыми вы столкнулись, рекомендуется использовать надежные тестовые данные. Спасибо.
Что касается второй проблемы, то в статье это объясняется, но если предположить, что ваша проблема связана с плохими тестовыми данными и дать подсказку, то когда диапазон находится в процессе расчета, вы всегда будете видеть статус "Calculating...", пока не будет достаточно данных для установки сессии лондонского диапазона или любой другой сессии, которую вы определите во входных данных. Предполагая, что вы используете настройки по умолчанию, с предлондонским временем 3, и ваше время из общего скриншота 13 февраля, 2 бара после 22:00, что составляет 2*15 минут = 30, следовательно, давая 22:30, находится вне времени расчета диапазона, поэтому данные на панели должны быть видны, так как предыдущий установленный диапазон все еще в игре, если первая сессия еще не найдена, и будет очищен, когда расчет диапазона будет достигнут с полуночи. См. ниже:
Вам может понадобиться следующая логика для определения диапазона
И как он устанавливается.
Смотрите изображение ниже, хотя мы не знаем год вашего тестирования, мы возьмем 2025, если это 2020, как в вашем случае, у нас нет качественных данных для этого, так что в любом случае мы используем 2025 и, таким образом, расчет диапазона должен начинаться в полночь.
Из изображения видно, что данные в 23:55 все еще нетронуты. Однако, когда наступит полночь, мы должны сбросить данные. Смотрите ниже.
Вы можете видеть, что мы сбросили данные в полночь для другого расчета диапазона. На самом деле, когда расчет диапазона завершен, визуализация может помочь вам понять, что произошло на самом деле. Например, в вашем случае при использовании настроек по умолчанию мы увидим столбики raneg с 03:00 до 08:00, потому что именно это мы и определили. См. ниже:
Надеюсь, это еще раз прояснит ситуацию. Вы можете настроить все в соответствии с вашим стилем торговли. Чтобы избежать проблем, с которыми вы столкнулись, рекомендуется использовать надежные тестовые данные. Спасибо.
Большое спасибо за исчерпывающий ответ.
Да, я действительно прочитал статью, следил за кодированием своей собственной копии, пока не столкнулся с проблемами, которые я описал. Я увидел, что панель не обновляется, даже в стандартное время. Мой скриншот должен был показать, что, хотя коробка была нарисована на графике, данные были собраны, но панель не была обновлена. Кроме того, в логах не было сообщений об ошибках, связанных с недействительными ценами или уровнями.
Я добавил сообщения в логах в свою версию; из них видно, что панель не обновляется, когда диапазон слишком большой или слишком маленький, так что это может быть частью причины.
Я еще раз проверю качество тестовых данных. И спасибо, что указали, на какой паре вы тестировали; я обязательно внесу коррективы для выбранных мной пар.
Большое спасибо за помощь.
Большое спасибо за исчерпывающий ответ.
Да, я прочитал статью, следил за кодированием своей собственной копии, пока не столкнулся с проблемами, которые я описал. Я увидел, что панель не обновляется, даже в стандартное время. Мой скриншот должен был показать, что, хотя коробка была нарисована на графике, данные были собраны, но панель не была обновлена. Кроме того, в логах не было сообщений об ошибках, связанных с недействительными ценами или уровнями.
Я добавил сообщения в логах в свою версию; из них видно, что панель не обновляется, когда диапазон слишком большой или слишком маленький, так что это может быть частью причины.
Я еще раз проверю качество тестовых данных. И спасибо, что указали, на какой паре вы тестировали; я обязательно внесу коррективы для выбранных мной пар.
Большое спасибо за помощь.
Конечно. Добро пожаловать.
Спасибо, что поделились с нами своим кодом.
Поскольку я сам писал сессионно-зависимых советников, могу сказать, что код работает только в том случае, если ваш брокер всегда находится в часовом поясе GMT+1, а также использует британское летнее время.
Во всех остальных случаях время начала сессии не будет работать. Почему? Потому что лондонская сессия начинается в 8:00 утра по британскому времени. Зимой это 8:00 по Гринвичу, а летом - 7:00 по Гринвичу.
TimeCurrent() возвращает не ваше местное время, а всегда время с торгового сервера.