Создание многомодульных советников

23 марта 2018, 08:41
Sergey Pavlov
12
2 005

Введение

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

Модульное программирование — метод разработки программ, предполагающий разделение программы на независимые модули.

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

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

  • Принцип утаивания информации Парнаса. Модуль служит для сокрытия информации и алгоритма решения конкретной задачи. Модуль в дальнейшем может быть заменён на другой.
  • Аксиома модульности Коуэна. Модуль — независимая программная единица, служащая для выполнения определённой функции программы.
  • Сборочное программирование Цейтина. Модули — это программные "кирпичи", из которых строится программа.

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

Рис. 1. Модульный торговый робот.
Рис. 1. Абстрактная схема модульного торгового робота

Принцип многомодульности

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

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

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

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

Основные характеристики модуля:

  • один вход и один выход — на входе программный модуль получает определённый набор исходных данных, обрабатывает их и возвращает один набор результирующих данных, т.е. реализуется принцип IPO (Input-Process-Output);
  • функциональная завершённость — чтобы выполнить отдельную функцию, модуль выполняет полный перечень регламентированных операций, достаточный для завершения начатой обработки;
  • логическая независимость — результат работы программного модуля зависит только от исходных данных, но не зависит от работы других модулей;
  • слабые информационные связи с другими программными модулями — обмен информацией между модулями должен быть по возможности минимизирован.

На языке MQL5 можно написать три вида программ: эксперт, индикатор или скрипт. Для главного модуля лучше всего подойдёт формат эксперта, в котором будет организовано управление всеми модулями и разместятся торговые функции. А вот остальные модули могут быть реализованы, например, в виде индикаторов. Индикаторы действительно идеально подходят для формирования модуля: данные, рассчитанные по заданному алгоритму, можно сохранить в индикаторных буферах и передать при необходимости в многомодульный советник. Эксперт, в свою очередь, может использовать или игнорировать эти данные, в зависимости от поставленной задачи. В каких-то проектах, возможно, будет оправдано использование экспертов в качестве внешних модулей, но при этом нужно детально продумать механизм обмена данными.

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

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

Главный модуль — эксперт

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

Рассмотрим в статье создание многомодульного советника на конкретном примере, взятом из CodeBase. Исходный советник торгует фиксированным лотом в канале индикатора iBands с разворотом позиции на границах канала. Эксперт полностью самодостаточен и не требует каких-либо внешних программ.

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

Так что же надо добавить в код, чтобы превратить его в модульный проект?

  1. Объявить внешние модули (индикаторы), которые в дальнейшем может применять пользователь. 
  2. Добавить необходимый функционал для их интеграции.
  3. Подготовить документацию для разработчиков внешних модулей (включить функцию генерации документации в отдельном файле). Так, для разработки внешних модулей понадобится информация о структуре данных, которые могут быть корректно использованы главным модулем. Например, в рассматриваемом примере модуль управления капиталом должен транслировать в советник размер лота, а модуль сопровождения позиции — расстояние в пунктах от текущей цены.

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

  • Модуль №1 — модуль управления капиталом. На выходе он дает размер лота.
  • Модуль №2 — модуль сопровождения позиции и установки SL. На выходе он дает расстояние до стоп-лосса в пунктах от цены открытия позиции.
  • Модуль №3 — модуль сопровождения позиции и установки TP. На выходе он дает расстояние до тейк-профита в пунктах от цены открытия позиции.
  • Модуль №4 — модуль сопровождения позиции и установки трейлинг-стопа. На выходе он дает расстояние до стоп-лосса в пунктах от текущей цены.
  • Модуль №5 — модуль генерации торговых сигналов. На выходе он дает значение сигнала.
  • Модуль №6 — модуль фильтрации торговых сигналов. На выходе он дает значение фильтра.
  • Модуль №7 — модуль сопровождения позиции и установки уровня безубытка. На выходе он дает расстояние до стоп-лосса от цены открытия позиции.



 

Рис. 2. Функция OnInit() и инициализация внешних модулей


Рис. 3. Функция OnTick() и чтение данных из внешних модулей


Рис. 4. Функция OnTrade() и чтение данных из внешних модулей



Рис. 5. Функция генерации торговых сигналов и чтение данных из внешних модулей

 

//****** project (module expert): test_module_exp.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project TEST Main module."
#property link      "The project uses 7 external modules."
//---
#include <Trade\Trade.mqh>
//---
MqlTick    last_tick;
CTrade     trade;
//---
input int                  e_bands_period=80;            // Moving average period
int                        e_bands_shift=0;              // shift
input double               e_deviation=3.0;              // Number of standard deviations
input ENUM_APPLIED_PRICE   e_applied_price=PRICE_CLOSE;  // Price type
input bool                 on_module=false;              // whether or not to use plug-ins
//---
double lot=0.01;           // Fixed lot
double min_lot=0.01;       // Minimum allowable lot
bool   on_trade=false;     // Trade function flag
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//--- module 1
bool   on_lot=false;
int    handle_m1;
//--- module 2
bool   on_SL=false;
int    handle_m2;
//--- module 3
bool   on_TP=false;
int    handle_m3;
//--- module 4
bool   on_Trail=false;
int    handle_m4;
//--- module 5
bool   on_signals=false;
int    handle_m5;
//--- module 6
bool   on_Filter=false;
int    handle_m6;
//--- module 7
bool   on_Breakeven=false;
int    handle_m7;
//+------------------------------------------------------------------+
//| Structure of trading signals                                     |
//+------------------------------------------------------------------+
struct sSignal
  {
   bool              Buy;    // Buy signal
   bool              Sell;   // Sell signal
  };
//+------------------------------------------------------------------+
//| Trading signals generator                                        |
//+------------------------------------------------------------------+
sSignal Buy_or_Sell()
  {
   sSignal res={false,false};
//--- MODULE 5
   if(on_signals)
     { // If there is an additional module
      double buffer_m5[];
      ArraySetAsSeries(buffer_m5,true);
      if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res);
      if(buffer_m5[0]<-1) res.Sell=true;
      if(buffer_m5[0]>1) res.Buy=true;
     }
//--- MODULE 6
   if(on_Filter)
     { // If there is an additional module
      double buffer_m6[];
      ArraySetAsSeries(buffer_m6,true);
      if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res);
      lot=buffer_m6[0];
      if(buffer_m6[0]<1) res.Buy=false;
      if(buffer_m6[0]>-1) res.Sell=false;
     }
//---
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- Timeseries
   double L[];
   double H[];
   ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L);
   ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H);
   if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true;
   if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true;
//---
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create the indicator handle
   handle_Bands=iBands(_Symbol,_Period,e_bands_period,e_bands_shift,e_deviation,e_applied_price);
   if(handle_Bands==INVALID_HANDLE)
      return(INIT_FAILED);
   else
      on_trade=true;
   if(on_module)
     {
      //--- MODULE 1
      //--- check: whether there is an external module?
      handle_m1=iCustom(NULL,0,"Market\\test_module_MM");
      if(handle_m1!=INVALID_HANDLE)
         on_lot=true;
      //--- MODULE 2
      //--- check: whether there is an external module?
      handle_m2=iCustom(NULL,0,"Market\\test_module_SL");
      if(handle_m2!=INVALID_HANDLE)
         on_SL=true;
      //--- MODULE 3
      //--- check: whether there is an external moduleь?
      handle_m3=iCustom(NULL,0,"Market\\test_module_TP");
      if(handle_m3!=INVALID_HANDLE)
         on_TP=true;
      //--- MODULE 4
      //--- check: whether there is an external module?
      handle_m4=iCustom(NULL,0,"Market\\test_module_Trail");
      if(handle_m4!=INVALID_HANDLE)
         on_Trail=true;
      //--- MODULE 5
      //--- check: whether there is an external module?
      handle_m5=iCustom(NULL,0,"Market\\test_module_signals");
      if(handle_m5!=INVALID_HANDLE)
         on_signals=true;
      //--- MODULE 6
      //--- check: whether there is an external module?
      handle_m6=iCustom(NULL,0,"Market\\test_module_Filter");
      if(handle_m6!=INVALID_HANDLE)
         on_Filter=true;
      //--- MODULE 7
      //--- check: whether there is an external module?
      handle_m7=iCustom(NULL,0,"Market\\test_module_Breakeven");
      if(handle_m7!=INVALID_HANDLE)
         on_Breakeven=true;
     }
//--- Minimum allowable volume for trading operationsn
   min_lot=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- MODULE 1
   if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }
//--- MODULE 4
   if(on_Trail)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m4[];
            ArraySetAsSeries(buffer_m4,true);
            if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return;
            double TR=buffer_m4[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
           }
//--- MODULE 7
   if(on_Breakeven)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m7[];
            ArraySetAsSeries(buffer_m7,true);
            if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return;
            double TRB=buffer_m7[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
           }
//---
   if(lot<min_lot) lot=min_lot;
//---
   if(on_trade)
     {
      sSignal signal=Buy_or_Sell();
      //--- The value of the required and free margin
      double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      //--- BUY
      if(signal.Buy)
        {
         if(!PositionSelect(_Symbol))
           {
            SymbolInfoTick(_Symbol,last_tick);
            if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin))
               if(margin<Equity)
                  trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: new position");
           }
         else
           {
            if(PositionGetDouble(POSITION_PROFIT)<0) return;
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               trade.PositionClose(_Symbol);
               SymbolInfoTick(_Symbol,last_tick);
               if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin))
                  if(margin<Equity)
                     trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: reversal");
              }
           }
        }
      //--- SELL
      if(signal.Sell)
        {
         if(!PositionSelect(_Symbol))
           {
            SymbolInfoTick(_Symbol,last_tick);
            if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin))
               if(margin<Equity)
                  trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: new position");
           }
         else
           {
            if(PositionGetDouble(POSITION_PROFIT)<0) return;
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               trade.PositionClose(_Symbol);
               SymbolInfoTick(_Symbol,last_tick);
               if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin))
                  if(margin<Equity)
                     trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: reversal");
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(on_SL && on_TP) // If there is an additional module
     {
      //--- MODULE 2
      double buffer_m2[];
      ArraySetAsSeries(buffer_m2,true);
      if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return;
      double SL=buffer_m2[0];
      //--- MODULE 3
      double buffer_m3[];
      ArraySetAsSeries(buffer_m3,true);
      if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return;
      double TP=buffer_m3[0];
      //--- Position modification
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_SL)==0)
           {
            //--- BUY
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
            //--- SELL
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
           }
     }
  }

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

Наиболее важные модули

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

  • модуль управления капиталом (рисками);
  • модуль сопровождения позиций (SL и TP);
  • модуль трейлинг-стопа;
  • модуль генерации торговых сигналов;
  • модуль фильтрации сигналов.

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

Технология создания вспомогательных модулей

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

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

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

Панель управления генератором многомодульных проектов

Видео 1. Панель управления генератором многомодульных проектов

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

Сгенерированные файлы помещаются в папку "Files" (см. рис. 2). Имя главного модуля "test_module_exp.mq5" состоит из имени проекта "test" и приставки "_module_exp.mq5". Его необходимо переместить в папку "Experts", а остальные файлы внешних модулей —  в папку "Indicators\Market".

Рис. 6. Сгенерированные файлы проекта "test".

Рис. 6. Сгенерированные файлы проекта "test".

После этого скомпилируем все файлы и приступим к тестированию многомодульного проекта.

Видео 2. Компиляция сгенерированных файлов проекта "test"
  

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

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

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

Пример 1: модуль управления капиталом

Этот внешний модуль рассчитывает объём лота для открытия очередного ордера. Ниже реализован самый простой вариант расчёта торгового объёма (в процентах от доступных средств на депозите):

//****** project (module MM): test_module_MM_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module MM"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
input double   lot_perc=0.1;  // Percentage of Equity value
double         Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- calculation of the lot of equity
   Buffer1[0]=NormalizeDouble(Equity*lot_perc/1000.0,2); // Lot size determination function
   if(Buffer1[0]<0.01) Buffer1[0]=0.01;
   return(rates_total);
  };

Если вызвать этот модуль по умолчанию (без указания значений входных параметров), то главный модуль получит размер разрешённого лота для совершения сделки в размере 0,1% от доступных средств. Пример вызова этого модуля из главной программы:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- MODULE 1
   if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }
  
  ...
  
  }

Пример 2: модуль сопровождения позиции (SL, TP и трейлинг)

Установка стоп-лосса (SL) и тейк-профита (TP) — это один из способов сопровождения открытой позиции. Поскольку в разных торговых стратегиях используются различные комбинации расчёта и выставления SL и TP, то напрашивается вариант с разделением на два модуля: отдельно для SL и TP. Но если мы всё-таки решаем объединить SL и TP в одном модуле, то их значения следует разместить в разных индикаторных буферах.

Модуль установки SL:

//****** project (module SL): test_module_SL_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module SL"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double SL=100; // SL in points
//--- calculation of the SL
   Buffer1[0]=SL;
   return(rates_total);
  };

Модуль установки TP:

//****** project (module TP): test_module_TP_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module TP"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double TP=100; // TP in points
//--- calculation of the TP
   Buffer1[0]=TP;
   return(rates_total);
  };

В приведённых кодах показан самый наглядный вариант для расчёта значений SL и TP — в пунктах. Вернее сказать, он не расчитывается, а задаётся константой. При этом значения указаны прямо в программе, а не во входных параметрах. Это сделано, чтобы продемонстрировать вариант реализации внешних модулей без входных данных. Такой код "на коленке" сможет написать любой начинающий программист.

Вызов рассмотренных модулей я рекомендую размещать в функции OnTrade. Выглядит это примерно вот так:

//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(on_SL && on_TP) // If there is an additional module
     {
      //--- MODULE 2
      double buffer_m2[];
      ArraySetAsSeries(buffer_m2,true);
      if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return;
      double SL=buffer_m2[0];
      //--- MODULE 3
      double buffer_m3[];
      ArraySetAsSeries(buffer_m3,true);
      if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return;
      double TP=buffer_m3[0];
      //--- Position modification
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_SL)==0)
           {
            //--- BUY
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
            //--- SELL
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
           }
     }
  }

Кроме статичных значений SL и TP, установленных сразу после открытия позиции, часто используется трейлинг-стоп, или плавающий SL. Чаще всего его выставляют после того, как позиция становится прибыльной. Посмотрите самый очевидный вариант реализации: мы задаём в пунктах отстояние SL от текущей цены.

//****** project (module Trail): test_module_Trail_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trail"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double TR=50;  // Trail in points
//--- calculation of Trail
   Buffer1[0]=TR;
   return(rates_total);
  };

Здесь, как и в предыдущих кодах для SL и TP, отстояние для расчёта трейлинг-стопа задано константой для упрощения программы и чтения.

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   ...

//--- MODULE 4
   if(on_Trail)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m4[];
            ArraySetAsSeries(buffer_m4,true);
            if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return;
            double TR=buffer_m4[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
           }

   ...

  }

Есть и еще один метод сопровождения позиции — выставление SL в точке "безубытка".  При срабатывании SL позиция закроется с нулевым результатом или с заранее заданной прибылью. Модуль может выглядеть примерно так:

//****** project (module Breakeven): test_module_Breakeven_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Breakeven"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double Breakeven=100; // Breakeven in points
//--- calculation of the Breakeven
   Buffer1[0]=Breakeven;
   return(rates_total);
  };

В этом модуле задаётся отступ текущей цены от цены открытия позиции в пунктах для выставления SL в точку "безубытка". Вызов модуля безубыточности также нужно разместить в функции OnTick.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   ...

//--- MODULE 7
   if(on_Breakeven)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m7[];
            ArraySetAsSeries(buffer_m7,true);
            if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return;
            double TRB=buffer_m7[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
           }

   ...
   
  }

Пример 3: модуль генерации торговых сигналов

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

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

//****** project (module signals): test_module_signals_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trading signals"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   handle_Bands=iBands(_Symbol,_Period,20,0,3.5,PRICE_CLOSE);
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double signal=0.0;
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- calculation of the Trading signals
   if(high[0]>UpperBuffer[0]) signal=-2.0;
   if(low[0]<LowerBuffer[0]) signal=2.0;
   Buffer1[0]=signal;
   return(rates_total);
  };

В индикаторный буфер сигнального модуля записываются значения:

  • 2.0 — если сформировался сигнал BUY;
  • 0.0 — если нет торговых сигналов;
  • -2.0 — если сформировался сигнал SELL.

Использовать полученные значения модуля торговых сигналов лучше всего в специальной функции главного модуля — например, вот так:

//+------------------------------------------------------------------+
//| Trading signals generator                                        |
//+------------------------------------------------------------------+
sSignal Buy_or_Sell()
  {
   sSignal res={false,false};
//--- MODULE 5
   if(on_signals)
     { // If there is an additional module
      double buffer_m5[];
      ArraySetAsSeries(buffer_m5,true);
      if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res);
      if(buffer_m5[0]<-1) res.Sell=true;
      if(buffer_m5[0]>1) res.Buy=true;
     }
//--- MODULE 6
   if(on_Filter)
     { // If there is an additional module
      double buffer_m6[];
      ArraySetAsSeries(buffer_m6,true);
      if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res);
      lot=buffer_m6[0];
      if(buffer_m6[0]<1) res.Buy=false;
      if(buffer_m6[0]>-1) res.Sell=false;
     }
//---
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- Timeseries
   double L[];
   double H[];
   ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L);
   ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H);
   if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true;
   if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true;
//---
   return(res);
  }

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

Пример 4: модуль фильтра сигналов

Чтобы повысить прибыльность торговых роботов, часто используют фильтры торговых сигналов. Это могут быть, например, учёт тренда, времени торговли, новостей, дополнительных сигнальных индикаторов и т.д.

//****** project (module Filter): test_module_Filter_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Filter"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   handle_Bands=iBands(_Symbol,_Period,35,0,4.1,PRICE_CLOSE);
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double filtr=0.0;
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- Indicator buffers
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true);  CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
//--- calculation of the filter
   if(high[0]<MiddleBuffer[0]) filtr=2.0;
   if(low[0]>MiddleBuffer[0]) filtr=-2.0;
   Buffer1[0]=filtr;
   return(rates_total);
  };

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

Оптимизация многомодульных экспертов

Оптимизация многомодульных советников — пожалуй, один из краеугольных вопросов. Действительно, как оптимизировать входные параметры внешних модулей в тестере стратегий? Если в главном модуле они не прописаны, то получается, что никак (или почти никак). Можно попробовать дискретно задавать входные данные внешних модулей и затем тестировать советник. Но эта кропотливая и, скорее всего, бессмысленная работа нам не подходит. Тогда как же быть?

Вот один из возможных вариантов: использовать в качестве внешних модулей самооптимизирующиеся индикаторы. На тему автооптимизации написано много статей и примеров. Внесу и я свой вклад в развитие этой темы. Воспользуемся идеями из статьи "Визуальное тестирование прибыльности индикаторов и сигналов". Ее автор предлагает использовать в качестве цены совершения виртуальной сделки максимальное значение свечи для открытия позиции BUY, и минимальное — для SELL. Следовательно, выбираются худшие торговые условия, и при таком подходе к ценам происходит оптимизация входных параметров. Предполагается, что при полученных оптимальных значениях в реальной торговле результат будет не хуже (на том же участке исторических данных). В реальной же торговле гарантировать прибыль нельзя после любой оптимизации.

Стратегия нашего советника основана на торговле внутри канала индикатора Bollinger с разворотом позиции на его границах. Давайте заменим этот индикатор, а канал построим на индикаторе Envelope: от индикатора скользящей средней сформируем границы, равноудаленные от МА на фиксированное расстояние. Новый сигнальный индикатор перед использованием будет автоматически самооптимизироваться. В качестве входных параметров будут использоваться оптимальные значения, при которых была показана максимальная прибыль. Для оптимизации выбраны два параметра — период МА и отстояние границ от скользящей средней.

Алгоритм создания сигнального индикатора с функцией автооптимизации:

  1. Определяем параметры и критерий оптимизации. В нашем случае параметрами будут период МА и дистанция смещения границ, а критерием — максимальная прибыль.
  2. Создаём блок оптимизации в индикаторе. В предлагаемом примере происходит полный перебор входных данных в заданном диапазоне с фиксированным шагом. Период скользящей средней от 10 до 100 с шагом 10, а для смещения значения перебираются в диапазоне от 1000 до 10000 с шагом 1000.
//+------------------------------------------------------------------+
//|                           Copyright 2018, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/ru/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/ru/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trading signals"
//---
#include <MovingAverages.mqh>
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//+------------------------------------------------------------------+
//| Структура результатов оптимизации                                |
//+------------------------------------------------------------------+
struct Opt
  {
   int               var1;          // оптимальное значение 1 параметра
   int               var2;          // оптимальное значение 2 параметра
   double            profit;        // прибыль
  };
//---
double      Buffer1[];
bool        optimum=false;
Opt         test={NULL,NULL,NULL};
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   optimum=false;
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   double signal=0.0;
   Buffer1[0]=signal;
//--- optimization of input parameters
   if(!optimum)
     {
      ArraySetAsSeries(close,false);
      int count=rates_total;
      int total=0;
      int total_profit=0;
      for(int d=1000;d<10001;d+=1000)
         for(int j=10;j<101;j+=10)
           {
            double shift=d*_Point;
            bool open_buy=false;
            bool open_sell=false;
            double price_buy=0;
            double price_sell=0;
            double profit=0;
            int order=0;
            for(int i=j+1;i<count;i++)
              {
               double ma=SimpleMA(i,j,close);
               double sell=ma+shift;
               double buy=ma-shift;
               //--- BUY
               if(buy>close[i] && !open_buy)
                 {
                  price_buy=high[i]+spread[i]*_Point;
                  if(order==0) profit=0;
                  else profit+=price_sell-price_buy;
                  order++;
                  open_buy=true;
                  open_sell=false;
                 }
               //--- SELL
               if(sell<close[i] && !open_sell)
                 {
                  price_sell=low[i]-spread[i]*_Point;
                  if(order==0) profit=0;
                  else profit+=price_sell-price_buy;
                  order++;
                  open_sell=true;
                  open_buy=false;
                 }
               //---
              }
            if(profit>0)
               if(profit>test.profit)
                 {
                  test.var1=j;
                  test.var2=d;
                  test.profit=profit;
                  total_profit++;
                 }
            //---
            Comment("Оптимизация входных параметров..."," проходов=",total," // прибыльных =",total_profit);
            total++;
           }
      //---
      Print(" Оптимизация завершена: ",test.var1," ",test.var2);
      Comment("Оптимизация завершена");
      optimum=true;
     }
//---
   if(optimum)
      if(test.profit>0)
        {
         ArraySetAsSeries(close,true);
         double ma=SimpleMA(0,test.var1,close);
         double sell=ma+test.var2*_Period;
         double buy=ma-test.var2*_Period;
         //--- calculation of the Trading signals
         if(buy>close[0]) signal=2.0;
         if(sell<close[0]) signal=-2.0;
        }
//--- Indicator buffers
   Buffer1[0]=signal;
   return(rates_total);
  };

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

Заключение

  1. Написать многомодульный советник на MQL5 возможно, а в некоторых случаях — целесообразно и даже коммерчески выгодно.
  2. В статье показан примтивный концепт торгового робота с внешними модулями. Тем не менее, технология модульного программирования позволяет создавать достаточно сложные проекты, под которые можно привлекать сторонних разработчиков. При создании модулей они вольны не раскрывать их код и таким образом сохранять свои авторские права на алгоритмы.
  3. Остаётся открытым вопрос оптимизации модульных проектов. Самооптимизация сигнальных индикаторов, используемых в качестве модулей сигналов или фильтров, — тема, которая требует развития.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (12)
Aleksandr Masterskikh
Aleksandr Masterskikh | 27 мар 2018 в 10:31
Aleksey Vyazmikin:

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

Важно различать:

- модульность программы (когда одна простая модель разбивается на модули)

- и модульность сложной модели (где каждый модуль - это независимая модель).


Любой традиционный индикатор - это простая модель, а нужна сложная модель,

так как простой модели недостаточно для получения достаточной точности

отображения процесса.

Aleksey Vyazmikin
Aleksey Vyazmikin | 27 мар 2018 в 11:05
Aleksandr Masterskikh:

Важно различать:

- модульность программы (когда одна простая модель разбивается на модули)

- и модульность сложной модели (где каждый модуль - это независимая модель).


Любой традиционный индикатор - это простая модель, а нужна сложная модель,

так как простой модели недостаточно для получения достаточной точности

отображения процесса.

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

Приведите пример сложной модели заложенной в индикаторе.

Sergey Genikhov
Sergey Genikhov | 3 май 2018 в 22:44

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

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

Как пример, вместо кода:

 if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }

код:

 if(on_lot)
     { 
        LotCulcFuntion(lot);//А функция реализованна в отдельном файле.
     }

P.S. Имена модульных индикаторов в главном файле, не совпадают с названиями файлов. В главном модуле нужно довавить "_ind". Например, должно быть: "Module_training_module_Filter_ind" ,  а не "Module_training_module_Filter"

Алексей Тарабанов
Алексей Тарабанов | 4 май 2018 в 00:38
Aleksandr Masterskikh:

Многомодульность торговой системы определяется прежде всего природой процесса (для финансовых рынков движение цен - это нестационарный процесс).

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

Ну и какого рожна я должен чего-нибудь моделировать, чтобы что-нибудь купить/продать? И при чем тут технологические моменты? Зачем о стационарности гутарим? 

Мальчик, код выложи, или ТЗ, или еще что, а после я обращу внимание на твои суждения. 

Vasily Belozerov
Vasily Belozerov | 31 май 2018 в 16:47
Автор статьи допустил принципиальную ошибку. Он поставил равенство между набором модулей и Торговой Системой. Объясняю. Признаки любой системы: 1. Иерархия, наличие главных и подчиненных (это есть), 2. Декомпозиция, например, разделение на модули (это тоже есть), 3. Взаимосвязь (в том числе и обратная), а этого нет. А должно быть так: взаимосвязь у любого модуля с любым другим модулем. Например, в Солнечной СИСТЕМЕ, Солнце является главным, система разделена на планеты (модули) и они все взаимосвязаны и влияют друг на друга. Так что автору есть еще над чем работать.
Создание пользовательской новостной ленты в MetaTrader 5 Создание пользовательской новостной ленты в MetaTrader 5

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

Синхронизация нескольких графиков по одному инструменту на разных таймфреймах Синхронизация нескольких графиков по одному инструменту на разных таймфреймах

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

Как создать графическую панель любой сложности и как это работает Как создать графическую панель любой сложности и как это работает

В статье подробно рассматривается, как создать панель на базе класса CAppDialog и как добавить в нее элементы управления. Описывается структура панели и схема наследования объектов в ней. Продемонстрировано, что нужно для обработки событий и как события раздаются подчинённым элементам управления. Приведены примеры изменения параметров панели: размера, цвета фона.

Работаем с результатами оптимизации через графический интерфейс Работаем с результатами оптимизации через графический интерфейс

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