Проекты позволяют создавать прибыльных торговых роботов! Но это не точно

MetaQuotes | 11 июня, 2020

Создание торгового робота всегда начинается с написания маленького файла, который затем начинает расти в размерах, наполняться множеством дополнительных функций и пользовательских объектов. Большинство разработчиков на MQL5 справляются с этой проблемой с помощью включаемых файлов (MQH). Но лучше сразу же начинать писать любую программу для трейдинга в проекте — это выгодно во всех отношениях.


Преимущества работы в проекте

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

Пример проекта

Таким образом, проект — это не просто набор файлов и папок в отдельном каталоге, но еще и возможность разбить сложную программу на продуманную структуру, в которой видны взаимосвязи и все необходимые данные находятся под рукой, а именно:

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


Создание проекта

Новый проект создается как и обычная MQL5-программа с помощью Мастера MQL5. Нажмите "Новый проект", а затем пройдите все шаги: задайте имя программы, добавьте входные параметры, укажите используемые обработчики событий. По окончании работы Мастера MQL5 откроется MQPROJ-файл для управления свойствами проекта.

Свойства проекта>


Здесь вы можете указать версию, задать описание программы, добавить иконку, а также управлять дополнительными опциями:

  1. Maximum optimization — оптимизация исполняемого EX5 файла для максимального быстродействия. Отключение этой опции ускорит компиляцию исходного кода, но получаемый EX5-файл может работать существенно медленней.
  2. Check floating point dividers — проверка вещественных чисел типа double и float на ноль в операциях деления. Отключение этой опции может повысить скорость работы, но делать это нужно сознательно.
  3. Use tester optimization cache — по умолчанию опция включена и тестер сохраняет все результаты выполненных проходов в кеш оптимизации, которые используются повторных расчетах. При необходимости кеш можно отключить с помощью свойства tester_no_cache, но в проекте это делается снятием галочки.

Если файл проекта закрыт, его всегда можно открыть заново с помощью команды контекстного меню "Свойства". Для более глубокого изучения содержимого MQPROJ-файла откройте его в текстовом виде с помощью команды "Открыть". Это поможет понять, как устроены проекты изнутри.

{
  "platform"    :"mt5",
  "program_type":"expert",
  "copyright"   :"Copyright 2019, MetaQuotes Software Corp.",
  "link"        :"https:\/\/www.mql5.com",
  "version"     :"1.00",
  "description" :"The mean reversion strategy: the price breaks the channel border outwards and reverts back towards the average. The channel is represented by Bollinger Bands. The Expert Advisor enters the market using limit orders, which can only be opened in the trend direction.",
  "icon"        :"Mean Reversion.ico",
  "optimize"    :"1",
  "fpzerocheck" :"1",
  "tester_no_cache":"0",
  "tester_everytick_calculate":"0",

  "files":
  [
    {
      "path":".\\Mean Reversion.mq5",
      "compile":"true",
      "relative_to_project":"true"
    },
    {
      "path":"MQL5\\Include\\Trade\\Trade.mqh",
      "compile":"false",
      "relative_to_project":"false"
    },
....


    Правила торговли

    Возьмем простые классические правила: входим в рынок при касании ценой линии Боллинджера. Это одна из разновидностей торговли на возврат к среднему.

    Вход в рынок на линиях Боллинджера

    Входить будем только лимитными отложенными ордерами. Кроме того, добавим дополнительное правило — торгуем только в направлении тренда. Это означает, что при восходящем тренде выставляем только Buy Limit на нижней границе канала, а при нисходящем — Sell Limit на верхней границе.

    Направление тренда можно определять множеством способов, возьмем самый простой вариант — по взаимному расположению двух скользящих средних. Если быстрая скользящая средняя (Fast EMA) находится выше медленной, то определяем восходящий тренд. Если наоборот, то — нисходящий.

    Определение тренда с помощью двух скользящих средних

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

    Здесь k — некоторый коэффициент, который необходимо подобрать.

    Вычисление ширины канала Боллинджера с помощью индикатора ATR


    Таким образом, при создании проекта нам необходимо указать 8 входных параметров для определения торговых сигналов. Торговать советник будет всегда фиксированным лотом, размер которого задается параметром InpLot. И есть еще один служебный неопимизируемый параметр InpMagicNumber, чтобы советник работал только со своими ордерами и позициями.

    //--- Параметры канала
    input int             InpBBPeriod   =20;           // период индикатора Боллинджера
    input double          InpBBDeviation=2.0;          // отклонение полос Боллинджера от средней
    //-- периоды EMA для вычисления тренда 
    input int             InpFastEMA    =12;           // период Fast EMA
    input int             InpSlowEMA    =26;           // период Slow EMA
    //-- ATR parameters
    input int             InpATRPeriod  =14;           // период индикатора ATR
    input double          InpATRCoeff   =1.0;          // коэффициент ATR для определения флета
    //--- управление капиталом
    input double          InpLot        =0.1;          // торговый объем в лотах
    //--- параметры таймфреймов
    input ENUM_TIMEFRAMES InpBBTF       =PERIOD_M15;   // таймфрейм, на котором берутся значения индикатора Боллинджера
    input ENUM_TIMEFRAMES InpMATF       =PERIOD_M15;   // таймфрейм, на котором определяется тернд
    //--- идентификатор советника для торговых транзакций
    input long            InpMagicNumber=245600;       // Magic Number

    Чтобы не подбирать вручную таймфрейм для определения тренда и ширины канала, добавлены входные параметры InpBBTF и InpMATF. Это позволит прямо во время оптимизации искать подходящие таймфреймы и сократит лишние операции. То есть мы можем запустить советника на таймфрейме M1, при этом он будет использовать индикатор Bollinger Bands на M15 и скользящие средние с M30. Для индикатора ATR мы не стали вводить отдельный входной параметр таймфрейма, чтобы не увеличивать число параметров.


    Пишем функции

    Итак, проект создан и пора приступать к написанию самого советника. Приведем только 3 основные функции, которые описывают правила.

    Вычисление ширины канала Боллинджера просто — копируем значения из индикаторных буферов.

    //+------------------------------------------------------------------+
    //| Получает значения границ канала                                  |
    //+------------------------------------------------------------------+
    bool ChannelBoundsCalculate(double &up, double &low)
      {
    //--- получим значения индикатора Bollinger Bands 
       double bbup_buffer[];
       double bblow_buffer[];
       if(CopyBuffer(ExtBBHandle, 1, 1, 1, bbup_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtBBHandle,0,1,2,bbup_buffer), code=%d", __FILE__, GetLastError());
          return(false);
         }
    
       if((CopyBuffer(ExtBBHandle, 2, 1, 1, bblow_buffer)==-1))
         {
          PrintFormat("%s: Failed CopyBuffer(ExtBBHandle,0,1,2,bblow_buffer), code=%d", __FILE__, GetLastError());
          return(false);
         }
       low=bblow_buffer[0];
       up =bbup_buffer[0];
    //--- успешно
       return(true);
      }

    Определение флета также не вызывает проблем. Сначала получаем значения границ канала, затем вычисляем ширину и сравниваем со значением индикатора ATR, умноженного на коэфициент InpATRCoeff.

    //+------------------------------------------------------------------+
    //|  Возвращает true, если канал слишком узкий (означает флет)       |
    //+------------------------------------------------------------------+
    int IsRange()
      {
    //--- получим значение ATR на последнем завершенном баре
       double atr_buffer[];
       if(CopyBuffer(ExtATRHandle, 0, 1, 1, atr_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtATRHandle,0,1,2,atr_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
       double atr=atr_buffer[0];
    //--- получим границы канала
       if(!ChannelBoundsCalculate(ExtUpChannel, ExtLowChannel))
          return(NO_VALUE);
       ExtChannelRange=ExtUpChannel-ExtLowChannel;
    //--- если ширина канала меньше ATR*коэффициент, то это флет
       if(ExtChannelRange<InpATRCoeff*atr)
          return(true);
    //--- флет не обнаружен
       return(false);
      }

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

    #define NO_VALUE      INT_MAX                      // невалидное значение при вычислении Сигнала или Тренда

    Самой большой по объему получилась функция для определения тренда.

    //+------------------------------------------------------------------+
    //| Возвращает 1 для UpTrend или -1 для DownTrend (0 = нет тренда)   |
    //+------------------------------------------------------------------+
    int TrendCalculate()
      {
    //--- сначала проверимся на флет 
       int is_range=IsRange();
    //--- проверим результат
       if(is_range==NO_VALUE)
         {
          //--- если проверка неуспешна, то выходим досрочно со значение "нет значения"
          return(NO_VALUE);
         }
    //--- во флете тренд не вычисляем
       if(is_range==true) // узкий диапазон, возвращаем "flat"
          return(0);
    //--- получим значение ATR на последнем завершенном баре
       double atr_buffer[];
       if(CopyBuffer(ExtBBHandle, 0, 1, 1, atr_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtATRHandle,0,1,2,atr_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- получим значение Fast EMA на последнем завершенном баре
       double fastma_buffer[];
       if(CopyBuffer(ExtFastMAHandle, 0, 1, 1, fastma_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtFastMAHandle,0,1,2,fastma_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- получим значение Slow EMA на последнем завершенном баре
       double slowma_buffer[];
       if(CopyBuffer(ExtSlowMAHandle, 0, 1, 1, slowma_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtSlowMAHandle,0,1,2,slowma_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- по умолчанию тренд не определен
       int trend=0;
    //--- если быстрая EMA выше медленной
       if(fastma_buffer[0]>slowma_buffer[0])
          trend=1;   // восходящий тренд (uptrend)
    //--- если быстрая EMA ниже медленной
       if(fastma_buffer[0]<slowma_buffer[0])
          trend=-1;  // нисходящий тренд (downtrend)
    //--- возвращаем направление тренда
       return(trend);
      }

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

    //+------------------------------------------------------------------+
    //| Checks the emergence of a new bar on the current timeframe,      |
    //| also calculates the trend and the signal                         |
    //+------------------------------------------------------------------+
    bool IsNewBar(int &trend)
      {
    //--- здесь постоянно хранится время открытия текущего бара между вызовами функции
       static datetime timeopen=0;
    //--- получим время открытия текущего бара 
       datetime time=iTime(NULL, InpMATF, 0);
    //--- если время не изменилось, значит бар не является новым, выходим со значением false
       if(time==timeopen)
          return(false);
    //--- бар является новым, значит нужно вычислить направление тренда
       trend=TrendCalculate();
    //--- если не удалось получить направление тренда, то выйдем и попробуем получить на следующем вызове
       if(trend==NO_VALUE)
          return(false);
    //--- все проверки прошли успешно: бар является новым и направление тренда получено
       timeopen=time; //запомним время открытия текущего бара для последующих вызовов.
    //---
       return(true);
      }

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

    Весь торговый алгоритм представлен в обработчике OnTick():

    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       static bool order_sent    =false;    // не удалось установить лимитный ордер на текущем баре
       static bool order_deleted =false;    // не удалось удалить лимитный ордер на текущем баре
       static bool order_modified=false;    // не удалось модифицировать лимитный ордер на текущем баре
    //--- если входные параметры невалидны, прекращаем тестирование на первом же тике
       if(!ExtInputsValidated)
          TesterStop();
    //--- проверим появление нового бара и направление тренда
       if(IsNewBar(ExtTrend))
         {
          //--- сбросим значения статических переменных в исходное состояние
          order_sent    =false;
          order_deleted =false;
          order_modified=false;
         }
    //--- создадим вспомогательные переменные, чтобы проверки вызывались только один раз на текущем баре
       bool order_exist   =OrderExist();
       bool trend_detected=TrendDetected(ExtTrend);
    //--- если нет тренда или есть открытая позиция, удалим отложенные ордеры
       if(!trend_detected || PositionExist())
          if(!order_deleted)
            {
             order_deleted=DeleteLimitOrders();
             //--- если ордера были успешно удалены, никаких других операций на текущем баре делать не требуется
             if(order_deleted)
               {
                //--- запрещаем установку и модификацию ордеров
                order_sent    =true;
                order_modified=true;
                return;
               }
            }
    
    //--- есть тренд
       if(trend_detected)
         {
          //--- установим ордер на границу канала, если его еще нет
          if(!order_exist && !order_sent)
            {
             order_sent=SendLimitOrder(ExtTrend);
             if(order_sent)
                order_modified=true;
            }
          //--- пропробуем передвинуть ордер на границу канала, если это еще не было сделано на текущем баре
          if(order_exist && !order_modified)
             order_modified=ModifyLimitOrder(ExtTrend);
         }
    //---
      }

    Остальные торговые функции советника являются достаточно стандартными и описываться не будут. Исходные коды проекта входят в поставку терминала MetaTrader 5 и находятся в каталоге MQL5\Experts\Examples.

    Расположение проекта MeanReversion в Навигаторе


    Оптимизация параметров и добавлениe Set-файлов

    После написания советника необходимо найти оптимальные значения входных параметров в тестере стратегий. Немногие знают, что тестер позволяет легко копировать значения вкладок "Настройки" и "Параметры" в буфер обмена с помощью стандартной комбинации Ctr+C. Это позволяет быстро передать ваши настройки другому человеку, например, заказчику через чат Фриланса, без необходимости сохранять их в set-файл. Заказчик может скопировать эти данные в буфер обмена и вставить в тестер на вкладке "Настройки" с помощью обратной операции Ctr+V.

    Разумеется, сохранение настроек в set-файл никто не отменял, и многие Продавцы в Маркете сопровождают своих советников такими файлами для удобства покупателей — те сразу же могут загрузить правильные наборы параметров для тестирования и оптимизации на нужном инструменте. Для каждого инструмента требуется создавать отдельный set-файл, если советников и таких файлов много, то можно легко запутаться. Проекты позволяют покупателям ваших роботов работать только со своими наборами входных параметров и не искать их на диске при смене символа.

    Покажем на примере, как проекты позволяют добавлять нужные наборы параметров прямо в EX5-файл советника. Выберите инструмент, на котором будет проводиться оптимизация, например, EURUSD. Установите значения Start, Step и Stop для оптимизируемых параметров и запустите оптимизацию. По окончании на вкладке "Оптимизация" сделайте двойной клик на лучшем на ваш взгляд проходе — значения входных параметров из этого прохода подставятся на вкладке "Параметры", после чего запустится одиночное тестирование. Теперь вы можете сохранить найденные параметры в set-файл, но передавать его отдельно уже не требуется. Сохраните набор параметров под именем, скажем, EURUSD.set, что означает, что параметры предназначены именно для этого символа, а не для GBPJPY.

    Сохранение настроек входных параметров в set-файл

    Проделайте эту операцию для каждого символа, на котором может работать ваш советник. Таким образом, пусть у вас будет набор из 9 set-файлов. Теперь просто добавьте эти файлы в ваш проект — создайте соответствующую папку "Settings and files\Set", чтобы они хранились отдельно от исходников. Проекты позволяют поддерживать порядок с помощью правильной файловой структуры.

    Добавление set-файлов в проект


    Теперь скомпилируйте проект и откройте тестер стратегий с советником MeanReversion. Во вкладке "Параметры" в контекстном меню появится новый пункт "Загрузить из советника", где будут предложены все варианты из вашего набора set-файлов.

    Загрузка входных параметров из советника

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


    Проверка стратегии на реальных данных

    Советник MeanReversion в сентябре 2019 года был запущен на демосчете для проверки на программные и торговые ошибки в режиме реального времени. При этом советник стартовал в режиме портфеля с торговлей на множестве символов, как и задумывалось при оптимизации. Был арендован встроенный VPS, для онлайн мониторинга был создан приватный сигнал Many MeanReversion Optimized

    Торговые результаты за 9 месяцев

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

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

    Распределение по символам

    Стоит отметить, что c момента запуска советника в торговлю его входные параметры ни разу не менялись, более того, ни разу не проводилась даже миграция. То есть скомпилированный девять месяцев назад советник был отправлен торговать на восьми графиках на встроенном VPS и работает с тех пор без вмешательства. Сейчас мы уже и не можем вспомнить, почему из девяти set-наборов в работу были взяты только восемь. Как и то, какие параметры были использованы. Но тем не менее, созданный в учебных целях проект советника MeanReversion всё еще остается в деле и на 10 июня 2020 года показывает прибыль.


    Переходите на проекты — это выгодно

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

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

    С помощью советника MeanReversion из стандартной поставки MetaTrader 5 вы можете изучить и оценить все преимущества работы с проектами. Просто создайте или скопируйте его в новую папку и начните экспериментировать. Пользуйтесь проектами — это удобно и выгодно во многих отношениях!