Обсуждение статьи "Разрабатываем мультивалютный советник (Часть 1): Совместная работа нескольких торговых стратегий"

 

Опубликована статья Разрабатываем мультивалютный советник (Часть 1): Совместная работа нескольких торговых стратегий:

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

Надо определиться, что мы хотим и что у нас есть.

У нас есть (ну или почти есть):

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

Мы хотим:

  • совместной работы всех выбранных стратегий на одном счете на нескольких символах и таймфреймах
  • распределения стартового депозита между всеми поровну или в соответствии с задаваемыми коэффициентами
  • автоматического расчета объемов открываемых позиций для соблюдения максимальной допустимой просадки
  • корректной обработки перезапуска терминала
  • возможности запуска в MT5 и MT4

Будем использовать объектно-ориентированный подход, MQL5, штатный тестер в MetaTrader 5.

Поставленная задача достаточно большая, поэтому будем решать ее поэтапно.

Автор: Yuriy Bykov

 
class CStrategy : public CObject {
protected:
   ulong             m_magic;          // Magic
   string            m_symbol;         // Символ (торговый инструмент)
   ENUM_TIMEFRAMES   m_timeframe;      // Период графика (таймфрейм)
   double            m_fixedLot;       // Размер открываемых позиций (фиксированный)

public:
   // Конструктор
   CStrategy(ulong p_magic,
             string p_symbol,
             ENUM_TIMEFRAMES p_timeframe,
             double p_fixedLot);

   virtual int       Init() = 0; // Инициализация стратегии - обработка событий OnInit
   virtual void      Tick() = 0; // Основной метод - обработка событий OnTick
};

Зачем нужен Init-метод, если есть конструктор?

По какой-то причине сразу ограничили класс ТС одним символов и таймфреймом.


Вроде, так логичнее.

class SYSTEM
{
public:
  virtual void OnTick() {}
};
 
fxsaber #:

Зачем нужен Init-метод, если есть конструктор?

По какой-то причине сразу ограничили класс ТС одним символов и таймфреймом.

А мне понравился подход автора. Есть в статье такой пассаж:

Методы Init() и Tick() объявлены чисто виртуальными (после заголовка метода стоит = 0). Это значит, что в классе CStrategy мы не будем писать реализацию этих методов. На основе этого класса будем создавать классы потомков, в которых методы Init() и Tick() должны обязательно быть и содержать реализацию конкретных правил торговли.

Тогда класс будет абстрактным, насколько понимаю...

 
Denis Kirichenko #:

Тогда класс будет абстрактным, насколько понимаю...

Будет. При этом он зачем-то используется сразу в наследнике. Если не делается Deinit (есть деструктор), логично не делать и Init (есть конструктор).

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CSimpleVolumeStrategy::CSimpleVolumeStrategy(
   ulong            p_magic,
   string           p_symbol,
   ENUM_TIMEFRAMES  p_timeframe,
   double           p_fixedLot,
   int              p_signalPeriod,
   double           p_signalDeviation,
   double           p_signaAddlDeviation,
   int              p_openDistance,
   double           p_stopLevel,
   double           p_takeLevel,
   int              p_ordersExpiration,
   int              p_maxCountOfOrders) :
   // Список инициализации
   CStrategy(p_magic, p_symbol, p_timeframe, p_fixedLot), // Вызов конструктора базового класса
   signalPeriod_(p_signalPeriod),
   signalDeviation_(p_signalDeviation),
   signaAddlDeviation_(p_signaAddlDeviation),
   openDistance_(p_openDistance),
   stopLevel_(p_stopLevel),
   takeLevel_(p_takeLevel),
   ordersExpiration_(p_ordersExpiration),
   maxCountOfOrders_(p_maxCountOfOrders)
{}

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int CSimpleVolumeStrategy::Init() {
// Загружаем индикатор для получения тиковых объемов
   iVolumesHandle = iVolumes(m_symbol, m_timeframe, VOLUME_TICK);

// Устанавливаем размер массива-приемника тиковых объемов и нужную адресацию
   ArrayResize(volumes, signalPeriod_);
   ArraySetAsSeries(volumes, true);

// Установим Magic Number для выставления ордеров через trade
   trade.SetExpertMagicNumber(m_magic);

   return(INIT_SUCCEEDED);
}

Ну и искусственное сильное сужение возможных ТС - странное решение.


Также хорошо видна громоздкость со входными из-за ООП. Хорошо бы убрать ее.

 

Init() оставлен пока по той причине, что из конструктора нельзя вернуть результат. Но, возможно, что нам никогда не понадобится возвращать в результате инициализации стратегии что-то кроме INIT_SUCCESS. Так что, вполне, возможно, этот метод уберется в дальнейшем.

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

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

 
Yuriy Bykov #:

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

Не помешает - 100%. Просто лишняя сущность получилась. В ООП же архитектурно идет принцип от общего к частному. Вы общий (базовый класс) сделали "частным". Хотя все, что там вызывается - CStrategy::Tick().

 
Denis Kirichenko #:

А мне понравился подход автора. Есть в статье такой пассаж:

Тогда класс будет абстрактным, насколько понимаю...

Так и есть, он будет использоваться только для получения дочерних классов. Создавать объекты базового класса CStrategy не понадобится. Зато любой объект дочернего класса может быть передан в объект эксперта для добавления в метод CAdvisor::AddStrategy(CStrategy &strategy).

 
Yuriy Bykov #:

Init() оставлен пока по той причине, что из конструктора нельзя вернуть результат. Но, возможно, что нам никогда не понадобится возвращать в результате инициализации стратегии что-то кроме INIT_SUCCESS. Так что, вполне, возможно, этот метод уберется в дальнейшем.

На случай, когда в конструкторе что-то пошло не так (хэндл индикатора не зарядился или память не выделилась и т.д.), некоторые держат такую общую переменную.
static int CStrategy::InitFlag = INIT_FAILED;
 
fxsaber #:
На случай, когда в конструкторе что-то пошло не так (хэндл индикатора не зарядился или память не выделилась), некоторые держат такую общую переменную.

Да, думал уже о чем-то подобном. Попробую так сделать.

 
Yuriy Bykov #:

любой объект дочернего класса может быть передан в объект эксперта для добавления в метод CAdvisor::AddStrategy(CStrategy &strategy).

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

   expert.AddStrategy(new CSimpleVolumeStrategy(
                         magic_ + 1, "EURGBP", PERIOD_H1,
                         NormalizeDouble(0.34 * depoPart_, 2),
                         130, 0.9, 1.4, 231, 3750, 50, 600, 3)

Должно быть так.

CAdvisor::AddStrategy(CStrategy* strategy)
 
Наследуетесь от CObject.
class CStrategy : public CObject {

При этом не используете это.

class CAdvisor : public CObject {
protected:
   CStrategy         *m_strategies[];  // Массив торговых стратегий

Заметил, что это довольно распространенная практика - наследование от CObject.

Причина обращения: