Универсальный торговый эксперт: Торговля в группе и управление портфелем стратегий (Часть 4)

Vasiliy Sokolov | 3 февраля, 2016

Оглавление

 

Введение

Довольно часто приходится создавать алгоритмы, которые должны "уживаться" друг с другом, т.е. на работу каждого отдельного алгоритма не должна влиять работа других алгоритмов, происходящая в это же время. Эта ситуация возникает, когда необходимо объединить несколько алгоритмов в один исполняющий модуль ex5. Несмотря на свою кажущуюся простоту, эти задачи скрывают несколько существенных "подводных камней" — алгоритмических особенностей, которые необходимо учитывать при построении движка торговых стратегий.

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

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

 

Менеджер стратегий CStrategyList

Вторая статья из серии "Универсальный торговый эксперт" была посвящена базовому классу CStrategy и основным модулям, из которых он состоит. Благодаря этому классу и его функционалу, заключенному в этих модулях, каждая наследуемая от него торговая стратегия обладает унифицированной торговой логикой. Однако организация торгового процесса с помощью роботов — это не только исполнение торговых приказов. Крайне важно обеспечить их совместную работу, в том числе работу нескольких алгоритмов в одном исполняющем модуле ex5.

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

Приведем заголовок класса CStrategyList:

//+------------------------------------------------------------------+
//| Класс-контейнер для управления стратегиями типа CStrategy        |
//+------------------------------------------------------------------+
class CStrategyList
  {
private:
   CLog*       Log;                 // Логирование
   CArrayObj   m_strategies;        // Стратегии типа CStrategy
   CLimits*    m_limits;
   void        ParseStrategies(CXmlElement* xmlStrategies, bool load_curr_symbol);
   void        ParseLimits(CXmlElement* xmlLimits);
   CStrBtn     StrButton;   
public:
   CStrategyList(void);
   ~CStrategyList(void);
   void LoadStrategiesFromXML(string xml_name, bool load_curr_symbol);
   bool AddStrategy(CStrategy* strategy);
   int  Total();
   CStrategy* At(int index);
   void OnTick();
   void OnTimer();
   void OnBookEvent(string symbol);
   void OnDeinit(const int reason);
   void OnChartEvent(const int id,
                     const long &lparam,
                     const double &dparam,
                     const string &sparam);
                     

  };

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

//+------------------------------------------------------------------+
//| Посылает всем стратегиям списка событие OnBookEvent              |
//+------------------------------------------------------------------+
void CStrategyList::OnBookEvent(string symbol)
  {
   for(int i=0; i<m_strategies.Total(); i++)
     {
      CStrategy *strategy=m_strategies.At(i);
      strategy.OnBookEvent(symbol);
     }
  }

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

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

 

Загрузка стратегий из XML-списка. Портфель стратегий

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

Рис. 1. Список параметров эксперта, торгующего по трем стратегиям

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

Прежде чем создать стратегию "на лету", необходимо дать ее полное и строгое описание. В это описание должно входить следующее:

Помимо перечисленных пунктов, в описание стратегий также могут входить и другие свойства. Лучше всего подобное описание сделать с помощью языка XML. Этот язык создавался как специальное средство описания чего-либо. Он очень удачно позволяет описывать сложные объекты, благодаря чему становится возможным конвертировать объект, например торговую стратегию, в текстовой документ XML, а текстовой документ — в стратегию. Например, руководствуясь XML-документом, торговый движок может создать стратегию и сконфигурировать ее параметры должным образом. Для работы с этим типом документов прямо из MQL5 необходимо воспользоваться специальной библиотекой XML-Parser, размещенной в CodeBase.

В качестве примера приведем XML-описание портфеля, загружающего три стратегии MovingAverage с различными параметрами:

<Global>
        <Strategies>
                <Strategy Name="MovingAverage" Magic="100" Timeframe="PERIOD_M1" Symbol="Si">
                        <TradeStateStart>Stop</TradeStateStart>
                        <Params>
                                <FastMA>1</FastMA>
                                <SlowMA>3</SlowMA>
                                <Shift>0</Shift>
                                <Method>MODE_SMA</Method>
                                <AppliedPrice>PRICE_CLOSE</AppliedPrice>
                        </Params>
                </Strategy>
                <Strategy Name="MovingAverage" Magic="101" Timeframe="PERIOD_M5" Symbol="SBRF">
                        <TradeStateStart>BuyOnly</TradeStateStart>
                        <Params>
                                <FastMA>15</FastMA>
                                <SlowMA>21</SlowMA>
                                <Shift>0</Shift>
                                <Method>MODE_SMA</Method>
                                <AppliedPrice>PRICE_CLOSE</AppliedPrice>
                        </Params>
                </Strategy>
                <Strategy Name="MovingAverage" Magic="102" Timeframe="PERIOD_M15" Symbol="GAZR">
                        <TradeStateStart>BuyAndSell</TradeStateStart>
                        <Params>
                                <FastMA>12</FastMA>
                                <SlowMA>45</SlowMA>
                                <Shift>1</Shift>
                                <Method>MODE_EMA</Method>
                                <AppliedPrice>PRICE_CLOSE</AppliedPrice>
                        </Params>
                </Strategy>
        </Strategies>
</Global>

Каждая из этих стратегий образует блок <Strategy>. В нем в качестве атрибутов указывается название инструмента (Symbol), таймфрейм (Timeframe), магический номер (Magic) и имя стратегии (StrategyName). Из приведенного листинга видно, что у каждой из трех стратегий свой уникальный инструмент, магический номер и рабочий таймфрейм. Помимо этих обязательных параметров, в XML-списке указываются и другие параметры стратегии. В секции <TradeStateStart> указывается торговый режим в момент запуска стратегии. Секция <Params> содержит параметры самой стратегии.

В момент запуска торговый движок попытается загрузить торговые стратегии из описанного выше XML-файла. Непосредственная загрузка и создание стратегий на основании этого документа происходит в классе CStrategyList в его методе LoadStrategiesFromXML. Приведем содержимое этого и связанного с ним методов:

//+------------------------------------------------------------------+
//| Загружает стратегии из переданного XML-файла "xml_name"          |
//| Если флаг load_curr_symbol установлен в истину, будут загружены  |
//| только те стратегии, символ которых соответствует текущему       |
//| символу CurrentSymbol()                                          |
//+------------------------------------------------------------------+
void CStrategyList::LoadStrategiesFromXML(string xml_name,bool load_curr_symbol)
  {
   CXmlDocument doc;
   string err;
   bool res=doc.CreateFromFile(xml_name,err);
   if(!res)
      printf(err);
   CXmlElement *global=GetPointer(doc.FDocumentElement);
   for(int i=0; i<global.GetChildCount(); i++)
     {
      CXmlElement* child = global.GetChild(i);
      if(child.GetName() == "Strategies")
         ParseStrategies(child,load_curr_symbol);
     }
  }
//+------------------------------------------------------------------+
//| Парсит XML стратегии                                             |
//+------------------------------------------------------------------+
void CStrategyList::ParseStrategies(CXmlElement *xmlStrategies,bool load_curr_symbol)
  {
   CParamsBase *params=NULL;
   for(int i=0; i<xmlStrategies.GetChildCount(); i++)
     {
      CXmlElement *xStrategy=xmlStrategies.GetChild(i);
      if(CheckPointer(params)!=POINTER_INVALID)
         delete params;
      params=new CParamsBase(xStrategy);
      if(!params.IsValid() || (params.Symbol()!=Symbol() && load_curr_symbol))
         continue;
      CStrategy *str=CStrategy::GetStrategy(params.Name());
      if(str==NULL)
         continue;
      str.ExpertMagic(params.Magic());
      str.ExpertSymbol(params.Symbol());
      str.Timeframe(params.Timeframe());
      str.ExpertName(params.Name());
      string name=str.ExpertName();
      CXmlElement *xml_params=xStrategy.GetChild("Params");
      if(xml_params!=NULL)
         str.ParseXmlParams(xml_params);
      CXmlElement *xml_mm=xStrategy.GetChild("MoneyManagment");
      if(xml_mm!=NULL)
        {
         if(!str.MM.ParseByXml(xml_mm))
           {
            string text="Strategy "+str.ExpertName()+" (Magic: "+(string)str.ExpertMagic()+") load MM from XML failed";
            CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
            Log.AddMessage(msg);
           }
        }
      CXmlElement *xml_regim=xStrategy.GetChild("TradeStateStart");
      if(xml_regim!=NULL)
        {
         string regim=xml_regim.GetText();
         if(regim=="BuyAndSell")
            str.TradeState(TRADE_BUY_AND_SELL);
         else if(regim=="BuyOnly")
            str.TradeState(TRADE_BUY_ONLY);
         else if(regim=="SellOnly")
            str.TradeState(TRADE_SELL_ONLY);
         else if(regim=="Stop")
            str.TradeState(TRADE_STOP);
         else if(regim=="Wait")
            str.TradeState(TRADE_WAIT);
         else if(regim=="NoNewEntry")
            str.TradeState(TRADE_NO_NEW_ENTRY);
         else
           {
            string text="For strategy "+str.ExpertName()+" (Magic: "+(string)str.ExpertMagic()+
                        ") set not correctly trade state: "+regim;
            CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
            Log.AddMessage(msg);
           }
        }
      AddStrategy(str);
     }
   if(CheckPointer(params)!=POINTER_INVALID)
      delete params;
  }

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

//+------------------------------------------------------------------+
//|                                              StrategyFactory.mqh |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
/*
   GetStrategy - фабрика стратегий. Создает объект стратегии, соответствующий определенному имени.
   Метод вынесен в отдельный файл для возможности автоматизации.
*/
#include <Strategy\Strategy.mqh>
#include <Strategy\Samples\MovingAverage.mqh>
#include <Strategy\Samples\ChannelSample.mqh>

CStrategy *CStrategy::GetStrategy(string name)
  {
   if(name=="MovingAverage")
      return new CMovingAverage();
   if(name=="BollingerBands")
      return new CChannel();
   CLog *mlog=CLog::GetLog();
   string text="Strategy with name "+name+" not defined in GetStrategy method. Please define strategy in 'StrategyFactory.mqh'";
   CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text);
   mlog.AddMessage(msg);
   return NULL;
  }

После того как стратегия создана, ее необходимо проинициализировать нужными параметрами, располагающимися в секции <Params>. Так как параметры у каждой стратегии уникальны, не представляется возможным выполнить инициализацию этих параметров на уровне торгового движка. Вместо этого базовый класс стратегии может вызвать виртуальный метод ParseXmlParams. Если стратегия в свою очередь переопределит этот метод и правильно распарсит переданный в него список параметров в виде XML-узла, она сможет самостоятельно установить свои параметры в нужное значение. В качестве примера обратимся к методу ParseXmlParams стратегии CMovingAverage, торгующей по двум скользящим средним, алгоритм работы которой приведен в самой первой главе данной статьи.

//+------------------------------------------------------------------+
//| Пример классической стратегии на двух скользящих средних.        |
//| Если быстрая скользящая средняя пересекает медленную снизу вверх |
//| то покупаем, если сверху вниз - то продаем.                      |
//+------------------------------------------------------------------+
class CMovingAverage : public CStrategy
  {
   ...
public:
   virtual bool      ParseXmlParams(CXmlElement *params);
  };
//+------------------------------------------------------------------+
//| Специфические для стратегии параметры парсятся в ней самой в     |
//| данном методе, переопределенном от CStrategy                     |
//+------------------------------------------------------------------+
bool CMovingAverage::ParseXmlParams(CXmlElement *params)
  {
   bool res=true;
   for(int i=0; i<params.GetChildCount(); i++)
     {
      CXmlElement *param=params.GetChild(i);
      string name=param.GetName();
      if(name=="FastMA")
        {
         int fastMA=(int)param.GetText();
         if(fastMA == 0)
           {
            string text="Parameter 'FastMA' must be a number";
            CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text);
            Log.AddMessage(msg);
            res=false;
           }
         else
            FastMA.MaPeriod(fastMA);
        }
      else if(name=="SlowMA")
        {
         int slowMA=(int)param.GetText();
         if(slowMA == 0)
           {
            string text="Parameter 'SlowMA' must be a number";
            CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text);
            Log.AddMessage(msg);
            res=false;
           }
         else
            SlowMA.MaPeriod(slowMA);
        }
      else if(name=="Shift")
        {
         FastMA.MaShift((int)param.GetText());
         SlowMA.MaShift((int)param.GetText());
        }
      else if(name=="Method")
        {
         string smethod=param.GetText();
         ENUM_MA_METHOD method=MODE_SMA;
         if(smethod== "MODE_SMA")
            method = MODE_SMA;
         else if(smethod=="MODE_EMA")
            method=MODE_EMA;
         else if(smethod=="MODE_SMMA")
            method=MODE_SMMA;
         else if(smethod=="MODE_LWMA")
            method=MODE_LWMA;
         else
           {
            string text="Parameter 'Method' must be type of ENUM_MA_METHOD";
            CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text);
            Log.AddMessage(msg);
            res=false;
           }
         FastMA.MaMethod(method);
         SlowMA.MaMethod(method);
        }
      else if(name=="AppliedPrice")
        {
         string price=param.GetText();
         ENUM_APPLIED_PRICE a_price=PRICE_CLOSE;
         if(price=="PRICE_CLOSE")
            a_price=PRICE_CLOSE;
         else if(price=="PRICE_OPEN")
            a_price=PRICE_OPEN;
         else if(price=="PRICE_HIGH")
            a_price=PRICE_HIGH;
         else if(price=="PRICE_LOW")
            a_price=PRICE_LOW;
         else if(price=="PRICE_MEDIAN")
            a_price=PRICE_MEDIAN;
         else if(price=="PRICE_TYPICAL")
            a_price=PRICE_TYPICAL;
         else if(price=="PRICE_WEIGHTED")
            a_price=PRICE_WEIGHTED;
         else
           {
            string text="Parameter 'AppliedPrice' must be type of ENUM_APPLIED_PRICE";
            CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text);
            Log.AddMessage(msg);
            res=false;
           }
         FastMA.AppliedPrice(a_price);
         SlowMA.AppliedPrice(a_price);
        }
     }
   return res;
  }

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

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

 

Управление стратегиями с помощью пользовательской панели

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

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

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

Панель по управлению экспертами реализуется в отдельном классе CPanel, который, в свою очередь, включает различные элементы управления, такие как список, кнопки и текстовые метки. Все классы для создания графического интерфейса находятся в каталоге <каталог_данных>\MQL5\Include\Panel. Для того чтобы панель работала, непосредственно в файле эксперта mq5 необходимо обрабатывать событие OnChartEvent. Сам обработчик событий на графике размещен в классе CStrategyList, поэтому в функции OnChartEvent достаточно вызывать именно его:

CStrategyList Manager;
...

void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   Manager.OnChartEvent(id,lparam,dparam,sparam);
  }

В свою очередь, обработчик этих событий в CStrategyList пересылает их непосредственно панели.

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

Рис. 2. Список режимов выбранной стратегии

Рис. 2. Список режимов выбранной стратегии

Так же действует покупка или продажа от имени выбранной стратегии. Указатель на стратегию вызывает методы Buy и Sell базового класса CStrategy, которые, в свою очередь, покупают и продают переданный в них объем. При этом магический номер при совершении этих торговых действий соответствует магическому номеру самой стратегии, и отличить эту ручную торговлю от действий эксперта становится невозможным.

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

 

Торговля экспертов в группе

Мы можем собрать портфель торговых стратегий. Для этого написанные стратегии необходимо снабдить методами, ответственными за парсинг XML-параметров, т.е. переопределить метод ParseXmlParams. Также необходимо добавить создание соответствующего типа стратегии в метод CStrategy::GetStrategy. Наконец, необходимо будет создать XML-файл с перечислением стратегий и их параметров. После этого класс CStrategyList самостоятельно создаст экземпляры стратегий и добавит их в свой список стратегий. Пользовательская панель при этом также начнет отображать эти стратегии.

В качестве примера создадим портфель стратегий, состоящий из описанных выше экспертов. Примеры парсинга XML-настроек для стратегии CMovingAverage и CChannel представлены в разделах 3.5 и 4.3.

Содержимое CStrategy::GetStrategy для создания двух перечисленных стратегий будет следующим:

//+------------------------------------------------------------------+
//|                                              StrategyFactory.mqh |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
/*
   GetStrategy - фабрика стратегий. Создает объект стратегии, соответствующий определенному имени.
   Метод вынесен в отдельный файл для возможности автоматизации.
*/
#include <Strategy\Strategy.mqh>
#include <Strategy\Samples\MovingAverage.mqh>
#include <Strategy\Samples\ChannelSample.mqh>

CStrategy *CStrategy::GetStrategy(string name)
  {
   if(name=="MovingAverage")
      return new CMovingAverage();
   if(name=="BollingerBands")
      return new CChannel();
   CLog *mlog=CLog::GetLog();
   string text="Strategy with name "+name+" not defined in GetStrategy method. Please define strategy in 'StrategyFactory.mqh'";
   CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text);
   mlog.AddMessage(msg);
   return NULL;
  }

Последним штрихом будет переопределение метода, ответственного за полное имя эксперта. Выполним это переопределение для стратегии CMovingAverage:

//+------------------------------------------------------------------+
//| Полное, уникальное имя эксперта                                  |
//+------------------------------------------------------------------+
string CMovingAverage::ExpertNameFull(void)
  {
   string name=ExpertName();
   name += "[" + ExpertSymbol();
   name += "-" + StringSubstr(EnumToString(Timeframe()), 7);
   name += "-" + (string)FastMA.MaPeriod();
   name += "-" + (string)SlowMA.MaPeriod();
   name += "-" + StringSubstr(EnumToString(SlowMA.MaMethod()), 5);
   name += "]";
   return name;
  }

Теперь все готово для создания портфеля стратегий. Наш портфель будет включать в себя четыре торговые системы. Каждая из них будет торговать на своем собственном символе. Две стратегии будут основаны на MovingAverage, а две другие — на BollingerBands. Более подробное описание этих стратегий дано в предыдущей статье: "Универсальный торговый движок: Пользовательские стратегии и вспомогательные торговые классы (часть 3)".

Наш XML-портфель будет следующим:

<Global>
        <Strategies>
                <Strategy Name="MovingAverage" Magic="100" Timeframe="PERIOD_M1" Symbol="Si">
                        <TradeStateStart>Stop</TradeStateStart>
                        <Params>
                                <FastMA>1</FastMA>
                                <SlowMA>3</SlowMA>
                                <Shift>0</Shift>
                                <Method>MODE_SMA</Method>
                                <AppliedPrice>PRICE_CLOSE</AppliedPrice>
                        </Params>
                </Strategy>
                <Strategy Name="MovingAverage" Magic="101" Timeframe="PERIOD_M5" Symbol="SBRF">
                        <TradeStateStart>BuyAndSell</TradeStateStart>
                        <Params>
                                <FastMA>15</FastMA>
                                <SlowMA>21</SlowMA>
                                <Shift>0</Shift>
                                <Method>MODE_SMA</Method>
                                <AppliedPrice>PRICE_CLOSE</AppliedPrice>
                        </Params>
                </Strategy>
                <Strategy Name="BollingerBands" Magic="102" Timeframe="PERIOD_M15" Symbol="GAZR">
                        <TradeStateStart>BuyAndSell</TradeStateStart>
                        <Params>
                                <Period>30</Period>
                                <StdDev>1.5</StdDev>
                        </Params>
                </Strategy>
                <Strategy Name="BollingerBands" Magic="103" Timeframe="PERIOD_M30" Symbol="ED">
                        <TradeStateStart>BuyAndSell</TradeStateStart>
                        <Params>
                                <Period>20</Period>
                                <StdDev>2.0</StdDev>
                        </Params>
                </Strategy>
        </Strategies>
</Global>

Данный файл необходимо сохранить в общем каталоге данных платформы MetaTrader под именем Strategies.xml.

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

//+------------------------------------------------------------------+
//|                                                       Expert.mq5 |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#property version   "1.00"
#include <Strategy\StrategiesList.mqh>

CStrategyList Manager;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Manager.LoadStrategiesFromXML(StrategiesXMLFile,LoadOnlyCurrentSymbol);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   Manager.OnTick();
  }
//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
   Manager.OnBookEvent(symbol);
  }

void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   Manager.OnChartEvent(id,lparam,dparam,sparam);
  }

Пользовательские переменные StrategiesXMLFile и LoadOnlyCurrentSymbol определены в классе CStrategyList и используются в нем самом для указания списка стратегий, который необходимо загрузить, и режима, позволяющего загружать только те стратегии, чей символ равен названию инструмента, на котором запущен эксперт. Также обратите внимание, что некоторые события, вроде OnBookEvent и OnTimer, не используются. Это значит, что они также не будут поступать в пользовательские стратегии.

Компиляция должна пройти нормально. После нее эксперт (в проекте он называется Agent.ex5) готов к работе. Попробуем запустить его на графике. Перед этим необходимо убедиться, что все используемые символы доступны в окне символов терминала MetaTrader. После запуска в правом верхнем углу графика должен появиться значок эксперта, а в левом верхнем углу — кнопка, раскрывающая пользовательскую панель. Если на этой панели выбрать список экспертов (он подписан словом Agent), раскроется список из четырех различных экспертов:

Рис. 3. Список загруженных экспертов

Рис. 3. Список загруженных экспертов

Как видно на приведенном скриншоте, это именно тот список экспертов, который сформировал наш XML-файл Strategies.xml. Через некоторое время стратегии начнут совершать торговые действия — каждая стратегия на своем символе.

 

Анализ работы экспертов в тестере стратегий

Сформировав портфель стратегий, мы можем прогнать его в тестере стратегий, для того чтобы убедиться, что он работает правильно. Для этого не придется предпринимать каких-то отдельных действий, так как XML-список стратегий находится в глобальном каталоге данных, доступном через тестер стратегий. Если запустить в нем модуль эксперта Agent.ex5, все необходимые символы для торговли будут подгружены автоматически. Каждый эксперт будет совершать торговые действия по своим торговым правилам, отрисовывая при этом свой набор индикаторов. На видео, представленном ниже, показан процесс тестирования портфеля стратегий на четырех разных инструментах:


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

 

Заключение

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