Discussing the article: "Developing a multi-currency Expert Advisor (Part 1): Collaboration of several trading strategies"

 

Check out the new article: Developing a multi-currency Expert Advisor (Part 1): Collaboration of several trading strategies.

There are quite a lot of different trading strategies. So, it might be useful to apply several strategies working in parallel to diversify risks and increase the stability of trading results. But if each strategy is implemented as a separate Expert Advisor (EA), then managing their work on one trading account becomes much more difficult. To solve this problem, it would be reasonable to implement the operation of different trading strategies within a single EA.

We need to decide what we want and what we have.

We have (or almost have):

  • some different trading strategies that work on different symbols and timeframes in the form of a ready-made EA code or just a formulated set of rules for performing trading operations
  • starting deposit
  • maximum permissible drawdown

We want:

  • collaboration of all selected strategies on one account on several symbols and timeframes
  • distribution of the starting deposit between everyone equally or in accordance with the specified ratios
  • automatic calculation of the volumes of opened positions to comply with the maximum allowable drawdown
  • correct handling of terminal restarts
  • ability to launch in MetaTrader 5 and 4

We will use an object-oriented approach, MQL5 and a standard tester in MetaTrader 5.

The task at hand is quite large, so we will solve it step by step.

Author: 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
};

Why do we need Init-method if there is a constructor?

For some reason, they immediately limited the TS class to one symbol and timeframe.


It seems to be more logical.

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

Why do you need an Init method when you have a constructor?

For some reason they immediately limited the TS class to one symbol and timeframe.

I liked the author's approach. There is such a passage in the article:

Init() and Tick() methods are declared purely virtual (after the method header is = 0). It means that we will not write the implementation of these methods in the CStrategy class. On the basis of this class we will create descendant classes, in which Init() and Tick() methods must necessarily be and contain the implementation of specific trade rules.

Then the class will be abstract, as far as I understand.....

 
Denis Kirichenko #:

Then the class will be abstract, as far as I understand.....

It will be. It is used in the descendant for some reason. If Deinit is not done (there is a destructor), it is logical not to do Init (there is a constructor).

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
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);
}

And artificial strong narrowing of possible TCs is a strange solution.


You can also clearly see the cumbersomeness with input because of OOP. It would be good to remove it.

 

Init() has been left out for now because it is impossible to return a result from the constructor. But it is possible that we will never need to return something other than INIT_SUCCESS as a result of strategy initialisation. So it is quite possible that this method will be removed in the future.

The allocation of mandatory strategy properties in the form of symbol and timeframe is a deliberate limitation. By design, trading on several symbols will be done by working with many instances of this class's descendants, but each particular instance works with a single symbol. I have not yet come across any strategies that would be hindered by such a limitation. On the contrary, these parameters were found in every strategy considered, that's why it was decided to put them into the base class at once.

But in the future I plan to consider some multi-symbol strategies that cannot be divided into several independent single-symbol strategies (if any). I don't think that the presence of symbol and timeframe properties in the base class will greatly hinder the implementation of a child class in which several symbols and several timeframes will be used.

 
Yuriy Bykov properties in the base class will prevent you from implementing a child class that will use multiple symbols and multiple timeframes.

It won't interfere - 100%. It's just an unnecessary entity. OOP architecturally follows the principle from the general to the particular. You have made the general (base class) "private". Although all that is called there is CStrategy::Tick().

 
Denis Kirichenko #:

And I liked the author's approach. There is such a passage in the article:

Then the class will be abstract, as far as I understand.....

It is, it will be used only for obtaining child classes. You will not need to create objects of the base class CStrategy. But any object of the child class can be passed to the Expert Advisor object to be added to the CAdvisor::AddStrategy(CStrategy &strategy) method.

 
Yuriy Bykov #:

Init() has been left out for now because it is impossible to return a result from the constructor. But it is possible that we will never need to return something other than INIT_SUCCESS as a result of strategy initialisation. So, it is quite possible that this method will be removed in the future.

In case something went wrong in the constructor (the indicator handle is not charged or memory is not allocated, etc.), some people keep such a common variable.
static int CStrategy::InitFlag = INIT_FAILED;
 
fxsaber #:
In case something went wrong in the constructor (indicator handle is not charged or memory is not allocated), some people keep such a shared variable.

Yes, I've already thought of something like that. I'll try to do it this way.

 
Yuriy Bykov #:

any child class object can be passed to the EA object to be added to the CAdvisor::AddStrategy(CStrategy &strategy) method.

It seems to be a compiler bug that it doesn't swear at this method signature when called like this.

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

It should be like this.

CAdvisor::AddStrategy(CStrategy* strategy)
 
Inherit from CObject.
class CStrategy : public CObject {

You don't use it.

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

I noticed that it is quite common practice to inherit from CObject.