Универсальный торговый эксперт: Интеграция со стандартными модулями сигналов MetaTrader (часть 7)

Vasiliy Sokolov | 27 июня, 2016

Оглавление


Введение

В предыдущих частях статьи были рассмотрены механизмы, способствующие простому и эффективному созданию торговых алгоритмов — в частности, мы создавали алгоритм CStartegy. Проект непрерывно развивается вот уже более полугода. За это время CStrategy обзавелся многими модулями, делающими процесс торговли более эффективным и безопасным с точки зрения технического проведения торговых операций. Однако ему недоставало одной важной возможности. Несмотря на то, что CStrategy — строго объектно-ориентированное приложение, он до сих пор оставался "вещью в себе". Объектно-ориентированный подход постулирует открытость и модульность кода. По сути, база кодов, в лучших традициях подхода, должна основываться на распространенных общих классах. Особенно это касается торговой модели и модели образования сигналов. И если торговая логика CStrategy в определенной степени построена на стандартном торговом модуле CTrade, то с базой сигналов в CStrategy до сегодняшнего дела обстояли не столь хорошо. Проще говоря, CStrategy не содержит каких-либо модулей, ответственных за генерацию торговых сигналов. До сегодняшнего момента любому пользователю, чтобы использовать CStrategy, приходилось заново писать логику эксперта "с нуля", тогда как в стандартной поставке MetaTrader 5 уже мог присутствовать готовый необходимый сигнал. Чтобы этого не происходило, было принято решение дополнить CStrategy механизмами для работы с базой стандартных сигналов MetaTrader 5. В этой статье я объясню, каким образом провести интеграцию CStrategy с одним из стандартных модулей сигналов и покажу, как создать свою собственную стратегию, прибегая исключительно к уже готовым алгоритмам.

 

Краткий обзор классов, используемых автогенератором стратегий

Для генерации автоматических стратегий с помощью мастера MQL используется набор различных классов, входящих в стандартную поставку MetaTrader. Они располагаются в виде mqh-файлов в соответствующих подкаталогах директории MQL5\Include. Условно данные классы (или модули) можно разделить на несколько категорий. Перечислим их.

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

 

Рис. 1. Схема наследования стандартных классов автогенератора стратегий

На рисунке представлены только основные и некоторые производные классы. Не показаны все индикаторы, наследуемые от CIndicators. Не отображены в этой схеме также и все модули трейлинга, управления капиталом и модули сигналов. Вместо этого намечены лишь основные взаимосвязи. Из всей иерархии классов нас будет интересовать лишь одна группа: классы сигналов CExpertSignal и производные от него. На рисунке 1 эта группа выделена зеленой пунктирной линией.

Помимо вертикальных связей, классы образуют сложные системы включения (горизонтальные связи). Так, например, модули сигналов активно используют классы индикаторов, которые, в свою очередь, задействуют индикаторные буферы. Различные множества являются частями друг друга. Например модули управления капиталом одновременно являются торговыми экспертами (по крайней мере, на уровне CExpertBase), хотя ответ на вопрос о том, могут ли эти модули иметь хоть что-то общее с экспертом, неочевиден.

Для создания объектов этих классов, как правило, необходимы сложные цепочки инициализации. Так например, для создания объекта сигнала, например CSignalMacd, в общем случае необходимо выполнить инициализацию самого сигнала, инициализировать соответствующие индикаторы, на которых он основан, а также инициализировать необходимые таймсерии (наследники класса CPriceSeries), необходимые сигналу для работы. Поскольку объект также инициализируется и сложными объектами, то и они требуют инициализации (как в случае с таймсериями). Таким образом, вопрос инициализации — один из самых сложных в использовании описываемой библиотеки.

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

//+------------------------------------------------------------------+
//| Инициализация модуля сигналов CSignalMacd                        |
//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(void)
{
   CSymbolInfo* info = new CSymbolInfo();                // Создание объекта, представляющего торговый инструмент стратегии
   info.Name(Symbol());                                  // Инициализация объекта, представляющего торговый инструмент стратегии
   m_signal_ma.Init(info, Period(), 10);                 // Инициализация модуля сигналов торговым инструментом и таймфреймом
   m_signal_ma.InitIndicators(GetPointer(m_indicators)); // Создание в модуле сигналов необходимых индикаторов на основе пустого списка индикаторов m_indicators
   m_signal_ma.EveryTick(true);                          // Режим тестирования
   m_signal_ma.Magic(ExpertMagic());                     // Магический номер
   m_signal_ma.PatternsUsage(8);                         // Маска паттерна
   m_open.Create(Symbol(), Period());                    // Инициализация таймсерии цен открытия
   m_high.Create(Symbol(), Period());                    // Инициализация таймсерии максимальных цен
   m_low.Create(Symbol(), Period());                     // Инициализация таймсерии минимальных цен
   m_close.Create(Symbol(), Period());                   // Инициализация таймсерии цен закрытия
   m_signal_ma.SetPriceSeries(GetPointer(m_open),        // Инициализация модуля сигналов объектами-таймсериями
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
}

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

 

Краткий обзор модулей сигналов, понятие паттерна

Как уже было сказано, модули сигналов базируются на общем классе CExpertSignal, который, в свою очередь, основан на CExpertBase. Каждый стандартный модуль сигналов, по сути, является классом с функциями по поиску одного или сразу нескольких паттернов — специальных логических условий, позволяющих определить момент для покупки или продажи. Например, если быстрая скользящая средняя пересекает медленную снизу вверх, то она образует некий паттерн покупки. Каждый индикатор может образовывать несколько условий на покупку и продажу. Простой пример — индикатор MACD. Сигналом для входа в рынок может служить как дивергенция этого индикатора, так и простое пересечение сигнальной линии с основной гистограммой индикатора. Оба этих торговых условия — отличные друг от друга паттерны. При наступлении одного или нескольких этих событий возможен вход в рынок. Важно отметить, что паттерны для покупок и продаж различаются и являются, как правило, зеркальными друг другу условиями. Сигнал, с точки зрения автогенератора стратегий, — некий обработчик одного или нескольких паттернов, объединенных общим индикатором. Например, сигнал на основе индикатора MACD способен определить наличие на рынке сразу нескольких паттернов. Конкретно взятый стандартный модуль сигналов на основе индикатора MACD содержит пять паттернов на продажу и пять — на покупку:

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

Сигнал способен не только определить паттерны, но и выдать некую рекомендацию в виде целого числа. Предполагается, что данное число должно показывать силу сигнала: чем число больше, тем сила сигнала выше. Стоит отметить, что саму силу сигнала для того или иного паттерна можно задать через специальную группу методов вида Pattern_x(int value), где x — номер паттерна. Например, в коде конфигуратора сигнала будет написано следующее:

//+------------------------------------------------------------------+
//| Инициализация модуля сигналов CSignalMacd                        |
//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(void)
{
   m_signal_ma.Pattern_0(0);
   m_signal_ma.Pattern_1(0);
   m_signal_ma.Pattern_2(0);
   m_signal_ma.Pattern_3(100);
   m_signal_ma.Pattern_4(0);
   m_signal_ma.Pattern_5(0);
}

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

Торговые рекомендации возвращаются двумя независимыми методами LongCondition и ShortCondition. Первый из них возвращает рекомендации для покупки, второй — соответственно, для продажи. Чтобы понять, как они работают, заглянем внутрь одного из них, LongCondition:

//+------------------------------------------------------------------+
//| Указание на то, что цена будет расти.                            |
//+------------------------------------------------------------------+
int CSignalMACD::LongCondition(void)
  {
   int result=0;
   int idx   =StartIndex();
//--- проверка направления основной линии
   double diff = DiffMain(idx);
   if(diff>0.0)
     {
      //--- основная линия направлена вверх, что подтверждает возможность роста цены
      if(IS_PATTERN_USAGE(0))
         result=m_pattern_0;      // подтверждение сигнала под номером 0
      //--- если используется модель 1, идет поиск разворота основной линии
      if(IS_PATTERN_USAGE(1) && DiffMain(idx+1)<0.0)
         result=m_pattern_1;      // сигнал номер 1
      //--- если используется модель 2, идет поиск пересечения основной и сигнальной линий
      if(IS_PATTERN_USAGE(2) && State(idx)>0.0 && State(idx+1)<0.0)
         result=m_pattern_2;      // сигнал номер 2
      //--- если используется модель 3, идет поиск пересечения главной линией нулевого уровня 
      if(IS_PATTERN_USAGE(3) && Main(idx)>0.0 && Main(idx+1)<0.0)
         result=m_pattern_3;      // сигнал номер 3
      //--- если используются модели 4 или 5, а главная линия ниже нулевого уровня и растет, идет поиск дивергенций 
      if((IS_PATTERN_USAGE(4) || IS_PATTERN_USAGE(5)) && Main(idx)<0.0)
        {
         //--- выполняется расширенный анализ состояния осциллятора 
         ExtState(idx);
         //--- если используется модель 4, ожидается сигнал "дивергенция" 
         if(IS_PATTERN_USAGE(4) && CompareMaps(1,1)) // 0000 0001b
            result=m_pattern_4;   // сигнал номер 4
         //--- если используется модель 5, ожидается сигнал "двойная дивергенция" 
         if(IS_PATTERN_USAGE(5) && CompareMaps(0x11,2)) // 0001 0001b
            return(m_pattern_5);  //сигнал номер 5
        }
     }
//--- возвращается результат
   return(result);
  }

 Метод работает с макросом определения паттерна IS_PATTERN_USAGE, основанного на битовой маске:

//--- проверка, используется ли рыночная модель
#define IS_PATTERN_USAGE(p)          ((m_patterns_usage&(((int)1)<<p))!=0)

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

Номер паттерна, который требуется использовать, задается специальной битовой маской. Например, если необходимо использовать паттерн №3, необходимо, чтобы четвертый бит 32 битовой переменной был равен единице (напомним, что индексация паттернов начинается с нуля, поэтому для третьего паттерна используется четвертый разряд, а для нулевого — первый разряд поля). Если число 1000 в двоичной системе сконвертировать в десятеричную, мы получим число 8. Именно это число необходимо передать в метод PatternsUsage. Возможно комбинировать паттерны. Например, чтобы использовать паттерн № 3 вместе с паттерном № 2, необходимо составить битовое поле, четвертый и третий разряд которого были бы равны единице: 1100. Это же значение в десятеричном формате будет числом 12.

 

Модуль сигналов. Первое использование

Теперь мы знаем достаточно, для того чтобы начать пользоваться модулями сигналов.  Наши эксперименты мы будем проводить на базе CStrategy, для чего создадим специальный экспериментальный класс, стратегию CSignalSamples:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/ru/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Expert\Signal\SignalMACD.mqh>
//+------------------------------------------------------------------+
//| Стратегия принимает события и выводит их в терминал.             |
//+------------------------------------------------------------------+
class CSignalSamples : public CStrategy
{
private:
   CSignalMACD       m_signal_macd;
   CSymbolInfo       m_info;
   CiOpen            m_open;
   CiHigh            m_high;
   CiLow             m_low;
   CiClose           m_close;
   CIndicators       m_indicators;
public:
                     CSignalSamples(void);
   virtual void      OnEvent(const MarketEvent& event);                     
};
//+------------------------------------------------------------------+
//| Инициализация модуля сигналов CSignalMacd                        |
//+------------------------------------------------------------------+
CSignalSamples::CSignalSamples(void)
{
   m_signal_macd.Pattern_0(0);
   m_signal_macd.Pattern_1(0);
   m_signal_macd.Pattern_2(0);
   m_signal_macd.Pattern_3(100);
   m_signal_macd.Pattern_4(0);
   m_signal_macd.Pattern_5(0);
   m_info.Name(Symbol());                                  // Инициализация объекта, представляющего торговый инструмент стратегии
   m_signal_macd.Init(GetPointer(m_info), Period(), 10);   // Инициализация модуля сигналов торговым инструментом и тайфреймом
   m_signal_macd.InitIndicators(GetPointer(m_indicators)); // Создание в модуле сигналов необходимых индикаторов на основе пустого списка индикаторов m_indicators
   m_signal_macd.EveryTick(true);                          // Режим тестирования
   m_signal_macd.Magic(ExpertMagic());                     // Магический номер
   m_signal_macd.PatternsUsage(8);                         // Маска паттерна
   m_open.Create(Symbol(), Period());                      // Инициализация таймсерии цен открытия
   m_high.Create(Symbol(), Period());                      // Инициализация таймсерии максимальных цен
   m_low.Create(Symbol(), Period());                       // Инициализация таймсерии минимальных цен
   m_close.Create(Symbol(), Period());                     // Инициализация таймсерии цен закрытия
   m_signal_macd.SetPriceSeries(GetPointer(m_open),        // Инициализация модуля сигналов объектами-таймесериями
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
                              
}
//+------------------------------------------------------------------+
//| Покупки.                                                         |
//+------------------------------------------------------------------+
void CSignalSamples::OnEvent(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0 || power_sell != 0)
      printf("PowerSell: " + (string)power_sell + " PowerBuy: " + (string)power_buy);
}
//+------------------------------------------------------------------+

В последних версиях CStrategy было добавлено новое широковещательное событие OnEvent, оно представлено одноименным методом OnEvent, который вызывается всякий раз при наступлении какого-либо события. В отличие от более знакомых нам методов BuyInit, SellInit, BuySupport и SellSupport, OnEvent вызывается независимо от режима торговой стратегии и расписания торгов. Таким образом, OnEvent предоставляет доступ к потоку событий из стратегии, поддерживая при этом строгую событийную модель. OnEvent очень удобно использовать для общих расчетов или действий, которые не связаны с конкретным направлением продажи или покупки.

Большую часть кода занимает инициализация модуля торговых сигналов. В качестве примера был использован модуль на основе индикатора MACD: CSignalMACD. В модуле используется только один паттерн, под номером 3. Ему задается вес 100, а также вызывается соответствующий метод PatternsUsage со значением 8. Для запуска данной стратегии необходимо составить соответствующий загрузчик стратегий или исполняющий модуль mq5. Приведем его содержимое:

//+------------------------------------------------------------------+
//|                                                       Agent.mq5  |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/ru/users/c-4"
#property version   "1.00"
#include <Strategy\StrategiesList.mqh>
#include <Strategy\Samples\SignalSamples.mqh>
CStrategyList Manager;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() 
{
   CSignalSamples* signal = new CSignalSamples();
   signal.ExpertMagic(2918);
   signal.ExpertName("MQL Signal Samples");
   signal.Timeframe(Period());
   if(!Manager.AddStrategy(signal))
      delete signal;
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
  Manager.OnTick();
}
//+------------------------------------------------------------------+
//| OnChartEvent function                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
   Manager.OnChartEvent(id, lparam, dparam, sparam);
}

Он состоит из инициализации стратегии в функции OnInit и обработчика новых тиков в OnTick. После прогона получившегося кода в тестере стратегий в режиме визуализации будут отображены соответствующие сообщения о полученных сигналах:

2016.06.20 16:34:31.697 tester agent shutdown finished
2016.06.20 16:34:31.642 shutdown tester machine
2016.06.20 16:34:31.599 tester agent shutdown started
2016.06.20 16:34:31.381 log file "Z:\MetaTrader 5\Tester\Agent-127.0.0.1-3000\logs\20160620.log" written
2016.06.20 16:34:31.381 325 Mb memory used including 28 Mb of history data, 64 Mb of tick data
2016.06.20 16:34:31.381 EURUSD,M1: 51350 ticks (12935 bars) generated in 0:00:00.780 (total bars in history 476937, total time 0:00:00.843)
2016.06.20 16:34:31.376 final balance 100000.00 USD
2016.06.20 16:34:31.373 2016.04.14 22:12:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 22:01:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 21:24:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:54:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 20:50:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:18:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 20:14:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:13:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:07:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.372 2016.04.14 19:48:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.372 2016.04.14 18:48:00   PowerSell: 0 PowerBuy: 100
...
...

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

 

Написание первой стратегии на основе CSignalMACD

Настало время написать полноценную стратегию на основе модуля сигналов CSignalMACD. Первым паттерном, которым мы воспользуемся, будет паттерн пересечения сигнальной линии с осциллятором индикатора. Воспользуемся документаций. В справке откроем раздел Справочник MQL5 -> Стандартная билиотека -> Базовые классы стратегий -> Модули торговых сигналов -> Сигналы осциллятора MACD. Найдем второй паттерн — "Пересечение основной и сигнальной линии". Приведем его описание.

    • Покупка: "Пересечение основной и сигнальной линии" — на анализируемом баре основная линия выше сигнальной, а на предыдущем — ниже.

    • Рис 2. Осциллятор пересекает сигнальную линию снизу вверх

    • Продажа: "Пересечение основной и сигнальной линии" — на анализируемом баре основная линия ниже сигнальной, а на предыдущем — выше.

    Рис. 3. Осциллятор пересекает сигнальную линию сверху вниз

    Нам необходимо определить номер паттерна, соответствующий данному описанию. В этом нам поможет заголовок класса CSignalMACD:

    //+------------------------------------------------------------------+
    //| Class CSignalMACD.                                               |
    //| Purpose: Class of generator of trade signals based on            |
    //|          the 'Moving Average Convergence/Divergence' oscillator. |
    //| Is derived from the CExpertSignal class.                         |
    //+------------------------------------------------------------------+
    class CSignalMACD : public CExpertSignal
      {
    protected:
       CiMACD            m_MACD;           // объект-осциллятор
       //--- adjusted parameters
       int               m_period_fast;    // параметр осциллятора "период быстрой EMA" 
       int               m_period_slow;    // параметр осциллятора "период медленной EMA" 
       int               m_period_signal;  // параметр осциллятора "период усреднения разницы" 
       ENUM_APPLIED_PRICE m_applied;       // параметр осциллятора "ценовые серии" 
       //--- "weights" of market models (0-100)
       int               m_pattern_0;      // модель 0 "осциллятор движется в требуемом направлении"
       int               m_pattern_1;      // модель 1 "разворот осциллятора к требуемому направлению"
       int               m_pattern_2;      // модель 2 "пересечение основной и сигнальной линий"
       int               m_pattern_3;      // модель 3 "пересечение основной линией нулевого уровня"
       int               m_pattern_4;      // модель 4 "дивергенция осциллятора и цены"
       int               m_pattern_5;      // модель 5 "двойная дивергенция осциллятора и цены"
       //--- variables
       double            m_extr_osc[10];   // массив значений экстремумов осциллятора 
       double            m_extr_pr[10];    // массив значений соответствующих экстремумов цены 
       int               m_extr_pos[10];   // массив сдвигов экстремумов (в барах) 
       uint              m_extr_map;       // результирующая битовая карта экстремумов осциллятора и цены 
    ...
      }

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

    Теперь, когда мы уже знаем номер паттерна, нам нужно соответствующим образом сконфигурировать сигнал. Во-первых, во избежание путаницы мы не будем использовать другие паттерны, для чего установим маску паттернов, равную 4 (100 в бинарном виде). Раз мы будем использовать только один паттерн, то знать силу сигнала (а значит, и конфигурировать силу паттернов) нам не требуется, ведь сигнал либо есть, либо его нет, и третьего не дано. Так как мы будем проверять сигнал только на открытии нового бара, необходимо также указать на это, вызвав соответствующий метод сигнала EveryTick с флагом false. Данное конфигурирование мы реализуем в конструкторе стратегии. Затем перейдем, собственно, к торговой логике. Для этого переопределим методы InitBuy, SupportBuy, InitSell, SupportSell. Саму стратегию мы назовем COnSignalMACD: приставка On указывает на то, что стратегия базируется на стандартном модуле сигналов. Код стратегии представлен ниже:

    //+------------------------------------------------------------------+
    //|                                                EventListener.mqh |
    //|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
    //|                                https://www.mql5.com/ru/users/c-4 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2016, Vasiliy Sokolov."
    #property link      "https://www.mql5.com/ru/users/c-4"
    #include <Strategy\Strategy.mqh>
    #include <Expert\Signal\SignalMACD.mqh>
    //+------------------------------------------------------------------+
    //| Стратегия принимает события и выводит их в терминал.             |
    //+------------------------------------------------------------------+
    class COnSignalMACD : public CStrategy
    {
    private:
       CSignalMACD       m_signal_macd;
       CSymbolInfo       m_info;
       CiOpen            m_open;
       CiHigh            m_high;
       CiLow             m_low;
       CiClose           m_close;
       CIndicators       m_indicators;
    public:
                         COnSignalMACD(void);
       virtual void      InitBuy(const MarketEvent &event);
       virtual void      InitSell(const MarketEvent &event);
       virtual void      SupportBuy(const MarketEvent& event, CPosition* pos);
       virtual void      SupportSell(const MarketEvent& event, CPosition* pos);
    };
    //+------------------------------------------------------------------+
    //| Инициализация модуля сигналов CSignalMacd                        |
    //+------------------------------------------------------------------+
    COnSignalMACD::COnSignalMACD(void)
    {
       m_info.Name(Symbol());                                  // Инициализация объекта, представляющего торговый инструмент стратегии
       m_signal_macd.Init(GetPointer(m_info), Period(), 10);   // Инициализация модуля сигналов торговым инструментом и тайфреймом
       m_signal_macd.InitIndicators(GetPointer(m_indicators)); // Создание в модуле сигналов необходимых индикаторов на основе пустого списка индикаторов m_indicators
       m_signal_macd.EveryTick(false);                         // Режим тестирования
       m_signal_macd.Magic(ExpertMagic());                     // Магический номер
       m_signal_macd.PatternsUsage(4);                         // Маска паттерна
       m_open.Create(Symbol(), Period());                      // Инициализация таймсерии цен открытия
       m_high.Create(Symbol(), Period());                      // Инициализация таймсерии максимальных цен
       m_low.Create(Symbol(), Period());                       // Инициализация таймсерии минимальных цен
       m_close.Create(Symbol(), Period());                     // Инициализация таймсерии цен закрытия
       m_signal_macd.SetPriceSeries(GetPointer(m_open),        // Инициализация модуля сигналов объектами-таймесериями
                                  GetPointer(m_high),
                                  GetPointer(m_low),
                                  GetPointer(m_close));
    }
    //+------------------------------------------------------------------+
    //| Покупки.                                                         |
    //+------------------------------------------------------------------+
    void COnSignalMACD::InitBuy(const MarketEvent &event)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       m_indicators.Refresh();
       m_signal_macd.SetDirection();
       int power_buy = m_signal_macd.LongCondition();
       if(power_buy != 0)
          Trade.Buy(1.0);
    }
    //+------------------------------------------------------------------+
    //| Закрытие покупок                                                 |
    //+------------------------------------------------------------------+
    void COnSignalMACD::SupportBuy(const MarketEvent &event, CPosition* pos)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       m_indicators.Refresh();
       m_signal_macd.SetDirection();
       int power_sell = m_signal_macd.ShortCondition();
       //printf("Power sell: " + (string)power_sell);
       if(power_sell != 0)
          pos.CloseAtMarket();
    }
    //+------------------------------------------------------------------+
    //| Продажи.                                                         |
    //+------------------------------------------------------------------+
    void COnSignalMACD::InitSell(const MarketEvent &event)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       m_indicators.Refresh();
       m_signal_macd.SetDirection();
       int power_sell = m_signal_macd.ShortCondition();
       if(power_sell != 0)
          Trade.Sell(1.0);
    }
    //+------------------------------------------------------------------+
    //| Закрытие продаж                                                  |
    //+------------------------------------------------------------------+
    void COnSignalMACD::SupportSell(const MarketEvent &event, CPosition* pos)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       m_indicators.Refresh();
       m_signal_macd.SetDirection();
       int power_buy = m_signal_macd.LongCondition();
       if(power_buy != 0)
          pos.CloseAtMarket();
    }
    //+------------------------------------------------------------------+
    

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

    Результат торговли можно уведить в тестере стратегий. Фрагмент истории тестирования представлен на рисунке ниже:

     

    Рис. 4. Открытие сделок на пересечении гистограммы MACD с ее сигнальной линией

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

     

    Адаптер сигналов

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

    Чтобы избежать этих трудностей, был введен специальный класс-адаптер, значительно упрощающий работу с сигналами. Этот класс называется CSignalAdapter, и располагается в общей директории проекта CStrategy. Адаптер обладает простым интерфейсом. Он позволяет создавать сигнал и получать флаги возникновения паттернов на покупку и продажу. Для создания сигнала необходимо передать параметры этого сигнала специальному методу CSignalAdapter::CreateSignal. Сами параметры сигнала содержатся в специальной структуре MqlSignalParams. Приведем определение этой структуры:

    //+--------------------------------------------------------------------+
    //| Signal parameters                                                  |
    //+--------------------------------------------------------------------+
    struct MqlSignalParams
    {
    public:
       string            symbol;           // Символ
       ENUM_TIMEFRAMES   period;           // Период графика
       ENUM_SIGNAL_TYPE  signal_type;      // Тип Сигнала
       int               index_pattern;    // Индекс паттерна
       int               magic;            // Магический номер эксперта
       double            point;            // Количество пунктов
       bool              every_tick;       // Режим тестирования "Каждый тик"
       void operator=(MqlSignalParams& params);
    };
    //+--------------------------------------------------------------------+
    //| Используется оператор копирования, т.к. структура использует строки|
    //+--------------------------------------------------------------------+
    void MqlSignalParams::operator=(MqlSignalParams& params)
    {
       symbol = params.symbol;
       period = params.period;
       signal_type = params.signal_type;
       usage_pattern = params.usage_pattern;
       magic = params.magic;
       point = params.point;
       every_tick = params.every_tick;
    }

    Структура содержит базовые типы, определяющие следующие характеристики сигнала:

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

    Отдельно необходимо сказать о параметре index_pattern. В отличие от модуля сигналов, он принимает не маску паттернов, а только индекс одного из них. Таким образом, каждый адаптер сигналов может использовать только один паттерн указанного сигнала. Значение index_pattern должно находиться в пределах от 1 до 31, а также соответствовать реальному числу паттернов используемого сигнала.

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

    В листинге ниже представлен способ конфигурирования сигнала CSignalMACD при помощи адаптера CSignalAdapter:

    //+------------------------------------------------------------------+
    //|                                                EventListener.mqh |
    //|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
    //|                                https://www.mql5.com/ru/users/c-4 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2016, Vasiliy Sokolov."
    #property link      "https://www.mql5.com/ru/users/c-4"
    #include <Strategy\Strategy.mqh>
    #include <Strategy\SignalAdapter.mqh>
    
    //+------------------------------------------------------------------+
    //| Стратегия принимает события и выводит их в терминал.             |
    //+------------------------------------------------------------------+
    class CAdapterMACD : public CStrategy
    {
    private:
       CSignalAdapter    m_signal;
       MqlSignalParams   m_params;
    public:
                         CAdapterMACD(void);
       virtual void      InitBuy(const MarketEvent &event);
       virtual void      InitSell(const MarketEvent &event);
       virtual void      SupportBuy(const MarketEvent& event, CPosition* pos);
       virtual void      SupportSell(const MarketEvent& event, CPosition* pos);
    };
    //+------------------------------------------------------------------+
    //| Конфигурируем адаптер                                            |
    //+------------------------------------------------------------------+
    CAdapterMACD::CAdapterMACD(void)
    {
       m_params.symbol = Symbol();
       m_params.period = Period();
       m_params.every_tick = false;
       m_params.signal_type = SIGNAL_MACD;
       m_params.magic = 1234;
       m_params.point = 1.0;
       m_params.usage_pattern = 2;
       CSignalMACD* macd = m_signal.CreateSignal(m_params);
       macd.PeriodFast(15);
       macd.PeriodSlow(32);
       macd.PeriodSignal(6);
    }

    Адаптер точно так же требует задания параметров. Однако, в отличие от первой версии эксперта, все параметры являются тривиальными, т.е. базовыми типами. Кроме того, создавать и отслеживать другие сложные объекты для работы сигнала, вроде таймсерий и индикаторов, не требуется. Все это делает сам адаптер. Именно поэтому его использование значительно упрощает работу с сигналами.  

    Обратите внимание, что после того, как сигнал был создан, мы продолжили конфигурировать его, задав свои собственные периоды индикатора MACD (15, 32, 6). Сделать это было просто, т.к. метод CreateSignal вернул нам соответствующий объект.

    После того, как сигнал сконфигурирован и настроен, можно начать использовать его. Для этих целей служат простые методы BuySignal и ShortSignal. Приведем продолжение нашего класса стратегии:

    //+------------------------------------------------------------------+
    //| Покупки.                                                         |
    //+------------------------------------------------------------------+
    void CAdapterMACD::InitBuy(const MarketEvent &event)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(m_signal.LongSignal())
          Trade.Buy(1.0);
    }
    //+------------------------------------------------------------------+
    //| Закрытие покупок                                                 |
    //+------------------------------------------------------------------+
    void CAdapterMACD::SupportBuy(const MarketEvent &event, CPosition* pos)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(m_signal.ShortSignal())
          pos.CloseAtMarket();
    }
    //+------------------------------------------------------------------+
    //| Продажи                                                          |
    //+------------------------------------------------------------------+
    void CAdapterMACD::InitSell(const MarketEvent &event)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(m_signal.ShortSignal())
          Trade.Sell(1.0);
    }
    //+------------------------------------------------------------------+
    //| Закрытие продаж                                                  |
    //+------------------------------------------------------------------+
    void CAdapterMACD::SupportSell(const MarketEvent &event, CPosition* pos)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(m_signal.LongSignal())
          pos.CloseAtMarket();
    }

    Описанная логика делает то же, что и предыдущий пример: открывает длинные и короткие позиции на пересечении гистограммы MACD с ее сигнальной линией. Однако, как видите, код стал еще более коротким. В отличие от первой версии работы с сигналом, теперь не требуется каждый раз устанавливать направление сигнала и обновлять значения индикаторов. Также не требуется размещать вспомогательные объекты в коде стратегии.  Все эти действия выполняет за нас адаптер.

     

    Объединение нескольких сигналов в коде одной торговой стратегии

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

    В качестве примера, напишем стратегию, которая будет принимать разные сигналы для входа и выхода из рынка. В качестве входа будет использованы паттерны индикатора RSI, основанные на перекупленности и перепроданности. В качестве выхода будем использовать второй паттерн от индикатора Accelerator Iscillator (AC), предложенного Биллом Вильямсом. Приведем правила стратегии более подробно.

    Покупка: Разворот за уровнем перепроданности — осциллятор развернулся вверх, и его значение на анализируемом баре находится за уровнем перепроданности (по умолчанию 30):

     

    Рис. 5. Условия входа в длинную позицию 

     

    Продажа: Разворот за уровнем перекупленности — осциллятор развернулся вниз, и его значение на анализируемом баре находится за уровнем перекупленности (по умолчанию 70):

     

    Рис. 6. Условия входа в короткую позицию

    Закрытие покупки: Значение индикатора AC выше 0, и оно снижается на анализируемом баре и двух предыдущих:


    Рис. 7. Условия выхода из длинной позицию 

    Закрытие продажи: Значение индикатора ниже 0, и оно растет на анализируемом баре и двух предыдущих.

     

    Рис. 8. Условия выхода из короткой позиции

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

    Эксперт, реализующий эту логику, представлен в листинге ниже:

    //+------------------------------------------------------------------+
    //|                                                EventListener.mqh |
    //|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
    //|                                https://www.mql5.com/ru/users/c-4 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2016, Vasiliy Sokolov."
    #property link      "https://www.mql5.com/ru/users/c-4"
    #include <Strategy\Strategy.mqh>
    #include <Strategy\SignalAdapter.mqh>
    input int RSI_Period = 14; // RSI Period
    //+------------------------------------------------------------------+
    //| Стратегия принимает события и выводит их в терминал.             |
    //+------------------------------------------------------------------+
    class COnSignal_RSI_AC : public CStrategy
    {
    private:
       CSignalAdapter    m_adapter_rsi;
       CSignalAdapter    m_adapter_ac;
    public:
                         COnSignal_RSI_AC(void);
       virtual void      InitBuy(const MarketEvent &event);
       virtual void      InitSell(const MarketEvent &event);
       virtual void      SupportBuy(const MarketEvent& event, CPosition* pos);
       virtual void      SupportSell(const MarketEvent& event, CPosition* pos);
    };
    //+------------------------------------------------------------------+
    //| Инициализация модуля сигналов CSignalMacd                        |
    //+------------------------------------------------------------------+
    COnSignal_RSI_AC::COnSignal_RSI_AC(void)
    {
       MqlSignalParams params;
       params.every_tick = false;
       params.magic = 32910;
       params.point = 10.0;
       params.symbol = Symbol();
       params.period = Period();
       params.usage_pattern = 2;
       params.signal_type = SIGNAL_AC;
       CSignalAC* ac = m_adapter_ac.CreateSignal(params);
       params.usage_pattern = 1;
       params.magic = 32911;
       params.signal_type = SIGNAL_RSI;
       CSignalRSI* rsi = m_adapter_rsi.CreateSignal(params);
       rsi.PeriodRSI(RSI_Period);
    }
    //+------------------------------------------------------------------+
    //| Покупки.                                                         |
    //+------------------------------------------------------------------+
    void COnSignal_RSI_AC::InitBuy(const MarketEvent &event)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(positions.open_buy > 0)
          return;
       if(m_adapter_rsi.LongSignal())
          Trade.Buy(1.0);
    }
    //+------------------------------------------------------------------+
    //| Закрытие покупок                                                 |
    //+------------------------------------------------------------------+
    void COnSignal_RSI_AC::SupportBuy(const MarketEvent &event, CPosition* pos)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(m_adapter_ac.ShortSignal())
          pos.CloseAtMarket();
    }
    //+------------------------------------------------------------------+
    //| Продажи.                                                         |
    //+------------------------------------------------------------------+
    void COnSignal_RSI_AC::InitSell(const MarketEvent &event)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(positions.open_sell > 0)
          return;
       if(m_adapter_rsi.ShortSignal())
          Trade.Sell(1.0);
    }
    //+------------------------------------------------------------------+
    //| Закрытие продаж                                                  |
    //+------------------------------------------------------------------+
    void COnSignal_RSI_AC::SupportSell(const MarketEvent &event, CPosition* pos)
    {
       if(event.type != MARKET_EVENT_BAR_OPEN)
          return;
       if(m_adapter_ac.LongSignal())
          pos.CloseAtMarket();
    }
    //+------------------------------------------------------------------+
    

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

    Результат работы этой стратегии представлен на графике ниже:

     

    Рис. 8. Результат работы стратегии 

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

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

     

    Заключение

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

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