English Deutsch 日本語
preview
Переосмысливаем классические стратегии (Часть 12):  Стратегия пробоев на паре EURUSD

Переосмысливаем классические стратегии (Часть 12): Стратегия пробоев на паре EURUSD

MetaTrader 5Примеры | 26 июня 2025, 12:20
88 1
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

В настоящей статье мы вместе построим торговую стратегию на MQL5. Мы будем внедрять торговую стратегию пробоев и последовательно совершенствовать ее, чтобы полностью раскрыть ее потенциал. Обсудим некоторые характеристики нашей стратегии.

Сосредоточимся на паре EURUSD и будем отслеживать ее движение на таймфрейме H1. Наша стратегия пробоев сначала зафиксирует текущие максимумы и минимумы цен, предлагаемых по паре EURUSD. Со временем мы будем ждать, когда ценовые уровни полностью откроются и закроются за пределами канала, созданного первоначальными максимумами и минимумами, которые мы зафиксировали.

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

Пока что указанная нами система будет открывать слишком много сделок. Нам нужно указать другие показатели силы или слабости, которые помогут нам отфильтровать убыточные сделки, которые мы потенциально можем совершить. Скользящая средняя может помочь нам быстро определить рыночные тенденции.

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

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

Мы протестируем нашу торговую стратегию в период с 1 января 2020 года по 30 ноября 2024 года на таймфрейме H1.

Наши технические индикаторы будут настроены следующим образом:

  1. Быстрая скользящая средняя: 5-периодная экспоненциальная скользящая средняя, применяемая к цене закрытия.
  2. Медленная скользящая средняя: 60-периодная экспоненциальная скользящая средняя, применяемая к цене закрытия.
  3. Средний истинный диапазон (Average True Range): 14-периодный индикатор ATR.
Наше торговое приложение работает в соответствии с основными торговыми правилами. Изначально, когда наша система загружается в первый раз, мы просто отмечаем предыдущий максимум и минимум свечи, а затем ждем, пока цена не пробьет их с одной из сторон. Пока этого не произойдет, наше смещение будет оставаться равным 0, и у нас не будет подтверждений или размещенных сделок.


    Рис. 1: Начальное состояние нашего торгового приложения.

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


    Рис. 2: Наше торговое приложение обнаружило отклонение на рынке.

    Если уровни цен подтвердят наше смещение, то у нас будет достаточно уверенности, чтобы открыть позицию на рынке. Изначально наша стратегия будет заключаться в следовании тренду. Поэтому, если цены пробьются выше канала, мы будем искать возможности для покупки.

      Рис. 3: Наши позиции открываются после подтверждения нашего смещения.


      Начинаем на MQL5

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

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

      Нам также нужно будет определить другие важные переменные в нашем торговом приложении, которые помогут нам отслеживать состояние рынка. Давайте ознакомимся с самыми важными из них.

      Переменная
      Предполагаемая цель
      Смещение
      Параметр bias (смещение) символизирует направление, в котором движутся цены, его допустимое значение равно 1, если тренд бычий, и -1, если тренд медвежий. В противном случае он будет установлен на 0.
      .
      Скользящие средние
      Быстрая скользящая средняя (ma_f) и медленная скользящая средняя (ma_s) определяют тенденцию. Если ma_f[0] > ma_s[0] и цена (c) находится выше быстрой скользящей средней, открывается сделка на покупку. В противном случае, если ma_f[0] < ma_s[0], а цена ниже медленной скользящей средней, открывается сделка на продажу.
      Пробой
      При пробое уровня канала (верхней или нижней границы) устанавливается направление движения (смещение).
      Уровни пробоев
      Уровень пробоев покажет нам, в каком направлении, по нашему мнению, продолжат двигаться рынки в будущем. Если рынки пробьют верхнюю границу, наши настроения будут бычьими.
      Подтверждение сигнала
      Наши сделки не будут совершаться без подтверждения сигнала. Сигнал подтверждается, если рынок сохраняет свое направление после пробоя. Если подтверждение утеряно, позиция может быть скорректирована или закрыта.
      Управление ордерами Сделки, которые мы будем заключать, будут зависеть от смещения, которое мы в настоящее время наблюдаем на рынке. В случае восходящего тренда (смещение == 1) отправляется команда: Trade.Buy(vol, Symbol(), ask, channel_low, 0, "Volatility Doctor AI"); В противном случае, в случае нисходящего тренда (смещение == -1), будет отправлена команда: Trade.Sell(vol, Symbol(), bid, channel_high, 0, "Volatility Doctor AI");
       Стоп-лосс  Изначально задавалось значение channel_low для сделок на покупку и channel_high для сделок на продажу, а в будущем обновлялось с использованием значения ATR.

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

      //+------------------------------------------------------------------+
      //|                                                MTF Channel 2.mq5 |
      //|                                        Gamuchirai Zororo Ndawana |
      //|                          https://www.mql5.com/en/gamuchiraindawa |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/en/gamuchiraindawa"
      #property version   "1.00"
      

      Теперь загрузим торговую библиотеку.

      //+------------------------------------------------------------------+
      //| Library                                                          |
      //+------------------------------------------------------------------+
      #include  <Trade/Trade.mqh>
      CTrade Trade;
      

      Определим константы для нашего торгового приложения, такие как периоды некоторых наших технических индикаторов.

      //+------------------------------------------------------------------+
      //| Constants                                                        |
      //+------------------------------------------------------------------+
      const  int ma_f_period = 5; //Slow MA
      const  int ma_s_period = 60; //Slow MA
      

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

      //+------------------------------------------------------------------+
      //| Inputs                                                           |
      //+------------------------------------------------------------------+
      input  group "Money Management"
      input int lot_multiple = 5; //Lot Multiple
      input int atr_multiple = 5; //ATR Multiple
      

      Глобальные переменные, которые мы будем использовать в большей части нашей программы.

      //+------------------------------------------------------------------+
      //| Global varaibles                                                 |
      //+------------------------------------------------------------------+
      double channel_high = 0;
      double channel_low  = 0;
      double o,h,l,c;
      int    bias = 0;
      double bias_level = 0;
      int    confirmation = 0;
      double vol,bid,ask,initial_sl;
      int    atr_handler,ma_fast,ma_slow;
      double atr[],ma_f[],ma_s[];
      double bo_h,bo_l;
      

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

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //---
         setup();
      //---
         return(INIT_SUCCEEDED);
        }
      

      Если мы более не используем советник, следует освободить ресурсы, которые больше не используем.

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //---
            IndicatorRelease(atr_handler);
            IndicatorRelease(ma_fast);
            IndicatorRelease(ma_slow);
        }

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

      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
      //--- If we have positions open
         if(PositionsTotal() > 0)
            manage_setup();
      
      //--- Keep track of time
         static datetime timestamp;
         datetime time = iTime(Symbol(),PERIOD_CURRENT,0);
         if(timestamp != time)
           {
            //--- Time Stamp
            timestamp = time;
            if(PositionsTotal() == 0)
               find_setup();
           }
        }
      

      Следующая функция будет отвечать за загрузку наших технических индикаторов и получение рыночных данных.

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
         channel_high = iHigh(Symbol(),PERIOD_M30,1);
         channel_low  = iLow(Symbol(),PERIOD_M30,1);
         vol = lot_multiple * SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
         atr_handler = iATR(Symbol(),PERIOD_CURRENT,14);
         ma_fast     = iMA(Symbol(),PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow     = iMA(Symbol(),PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
        }
      

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

      //+---------------------------------------------------------------+
      //| Update channel                                                |
      //+---------------------------------------------------------------+
      void update_channel(double new_high, double new_low)
        {
         channel_high = new_high;
         channel_low  = new_low;
         ObjectDelete(0,"Channel High");
         ObjectDelete(0,"Channel Low");
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
        }
      

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

      //+---------------------------------------------------------------+
      //| Manage setup                                                  |
      //+---------------------------------------------------------------+
      void manage_setup(void)
        {
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         CopyBuffer(atr_handler,0,0,1,atr);
         Print("Managing Position");
      
         if(PositionSelect(Symbol()))
           {
            Print("Position Found");
            initial_sl = PositionGetDouble(POSITION_SL);
           }
      
         if(bias == 1)
           {
            Print("Position Buy");
            double new_sl = (ask - (atr[0] * atr_multiple));
            Print("Initial: ",initial_sl,"\nNew: ",new_sl);
            if(initial_sl < new_sl)
              {
               Trade.PositionModify(Symbol(),new_sl,0);
               Print("DONE");
              }
           }
      
         if(bias == -1)
           {
            Print("Position Sell");
            double new_sl = (bid + (atr[0] * atr_multiple));
            Print("Initial: ",initial_sl,"\nNew: ",new_sl);
            if(initial_sl > new_sl)
              {
               Trade.PositionModify(Symbol(),new_sl,0);
               Print("DONE");
              }
           }
      
        }
      

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

      //+---------------------------------------------------------------+
      //| Find Setup                                                    |
      //+---------------------------------------------------------------+
      void find_setup(void)
        {
      //--- We are updating the system
         o = iOpen(Symbol(),PERIOD_CURRENT,1);
         h = iHigh(Symbol(),PERIOD_CURRENT,1);
         l = iLow(Symbol(),PERIOD_CURRENT,1);
         c = iClose(Symbol(),PERIOD_CURRENT,1);
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         CopyBuffer(atr_handler,0,0,1,atr);
         CopyBuffer(ma_fast,0,0,1,ma_f);
         CopyBuffer(ma_slow,0,0,1,ma_s);
      
      //--- If we have no market bias
         if(bias == 0)
           {
            //--- Our bias is bullish
            if
            (
               (o > channel_high) &&
               (h > channel_high) &&
               (l > channel_high) &&
               (c > channel_high)
            )
              {
               bias = 1;
               bias_level = h;
               bo_h = h;
               bo_l = l;
               mark_bias(h);
              }
      
            //--- Our bias is bearish
            if
            (
               (o < channel_low) &&
               (h < channel_low) &&
               (l < channel_low) &&
               (c < channel_low)
            )
              {
               bias = -1;
               bias_level = l;
               bo_h = h;
               bo_l = l;
               mark_bias(l);
              }
           }
      
      //--- Is our bias valid?
         if(bias != 0)
           {
      
            //--- Our bearish bias has been violated
            if
            (
               (o > channel_high) &&
               (h > channel_high) &&
               (l > channel_high) &&
               (c > channel_high) &&
               (bias == -1)
            )
              {
               forget_bias();
              }
            //--- Our bullish bias has been violated
            if
            (
               (o < channel_low) &&
               (h < channel_low) &&
               (l < channel_low) &&
               (c < channel_low) &&
               (bias == 1)
            )
              {
               forget_bias();
              }
      
            //--- Our bullish bias has been violated
            if
            (
               ((o < channel_high) && (c > channel_low))
            )
              {
               forget_bias();
              }
      
            //--- Check if we have confirmation
            if((confirmation == 0) && (bias != 0))
              {
               //--- Check if we are above the bias level
               if
               (
                  (o > bias_level) &&
                  (h > bias_level) &&
                  (l > bias_level) &&
                  (c > bias_level) &&
                  (bias == 1)
               )
                 {
                  confirmation = 1;
                 }
      
               //--- Check if we are below the bias level
               if
               (
                  (o < bias_level) &&
                  (h < bias_level) &&
                  (l < bias_level) &&
                  (c < bias_level) &&
                  (bias == -1)
               )
                 {
                  confirmation = 1;
                 }
              }
           }
      
      //--- Check if our confirmation is still valid
         if(confirmation == 1)
           {
            //--- Our bias is bullish
            if(bias == 1)
              {
               //--- Confirmation is lost if we fall beneath the breakout level
               if
               (
                  (o < bias_level) &&
                  (h < bias_level) &&
                  (l < bias_level) &&
                  (c < bias_level)
               )
                 {
                  confirmation = 0;
                 }
              }
      
            //--- Our bias is bearish
            if(bias == -1)
              {
               //--- Confirmation is lost if we rise above the breakout level
               if
               (
                  (o > bias_level) &&
                  (h > bias_level) &&
                  (l > bias_level) &&
                  (c > bias_level)
               )
                 {
                  confirmation = 0;
                 }
              }
           }
      
      //--- Do we have a setup?
         if((confirmation == 1) && (bias == 1))
           {
            if(ma_f[0] > ma_s[0])
              {
               if(c > ma_f[0])
                 {
                  Trade.Buy(vol,Symbol(),ask,channel_low,0,"Volatility Doctor AI");
                  initial_sl = channel_low;
                 }
              }
           }
      
         if((confirmation == 1) && (bias == -1))
           {
            if(ma_f[0] < ma_s[0])
              {
               if(c < ma_s[0])
                 {
                  Trade.Sell(vol,Symbol(),bid,channel_high,0,"Volatility Doctor AI");
                  initial_sl = channel_high;
                 }
              }
           }
         Comment("O: ",o,"\nH: ",h,"\nL: ",l,"\nC:",c,"\nC H: ",channel_high,"\nC L:",channel_low,"\nBias: ",bias,"\nBias Level: ",bias_level,"\nConfirmation: ",confirmation,"\nMA F: ",ma_f[0],"\nMA S: ",ma_s[0]);
        }
      

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

      //+---------------------------------------------------------------+
      //| Mark our bias levels                                          |
      //+---------------------------------------------------------------+
      void mark_bias(double f_level)
        {
         ObjectCreate(0,"Bias",OBJ_HLINE,0,0,f_level);the
        }
      

      Наконец, если ценовые уровни вернутся в торговый канал после предыдущего пробоя, мы будем считать старый канал недействительным и обновим новую позицию канала до уровней, созданных свечой пробоя.

      //+---------------------------------------------------------------+
      //| Forget our bias levels                                        |
      //+---------------------------------------------------------------+
      void forget_bias()
        {
         update_channel(bo_h,bo_l);
         bias = 0;
         bias_level = 0;
         confirmation = 0;
         ObjectDelete(0,"Bias");
        }
      //+------------------------------------------------------------------+
      

      Теперь мы готовы к бэк-тестированию торговой стратегии пробоев. Я назвал приложение "MTF Channel 2", что означает канал с множеством таймфреймов (Multiple Time Frame Channel). Мной выбран символ EURUSD на таймфрейме H1. Даты наших тестов совпадают с датами, указанными нами ранее. Читатель заметит, что эти 3 конкретные настройки зафиксированы во всех 3 тестах.

      Our initial settings

      Рис. 4: Первый пакет настроек, использованный для нашего первоначального бэк-теста.

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

      Our second settings

      Рис. 5: Второй пакет настроек, отобранных для тестирования нашей стратегии.

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

      Our system settings

      Рис. 6: Наши настройки управления капиталом.

      Посмотрим на нашу стратегию в действии. На рисунке 7 ниже, в правой части скриншота, мы видим внутренние переменные, которые наше приложение использует для принятия решений. Обратите внимание, что все наши сделки будут заключены только в том случае, если для подтверждения установлено значение 1.

      Наша система в действии

      Рис. 7: Бэк-тестирование нашей торговой стратегии на паре EURUSD.

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

      Our account balance over time.

      Рис. 8: Просмотр графика, связанного с нашим бэк-тестированием.

      Давайте подробнее остановимся на только что проведенном нами тесте. Очевидно, что наша стратегия выявила в общей сложности 53 сделки, и 70% из них были убыточными. Наш коэффициент Шарпа является отрицательным. Это плохие показатели эффективности.

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

      Detailed analysis of system 1

      Рис. 9: Подробности нашего бэк-теста.

      Улучшаем наши первые результаты

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

      Один из вариантов, который у нас есть, - это сравнить динамику EUR и USD с общим эталоном. Для этого мы можем использовать фунты стерлингов (GBP). Мы сравним динамику пары EURGBP и GBPUSD, прежде чем совершим открытие позиции. То есть, если на нашем графике мы видим, что пара EURUSD находится в сильном бычьем тренде, мы также хотели бы видеть, что пара EURGBP движется в том же тренде, и мы надеемся, что пара GBPUSD также будет в бычьем тренде.

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

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

      //+------------------------------------------------------------------+
      //| Global variables                                                 |
      //+------------------------------------------------------------------+
      double channel_high = 0;
      double channel_low  = 0;
      double o,h,l,c;
      int    bias = 0;
      double bias_level = 0;
      int    confirmation = 0;
      double vol,bid,ask,initial_sl;
      int    atr_handler,ma_fast,ma_slow;
      double atr[],ma_f[],ma_s[];
      double bo_h,bo_l;
      int    last_trade_state,current_state;
      int    eurgbp_willr, gbpusd_willr;
      string symbols[] = {"EURGBP","GBPUSD"};
      

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

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
      //--- Select the symbols we need
         SymbolSelect("EURGBP",true);
         SymbolSelect("GBPUSD",true);
      //--- Reset our last trade state
         last_trade_state = 0;
      //--- Mark the current high and low
         channel_high = iHigh("EURUSD",PERIOD_M30,1);
         channel_low  = iLow("EURUSD",PERIOD_M30,1);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
      //--- Our trading volums
         vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
      //--- Our technical indicators
         atr_handler  = iATR("EURUSD",PERIOD_CURRENT,14);
         eurgbp_willr = iWPR(symbols[0],PERIOD_CURRENT,wpr_period);
         gbpusd_willr = iWPR(symbols[1],PERIOD_CURRENT,wpr_period);
         ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
        }
      

      Аналогичным образом, когда наше торговое приложение больше не используется, можно выпустить несколько дополнительных технических индикаторов.

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //---
         IndicatorRelease(eurgbp_willr);
         IndicatorRelease(gbpusd_willr);
         IndicatorRelease(atr_handler);
         IndicatorRelease(ma_fast);
         IndicatorRelease(ma_slow);
        }
      

      Наша функция OnTick останется прежней. Однако функции, которые она будет вызывать, будут изменены. Во-первых, всякий раз, когда мы обновляем наш канал, мы должны обновлять 3 канала на рынках, за которыми мы следим. Один на EURUSD, второй на EURGBP и последний на GBPUSD.

      //+---------------------------------------------------------------+
      //| Update channel                                                |
      //+---------------------------------------------------------------+
      void update_channel(double new_high, double new_low)
        {
         channel_high = new_high;
         channel_low  = new_low;
         ObjectDelete(0,"Channel High");
         ObjectDelete(0,"Channel Low");
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
        }
      

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

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

      //+---------------------------------------------------------------+
      //| Find Setup                                                    |
      //+---------------------------------------------------------------+
      void find_setup(void)
        {
      //--- I have omitted code pieces that were unchanged
      //--- Do we have a setup?
         if((confirmation == 1) && (bias == 1) && (current_state != last_trade_state))
           {
            if(ma_f[0] > ma_s[0])
              {
               if(c > ma_f[0])
                 {
                  if(additional_confirmation(1))
                    {
                     Trade.Buy(vol,"EURUSD",ask,channel_low,0,"Volatility Doctor");
                     initial_sl = channel_low;
                     last_trade_state = 1;
                    }
                 }
              }
           }
      
         if((confirmation == 1) && (bias == -1)  && (current_state != last_trade_state))
           {
            if(ma_f[0] < ma_s[0])
              {
               if(c < ma_s[0])
                 {
                  if(additional_confirmation(-1))
                    {
                     Trade.Sell(vol,"EURUSD",bid,channel_high,0,"Volatility Doctor");
                     initial_sl = channel_high;
                     last_trade_state = -1;
                    }
                 }
              }
           }
      }

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

      //+---------------------------------------------------------------+
      //| Check for true strength                                       |
      //+---------------------------------------------------------------+
      bool additional_confirmation(int flag)
        {
      //--- Do we have additional confirmation from our benchmark pairs?
      
      //--- Record the average change in the EURGBP and GBPUSD Market
         vector eurgbp_willr_f = vector::Zeros(1);
         vector gbpusd_willr_f = vector::Zeros(1);
      
         eurgbp_willr_f.CopyIndicatorBuffer(eurgbp_willr,0,0,1);
         gbpusd_willr_f.CopyIndicatorBuffer(gbpusd_willr,0,0,1);
      
         if((flag == 1) && (eurgbp_willr_f[0] > -50) && (gbpusd_willr_f[0] < -50))
            return(true);
         if((flag == -1) && (eurgbp_willr_f[0] < -50) && (gbpusd_willr_f[0] > -50))
            return(true);
      
         Print("EURGBP WPR: ",eurgbp_willr_f[0],"\nGBPUSD WPR: ",gbpusd_willr_f[0]);
         return(false);
        }
      

      Эта версия нашего приложения будет называться "MTF EURUSD Channel». Первая версия, которую мы создали, была более обобщенной и могла быть легко использована для торговли любым другим символом в нашем терминале. Однако в этой версии в качестве эталонов будут использоваться пары EURGBP и GBPUSD, и поэтому она более специализирована и предназначена только для торговли по паре EURUSD. Читатель заметит, что все наши условия тестирования идентичны первому тесту. Мы проведем этот бэк-тест, используя те же таймфреймы и те же периоды времени, что и при первом тестировании, с 1 января 2020 года по 30 ноября 2024 года.

      Our second batch EA to be tested

      Рис. 10: Первый пакет настроек для нашего бэк-тестирования стратегии пробоев канала EURUSD.

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

      Our second batch of inputs for our trading application.

      Рис. 11: Нам нужно, чтобы второй пакет настроек был идентичен настройкам, которые мы использовали в первом тесте.

      Использование лота, кратного 1, означает, что все мои сделки будут совершаться с минимальным размером лота. Если мы сможем сделать нашу систему прибыльной при минимальном размере лота, то увеличение кратности лота пойдет нам на пользу. Однако, если наша система не приносит прибыли при минимальном лоте, мы ничего не выиграем, увеличив размер лота.

      Our parameter settings

      Рис. 12: Параметры, которые мы будем использовать для управления поведением нашего приложения.

      Теперь мы можем увидеть, как наша торговая система работает на исторических данных. Обратите внимание, что эта версия нашей системы отслеживает сразу 3 рынка. Во-первых, мы всегда будем следить за парой EURUSD, чтобы по ней можно было судить о нашем смещении.

      Our system in action on the EURUSD

      Рис. 13: Наша система в действии на паре EURUSD.

      Наши позиции могут быть открыты только в том случае, если мы наблюдаем за парами EURGBP и GBPUSD, которые движутся в противоположных направлениях, как показано на рис. 14 и 15 ниже. Мы будем оценивать тенденцию на двух рынках, используя индикатор «Процентный диапазон Уильямса» (Williams Percent Range, WPR). Если WPR находится выше уровня 50, мы считаем тренд бычьим.

      Our firts benchmark pair

      Рис. 14: Наша первая подтверждающая пара - GBPUSD

      В данном случае мы нашли торговую возможность купить EURUSD. Мы определили эту возможность, потому что показатели WPR на двух рынках находились по разные стороны от уровня 50. За этим дисбалансом, вероятно, последуют волатильные рыночные условия, идеальные для любой стратегии пробоев.

      Our second benchmark pair.

      Рис. 15: Наша вторая эталонная пара.

      На рисунке 9 ниже показано, как баланс нашего имитируемого торгового счета меняется с течением времени.  Наша цель - глубоко понимать, почему наша стратегия терпит неудачу, чтобы мы могли попытаться исправить ее недостатки.

      The changes in account balance over time

      Рис. 16: Представление графика баланса нашего счета с течением времени.

      К сожалению, изменения, которые мы внесли в нашу систему, снизили прибыльность нашего торгового приложения. Наши средние убытки и прибыль увеличились на те же суммы. А доля прибыльных сделок незначительно снизилась. 

      Detailed analysis of system 2.

      Рис. 17: Подробные результаты нашего бэк-теста.

      Последняя попытка улучшения

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

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

      Для начала я создал скрипт, который поможет нам извлекать исторические рыночные данные. Чтобы можно было начать работу, просто перетащите скрипт на тот рынок, на котором вы хотите торговать. Скрипт будет извлекать рыночные данные для вас, а также две скользящие средние, которые нужны для нашей стратегии, в том же формате, который используется в нашем торговом приложении.

      //+------------------------------------------------------------------+
      //|                                                      ProjectName |
      //|                                      Copyright 2020, CompanyName |
      //|                                       http://www.companyname.net |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/en/users/gamuchiraindawa"
      #property version   "1.00"
      #property script_show_inputs
      
      //+------------------------------------------------------------------+
      //| Script Inputs                                                    |
      //+------------------------------------------------------------------+
      input int size = 100000; //How much data should we fetch?
      
      //+------------------------------------------------------------------+
      //| Global variables                                                 |
      //+------------------------------------------------------------------+
      int    ma_f_handler,ma_s_handler;
      double ma_f_reading[],ma_s_reading[];
      
      //+------------------------------------------------------------------+
      //| On start function                                                |
      //+------------------------------------------------------------------+
      void OnStart()
        {
      //--- Load indicator
         ma_s_handler  = iMA(Symbol(),PERIOD_CURRENT,60,0,MODE_EMA,PRICE_CLOSE);
         ma_f_handler  = iMA(Symbol(),PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
      
      //--- Load the indicator values
         CopyBuffer(ma_f_handler,0,0,size,ma_f_reading);
         CopyBuffer(ma_s_handler,0,0,size,ma_s_reading);
      
         ArraySetAsSeries(ma_f_reading,true);
         ArraySetAsSeries(ma_s_reading,true);
      
      //--- File name
         string file_name = "Market Data " + Symbol() +" MA Cross" +  " As Series.csv";
      
      //--- Write to file
         int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
      
         for(int i= size;i>=0;i--)
           {
            if(i == size)
              {
               FileWrite(file_handle,"Time","Open","High","Low","Close","MA 5","MA 60");
              }
      
            else
              {
               FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                         iOpen(Symbol(),PERIOD_CURRENT,i),
                         iHigh(Symbol(),PERIOD_CURRENT,i),
                         iLow(Symbol(),PERIOD_CURRENT,i),
                         iClose(Symbol(),PERIOD_CURRENT,i),
                         ma_f_reading[i],
                         ma_s_reading[i]
                        );
              }
           }
      //--- Close the file
         FileClose(file_handle);
      
        }
      //+------------------------------------------------------------------+
      

      Анализ данных на Python

      Теперь, когда у вас есть рыночные данные в формате CSV, можно приступить к созданию модели ИИ, которая, как мы надеемся, поможет нам прогнозировать ложные пробои и избегать их.

      import pandas as pd
      import numpy  as np
      from   sklearn.model_selection import TimeSeriesSplit,cross_val_score
      from   sklearn.linear_model    import Ridge
      from sklearn.metrics import mean_squared_error
      import matplotlib.pyplot as plt
      import seaborn as sns

      Ознакомимся с рыночными данными, извлеченными нами ранее. Обратите внимание на столбец «время» (Time) в моем фрейме данных, обратите внимание, что последняя запись, которая у меня есть, датирована 18 апреля 2019 года. Это делается намеренно. Напомним, что даты начала по обоим предыдущим тестам были 1 января 2020 года. Это означает, что мы не обманываем себя, предоставляя модели все ответы на наш тест для нее.

      #Define the forecast horizon
      look_ahead          = 24
      #Read in the data
      data                = pd.read_csv('Market Data EURUSD MA Cross As Series.csv')
      #Drop the last 4 years
      data                =  data.iloc[:(-24 * 365 * 4),:]
      data.reset_index(drop=True,inplace=True)
      #Label the data
      data['Target']      = data['Close'].shift(-look_ahead)
      data['MA 5 Target']      = data['MA 5'].shift(-look_ahead)
      data['MA 5 Close Target']      = data['Target'] - data['MA 5 Target']
      data['MA 60 Target']      = data['MA 60'].shift(-look_ahead)
      data['MA 60 Close Target']      = data['Target'] - data['MA 60 Target']
      data.dropna(inplace=True)
      data.reset_index(drop=True,inplace=True)
      data


      Рис. 18: Наши исторические рыночные данные.

      Проверим, действительно ли на рынке EURUSD по скользящим средним все еще легче предсказать саму цену. Чтобы проверить нашу гипотезу, мы обучим 30 идентичных нейронных сетей прогнозировать три цели одну за другой. Сначала спрогнозируем будущую цену, 5-периодную скользящую среднюю и 60-периодную скользящую среднюю. Все цели будут спроецированы на 24 шага в будущее. Во-первых, мы напрямую зафиксируем нашу точность прогнозирования цены.

      #Classical error
      classical_error = []
      epochs = 1000
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs')
        classical_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close']],data.loc[:,'Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      Далее мы зафиксируем нашу точность, прогнозируя 5-периодную скользящую среднюю.

      #MA Cross Over error
      ma_5_error = []
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs')
        ma_5_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 5']],data.loc[:,'MA 5 Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      Наконец, мы зафиксируем нашу точность, прогнозируя 60-периодную скользящую среднюю.

      #New error
      ma_60_error = []
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=10000,early_stopping=False,solver='lbfgs')
        ma_60_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 60']],data.loc[:,'MA 60 Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      Когда мы представляем графически наши результаты. Как видно из приведенного ниже рисунка 12, прогнозирование 60-периодной скользящей средней привело к наибольшей ошибке в нашей системе, а прогнозирование 5-периодной скользящей средней привело к меньшей ошибке, чем прямое прогнозирование цены.

      plt.plot(classical_error)
      plt.plot(ma_5_error)
      plt.plot(ma_60_error)
      plt.legend(['OHLC','MA 5 ','MA 60'])
      plt.axhline(np.mean(classical_error),color='blue',linestyle='--')
      plt.axhline(np.mean(ma_5_error),color='orange',linestyle='--')
      plt.axhline(np.mean(ma_60_error),color='green',linestyle='--')
      plt.grid()
      plt.ylabel('Cross Validated Error')
      plt.xlabel('Iteration')
      plt.title('Comparing Different The Error Associated With Different Targets')
      plt.show()

      Рис. 19: Визуализация ошибки, связанной с различными целями.

      Теперь попробуем экспортировать модель для нашего торгового приложения. Импортируем необходимые нам библиотеки.

      import onnx
      from skl2onnx import convert_sklearn
      from skl2onnx.common.data_types import FloatTensorType
      from sklearn.neural_network import MLPRegressor

      Укажем нужные нам модели. Для этой задачи я буду использовать 2 модели, поскольку скользящую среднюю за короткий период легко прогнозировать, я буду использовать простую модель Ridge для ее прогнозирования. Однако наша 60-периодная скользящая средняя оказалась сложной задачей. Поэтому я использую нейронную сеть для прогнозирования долгосрочной скользящей средней.

      ma_5_model = Ridge()
      ma_5_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Target'])
      ma_5_height_model = Ridge()
      ma_5_height_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Close Target'])
      ma_60_model = Ridge()
      ma_60_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Target'])
      ma_60_height_model = Ridge()
      ma_60_height_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Close Target'])

      Подготовка к экспортированию в ONNX.

      initial_type = [('float_input', FloatTensorType([1, 5]))]
      ma_5_onx = convert_sklearn(ma_5_model, initial_types=initial_type, target_opset=12 )
      ma_5_height_onx = convert_sklearn(ma_5_height_model, initial_types=initial_type, target_opset=12 )
      ma_60_height_onx = convert_sklearn(ma_60_height_model, initial_types=initial_type, target_opset=12 )
      ma_60_onx = convert_sklearn(ma_60_model, initial_types=initial_type, target_opset=12 )

      Сохраним в формат ONNX.

      onnx.save(ma_5_onx,'eurchf_ma_5_model.onnx')
      onnx.save(ma_60_onx,'eurchf_ma_60_model.onnx')
      onnx.save(ma_5_height_onx,'eurusd_ma_5_height_model.onnx')
      onnx.save(ma_60_height_onx,'eurusd_ma_60_height_model.onnx')

      Окончательные обновления в MQL5

      Применим наши новые модели, чтобы посмотреть, смогут ли они помочь нам отфильтровать ложные пробои на рынке. Первое обновление, которое нам нужно сделать, - это импортировать только что созданные модели ONNX.

      //+------------------------------------------------------------------+
      //|                                                MTF Channel 2.mq5 |
      //|                                        Gamuchirai Zororo Ndawana |
      //|                          https://www.mql5.com/en/gamuchiraindawa |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/en/gamuchiraindawa"
      #property version   "1.00"
      
      //+------------------------------------------------------------------+
      //| ONNX Resources                                                   |
      //+------------------------------------------------------------------+
      #resource "\\Files\\eurusd_ma_5_model.onnx"         as const uchar eurusd_ma_5_buffer[];
      #resource "\\Files\\eurusd_ma_60_model.onnx"        as const uchar eurusd_ma_60_buffer[];
      #resource "\\Files\\eurusd_ma_5_height_model.onnx"  as const uchar eurusd_ma_5_height_buffer[];
      #resource "\\Files\\eurusd_ma_60_height_model.onnx" as const uchar eurusd_ma_60_height_buffer[];

      Далее нам нужно создать несколько новых переменных, связанных с нашими моделями.

      //+------------------------------------------------------------------+
      //| Global varaibles                                                 |
      //+------------------------------------------------------------------+
      int     bias = 0;
      int     state = 0;
      int     confirmation = 0;
      int     last_cross_over_state = 0;
      int     atr_handler,ma_fast,ma_slow;
      int     last_trade_state,current_state;
      long    ma_5_model;
      long    ma_60_model;
      long    ma_5_height_model;
      long    ma_60_height_model;
      double  channel_high = 0;
      double  channel_low  = 0;
      double  o,h,l,c;
      double  bias_level = 0;
      double  vol,bid,ask,initial_sl;
      double  atr[],ma_f[],ma_s[];
      double  bo_h,bo_l;
      vectorf ma_5_forecast = vectorf::Zeros(1);
      vectorf ma_60_forecast = vectorf::Zeros(1);
      vectorf ma_5_height_forecast = vectorf::Zeros(1);
      vectorf ma_60_height_forecast = vectorf::Zeros(1);
      

      Необходимо расширить процедуру инициализации, чтобы теперь она настраивала наши ONNX-модели для нас.

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
      //--- Reset our last trade state
         last_trade_state = 0;
      //--- Mark the current high and low
         channel_high = iHigh("EURUSD",PERIOD_M30,1);
         channel_low  = iLow("EURUSD",PERIOD_M30,1);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
      //--- Our trading volums
         vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
      //--- Our technical indicators
         atr_handler  = iATR("EURUSD",PERIOD_CURRENT,14);
         ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
      //--- Setup our ONNX models
      //--- Define our ONNX model
         ulong input_shape [] = {1,5};
         ulong output_shape [] = {1,1};
      
      //--- Create the model
         ma_5_model = OnnxCreateFromBuffer(eurusd_ma_5_buffer,ONNX_DEFAULT);
         ma_60_model = OnnxCreateFromBuffer(eurusd_ma_60_buffer,ONNX_DEFAULT);
         ma_5_height_model = OnnxCreateFromBuffer(eurusd_ma_5_height_buffer,ONNX_DEFAULT);
         ma_60_height_model = OnnxCreateFromBuffer(eurusd_ma_60_height_buffer,ONNX_DEFAULT);
      
      //--- Store our models in a list
         long onnx_models[] = {ma_5_model,ma_5_height_model,ma_60_model,ma_60_height_model};
      
      //--- Loop over the models and set them up
         for(int i = 0; i < 4; i++)
           {
            if(onnx_models[i] == INVALID_HANDLE)
              {
               Comment("Failed to load AI module correctly: Invalid handle");
              }
      
            //--- Validate I/O
            if(!OnnxSetInputShape(onnx_models[i],0,input_shape))
              {
               Comment("Failed to set input shape correctly:  Wrong input shape ",GetLastError()," Actual shape: ",OnnxGetInputCount(ma_5_model));
              }
      
            if(!OnnxSetOutputShape(onnx_models[i],0,output_shape))
              {
               Comment("Failed to load AI module correctly: Wrong output shape ",GetLastError()," Actual shape: ",OnnxGetOutputCount(ma_5_model));
              }
           }
        }
      

      Если наша система больше не используется, необходимо освободить ресурсы, которые больше не используем.

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //--- Free the resources we don't need
         IndicatorRelease(atr_handler);
         IndicatorRelease(ma_fast);
         IndicatorRelease(ma_slow);
         OnnxRelease(ma_5_model);
         OnnxRelease(ma_5_height_model);
         OnnxRelease(ma_60_model);
         OnnxRelease(ma_60_height_model);
        }

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

      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
      //--- Keep track of time
         static datetime timestamp;
         datetime time = iTime(Symbol(),PERIOD_CURRENT,0);
         if(timestamp != time)
           {
            //--- Time Stamp
            timestamp = time;
            //--- Update system variables
            update();
            //--- Make a new prediction
            model_predict();
            if(PositionsTotal() == 0)
              {
               state = 0;
               find_setup();
              }
           }
      
      //--- If we have positions open
         if(PositionsTotal() > 0)
            manage_setup();
        }
      
      

      Необходимо определить функцию, ответственную за получение прогноза из наших ONNX-моделей в MQL5.

      //+------------------------------------------------------------------+
      //| Get a prediction from our model                                  |
      //+------------------------------------------------------------------+
      void model_predict(void)
        {
         //--- Moving average inputs
         float  a = (float) ma_f[0];
         float  b = (float) ma_s[0];
      
         //--- Price quotes
         float op = (float) iOpen("EURUSD",PERIOD_H1,0);
         float hi = (float) iHigh("EURUSD",PERIOD_H1,0);
         float lo = (float) iLow("EURUSD",PERIOD_H1,0);
         float cl = (float) iClose("EURUSD",PERIOD_H1,0);
      
         //--- ONNX inputs
         vectorf fast_inputs = {op,hi,lo,cl,a};
         vectorf slow_inputs = {op,hi,lo,cl,b};
      
         Print("Fast inputs: ",fast_inputs);
         Print("Slow inputs: ",slow_inputs);
      
         //--- Inference
         OnnxRun(ma_5_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_forecast);
         OnnxRun(ma_5_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_height_forecast);
         OnnxRun(ma_60_model,ONNX_DEFAULT,slow_inputs,ma_60_forecast);
         OnnxRun(ma_60_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_60_height_forecast);
        }
      

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

      Обратите внимание, что вызывается новая функция, valid setup, эта функция просто возвращает значение true, если наши условия пробоя верны.

      //+---------------------------------------------------------------+ //| Find a setup | //+---------------------------------------------------------------+ void find_setup(void) { //--- I have skipped parts of the code that remained the same    if(valid_setup())      {       //--- Both models are forecasting rising prices       if((c < (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c < (ma_5_forecast[0] + ma_5_height_forecast[0])))         {          if(last_trade_state != 1)            {             Trade.Buy(vol,"EURUSD",ask,0,0,"Volatility Doctor");             initial_sl = channel_low;             last_trade_state = 1;             last_cross_over_state = current_state;            }         }       //--- Both models are forecasting falling prices       if((c > (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c > (ma_5_forecast[0] + ma_5_height_forecast[0])))         {          if(last_trade_state != -1)            {             Trade.Sell(vol,"EURUSD",bid,0,0,"Volatility Doctor");             initial_sl = channel_high;             last_trade_state = -1;             last_cross_over_state = current_state;            }         }      }

      Проверим, не вышли ли мы запределы канала. Если вышли, функция вернет значение true, в противном случае — false.

      //+---------------------------------------------------------------+
      //| Do we have a valid setup?                                     |
      //+---------------------------------------------------------------+
      bool valid_setup(void)
        {
         return(((confirmation == 1) && (bias == -1)  && (current_state != last_cross_over_state)) || ((confirmation == 1) && (bias == 1) && (current_state != last_cross_over_state)));
        }
      

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

      Our input settings

      Рис. 20: Некоторые настройки, которые мы будем использовать для бэк-тестирования нашей последней торговой стратегии.

      Напомним, что наша модель обучалась только до 2019 года, но наше тестирование начнётся в 2020 году. Поэтому мы тщательно моделируем то, что на самом деле произошло бы, если бы мы разработали эту систему в прошлом.

      Our second batch of settings for the third test

      Рис. 21: Второй пакет настроек, которые мы будем использовать для бэк-тестирования нашей последней торговой стратегии.

      Ещё раз, наши настройки одинаковы во всех трех тестах.

      Our application settings

      Рис. 22: Настройки, которые мы будем использовать для управления нашим приложением в последнем тесте.

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

      Наша система ИИ в действии

      Рис. 23: Наша окончательная основанная на модели версия стратегии пробоев в действии.

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

      Our account balance over time

      Рис. 24: Результаты бэк-тестирования в результате тестирования нашей новой основанной на модели стратегии.

      Нашей целью было увеличить среднюю прибыль и уменьшить долю убыточных сделок, что мы и сделали. Наш общий убыток составил 498 долларов в первом тесте, 403 доллара во втором, а сейчас он составляет 298 долларов. В то же время наша общая прибыль составила 378 долларов в первом тесте и составляет 341 доллар в этом последнем тесте. Таким образом, очевидно, что внесенные нами изменения привели к сокращению наших общих убытков при сохранении общей прибыли практически на прежнем уровне. В нашей первой системе 70% всех наших сделок были убыточными. Однако при использовании нашей новой системы только 55% всех наших сделок были убыточными.

      Detailed analysis of our model based trading system

      Рис. 25: Подробные результаты бэк-тестирования нашей основанной на модели стратегии.

      Заключение

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

      Название файла 
      Описание
      MQL5 EURUSD AI
      Для построения нашей модели рынка EURUSD использовался ноутбук Jupyter.
      EURUSD MA 60 Model
      Модель ONNX использовалась для прогнозирования скользящей средней за 60 периодов.
      EURUSD MA 60 Height Model Модель ONNX, используемая для прогнозирования разницы между будущей ценой закрытия и будущей скользящей средней за 60 периодов
      EURUSD MA 5 Model
      Модель ONNX предназначенная для прогнозирования 5-периодной скользящей средней.
      EURUSD MA 5 Height Model Модель ONNX, используемая для прогнозирования разницы между будущей ценой закрытия и будущей скользящей средней за 5 периодов 
      MTF Channel 2
      Первая реализация нашей стратегии пробоев.
      MTF Channel 2 EURUSD
      Вторая реализация нашей стратегии пробоев, которая использовала подтверждение от эталонных пар.
      MTF Channel 2 EURUSD AI
      Третья реализация нашей основанной на модели стратегии пробоев.





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

      Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
      Aliaksandr Kazunka
      Aliaksandr Kazunka | 6 апр. 2025 в 04:23
      Здравствуйте! Почему мы используем скользящие средние с периодами 5 и 60? Не лучше ли сначала провести оптимизацию и выбрать лучшие периоды на основе исторических данных?
      Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
      Написание пользовательских индикаторов в торговой системе MetaTrader 4
      Анализ временных разрывов цен в MQL5 (Часть I): Создаем базовый индикатор Анализ временных разрывов цен в MQL5 (Часть I): Создаем базовый индикатор
      Анализ временных разрывов (таймгэпов) помогает трейдеру выявлять потенциальные точки разворота рынка. В статье рассматривается, что такое таймгэп, как его интерпретировать, а также каким образом с его помощью можно обнаружить вливание крупного объема в рынок.
      Особенности написания экспертов Особенности написания экспертов
      Написание и тестирование экспертов в торговой системе MetaTrader 4.
      Возможности Мастера MQL5, которые вам нужно знать (Часть 48): Аллигатор Билла Вильямса Возможности Мастера MQL5, которые вам нужно знать (Часть 48): Аллигатор Билла Вильямса
      Аллигатор, детище Билла Вильямса, представляет собой универсальный индикатор определения тренда, который дает четкие сигналы и часто сочетается с другими индикаторами. Классы Мастера MQL5 позволяют нам тестировать различные сигналы на основе паттернов, что позволяет нам рассмотреть и этот индикатор.