English Español Deutsch 日本語
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 17): Мультивалютная торговля

Возможности Мастера MQL5, которые вам нужно знать (Часть 17): Мультивалютная торговля

MetaTrader 5Примеры | 11 сентября 2024, 15:44
152 0
Stephen Njuki
Stephen Njuki

Предисловие

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

Я намеревался рассмотреть поиск нейронной архитектуры (neural architecture search, NAS), но понял, что еще остались некоторые основные принципы, которые я не затрагивал, но которые стоит рассмотреть, и главный из них — торговля несколькими ценными бумагами с помощью советника, собранного с помощью Мастера. Поэтому мы пока отвлечемся от рассмотрения новых торговых схем и рассмотрим некоторые основы. NAS будет рассмотрен в следующей статье.


Введение

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

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

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

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

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


Шаблон для Мастера MQL5 (первый сценарий):

Советники, собранные с помощью Мастера, имеют три основных якорных класса. Каждый из которых находится в отдельном файле. Это ExpertBase, ExpertTrade и Expert. Помимо них существуют еще три вспомогательных класса, а именно ExpertSignal, ExpertMoney и ExpertTrailling, которые соответственно наследуются при построении классов сигналов советника, управлении капиталом и трейлинге. Во всех этих классах именно класс ExpertBase определяет объект класса символа, который используется для доступа к рыночной информации о торгуемом символе по умолчанию (m_symbol). Как упоминалось выше, по умолчанию все собранные с помощью Мастера советники торгуют только одним символом, поэтому класс символа, инициализированный в ExpertBase, предназначен для обработки только одного символа.

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

Более осуществимый подход, который мы рассмотрим, — сделать экземпляр зонтичного класса Expert массивом, размер которого соответствует количеству торговых символов, которые мы будем тестировать. Экземпляр этого класса всегда объявляется в файле *.mq5 собранного Мастером советника. Нам необходимо лишь преобразовать его в массив.

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert[__FOLIO];
CExpertSignal *signals[__FOLIO];

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

//+------------------------------------------------------------------+
//| 'Commodity' Currency folio                                       |
//+------------------------------------------------------------------+
#define                          __FOLIO 9
string                           __F[__FOLIO] 

                                 = 
         
                                 {
                                 "AUDNZD","AUDUSD","AUDCAD","AUDJPY",
                                 "NZDUSD","NZDCAD","NZDJPY",
                                 "USDCAD",
                                 "CADJPY"
                                 };
input string                     __prefix="";
input string                     __suffix="";


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

//+------------------------------------------------------------------+
//| Initialization and checking for input parameters                 |
//+------------------------------------------------------------------+
bool CExpert::Init(string symbol,ENUM_TIMEFRAMES period,bool every_tick,ulong magic)
  {
//--- returns false if the EA is initialized on a timeframe different from the current one
   if(period!=::Period())
     {
      PrintFormat(__FUNCTION__+": wrong timeframe (must be: %s)",EnumToString(period));
      return(false);
     }
     
   if(m_on_timer_process && !EventSetTimer(PeriodSeconds(period)))
      {
      PrintFormat(__FUNCTION__+": cannot set timer at: ",EnumToString(period));
      return(false);
      }
//--- initialize common information
   if(m_symbol==NULL)
     {
      if((m_symbol=new CSymbolInfo)==NULL)
         return(false);
     }
   if(!m_symbol.Name(symbol))
      return(false);
   
....
....


//--- ok
   return(true);
  }

Вышеуказанные изменения можно внести в копию файла класса советника, которую затем необходимо переименовать, как указано выше, или можно создать новый класс, который наследует от класса советника в Expert.mqh, а затем добавить "переопределяющую" функцию Init в этот новый класс. На этот новый класс и файл будут ссылаться в основном файле советника. В коде это выглядит так:

#include "Expert.mqh"
//+------------------------------------------------------------------+
//| Class CExfolio.                                                  |
//| Purpose: Base class expert advisor.                              |
//| Derives from class CExpertBase.                                  |
//+------------------------------------------------------------------+
class CExfolio : public CExpert
  {
protected:

   
public:

                     CExfolio(void);
                    ~CExfolio(void);
                    
   //--- initialization
   virtual bool      Init(string symbol,ENUM_TIMEFRAMES period,bool every_tick,ulong magic=0) override;
   
   //...

  };

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

//+------------------------------------------------------------------+
//| Initialization and checking for input parameters                 |
//+------------------------------------------------------------------+
bool CExfolio::Init(string symbol,ENUM_TIMEFRAMES period,bool every_tick,ulong magic)
  {
//--- returns false if the EA is initialized on a symbol/timeframe different from the current one
      
      bool _init=true;
      
      if(!CExpert::Init(symbol,period,every_tick,magic))
      {
         _init=false;
         //
         if(symbol!=_Symbol)
         {
            if(Reinit(symbol,period,every_tick,magic)){ _init=true; }
         }
      }
      
      CExpert::OnTimerProcess(true);
      
      if(CExpert::m_on_timer_process && !EventSetTimer(PeriodSeconds(_Period)))
      {
         printf(__FUNCTION__+": cannot set timer at: ",EnumToString(period));
         _init=false;
      }
      
//--- ok
      return(_init);
  }

Изменения гарантируют, что инициализация не даст сбоя, если символ графика не совпадает с символом портфеля. Это, безусловно, был бы более "аккуратный" вариант, поскольку созданный нами файл класса значительно меньше, а значит, у нас меньше дублирования, чем в первом случае. Однако нам пришлось бы изменять встроенный файл советника, а это значит, что для каждого обновления терминала нам пришлось бы добавлять спецификатор виртуальной функции в функцию init.

Помимо этих изменений класса советника, в файле советника, который создается Мастером, необходимо будет изменить каждую из функций OnInit, OnTick, OnTimer, OnDeInit и Trade. Будет добавлен цикл for, который перебирает все валюты, предварительно объявленные в настраиваемом классе советника. Их объявление сопровождается входными параметрами строки префикса и суффикса, которые гарантируют, что их имена правильно сформированы и находятся в "Обзоре рынка". Вместо того, чтобы явно называть символы и управлять префиксными/суффиксными именами, можно воспользоваться более гибким способом, описанным здесь, но даже в этом случае нам понадобится заранее определенный список символов, которые нас интересуют, чтобы правильно отфильтровать зачастую очень длинный список символов, доступных в "Обзоре рынка".

Символы, выбранные в пользовательском классе советника, — это то, что раньше называлось "товарами" (commodities), особенно в эпоху холодной войны, поскольку их динамика чрезмерно зависела от цен на товары. Итак, в нашем списке в качестве основных товаров используются валюты AUD, NZD и CAD. Добавление USD и JPY обусловлено волатильностью. Сами по себе они не входят в группу "товаров".

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


Шаблон для Мастера MQL5 (второй сценарий):

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

Чтобы добиться этого, в первую очередь нам следует включить торговлю по времени, которая, как ни странно, по умолчанию не включена. Мы делаем это с помощью одной строки в функции OnInit:

//
ExtExpert[f].OnTimerProcess(true);

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

Мы используем наибольшее положительное значение, а не просто величину, поскольку мы ориентируемся на трендовые символы или символ с самым сильным трендом из портфеля. Затем мы определяем направление тренда, глядя на изменение буфера цены закрытия между самым новым по времени (latest) значением и последним (last) значением. Положительное изменение будет иметь бычий характер, отрицательное — медвежий. Ниже представлен листинг:

//+------------------------------------------------------------------+
//| Symbol Selector via Correlation                                  |
//+------------------------------------------------------------------+
bool  Select(double Direction, int &Index, ENUM_POSITION_TYPE &Type)
{  if(PositionsTotal() == 0)
   {  double _max = 0.0;
      int _index = -1;
      Type = INVALID_HANDLE;
      for(int f = 0; f < __FOLIO; f++)
      {  vector _v_0, _v_1;
         _v_0.CopyRates(__F[f], Period(), 8, 0, 30);
         _v_1.CopyRates(__F[f], Period(), 8, 30, 30);
         double _corr = _v_0.CorrCoef(_v_1);
         if(_max < _corr && ((Direction > 0.0 && _v_0[0] > _v_0[29]) || (Direction < 0.0 && _v_0[0] < _v_0[29])))
         {  _max = _corr;
            _index = f;
            if(_v_0[0] > _v_0[29])
            {  Type = POSITION_TYPE_BUY;
            }
            else if(_v_0[0] < _v_0[29])
            {  Type = POSITION_TYPE_SELL;
            }
         }
      }
      Index = _index;
      return(true);
   }
   else if(PositionsTotal() == 1)
   {  

//...
//...

   }
   return(false);
}


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

//+------------------------------------------------------------------+
//| Symbol Selector via Correlation                                  |
//+------------------------------------------------------------------+
bool  Select(double Direction, int &Index, ENUM_POSITION_TYPE &Type)
{  if(PositionsTotal() == 0)
   {  
//...
//...

   }
   else if(PositionsTotal() == 1)
   {  ulong _ticket = PositionGetTicket(0);
      if(PositionSelectByTicket(_ticket))
      {  double _float = PositionGetDouble(POSITION_PROFIT);
         ENUM_POSITION_TYPE _type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         int _index = ArrayBsearch(__F, PositionGetString(POSITION_SYMBOL));
         double _max = 0.0;
         Type = INVALID_HANDLE;
         for(int f = 0; f < __FOLIO; f++)
         {  if(f == _index)
            {  continue;
            }
            else
            {  vector _v_0, _v_1;
               _v_0.CopyRates(__F[_index], Period(), 8, 0, 30);
               _v_1.CopyRates(__F[f], Period(), 8, 0, 30);
               double _corr = fabs(_v_0.CorrCoef(_v_1));
               if(_float < 0.0 && _max < _corr)
               {  _max = _corr;
                  Index = f;
                  if(_v_1[0] > _v_1[29])
                  {  Type = POSITION_TYPE_BUY;
                  }
                  else  if(_v_1[0] < _v_1[29])
                  {  Type = POSITION_TYPE_SELL;
                  }
               }
            }
         }
      }
      return(true);
   }
   return(false);
}

Чтобы использовать функцию Select в среде советника, собранного с помощью Мастера, нам необходимо внести еще несколько изменений в класс советника. Вот почему создание копии экземпляра класса советника и ее переименование, каким бы громоздким это ни казалось, будет более практичным решением. Первое изменение, которое нам необходимо сделать, — это сделать функцию Refresh общедоступной. Это означает ее перемещение из раздела Protected интерфейса класса в раздел Public. Аналогично, функция Process также должна быть общедоступной. Это изменение будет выглядеть так, как указано ниже:

//+------------------------------------------------------------------+
//| Class CExpert.                                                   |
//| Purpose: Base class expert advisor.                              |
//| Derives from class CExpertBase.                                  |
//+------------------------------------------------------------------+
class CExpert : public CExpertBase
  {
protected:
   
   //...
   //...

public:
                     CExpert(void);
                    ~CExpert(void);
   
   //...

   //--- refreshing 
   virtual bool      Refresh(void);
   //--- processing (main method)
   virtual bool      Processing(void);
   
protected:
   
   //...
   //...

  };


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

//+------------------------------------------------------------------+
//| "Timer" event handler function                                   |
//+------------------------------------------------------------------+
void OnTimer()
{  for(int f = 0; f < __FOLIO; f++)
   {  ExtExpert[f].Refresh();
   }
   for(int f = 0; f < __FOLIO; f++)
   {  int _index = -1;
      ENUM_POSITION_TYPE _type = INVALID_HANDLE;
      ExtExpert[f].Magic(f);
      if(Select(signals[f].Direction(), _index, _type))
      {  ExtExpert[f].OnTimer();
         ExtExpert[f].Processing();
      }
   }
}


При выборе символа для торговли мы отслеживаем его, назначая его индекс в исходном массиве портфеля в качестве его магического числа. Это означает, что в каждом цикле for во всех функциях обработки по умолчанию OnTick, OnTrade и OnTimer нам необходимо будет обновить магическое число, используемое классом торговли, перед обработкой. Кроме того, поскольку для этого тестирования использовались пользовательские символы, по соображениям качества данных названия символов в массиве портфолио необходимо было включить с их суффиксом (или префиксом, если применимо). По какой-то причине тестер стратегий не может синхронизировать пользовательские символы, даже если все данные присутствуют на тестовом компьютере, если вы добавляете префикс и суффикс при инициализации.

Кроме того, мы хотим торговать по OnTimer, а не по OnTick по умолчанию. Теоретически это можно легко сделать, настроив параметры OnProcessTimer и OnProcessTick. Однако их изменение приводит либо к отсутствию сделок, либо к торговле по тикам. Это требует более существенных изменений в функциях OnTick и OnTimer класса советника - OnProcess была отключена в OnTick, а функция OnProcess, которая была сделана общедоступной выше, теперь вызывается независимо в функции OnTimer советника, как показано в коде OnTimer выше. Это необходимо, так как на момент написания статьи функция Process в OnTimer класса ExpertFolio не выполняется. Мы можем обновлять цены и значения индикаторов по всем символам, но функцию Process необходимо вызывать отдельно. И таймер нужно объявлять и удалять вручную, как в обычных советниках.


Тестирование и оптимизация

Проведем тестовые прогоны на "товарном" кросс-курсе AUDJPY H4 с 2023 по 2024 год. Наши тестовые советники не используют стоп-лосс, поскольку во втором сценарии, как описано выше, убыточные позиции регулируются межрыночными возможностями. Мы оптимизируем только период RSI, поскольку он является индикатором класса сигнала, а также пороговые значения открытия и закрытия, истечения срока действия лимитных ордеров и разрыва входа для лимитных ордеров. Управление капиталом осуществлялось с использованием фиксированных лотов минимального размера. Отчеты и кривые капитала обоих сценариев представлены ниже:

r1

c1


r2

c2

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

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


Заключение

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

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14806

Прикрепленные файлы |
ExpertFolio_1.mqh (121.5 KB)
ExpertFolio_2.mqh (121.49 KB)
opt_17_1.mq5 (7.04 KB)
opt_17_2.mq5 (10.18 KB)
Алгоритм стрельбы из лука — Archery Algorithm (AA) Алгоритм стрельбы из лука — Archery Algorithm (AA)
В данной статье подробно рассматривается алгоритм оптимизации, вдохновленный стрельбой из лука, с акцентом на использование метода рулетки в качестве механизма выбора перспективных областей для "стрел". Этот метод позволяет оценивать качество решений и отбирать наиболее многообещающие позиции для дальнейшего изучения.
Метод группового учета аргументов: реализация комбинаторного алгоритма на MQL5 Метод группового учета аргументов: реализация комбинаторного алгоритма на MQL5
В этой статье мы продолжаем изучение семейства алгоритмов группового учета аргументов. Реализуем средствами MQL5 комбинаторный алгоритм, а также его усовершенствованную версию — комбинаторный селективный алгоритм.
Простые решения для удобной работы с индикаторами Простые решения для удобной работы с индикаторами
В этой статье расскажу, как сделать простенькую панельку для изменения настроек индикатора прямо с графика, и какие изменения нужно внести в индикатор, чтобы подключить эту панель. Статья рассчитана исключительно на тех, кто только начал знакомиться с языком MQL5.
Нейросети в трейдинге: Transformer для облака точек (Pointformer) Нейросети в трейдинге: Transformer для облака точек (Pointformer)
В данной статье мы поговорим об алгоритмах использования методов внимания при решении задач обнаружения объектов в облаке точек. Обнаружение объектов в облаках точек имеет важное значение для многих реальных приложений.