Изучаем классы торговых стратегий из Стандартной Библиотеки - Пользовательские стратегии

Harvester Trading | 21 сентября, 2012

Введение

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

MetaTrader 5 предоставляет возможность торговли с помощью советников с минимальными или нулевыми знаниями языка программирования, благодаря замечательной возможности MetaEditor: Мастеру MQL5. Мастер (в этой статье мы не будем объяснить подробно как он работает) предназначен для создания готовых программ (файлы .mq5 и. ex5), алгоритмов и кода. Его мощь заключается в использовании Стандартной Библиотеки MQL5 и ее классов торговых стратегий, которые открывают перед разработчиком большие возможности.

Изучаем классы торговых стратегий из Стандартной Библиотеки - Пользовательские стратегии

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

Для выявления торговых сигналов из этих классов торговых стратегий, Мастер MQL5 использует механизм обращения к индикаторам, чья логика запрограммирована в виде "торговых моделей". И каждый конкретный сгенерированный советник обращается к индикаторам (через директивы #include) и их наборам моделей и торговых решений, которые затем импортируются в ядро ​​советника для ведения торговли.


Мастер MQL5

Сначала, нам потребуется создать советник в Мастере MQL5. Чтобы открыть Мастер MQL5 в MetaEditor выберите "Создать" из меню "Файл" или нажмите кнопку "Создать", затем выберите опцию "Советник (сгенерировать)".

Рисунок 1. Создание нового файла (опция "сгенерировать" в Мастере)

Назовем наш эксперт как MyExpert.

Рисунок 2. Имя и параметры советника, сгенерированного в Мастере MQL5

Затем мы добавим два индикатора/сигнала (вы можете выбрать сколько угодно существующих индикаторов). Для нашего примера добавим два известных индикатора: индекс относительной силы (RSI - Relative Strength Index) и скользящую среднюю (MA - Moving Average). Добавьте индикатор RSI первым, затем - индикатор MA.

Рисунок 3. Сначала индикатор RSI, затем MA

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

Рисунок 4. Параметры сигналов

После нажатия кнопки OK и перехода на следующую страницу Мастера мы не будем выбирать трейлинг стоп, но если хотите, вы можете его добавить: это не повлияет на тематику этой статьи. В следующем окне мы выберем 5.0 для процента риска и 0.1 для лота, или любые другие параметры, какие вы хотите: опять же, это не повлияет на суть нашей статьи.


Анализируем сгенерированный код

Пройдя все этапы, мы получим файл "MyExpert.mq5". Проанализируем ключевые моменты в сгенерированном коде.

//+------------------------------------------------------------------+
//|                                                     MyExpert.mq5 |
//|                                                        Harvester |
//|                        https://www.mql5.com/en/users/Harvester |
//+------------------------------------------------------------------+
#property copyright "Harvester"
#property link      "https://www.mql5.com/en/users/Harvester"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
//--- available signals
#include <Expert\Signal\SignalRSI.mqh>
#include <Expert\Signal\SignalMA.mqh>
//--- available trailing
#include <Expert\Trailing\TrailingNone.mqh>
//--- available money management
#include <Expert\Money\MoneyFixedLot.mqh>

Для начала обратите внимание на файлы #include, добавленные в сгенерированный код Мастером. Мы видим:

Затем следующий кусок кода:

//--- Creating filter CSignalRSI
   CSignalRSI *filter0=new CSignalRSI;
   if(filter0==NULL)
     {
      //--- failed
      printf(__FUNCTION__+": error creating filter0");
      ExtExpert.Deinit();
      return(-3);
     }
   signal.AddFilter(filter0);

Как нам подсказывает заголовок в комментарии, это "фильтр", который будет применяться к рыночным условиям сгенерированного советника. filter0 - это первый фильтр с индексом 0, для него мы первым выбрали индикатор RSI в нашем примере.

CSignalRSI означает класс сигнала RSI. Этот класс используется для вызова индикатора RSI и применения к нему некоторых условий для создания сигналов на покупку или продажу, используя логику моделей Мастера. Итак, RSI - наш первый фильтр (номер 0).

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

Идем дальше:

//--- Tuning of all necessary indicators
   if(!ExtExpert.InitIndicators())
     {
      //--- failed
      printf(__FUNCTION__+": error initializing indicators");
      ExtExpert.Deinit();
      return(-10);
     }
//--- ok
   return(0);
  }

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

И в последней части сгенерированного кода советника мы видим код деинициализации и другие стандартные события эксперта:

//+------------------------------------------------------------------+
//| Deinitialization function of the expert                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtExpert.Deinit();
  }
//+------------------------------------------------------------------+
//| "Tick" event handler function                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   ExtExpert.OnTick();
  }
//+------------------------------------------------------------------+
//| "Trade" event handler function                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   ExtExpert.OnTrade();
  }
//+------------------------------------------------------------------+
//| "Timer" event handler function                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   ExtExpert.OnTimer();
  }
//+------------------------------------------------------------------+

Фактически, наш советник использует два индикатора (RSI и MA) для принятия торговых решений с помощью стандартной библиотеку торговых классов, которые используют логику "фильтров" и "весов". Вы можете найти более подробную информацию в разделе Модули торговых сигналов Справочника MQL5. Но нашей целью является использовать наши собственные торговые стратегии в качестве новых фильтров.

Для этого мы собираемся немного изменить наш файл MyExpert.mq5. Прежде всего, добавим еще один фильтр. Это будет filter2 и мы разместим его сразу после кода filter1.

//--- Creating filter CSignalCCIxx
   CSignalCCIxx *filter2=new CSignalCCIxx;
   if(filter2==NULL)
     {
      //--- failed
      printf(__FUNCTION__+": error creating filter2");
      ExtExpert.Deinit();
      return(-4);
     }
   signal.AddFilter(filter2);
//--- Set filter parameters
   filter2.PeriodCCIxx(Signal_CCIxx_PeriodCCI);
   filter2.Applied(Signal_CCIxx_Applied);
   filter2.Weight(Signal_CCIxx_Weight);

Давайте вернемся к #include файлам, которые являются сутью фильтров и принятия рыночных решений. Первый файл #include <Expert\Expert.mqh> в свою очередь включает в себя другие файлы:

Эти файлы представляют собой: основной модуль советника, торговый модуль, модуль сигналов, модуль управления капиталом и модуль управления трейлинг стопом. Мы не собираемся анализировать эти файлы и изменять их. Нашей целью является добавление собственной стратегии на основе стандартных индикаторов MetaTrader 5.

В коде MyExpert.mq5 у нас есть #include файлы индикаторов RSI и MA, которые мы использовали в этом примере в качестве сигналов/фильтры для принятия торговых решений. Теперь добавим наш собственный включаемый файл. Для этого мы используем модифицированную ("улучшенную") версию сигналов индикатора CCI.

//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
//--- available signals
#include <Expert\Signal\SignalRSI.mqh>
#include <Expert\Signal\SignalMA.mqh>

#include <Expert\Signal\SignalCCIxx.mqh>   // Это наш пользовательский индикатор

Файл SignalCCIxx.mqh должен находиться в папке \MQL5\Include\Expert\Signal\. Этот файл органично вписывается в целостность созданного Мастером советника, как и другие #include файлы торговых классов Стандартной библиотеки - эти файлы (SignalRSI.mqh и SignalMA.mqh) изначально присутствуют в этой папке.

В нашем примере мы собираемся скопировать исходный файл CCI, создать на его основе новый файл CCIxx со слегка модифицированным кодом и использовать его в качестве #include файла. Для простоты мы используем копию индикатора CCI из Стандартной библиотеки.

Скопируйте файл "\MQL5\Include\Expert\Signal\SignalCCI.mqh" в файл "\MQL5\Include\Expert\Signal\SignalCCIxx.mqh". Для этого сделайте копию файла, а затем переименуйте ее.

Давайте посмотрим на этот файл. Интеграция этого пользовательского индикатора в созданный Мастером советник MyExpert.mq5 практически готова. Мы добавили код для filter2, как описано выше, и теперь осталось лишь разобраться с реализацией сигнала. Итак, мы больше не будем останавливаться на коде MyExpert.mq5. Теперь мы сосредоточим свое внимание на файле SignalCCIxx.mqh - это суть нашей пользовательской стратегии.


Пользовательская стратегия

Вернемся к добавлению фильтров "полу-пользовательской" стратегии CCIxx, которая является модифицированной версией SignalCCI.mqh. Я говорю "полу-пользовательской", потому что на самом деле это не новый пользовательский сигнал, а переработанная версия индикатора CCI со стандартным набором индикаторов MetaTrader 5. Даже начинающие пользователи и программисты могут немного изменить паттерны и фильтры советника, созданного в Мастере MQL5, создавая свои собственные версии фильтров и паттернов для генерирования сигналов о покупке или продаже. Это отличный способ начать работать со стратегиями.

Наш пример будет полезен для тех, кто просто хочет добавить некоторые пользовательские модели в существующие индикаторы, и для тех, кто хочет участвовать в Automated Trading Championship, используя только лишь Мастер MQL5 для быстрого создания полнофункциональных (и работающих) кастомных советников.

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

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

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

Мы продолжим работать с файлом SignalCCIxx.mqh и создадим нашу собственную торговую модель по сигналам индикатора CCI. Прежде всего, в файле MyExpert.mq5 добавим новые входные параметры (выделено желтым):

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input string             Expert_Title         ="MyExpert";  // Document name
ulong                    Expert_MagicNumber   =26287;       // 
bool                     Expert_EveryTick     =false;       // 
//--- inputs for main signal
input int                Signal_ThresholdOpen =40;          // Signal threshold value to open [0...100]
input int                Signal_ThresholdClose=60;          // Signal threshold value to close [0...100]
input double             Signal_PriceLevel    =0.0;         // Price level to execute a deal
input double             Signal_StopLevel     =50.0;        // Stop Loss level (in points)
input double             Signal_TakeLevel     =50.0;        // Take Profit level (in points)
input int                Signal_Expiration    =4;           // Expiration of pending orders (in bars)
input int                Signal_RSI_PeriodRSI =8;           // Relative Strength Index(8,...) Period of calculation
input ENUM_APPLIED_PRICE Signal_RSI_Applied   =PRICE_CLOSE; // Relative Strength Index(8,...) Prices series
input double             Signal_RSI_Weight    =0.7;         // Relative Strength Index(8,...) Weight [0...1.0]
input int                Signal_MA_PeriodMA   =90;          // Moving Average(12,0,...) Period of averaging
input int                Signal_MA_Shift      =0;           // Moving Average(12,0,...) Time shift
input ENUM_MA_METHOD     Signal_MA_Method     =MODE_SMA;    // Moving Average(12,0,...) Method of averaging
input ENUM_APPLIED_PRICE Signal_MA_Applied    =PRICE_CLOSE; // Moving Average(12,0,...) Prices series
input double             Signal_MA_Weight     =0.6;         // Moving Average(12,0,...) Weight [0...1.0]

input int                Signal_CCIxx_PeriodCCI =8;            // Commodity Channel Index(8,...) Period of calculation
input ENUM_APPLIED_PRICE Signal_CCIxx_Applied   =PRICE_CLOSE;  // Commodity Channel Index(8,...) Prices series
input double             Signal_CCIxx_Weight    =0.8;          // Commodity Channel Index(8,...) Weight [0...1.0]

Мы изменили значения переменных Signal_RSI_Weight и Signal_MA_Weight с 1.0 на 0.7 и 0.6 соответственно. Для корректной работы с входными параметрами для CCIxx (модифицированной версии индикатора CCI), мы скопировали эти 3 строчки кода из файла SignalCCI.mqh и просто добавили постфикс "хх".

В секции "protected" объявления класса есть много интересных элементов:

class CSignalCCI : public CExpertSignal
  {
protected:
   CiCCI             m_cci;            // object-oscillator
   //--- adjusted parameters
   int               m_periodCCI;      // the "period of calculation" parameter of the oscillator
   ENUM_APPLIED_PRICE m_applied;       // the "prices series" parameter of the oscillator
   //--- "weights" of market models (0-100)
   int               m_pattern_0;      // model 0 "the oscillator has required direction"
   int               m_pattern_1;      // model 1 "reverse behind the level of overbuying/overselling"
   int               m_pattern_2;      // model 2 "divergence of the oscillator and price"
   int               m_pattern_3;      // model 3 "double divergence of the oscillator and price"
   //--- variables
   double            m_extr_osc[10];   // array of values of extremums of the oscillator
   double            m_extr_pr[10];    // array of values of the corresponding extremums of price
   int               m_extr_pos[10];   // array of shifts of extremums (in bars)
   uint              m_extr_map;       // resulting bit-map of ratio of extremums of the oscillator and the price

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

Мы собираемся добавить две пользовательских модели: m_pattern_4 и m_pattern_5. Это делается просто путем добавления двух строк кода (двух целочисленных переменных).

//--- "weights" of market models (0-100)
   int               m_pattern_0;      // model 0 "the oscillator has required direction"
   int               m_pattern_1;      // model 1 "reverse behind the level of overbuying/overselling"
   int               m_pattern_2;      // model 2 "divergence of the oscillator and price"
   int               m_pattern_3;      // model 3 "double divergence of the oscillator and price"

   int               m_pattern_4;      // model 4 "our own first new pattern: values cross the zero"
   int               m_pattern_5;      // model 5 "our own second new pattern: values bounce around the zero"

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

Нам также потребуется выполнить следующее: в файле CSignalCCIxx.mqh нажмите Ctrl+H, в качестве искомого текста введите "CCI", а в качестве замены - "CCIxx". Нажмите кнопку "Заменить все" - должны быть найдены и заменены 41 вхождение. Перейдем в верхнюю часть файла:

//+------------------------------------------------------------------+
//| Class CSignalCCIxx.                                              |
//| Purpose: Class of generator of trade signals based on            |
//|          the 'Commodity Channel Index' oscillator.               |
//| Is derived from the CExpertSignal class.                         |
//+------------------------------------------------------------------+
class CSignalCCIxx : public CExpertSignal
  {
protected:
   CiCCIxx             m_CCIxx;            // object-oscillator

и заменим:

protected:
   CiCCIxx             m_CCIxx;            // object-oscillator

на следующий фрагмент из исходного файла SignalCCI.mqh:

protected:
   CiCCI             m_CCIxx;            // object-oscillator

Мы сделали это потому, что CiCCI вызывается из другого инклюд файла. В противном случае будет несколько ошибок при компиляции. Теперь мы можем скомпилировать файл SignalCCIxx.mqh, в результате должно быть 0 ошибок и 0 предупреждений. Если ошибки все же есть, возможно, вы сделали что-то неправильно и должны повторить процедуру.

Теперь перейдем к сути: добавим наши собственные паттерны. К примеру, добавим 2 торговых модели. В общей сложности у нас будет 4 новых сигнала (паттерна) - 2 для покупки и 2 для продажи. Вот фрагмент кода, который мы изменим:

//+------------------------------------------------------------------+
//| Constructor CSignalCCIxx.                                        |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CSignalCCIxx::CSignalCCIxx()
  {
//--- initialization of protected data
   m_used_series=USE_SERIES_HIGH+USE_SERIES_LOW;
//--- setting default values for the oscillator parameters
   m_periodCCIxx  =14;
//--- setting default "weights" of the market models
   m_pattern_0  =90;         // model 0 "the oscillator has required direction"
   m_pattern_1  =60;         // model 1 "reverse behind the level of overbuying/overselling"
   m_pattern_2  =100;        // model 2 "divergence of the oscillator and price"
   m_pattern_3  =50;         // model 3 "double divergence of the oscillator and price"
   m_pattern_4  =90;         // model 4 "our own first new pattern: "
   m_pattern_5  =90;         // model 5 "our own second new pattern: 
}

Мы присвоили значение 90 переменным m_pattern_4 и m_pattern_5, но вам необходимо подобрать свои значения: это веса для новых рыночных моделей и они целиком и полностью влияют на все торговое поведение советника.

Добавим две новые модели рынка. Они будут очень простыми - они предназначены только для самообразования и не являются проверенными торговыми сигналами, так что не торгуйте с ними. Инструмент перекрестье поможет нам определить значения индикатора CCI (см. рисунки ниже) для соответствующих баров.


Первая модель

Пересечение нулевой линии снизу вверх

Это наша первая модель для условия "voting that price will grow" (ставка на повышение цены).

Рисунок 5. Наша первая модель, Повышение цены - CCI на Баре 1      Рисунок 6. Наша первая модель, Повышение цены - индикатор CCI на Баре 2


Пересечение нулевой линии сверху вниз

Это наша первая модель для условия "voting that price will fall" (ставка на понижение цены).

Рисунок 7. Наша первая модель, Понижение цены - индикатор CCI на Баре 1      Рисунок 8. Наша первая модель, Понижение цены - индикатор CCI на Баре 2


Вторая модель

Пересечение нулевой линии сверху вниз, затем снова вверх

Это наша вторая модель для условия "voting that price will grow" (ставка на повышение цены).

Рисунок 9. Наша вторая модель, Повышение цены - индикатор CCI на Баре 1   Рисунок 10. Наша вторая модель, Повышение цены - индикатор CCI на Баре 2   Рисунок 11. Наша вторая модель, Повышение цены - индикатор CCI на Баре 3


Пересечение нулевой линии снизу вверх, затем снова вниз

Это наша вторая модель для условия "voting that price will fall" (ставка на понижение цены).

Рисунок 12. Наша вторая модель, Понижение цены - индикатор CCI на Баре 1   Рисунок 13. Наша вторая модель, Понижение цены - индикатор CCI на Баре 2   Рисунок 14. Наша вторая модель, Понижение цены - индикатор CCI на Баре 3


Реализация паттернов

Для реализации этих четырех условий (по два на модель) мы должны изменить следующий кусок кода таким образом. В нижней части мы добавили строки кода (выделены желтым) для условия покупки (см. комментарий: "Voting" that price will grow).

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//| INPUT:  no.                                                      |
//| OUTPUT: number of "votes" that price will grow.                  |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CSignalCCIxx::LongCondition()
  {
   int result=0;
   int idx   =StartIndex();
//---
   if(Diff(idx)>0.0)
     {
      //--- the oscillator is directed upwards confirming the possibility of price growth
      if(IS_PATTERN_USAGE(0)) result=m_pattern_0;      // "confirming" signal number 0
      //--- if the model 1 is used, search for a reverse of the oscillator upwards behind the level of overselling
      if(IS_PATTERN_USAGE(1) && Diff(idx+1)<0.0 && CCIxx(idx+1)<-100.0)
         result=m_pattern_1;      // signal number 1
      //--- if the model 2 or 3 is used, perform the extended analysis of the oscillator state
      if(IS_PATTERN_USAGE(2) || IS_PATTERN_USAGE(3))
        {
         ExtState(idx);
         //--- if the model 2 is used, search for the "divergence" signal
         if(IS_PATTERN_USAGE(2) && CompareMaps(1,1))      // 00000001b
            result=m_pattern_2;   // signal number 2
         //--- if the model 3 is used, search for the "double divergence" signal
         if(IS_PATTERN_USAGE(3) && CompareMaps(0x11,2))   // 00010001b
            return(m_pattern_3);  // signal number 3
        }
      // if the model 4 is used, look for crossing of the zero line
      if(IS_PATTERN_USAGE(4) && CCIxx(idx+1)>0.0 && CCIxx(idx+2)<0.0)
         result=m_pattern_4;      // signal number 4 
      // if the model 5 is used, look for the bouncing around the zero line
      if(IS_PATTERN_USAGE(5) && CCIxx(idx+1)>0.0 && CCIxx(idx+2)<0.0 && CCIxx(idx+3)>0.0)
         result=m_pattern_5;      // signal number 5
     }
//--- return the result
   return(result);
  }

Давайте также изменим соответствующий код для условия продажи. В нижней части мы добавили строки кода (выделены желтым) для условия продажи (см. комментарий: "Voting" that price will fall).

//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//| INPUT:  no.                                                      |
//| OUTPUT: number of "votes" that price will fall.                  |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CSignalCCIxx::ShortCondition()
  {
   int result=0;
   int idx   =StartIndex();
//---
   if(Diff(idx)<0.0)
     {
      //--- the oscillator is directed downwards confirming the possibility of falling of price
      if(IS_PATTERN_USAGE(0)) result=m_pattern_0;      // "confirming" signal number 0
      //--- if the model 1 is used, search for a reverse of the oscillator downwards behind the level of overbuying
      if(IS_PATTERN_USAGE(1) && Diff(idx+1)>0.0 && CCIxx(idx+1)>100.0)
         result=m_pattern_1;      // signal number 1
      //--- if the model 2 or 3 is used, perform the extended analysis of the oscillator state
      if(IS_PATTERN_USAGE(2) || IS_PATTERN_USAGE(3))
        {
         ExtState(idx);
         //--- if the model 2 is used, search for the "divergence" signal
         if(IS_PATTERN_USAGE(2) && CompareMaps(1,1))      // 00000001b
            result=m_pattern_2;   // signal number 2
         //--- if the model 3 is used, search for the "double divergence" signal
         if(IS_PATTERN_USAGE(3) && CompareMaps(0x11,2))   // 00010001b
            return(m_pattern_3);  // signal number 3
        }
      if(IS_PATTERN_USAGE(4) && CCIxx(idx+1)<0.0 && CCIxx(idx+2)>0.0)
         result=m_pattern_4;      // signal number 4 
      if(IS_PATTERN_USAGE(5) && CCIxx(idx+1)<0.0 && CCIxx(idx+2)>0.0 && CCIxx(idx+3)<0.0)
         result=m_pattern_5;      // signal number 5  
     }
//--- return the result
   return(result);
  }

Обратите внимание на (idx+1), (idx+2) ... (idx+n) в добавленных строчках: +1, +2, +3 и т.д. - это номера баров (свечей), предшествующих текущему бару (свече) (текущей является еще не сформировавшаяся свеча, т.е. нулевой бар).

Рисунок 15. Соответствие баров (свечей) переменной (idx) в коде.

Таким образом, чем больше значение idx+N, тем дальше мы от текущего бара. Каждый (idx+n)-й бар соответствует значению индикатора в той же "вертикальной плоскости" на одном таймфрейме.

Рисунок 16. Каждый (idx)-й бар соответствует относительному значению CCI

На рисунке 16 нулевой бар (первая справа свеча, соответствующая значению idx или (idx+0) в коде) соответствует значению CCI ниже уровня 0.00. Также, второй бар (idx +1) и третий бар (idx +2) имеют значения ниже линии 0.00. Мы не пометили другие бары вертикальными стрелками, но если вы наведете курсор мыши на 4-й бар (idx +3), то увидите, что его соответствующее значение CCI выше 0.00.

Для большинства пользователей этот факт является очевидным, но начинающим пользователям лучше знать, как соотносятся между собой бары/свечи на ценовом графике, графическое представление индикатора CCI, переменная (idx) и значение переменной индикатора CCIxx.

Просматривая индикаторы на графике, важно попытаться "увидеть" соответствия между барами/свечами и поведением выбранного индикатора - так вы можете сделать предположение о существовании стратегии, которую можно легко запрограммировать кода с помощью индекса бара (idx) и значения переменной индикатора.

В файле SignalCCIxx.mqh следующий код:

CCIxx(idx+1)>0.0 && CCIxx(idx+2)<0.0

буквально означает следующее:

Значение индикатора CCI (один бар назад, т.е. idx+1) выше нулевого уровня индикатора CCI
И
Значение индикатора CCI (два бара назад, т.е. idx+2) ниже нулевого уровня индикатора CCI

Это самый простой пример того, как просто добавить два пользовательских паттерна, основываясь только на выбранном значении индикатора (в данном случае - CCI).

Условие "цена будет расти" или "цена будет падать" следует добавить ​в раздел с описанием паттернов, и никто не запрещает вам создавать более сложные условия. Перед окончательным тестированием давайте рассмотрим механизмы открытия и закрытия позиций.

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

Вкратце, в файле MyExpert.mq5 у нас есть 2 входных параметра (две целочисленные переменные):

//--- inputs for main signal
input int                Signal_ThresholdOpen =40;          // Signal threshold value to open [0...100]
input int                Signal_ThresholdClose=60;          // Signal threshold value to close [0...100]

Эти пороги для открытия и закрытия представляют собой два значения, которые используются для вычислений, если (согласно нашей модели торговли) была открыта, а затем закрыта длинная или короткая позиция. В качестве значения порога используется целое число от 0 до 100. Что означают эти параметры?

Signal_ThresholdOpen - это значение для открытия длинной или короткой позиции, Signal_ThresholdClose - значение для закрытия ранее открытой позиции. Эти значения вычисляются в контексте логики экспертов, сгенерированных Мастером MQL5.

Каждый сигнал в файлах Signal__.mqh (вместо __ подставляется имя используемого индикатора, в нашем случае - MA, RSI и CCIxx) состоит из описанных ранее моделей. Давайте посмотрим на них еще раз в нашем примере. В файле SignalMA.mqh у нас есть 4 паттерна, каждый из который имеет свой относительный "вес":

//--- setting default "weights" of the market models
   m_pattern_0 =80;          // model 0 "price is on the necessary side from the indicator"
   m_pattern_1 =10;          // model 1 "price crossed the indicator with opposite direction"
   m_pattern_2 =60;          // model 2 "price crossed the indicator with the same direction"
   m_pattern_3 =60;          // model 3 "piercing"

Для сигналов RSI в файле SignalRSI.mqh аналогично:

//--- setting default "weights" of the market models
   m_pattern_0  =70;         // model 0 "the oscillator has required direction"
   m_pattern_1  =100;        // model 1 "reverse behind the level of overbuying/overselling"
   m_pattern_2  =90;         // model 2 "failed swing"
   m_pattern_3  =80;         // model 3 "divergence of the oscillator and price"
   m_pattern_4  =100;        // model 4 "double divergence of the oscillator and price"
   m_pattern_5  =20;         // model 5 "head/shoulders"

В нашем пользовательском сигнале SignalCCIxx.mqh (это практически точная копия SignalCCI.mqh) мы имеем следующие значения:

//--- setting default "weights" of the market models
   m_pattern_0  =90;         // model 0 "the oscillator has required direction"
   m_pattern_1  =60;         // model 1 "reverse behind the level of overbuying/overselling"
   m_pattern_2  =100;        // model 3 "divergence of the oscillator and price"
   m_pattern_3  =50;         // model 4 "double divergence of the oscillator and price"
   m_pattern_4  =80;         // model 4 "our own first new pattern: "
   m_pattern_5  =90;         // model 5 "our own second new pattern: "

Стандартные модели 0, 1, 2, 3 плюс наши собственные 4 и 5 со значениями 80 и 90, соответственно. Когда мы добавляем MyExpert.ex5 на график или тестируем его в Тестере стратегий, последовательно рассчитываются модели всех выбранных сигналов (RSI, MA и CCIxx).

Если условие одного или нескольких паттернов выполняется, сигнал используется при следующем вычислении. Например, если условие для m_pattern_4 из файла SignalCCIxx.mqh выполняется:

// if the model 4 is used, look for crossing of the zero line
       if(IS_PATTERN_USAGE(4) && CCIxx(idx+1)>0.0 && CCIxx(idx+2)<0.0)
          result=m_pattern_4;      // signal number 4 

паттерн становится потенциальным торговым сигналом. Другими словами, если значение CCI на баре 1 > 0.0 и в то же время значение CCI на баре 2 было < 0.0, как показано на рисунках 5 и 6, условие выполняется и m_pattern_4 (сигнал №4) активируется.

Значение веса, заданное для этого сигнала в нашей стратегии CCIxx, равно абсолютному значению 80, но в случае "voting that price will fall" (ставка на понижение цены) будет использоваться значение -80, а в случае "voting that the price will grow" (ставка на повышение цены) - значение 80. Ставка на понижение цены просто добавит знак минус к первоначальному значению веса паттерна.

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

Таким образом, мы можем рассмотреть каждую модель как конкурента 2 фракций: сигналы быков и сигналы медведей. Когда эти однонаправленные модели/сигналы (ставка на повышение цены) активированы (их условие выполняется), они суммируются друг с другом, а результат сравнивается со значением Signal_ThresholdOpen. Если открытых позиций нет или сумма сравнивается со значением Signal_ThresholdClose для развернутой ранее позиции (в нашем примере это короткая позиция), m_pattern_4 из SignalCCIxx.mqh имеет значение:

Предположим, что ВСЕ остальные модели ВСЕХ сигналов (SignalRSI.mqh, SignalMA.mqh и модели 0,1,2,3 и 5 из SignalCCIxx.mqh) получают значение 0. То есть, сигналы "конкурентов" вне "игры", конкурируют только два m_pattern_4 - по одному на покупку и продажу. Таким образом, работает только m_pattern_4, потому что имеет отличное от 0 значение, т.е. 80.

//--- setting default "weights" of the market models
   m_pattern_0 =0;          // model 0 "price is on the necessary side from the indicator"
   m_pattern_1 =0;          // model 1 "price crossed the indicator with opposite direction"
   m_pattern_2 =0;          // model 2 "price crossed the indicator with the same direction"
   m_pattern_3 =0;          // model 3 "piercing"

Для сигналов RSI в файле SignalRSI.mqh аналогично:

//--- setting default "weights" of the market models
   m_pattern_0  =0;         // model 0 "the oscillator has required direction"
   m_pattern_1  =0;        // model 1 "reverse behind the level of overbuying/overselling"
   m_pattern_2  =0;        // model 2 "failed swing"
   m_pattern_3  =0;        // model 3 "divergence of the oscillator and price"
   m_pattern_4  =0;        // model 4 "double divergence of the oscillator and price"
   m_pattern_5  =0;        // model 5 "head/shoulders"

В нашем пользовательском сигнале SignalCCIxx.mqh (это практически точная копия SignalCCI.mqh) мы имеем следующие значения:

//--- setting default "weights" of the market models
   m_pattern_0  =0;        // model 0 "the oscillator has required direction"
   m_pattern_1  =0;        // model 1 "reverse behind the level of overbuying/overselling"
   m_pattern_2  =0;        // model 3 "divergence of the oscillator and price"
   m_pattern_3  =0;        // model 4 "double divergence of the oscillator and price"
   m_pattern_4  =80;       // model 4 "our own first new pattern: "
   m_pattern_5  =0;        // model 5 "our own second new pattern: "

В начале статьи мы добавили следующие строчки:

input int                Signal_CCIxx_PeriodCCI =8;            // Commodity Channel Index(8,...) Period of calculation
input ENUM_APPLIED_PRICE Signal_CCIxx_Applied   =PRICE_CLOSE;  // Commodity Channel Index(8,...) Prices series
input double             Signal_CCIxx_Weight    =0.8;          // Commodity Channel Index(8,...) Weight [0...1.0]

Мы рассматривали переменную Signal_CCIxx_Weight, которая имела значение 0.8. При достижении порогового значения срабатывает Signal_ThresholdOpen. Значение рассчитывается следующим образом:

0.8 (входной параметр Signal_CCIxx_Weight)
*
80 (значение веса m_pattern_4)
= 64 это сила сигнала для ставки на повышение цены

Это предположение, что цена будет расти, потому что алгоритм получил сигнал "роста цены" (m_pattern_4 из SignalCCIxx) и значение 80.

Если гипотетически он получит сигнал "падения цены" (m_pattern_4 из SignalCCIxx), значение будет равным -80. Алгоритм просто добавит знак минус в паттерн. Для предположения, что цена будет падать, расчеты будут следующие:

0.8 (входной параметр Signal_CCIxx_Weight)
*
-80 (значение веса m_pattern_4)
= -64 отрицательное значение для коротких позиций

-64 --> 64 (абсолютная величина) представляет собой силу сигнала для ставка на понижение цены. Сила сигнала всегда выражается в абсолютной величине, в то время как для значений коротких позиций ставится знак минус, а для значений длинных позиции - знак плюс.

Вернемся к примеру для длинной позиции с достигнутым значением 64 и силой сигнала равной 64. Если нет других противоположных (со знаком минус) конкурирующих сигналов (m_pattern_N из Signal__), уровень Signal_ThresholdOpen со значением 40 будет достигнут и превышен на 24, поскольку сила длинного сигнала составляет 64 (40+24=64), следовательно открывается длинная позиция.

Например, если для Signal_CCIxx_Weight мы установим значение 0.4, длинные позиции открываться не будут, потому что:

0.4 (Signal_CCIxx_Weight)
*
80(m_pattern_4)
= 32 (сила "длинного сигнала")

а уровень 40 (Signal_ThresholdOpen) не достигается, так как 32 < 40.

Приведенные выше значения (все значения равны 0, за исключением 80 для m_pattern_4 из SignalCCIxx.mqh) взяты "с потолка" чисто для понимания логики Мастера MQL5 и его системы весов и порогов. Для своих советников вам потребуется присвоить предпочтительные веса для каждого m_pattern_N в каждом Signal__. Если паттерну присвоить значение от 0, это просто означает, что эта модель просто не будет использоваться.

Если мы хотим задать другое значения в примере выше (все параметры установлены ​​в 0, за исключением m_pattern_4 из SignalCCIxx.mqh), скажем 100 для m_pattern_1 из SignalRSI.mqh, расчет изменятся, так что теперь мы получим 4 конкурента:

m_pattern_4 Bullish --> 0.8 * 80 = 64
m_pattern_2 Bullish --> 0.7 * 100 = 70
m_pattern_4 Bearish --> 0.8 * (-80) = -64
m_pattern_2 Bearish --> 0.7 * (-100) = -70

Таким образом, мы получаем 4 возможные комбинации:

A) m_pattern_4 Bullish + m_pattern_2 Bullish = {[0.8 * (80)] + [0.7 * (100)]}/2 = [64 + (70)]/2 = 134/2 = 67
B) m_pattern_4 Bullish + m_pattern_2 Bearish = {[0.8 * (80)] + [0.7 * (-100)]}/2 = [64 + (-70)]/2 = -6/2 = -3
C) m_pattern_4 Bearish + m_pattern_2 Bullish = {[0.8 * (-80)] + [0.7 * (100)]}/2 = [(-64) + 70]/2 = 6/2 = 3
D) m_pattern_4 Bearish + m_pattern_2 Bearish = {[0.8 * (-80)] + [0.7 * (-100)]}/2 = [(-64) + (-70)]/2 = -134/2 = -67

Вариант А
Положительное значение 67. Длинная позиция открывается, потому что достигнут и превзойден порог Signal_ThresholdOpen со значением 40. Длинная позиция затем закрывается, когда достигается и превышается порог Signal_ThresholdClose со значением 60 по абсолютной величине Варианта D = -67 = |67|, потому что сила сигнала Варианта D по абсолютной величине 67 > 60 (т.е. порога Signal_ThresholdClose).

Вариант B
Отрицательное значение -3. Короткие позиции не открываются, потому что порог Signal_ThresholdOpen со значением 40 не достигнут и не превзойден по абсолютной величине: -3 стало равным 3, когда мы рассматриваем его абсолютное значение для вычисления "силы сигнала", а 3 < 40 (значение сигнала для открытия позиции). Нет открытых коротких позиций и, соответственно, нет никаких расчетов для закрытия коротких позиций.

Вариант C
Положительное значение 3. Длинные позиции не открываются, потому что порог Signal_ThresholdOpen со значением 40 не достигнут и не превзойден 3 < 40 (значение сигнала для открытия позиции). Нет открытых длинных позиций и, соответственно, нет никаких расчетов для закрытия длинных позиций.

Вариант D
Отрицательное значение -67. Короткая позиция открывается, потому что порог Signal_ThresholdOpen со значением 40 достигнут и превзойден силой сигнала, которая рассчитывается как абсолютная величина -67, т.е. 67, а 67 > 40. Короткая позиция затем закрывается, когда достигается и превышается порог Signal_ThresholdClose со значением 60 по величине Варианта A = 67, потому что 67 (сила сигнала Варианта A) > 60 (т.е. порога Signal_ThresholdClose).

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

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

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

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


Подробнее о позиции

Давайте рассмотрим позицию поподробнее:

Длинная позиция открывается если:

Open_long >= Signal_ThresholdOpen
ЕСЛИ Signal_ThresholdClose <= Signal_ThresholdOpen
Получаем сигнал на продажу, поэтому длинная позиция будет развернута если:
Open_short > Signal_ThresholdClose И Open_short > Signal_ThresholdOpen
Получаем сигнал на продажу, поэтому длинная позиция будет закрыта если:
Open_short > Signal_ThresholdClose И Open_short < Signal_ThresholdOpen

ЕСЛИ Signal_ThresholdClose >= Signal_ThresholdOpen
Получаем сигнал на продажу, поэтому длинная позиция будет развернута если:
Open_short > Signal_ThresholdClose ИЛИ Open_short > Signal_ThresholdOpen
Получаем сигнал на продажу, поэтому длинная позиция будет закрыта если:
Open_short > Signal_ThresholdClose ИЛИ Open_short < Signal_ThresholdOpen

В случае Signal_ThresholdClose >= Signal_ThresholdOpen будет логическое "ИЛИ", поскольку выражение Signal_ThresholdClose >= Signal_ThresholdOpen уже включает в себя значение Signal_ThresholdOpen. Таким образом, позиция будет закрыта и переопределена значением Signal_ThresholdClose >= Signal_ThresholdOpen, т.е. будет в любом случае развернута в сторону продажи.


Короткая позиция открывается если:

Open_short >= Signal_ThresholdOpen.
Signal_ThresholdClose <= Signal_ThresholdOpen
Получаем сигнал на покупку, поэтому короткая позиция будет развернута если:
Open_long > Signal_ThresholdClose И Open_long > Signal_ThresholdOpen
Получаем сигнал на покупку, поэтому короткая позиция будет закрыта если:
Open_long > Signal_ThresholdClose И Open_long < Signal_ThresholdOpen

ЕСЛИ Signal_ThresholdClose >= Signal_ThresholdOpen
Получаем сигнал на покупку, поэтому короткая позиция будет развернута если:
Open_long > Signal_ThresholdClose ИЛИ Open_long > Signal_ThresholdOpen
Получаем сигнал на покупку, поэтому короткая позиция будет закрыта если:
Open_long > Signal_ThresholdClose ИЛИ Open_long < Signal_ThresholdOpen

В случае Signal_ThresholdClose >= Signal_ThresholdOpen будет логическое "ИЛИ", поскольку выражение Signal_ThresholdClose >= Signal_ThresholdOpen уже включает в себя значение Signal_ThresholdOpen. Таким образом, позиция будет закрыта и переопределена значением Signal_ThresholdClose >= Signal_ThresholdOpen, т.е. будет в любом случае развернута в сторону покупки.

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


Уровень цен и сигнал истечения

Есть еще одна важная переменная:

input double             Signal_PriceLevel    =0.0;         // Price level to execute a deal

Эта переменная очень важна для базового понимания механизма созданного мастером советника. Упрощенно его можно представить следующим образом:

Рисунок 17. Стоп-ордера и лимитные ордера в зависимости от значения Signal_PriceLevel

Например:

EURUSD - Длинные позиции

Signal_PriceLevel = -70 (минус 70)
при получении сигнала на открытие позиции (например, текущая цена = 1.2500),
советник выставит ордер Buy Stop, состоящий из 1.2500 + 70 = 1,2570
(хуже текущей цены с точки зрения быков)


Signal_PriceLevel = 60 (плюс 60)
при получении сигнала на открытие позиции (например, текущая цена = 1.2500),
советник выставит ордер Buy Limit, состоящий из 1.2500 - 60 = 1,2440
(лучше текущей цены с точки зрения быков)


EURUSD - Короткие позиции

Signal_PriceLevel = -70 (минус 70)
при получении сигнала на открытие позиции (например, текущая цена = 1.2500),
советник выставит ордер Sell Stop, состоящий из 1.2500 - 70 = 1,2430
(лучше текущей цены с точки зрения медведей)


Signal_PriceLevel = 60 (плюс 60)
при получении сигнала на открытие позиции (например, текущая цена = 1.2500),
советник выставит ордер Sell Limit, состоящий из 1.2500 + 60 = 1,2560
(хуже текущей цены с точки зрения медведей)


Наконец, входной параметр

input int                Signal_Expiration    =4;           // Expiration of pending orders (in bars)

определяет, сколько раз (в барах) Stop/Limit приказы будут действовать.


Блок-схема

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

Рисунок 18. Упрощенная схема обработки ордеров и позиций


Тестер стратегий

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

Теперь давайте скомпилируем файл MyExpert.mq5, и если все в порядке, компилятор выдаст 0 ошибок и 0 предупреждений. Что ж, давайте погоняем его в Тестере стратегий. Я использовал параметры для EUR/USD на временном промежутке, соответствующем последнему Automated Trading Championship 2011.

Рисунок 19. Настройки параметров для эмуляции ATC2011 с MyExpert.mq5

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

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

Рисунок 20. Результаты псевдо ATC2011 с MyExpert.mq5

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


Заключение

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

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

Вы можете легко написать собственную стратегию при помощи стандартных индикаторов MetaTrader 5, упаковав их в готовый к Чемпионату советник.