English 中文 Español Deutsch 日本語 Português
Универсальный торговый эксперт: Работа с пользовательскими трейлинг-стопами (часть 6)

Универсальный торговый эксперт: Работа с пользовательскими трейлинг-стопами (часть 6)

MetaTrader 5Примеры | 19 мая 2016, 10:07
6 976 7
Vasiliy Sokolov
Vasiliy Sokolov

Оглавление


Введение

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

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

 

Возможные реализации функции трейлинга

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

class CStrategy
   {
public:
   double TrailingMode1(CPosition* pos);
   };

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

void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos)
  {
   double new_sl = TrailingMode1(pos);
   pos.StopLossValue(new_sl);
  }

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

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

void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos)
  {
   pos.TrailingMode1();
  }

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

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

 

Стандартизация функций трейлинга. Класс CTrailing

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

При разработке данного класса необходимо решить два вопроса.

  1. Внутреннее устройство класса трейлинг-стопа. Стандартизация его работы.
  2. Взаимодействие спроектированного класса с другими модулями торгового движка CStrategy.

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

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

//+------------------------------------------------------------------+
//|                                                     Trailing.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Object.mqh>
#include "..\PositionMT5.mqh"
#include "..\Logs.mqh"
class CPosition;
//+------------------------------------------------------------------+
//| Базовый класс трейлинг-стопа                                     |
//+------------------------------------------------------------------+
class CTrailing : public CObject
  {
protected:
   CPosition         *m_position;     // Позиция, стоп-лосс которой требуется модифицировать.
   CLog              *Log;
public:
                      CTrailing(void);
   void               SetPosition(CPosition *position);
   CPosition         *GetPosition(void);
   virtual bool       Modify(void);
   virtual CTrailing* Copy(void);
  };
//+------------------------------------------------------------------+
//| Конструктор. Получает модуль логирования                         |
//+------------------------------------------------------------------+
CTrailing::CTrailing(void)
  {
   Log=CLog::GetLog();
  }
//+------------------------------------------------------------------+
//| Метод модификациии трейлинг-стопа, который необходимо            |
//| переопределить в производном классе трейлинга                    |
//+------------------------------------------------------------------+
bool CTrailing::Modify(void)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает копию экземпляра                                      |
//+------------------------------------------------------------------+  
CTrailing* CTrailing::Copy(void)
{
   return new CTrailing();
}
//+------------------------------------------------------------------+
//| Устанавливает позицию, стоп-лосс которой требуется модифицировать|
//+------------------------------------------------------------------+
void CTrailing::SetPosition(CPosition *position)
  {
   m_position=position;
  }
//+------------------------------------------------------------------+
//| Возвращает позицию, стоп-лосс которой требуется модифицировать   |
//+------------------------------------------------------------------+
CPosition *CTrailing::GetPosition(void)
  {
   return m_position;
  }
//+------------------------------------------------------------------+

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

 

Взаимодействие класса CTrailing с другими модулями стратегии

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

class CPosition
  {
public:
   CTrailing* Trailing;    // Модуль трейлинг-стопа
  };

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

void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos)
  {
   pos.Trailing.Modify();
  }

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

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

class CStrategy
  {
public:
   CTrailing* Trailing;   // Модуль трейлинг-стопа для всех позиций
  };

Кроме того, потребуется дополнить метод CallSupport, принадлежащий классу CStrategy:

//+------------------------------------------------------------------+
//| Вызывает логику сопровождения позиций при условии, что торговое  |
//| состояние не равно TRADE_WAIT.                                   |
//+------------------------------------------------------------------+
void CStrategy::CallSupport(const MarketEvent &event)
  {
   m_trade_state=m_state.GetTradeState();
   if(m_trade_state == TRADE_WAIT)return;
   SpyEnvironment();
   for(int i=ActivePositions.Total()-1; i>=0; i--)
     {
      CPosition *pos=ActivePositions.At(i);
      if(pos.ExpertMagic()!=m_expert_magic)continue;
      if(pos.Symbol()!=ExpertSymbol())continue;
      if(CheckPointer(Trailing)!=POINTER_INVALID)
        {
         if(CheckPointer(Trailing)==POINTER_INVALID)
            pos.Trailing=Trailing.Copy();
         pos.Trailing.Modify();
         if(!pos.IsActive())
            continue;
        }
      if(pos.Direction()==POSITION_TYPE_BUY)
         SupportBuy(event,pos);
      else
         SupportSell(event,pos);
      if(m_trade_state==TRADE_STOP && pos.IsActive())
         ExitByStopRegim(pos);
     }
  }

Нововведения выделены желтым маркером. Они очень просты: если по умолчанию установлен какой-либо трейлинг-стоп, а текущая позиция его не имеет, то он также устанавливается на текущую позицию, после чего происходит модификация стоп-лосса позиции согласно логике трейлинга. Однако есть одна очень важная особенность, которую необходимо учитывать и понимать. Каждой новой позиции присваивается копия трейлинг-стопа, установленного по умолчанию, а не он сам. Это необходимо для избежания путаницы в самой логике трейлинг-стопа. Представьте, что один и тот же экземпляр трейлинг-стопа будет управлять сразу несколькими позициями. Если он внутри своей логики рассчитывает какие-то переменные для одной позиции и запоминает их, то при следующем вызове они будут уже недействительны, т.к. позиция, переданная для управления, будет уже иной. В этом случае будут возникать очень странные и трудноуловимые ошибки. Чтобы этого не происходило, каждой позиции назначается индивидуальный экземпляр трейлинг-стопа, который не меняется в течении всей "жизни" позиции. Чтобы назначить индивидуальный трейлинг-стоп, необходимо выполнить процедуру копирования трейлинг-стопа по умолчанию. Так как CStrategy не знает, какие данные и внутренние переменные необходимо копировать, то процедура копирования делегируется конечному классу трейлинга. Он должен переопределить виртуальный метод Copy() базового класса CTrailing и вернуть ссылку, указывающую на созданную копию самого себя, в виде обобщенного класса CTrailing. Забегая вперед, приведем пример реализации метода копирования для классического трейлинг-стопа CTrailingClassic:

//+------------------------------------------------------------------+
//| Возвращает копию экземпляра                                      |
//+------------------------------------------------------------------+  
CTrailing *CTrailingClassic::Copy(void)
  {
   CTrailingClassic *tral=new CTrailingClassic();
   tral.SetDiffExtremum(m_diff_extremum);
   tral.SetStepModify(m_step_modify);
   tral.SetPosition(m_position);
   return tral;
  }

Метод создает экземпляр типа CTrailingClassic, устанавливает его параметры равными параметрам текущего экземпляра, после чего возвращает объект в виде указателя на тип CTrailing.

Пользователю необходимо запомнить простое правило при написании своего класса трейлинга: 

Для установки трейлинг-стопа по умолчанию необходимо переопределить метод копирования Copy базового класса CTrailing. В противном случае, CStrategy не сможет в автоматическом режиме сопровождать открытые позиции. Если трейлинг-стоп планируется использовать только в методах BuySupport и SellSupport, то переопределять виртуальный метод Copy необязательно.

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

Теперь, после нововведений, CStrategy может самостоятельно сопровождать позиции, используя переданный трейлинг-стоп в качестве логики сопровождения. Если в конструкторе пользовательской стратегии связать указатель CStrategy::Trailing с каким-либо алгоритмом трейлинг-стопа, то он станет трейлинг-стопом  по умолчанию для всех позиций, которые будут принадлежать текущему эксперту.  Следовательно, для стратегий, которые будут сопровождать позиции только по трейлингу, вообще не будет необходимости в переопределении методов BuySupport и SellSupport. Сопровождение позиций будет осуществляться в полностью автоматическом режиме на уровне CStrategy.

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

В качестве функции трейлинг-стопа фактически может выступать любой алгоритм по управлению позицией. Изменение ее стоп-лосса необязательно. Алгоритм управления позицией может закрывать ее по определенным условиям. Такое действие ожидаемо, и CStrategy отработает его в штатном режиме.


Практическое использование трейлинг-стопа. Пример реализации классического трейлинг-стопа

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

Также наш трейлинг-стоп будет иметь второй параметр. Он будет необязательным. Чтобы изменение стоп-лосса было не слишком частым, мы введем дополнительное ограничение: уровень нового стоп-лосса должен отличаться от предыдущего уровня хотя бы на StepModify пунктов. Величина StepModify будет задаваться в виде отдельного параметра. Этот параметр становится важным для торговли на ФОРТС. Согласно регламенту торговли на этом рынке, биржа взимает дополнительный сбор за так называемые "неэффективные транзакции". Если перестановок стоп-лосса будет достаточно много, а фактически заключенных сделок мало — биржа выставит дополнительный счет трейдеру. Поэтому алгоритмы должны учитывать эту особенность.

Итак, приведем код нашего первого трейлинг-стопа. Он базируется на классе CTrailing и переопределяет метод Modify:  

//+------------------------------------------------------------------+
//|                                              TrailingClassic.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include "Trailing.mqh"
//+------------------------------------------------------------------+
//| интегрируем параметры трейлинг-стопа прямо в список параметров   |
//| эксперта                                                         |
//+------------------------------------------------------------------+
#ifdef SHOW_TRAILING_CLASSIC_PARAMS
input double PointsModify=0.00200;
input double StepModify=0.00005;
#endif
//+------------------------------------------------------------------+
//| Классический трейлинг-стоп                                       |
//+------------------------------------------------------------------+
class CTrailingClassic : public CTrailing
  {
private:
   double            m_diff_extremum;  // Расстояние в пунктах от достигнутого экстремума до стоп-лосса позиции
   double            m_step_modify;    // Минимальное количество пунктов для изменения стоп-лосса
   double            FindExtremum(CPosition *pos);
public:
                     CTrailingClassic(void);
   void              SetDiffExtremum(double points);
   double            GetDiffExtremum(void);
   void              SetStepModify(double points_step);
   double            GetStepModify(void);
   virtual bool      Modify(void);
   virtual CTrailing *Copy(void);
  };
//+------------------------------------------------------------------+
//| Конструктор. Инициализирует параметры по умолчанию               |
//+------------------------------------------------------------------+
CTrailingClassic::CTrailingClassic(void) : m_diff_extremum(0.0),
                                           m_step_modify(0.0)
  {
#ifdef SHOW_TRAILING_CLASSIC_PARAMS
   m_diff_extremum=PointsModify;
   m_step_modify=StepModify;
#endif
  }
//+------------------------------------------------------------------+
//| Возвращает копию экземпляра                                      |
//+------------------------------------------------------------------+  
CTrailing *CTrailingClassic::Copy(void)
  {
   CTrailingClassic *tral=new CTrailingClassic();
   tral.SetDiffExtremum(m_diff_extremum);
   tral.SetStepModify(m_step_modify);
   tral.SetPosition(m_position);
   return tral;
  }
//+------------------------------------------------------------------+
//| Устанавливает количество пунктов от достигнутого экстремума      |
//+------------------------------------------------------------------+
void CTrailingClassic::SetDiffExtremum(double points)
  {
   m_diff_extremum=points;
  }
//+------------------------------------------------------------------+
//| Устанавливает величину минимальной модификации в пунктах         |
//+------------------------------------------------------------------+
void CTrailingClassic::SetStepModify(double points_step)
  {
   m_step_modify=points_step;
  }
//+------------------------------------------------------------------+
//| Возвращает количество пунктов от достигнутого экстремума         |
//+------------------------------------------------------------------+
double CTrailingClassic::GetDiffExtremum(void)
  {
   return m_diff_extremum;
  }
//+------------------------------------------------------------------+
//| Возвращает величину минимальной модификации в пунктах            |
//+------------------------------------------------------------------+
double CTrailingClassic::GetStepModify(void)
  {
   return m_step_modify;
  }
//+------------------------------------------------------------------+
//| Модифицирует трейлинг-стоп согласно логике классического         |
//| трейлинга                                                        |
//+------------------------------------------------------------------+
bool CTrailingClassic::Modify(void)
  {

   if(CheckPointer(m_position)==POINTER_INVALID)
     {
      string text="Invalid position for current trailing-stop. Set position with 'SetPosition' method";
      CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
      Log.AddMessage(msg);
      return false;
     }
   if(m_diff_extremum<=0.0)
     {
      string text="Set points trailing-stop with 'SetDiffExtremum' method";
      CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
      Log.AddMessage(msg);
      return false;
     }
   double extremum=FindExtremum(m_position);
   if(extremum == 0.0)return false;
   double n_sl = 0.0;
   if(m_position.Direction()==POSITION_TYPE_BUY)
      n_sl=extremum-m_diff_extremum;
   else
      n_sl=extremum+m_diff_extremum;
   if(n_sl!=m_position.StopLossValue())
      return m_position.StopLossValue(n_sl);
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает достигнутый экстремум цены за время удеражания        |
//| позиции. Для длинной позиции будет возвращена максимально        |
//| достигнутая цена. Для короткой - минимальная цена, которая была  |
//| достигнута.                                                      |
//+------------------------------------------------------------------+
double CTrailingClassic::FindExtremum(CPosition *pos)
  {
   double prices[];
   if(pos.Direction()==POSITION_TYPE_BUY)
     {
      if(CopyHigh(pos.Symbol(),PERIOD_M1,pos.TimeOpen(),TimeCurrent(),prices)>1)
         return prices[ArrayMaximum(prices)];
     }
   else
     {
      if(CopyLow(pos.Symbol(),PERIOD_M1,pos.TimeOpen(),TimeCurrent(),prices)>1)
         return prices[ArrayMinimum(prices)];
     }
   return 0.0;
  }
//+------------------------------------------------------------------+

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

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

 

Подключаем трейлинг-стоп к стратегии CImpulse

В предыдущей части статьи "Универсальный торговый эксперт: работа с отложенными ордерами и поддержка хеджинга" мы впервые познакомились с стратегией CImpulse. Эта простая торговая тактика основана на входе в моменты резкого движения цены. Представленная в статье стратегия сопровождала свои позиции с помощью скользящей средней. Длинная позиция закрывалась, когда цена открытия бара становилась ниже скользящей средней. Короткая позиция закрывалась, когда цена становилась выше скользящей средней. Еще раз приведем код, описанный в предыдущей статье, который реализует эту логику:

//+------------------------------------------------------------------+
//| Сопровождение длинной позиции по скользящей средней Moving       |
//+------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      double target = Bid() - Bid()*(m_percent/100.0);
      if(target < Moving.OutValue(0))
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(Bid() < Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Сопровождение короткой позиции по скользящей средней Moving      |
//+------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      double target = Ask() + Ask()*(m_percent/100.0);
      if(target > Moving.OutValue(0))
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}

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

//+------------------------------------------------------------------+
//| Инициализация стратегии и конфигурирование трейлинг-стопа        |
//| при запуске                                                      |
//+------------------------------------------------------------------+
CImpulseTrailing::CImpulseTrailing(void)
{
   CTrailingClassic* classic = new CTrailingClassic();
   classic.SetDiffExtremum(0.00100);
   Trailing = classic;
}

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

Полный исходный код стратегии CImpulse с автоматическим трейлинг-стопом представлен в файле  ImpulseTrailingAuto.mqh.

 

Подключение параметров трейлинга к настройкам эксперта

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

//+------------------------------------------------------------------+
//|                                              TrailingClassic.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include "Trailing.mqh"
//+------------------------------------------------------------------+
//| интегрируем параметры трейлинг-стопа прямо в список параметров   |
//| эксперта                                                         |
//+------------------------------------------------------------------+
#ifdef SHOW_TRAILING_CLASSIC_PARAMS
input double PointsModify = 0.00100;
input double StepModify =   0.00005;
#endif
//+------------------------------------------------------------------+
//| Классический трейлинг-стоп                                       |
//+------------------------------------------------------------------+
class CTrailingClassic : public CTrailing
  {
   ...
public:
                     CTrailingClassic(void);
   ...
  };
//+------------------------------------------------------------------+
//| Конструктор. Инициализирует параметры по умолчанию               |
//+------------------------------------------------------------------+
CTrailingClassic::CTrailingClassic(void) : m_diff_extremum(0.0),
                                           m_step_modify(0.0)
  {
   #ifdef SHOW_TRAILING_CLASSIC_PARAMS
   m_diff_extremum = PointsModify;
   m_step_modify = StepModify;
   #endif
  }

Теперь, если макрос  SHOW_TRAILING_CLASSIC_PARAMS определен, в настройки эксперта в момент компиляции будут интегрированы параметры трейлинга:

 

Рис 1. Динамически подключаемые параметры PointsModify и StepModify.

При закомментированом макросе  SHOW_TRAILING_CLASSIC_PARAMS или его отсутствии настройки трейлинга исчезнут из параметров эксперта:

 

Рис 2. Выключенные параметры трейлинг-стопа

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

 

Трейлинг-стоп на основе скользящей средней

В предыдущей части статьи стратегия CImpulse закрывала свои позиции, если цена оказывалась ниже или выше скользящей средней. Скользящая средняя была представлена в виде класса индикатора CIndMovingAverage. Класс CIndMovingAverage сам по себе очень похож на класс трейлинга. Он вычисляет значения скользящей средней и позволяет гибко конфигурировать сами параметры этого индикатора. Единственное, что отличает его от трейлинг-стопа, — это отсутствие собственно алгоритма по сопровождению позиции. В классе CIndMovingAverage нет метода Modify(). С другой стороны, базовый класс CTrailing уже обладает всеми необходимыми методами, но в нем нет алгоритмов по работе со скользящей средней. Метод композиции позволяет объединить полезные свойства каждого из этих классов, создав на их основе новый тип трейлинг-стопа: трейлинг-стоп на основе скользящей средней. Работа алгоритма будет крайне проста: он будет устанавливать стоп-лосс позиции, равный уровню скользящей средней. Также в методе Modify будет введена дополнительная проверка: если по каким-то причинам текущая цена ниже (для покупки) или выше (для продажи) рассчитанного уровня стоп-лосса, то позиция будет закрываться по текущим рыночным ценам. Весь класс приведен ниже:

//+------------------------------------------------------------------+
//|                                               TrailingMoving.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include "Trailing.mqh"
#include "..\Indicators\MovingAverage.mqh"

//+------------------------------------------------------------------+
//| Трейлинг-стоп на основе MovingAverage. Устанавливает стоп-лосс   |
//| позиции равным уровню скользящей средней                         |
//+------------------------------------------------------------------+
class CTrailingMoving : public CTrailing
{
public:
   virtual bool       Modify(void);
   CIndMovingAverage* Moving;
   virtual CTrailing* Copy(void);
};
//+------------------------------------------------------------------+
//| Устанавливает стоп-лосс позиции равным уровню скользящей средней |
//+------------------------------------------------------------------+
bool CTrailingMoving::Modify(void)
{
   if(CheckPointer(Moving) == POINTER_INVALID)
      return false;
   double value = Moving.OutValue(1);
   if(m_position.Direction() == POSITION_TYPE_BUY &&
      value > m_position.CurrentPrice())
      m_position.CloseAtMarket();
   else if(m_position.Direction() == POSITION_TYPE_SELL &&
      value < m_position.CurrentPrice())
      m_position.CloseAtMarket();
   else if(m_position.StopLossValue() != value)
      return m_position.StopLossValue(value);
   return false;
}
//+------------------------------------------------------------------+
//| Возвращает точную копию экземпляра CTrailingMoving               |
//+------------------------------------------------------------------+
CTrailing* CTrailingMoving::Copy(void)
{
   CTrailingMoving* mov = new CTrailingMoving();
   mov.Moving = Moving;
   return mov;
}

Функция Modify сверяет уровень скользящей средней с уровнем текущего стопа, и если уровни не совпадают, устанавливает новый уровень для позиции. Уровень скользящей средней берется для уже сформировавшегося предыдущего бара, так как текущий бар всегда находится в стадии формирования. Также обратите внимание, что сам индикатор MovingAverage объявлен в виде указателя. Это сделано специально, чтобы пользователь смог подключить к этому трейлинг-стопу любой объект типа CIndMovingAverage. 

Теперь, когда класс написан, проверим его работу в тестере стратегий. Вот небольшое видео, демонстрирующее его работу:

 

 

Индивидуальный трейлинг-стоп для позиции

Мы разобрали механизм работы с трейлинг-стопом. Благодаря унифицированному виртуальному методу Modify, торговый движок CStrategy может в автоматическом режиме устанавливать трейлинг-стоп для каждой позиции и вызывать его алгоритм расчета. Как правило, этого бывает достаточно, однако в некоторых случаях необходимо сопровождать каждую позицию индивидуально. Так, для одной позиции необходимо установить один тип трейлинга, а для другой — иной тип. Такие особенности сопровождения не могут быть унифицированы и выполнены на стороне торгового движка, поэтому управление трейлинг-стопами необходимо реализовать в самой стратегии. Для этого достаточно переопределить хорошо знакомые нам методы BuySupport и SellSupport. Кроме того, в этом случае уже не требуется инициализация трейлинга по умолчанию, которую мы проводили в конструкторе пользовательской стратегии.

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

//+------------------------------------------------------------------+
//| Сопровождение длинной позиции по скользящей средней Moving       |
//+------------------------------------------------------------------+
void CImpulseTrailing::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))
      return;
   if(pos.Trailing == NULL)
   {
      CTrailingMoving* trailing = new CTrailingMoving();
      trailing.Moving = GetPointer(this.Moving);
      pos.Trailing = trailing;
   }
   pos.Trailing.Modify();
}
//+------------------------------------------------------------------+
//| Сопровождение короткой позиции по скользящей средней Moving      |
//+------------------------------------------------------------------+
void CImpulseTrailing::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))
      return;
   if(pos.Trailing == NULL)
   {
      CTrailingClassic* trailing = new CTrailingClassic();
      trailing.SetDiffExtremum(0.00100);
      pos.Trailing = trailing;
   } 
   pos.Trailing.Modify();
}

Обратите внимание, что в случае с трейлингом по скользящей средней в качестве параметра устанавливается целый класс CIndMovingAverage, уже имеющийся в стратегии в качестве объекта Moving. С помощью одной строчки кода мы указали трейлингу, какой объект использовать в качестве расчета уровня стоп-лосса. 

В методе SupportSell на новые позиции устанавливается уже другой тип трейлинга, для которого задается свой набор параметров. В данном случае устанавливается коридор следования от достигнутого экстремума цены в размере 0.00100 пунктов. 

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

 

Краткий перечень исправлений в последней версии

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

  • Включение в проект панели торговой стратегии. Из-за ошибки в компиляторе предыдущей версии, в 3 и 4 частях статьи торговая панель была отключена из за проблем совместимости с компилятором. После исправления компилятора в пятой части статьи она вновь появилась, но не работала должным образом. В шестой редакции торгового движка работоспособность панели полностью восстановлена.
  • В торговой панели была допущена ошибка: вместо отображения режима "SellOnly" дважды отображался режим "BuyOnly". Эта ошибка исправлена.
  • В предыдущих версиях при смене торгового режима с помощью панели фактический режим стратегии не менялся. В шестой версии ошибка исправлена.
  • Теперь при смене торгового режима добавлено новое поведение: при режиме SellOnly автоматически закрываются не только все позиции на покупку, но также и все отложенные ордера на покупку, принадлежащие текущей стратегии. То же верно для режима BuyOnly: все отложенные ордера на продажу в этом режиме также отменяются. При выборе режима "Stop" все отложенные ордера, какого бы они ни были направления, также удаляются.

Я прошу читателей при выявлении новых багов или ошибок сообщать об этом. Найденные ошибки будут исправлены. 


Заключение

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

  • Делегирование работы с трейлинг-стопом торговому движку стратегии или режим Автопилота. В этом случае каждая позиция будет автоматически сопровождаться трейлингом по умолчанию. Каких-либо действий по управлению со стороны пользовательской стратегии не требуется. 
  • Управление трейлнг-стопом на уровне пользовательской стратегии. В этом случае сама пользовательская стратегия управляет своими позициями при помощи трейлинга. Этот режим необходимо использовать для реализации сложных тактик управления — например, когда для разных позиций используются разные алгоритмы трейлинга.

Каждый трейлинг-стоп содержит полную информацию о позиции, которую ему необходимо сопроводить. Также метод модификации позиции может закрыть ее в любой момент. Благодаря этому достигается высокая гибкость алгоритмов трейлинга. Формально, алгоритмом трейлинг-стопа может быть любой алгоритм управления позицией. Например, вместо изменения уровня стоп-лосса, алгоритм может менять уровень тейк-профита. Такие изменения не приведут к нарушению логики, и торговый движок будет работать корректно.
Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
Vasiliy Sokolov
Vasiliy Sokolov | 25 мая 2016 в 10:41
igorbel:
Здравствуйте. Отличная работа, большое спасибо! Скажите, не планируете ли вы внедрить в движок подходы для торговли на MOEX, описанные вами же в статье https://www.mql5.com/ru/articles/1683? Конкретно интересует анализ рыночной ликвидности и на его основании вход с заданным отклонением (макс. проскальзыванием).
Предложение интересное. Посмотрим.
igorbel
igorbel | 27 мая 2016 в 14:16
//+------------------------------------------------------------------+
//| Закрывает текущую позицию по рынку, устанавливая закрывающий     |
//| комментарий равный comment                                       |
//+------------------------------------------------------------------+
bool CPosition::CloseAtMarket(string comment="")
  {
   if(!IsActive())
      return false;
   m_trade.PositionModify(m_id, 0.0, 0.0);
   ENUM_ACCOUNT_MARGIN_MODE mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
      return m_trade.PositionClose(m_symbol);
   return m_trade.PositionClose(m_id);
  }

 

Скажите, а зачем m_trade.PositionModify(m_id, 0.0, 0.0) в этой функции? У меня в журнале ошибки Invalid Stops из-за этой операции.

Vasiliy Sokolov
Vasiliy Sokolov | 27 мая 2016 в 14:31
igorbel:

Скажите, а зачем m_trade.PositionModify(m_id, 0.0, 0.0) в этой функции? У меня в журнале ошибки Invalid Stops из-за этой операции.

Это рудименты старой версии. Спасибо что указали, будет исправлено.
igorbel
igorbel | 4 июн. 2016 в 21:26

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

'CTrailing' - declaration without type  PositionMT5.mqh 48      4
'Trailing' - undeclared identifier      PositionMT5.mqh 73      20
'Trailing' - object pointer expected    PositionMT5.mqh 73      20
'Trailing' - object pointer expected    PositionMT5.mqh 74      14
igorbel
igorbel | 19 сент. 2016 в 19:19
bool CTrailingClassic::Modify(void)
  {

   if(CheckPointer(m_position)==POINTER_INVALID)
     {
      string text="Invalid position for current trailing-stop. Set position with 'SetPosition' method";
      CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
      Log.AddMessage(msg);
      return false;
     }
   if(m_diff_extremum<=0.0)
     {
      string text="Set points trailing-stop with 'SetDiffExtremum' method";
      CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
      Log.AddMessage(msg);
      return false;
     }
   double extremum=FindExtremum(m_position);
   if(extremum == 0.0)return false;
   double n_sl = 0.0;
   if(m_position.Direction()==POSITION_TYPE_BUY)
      n_sl=extremum-m_diff_extremum;
   else
      n_sl=extremum+m_diff_extremum;
   if(n_sl!=m_position.StopLossValue())
      return m_position.StopLossValue(n_sl);
   return false;
  }

 Не помешала бы проверка, что новый sl ниже текущей цены для длинной позиции и выше текущей цены для короткой позиции.

Битва за скорость: QLUA vs MQL5 - почему MQL5 быстрее от 50 до 600 раз? Битва за скорость: QLUA vs MQL5 - почему MQL5 быстрее от 50 до 600 раз?
Для сравнения языков MQL5 и QLUA мы написали несколько тестов, которые замеряют скорость выполнения базовых операций. В тестах использовался компьютер с Windows 7 Professional 64 bit , MetaTrader 5 build 1340 и QUIK версии 7.2.0.45.
Графические интерфейсы V: Элемент "Комбинированный список" (Глава 3) Графические интерфейсы V: Элемент "Комбинированный список" (Глава 3)
В первых двух главах пятой части серии о графических интерфейсах были разработаны классы для создания полосы прокрутки и списка. В этой главе рассмотрим класс для создания такого элемента управления, как «Комбинированный список». Это тоже составной элемент, в числе частей которого есть элементы, рассмотренные в первых двух главах пятой части.
Графические интерфейсы VI: Элементы "Чекбокс", "Поле ввода" и их смешанные типы (Глава 1) Графические интерфейсы VI: Элементы "Чекбокс", "Поле ввода" и их смешанные типы (Глава 1)
С этой статьи начинается шестая часть серии о разработке библиотеки для создания графических интерфейсов в терминалах MetaTrader. В первой главе речь пойдёт о таких элементах управления, как «чекбокс», «поле ввода», а также о смешанных типах этих элементов.
Создание бота для Telegram на языке MQL5 Создание бота для Telegram на языке MQL5
Эта статья — пошаговое руководство по созданию бота для Telegram на языке MQL5. Данный материал будет интересен тем, кто хочет связать торгового робота со своим мобильным устройством. В статье приведены примеры ботов, выполняющие рассылку торговых сигналов, поиск информации на сайте, присылающие информацию о состоянии торгового счета, котировки и скриншоты графиков на ваш смартфон.