Кроссплатформенный торговый советник: повторное использование компонентов из Стандартной библиотеки MQL5

Enrico Lambino | 30 августа, 2016

Оглавление


Введение

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

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

  2. Скопировать классы и модифицировать их так, чтобы заголовочные файлы компилировались в MQL4.

В этой статье мы рассмотрим второй вариант. Использование первого метода могло бы сделать довольно свободной имплементацию на стороне MQL4. Однако главным его недостатком является то, что мы не хотим переписывать много кода. Легче было бы сделать обновление до последней версии классов MQL5, чем переписывать все с нуля.

Структура папок

В отличие от большинства классов, которые будут использоваться, объекты класса, которые повторно используются из Стандартной библиотеки MQL5, нужно будет скопировать в папку Include внутри каталога данных MQL4. Поэтому, чтобы советника можно было использовать независимо от версии, потребуется провести небольшую организационную работу. Методы для достижения этой цели могут варьироваться. Но один из самых простых способов — привязать конкретный заголовочный файл и заставить его работать с соответствующим заголовочным файлом, который мы будем использовать. Способ чем-то похож на тот, который обычно принят в пользовательских объектах классов:

  1. Для версии MQL5 основной заголовочный файл должен ссылаться на исходное местоположение используемого заголовочного файла.

  2. В версии MQL4 основной заголовочный файл должен ссылаться на копию (модифицированную) используемого заголовочного файла.

Один из способов сделать это — создать папку "Lib" в основной директории. Он будет размещен там же, где основной заголовочный файл. Аналогичная папка будет создана в MQL4-папке кроссплатформенного каталога (пусть она называется "MQLx-Reuse"). В ней будут размещены модифицированные копии соответствующих MQL5-классов.

/Include

/MQLx-Reuse

/Base

/Lib

<Main Header Files>

/MQL4

/Lib

<Copy of MQL5 Header Files>

/MQL5

В папке MQL5 не будет папки "Lib", поскольку основные заголовочные файлы будут напрямую ссылаться на находящийся внутри каталога данных MQL5 заголовочный файл. Например, нам нужно повторно использовать в кроссплатформенном советнике CSymbolInfo. Конкретно этот класс — часть стандартной библиотеки MQL5, относящаяся к торговым классам. Доступ к нему может быть получен с использованием такой директивы #include:

#include <Trade\SymbolInfo.mqh>

Чтобы использовать это в наших текущих настройках, нам нужно создать основной заголовочный файл в подпапке Lib внутри каталога Base, с помощью следующего кода:

(/Base/Lib/SymbolInfo.mqh)

#ifdef __MQL5__
   #include <Trade\SymbolInfo.mqh>
#else
   #include "..\..\MQL4\Lib\SymbolInfo.mqh"
#endif

MQL4-версия вызывает директиву включения заголовочного файла, расположенного в папке "Lib". Но к настоящему моменту это будет папка "Lib" каталога "MQL4" в нашей пользовательской директории (“MQLx-Reuse”).

Как уже говорилось ранее, версия MQL5 не нуждается в папке "Lib", поскольку основной заголовочный файл находится в папке Base. Он уже указывает на <Trade\SymbolInfo.mqh>, который обычно находится внутри папки Include MQL5.

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

CSymbolInfo

При использовании компилятора MQL4 компиляция заголовочного файла оригинального класса CSymbolInfo выдает ошибки. Ошибки преимущественно вызваны несовместимостью с MQL4, в частности связаны с использованием функций SymbolInfoDouble и SymbolInfoInteger. Большинство вызовов этих функций можно увидеть в методе Refresh(). Оригинальный (неизменненый) код класса CSymbolInfo приведен ниже:

bool CSymbolInfo::Refresh(void)
  {
   long tmp=0;
//--- 
   if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short))
      return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp))
      return(false);
   m_digits=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp))
      return(false);
   m_order_mode=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp))
      return(false);
   m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp))
      return(false);
   m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp))
      return(false);
   m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp))
      return(false);
   m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp))
      return(false);
   m_swap3=(ENUM_DAY_OF_WEEK)tmp;
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LONG,m_margin_long))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_SHORT,m_margin_short))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LIMIT,m_margin_limit))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOP,m_margin_stop))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOPLIMIT,m_margin_stoplimit))
      return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp))
      return(false);
   m_trade_time_flags=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp))
      return(false);
   m_trade_fill_flags=(int)tmp;
//--- удача
   return(true);
  }

Подобно тому, как обсуждалось в первой статье этого цикла, мы используем общий заголовочный файл, который в идеале консолидирует сходства в коде между MQL4- и MQL5-версиями. На самом деле, можно переписать класс CSymbolinfo на три раздельных файла, так, чтобы сходства были объединены в одном файле, а различия разнесены на два других файла. Однако в этой статье мы пойдем простейшим (и самым быстрым) путем: скопируем файл класса CSymbolinfo, после чего закомментируем строки, которые несовместимы с MQL4. Для обеих версий итоговая файловая структура будет выглядеть следующим образом:

Некоторые идентификаторы свойств — такие как SYMBOL_TRADE_TICK_VALUE_PROFIT и SYMBOL_TRADE_TICK_VALUE_LOSS не поддерживаются в MQL4. Лучше закомментировать их до тех пор, пока не появятся новые разработки, позволяющие создать обходной путь для их использования.

В нижеследующем коде продемонстрировано, как закомментированы функции, которые могут привести к ошибке при вызове метода Refresh в MQL4.

bool CSymbolInfo::Refresh(void)
  {
   long tmp=0;
//--- 
   if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value))
      return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss))
//return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step))
      return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit))
//return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short))
      return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp))
      return(false);
   m_digits=(int)tmp;
//if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp))
//return(false);
//m_order_mode=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp))
      return(false);
   m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp))
      return(false);
   m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp))
      return(false);
   m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp))
      return(false);
   m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp))
      return(false);
   m_swap3=(ENUM_DAY_OF_WEEK)tmp;
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance))
      return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LONG,m_margin_long))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_SHORT,m_margin_short))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LIMIT,m_margin_limit))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOP,m_margin_stop))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOPLIMIT,m_margin_stoplimit))
//return(false);
//if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp))
//return(false);
//m_trade_time_flags=(int)tmp;
//if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp))
//return(false);
//m_trade_fill_flags=(int)tmp;
//--- удача
   return(true);
  }

Есть встроенные перечисления, доступные в MQL5, но не в MQL4. Для этого класса ENUM_SYMBOL_CALC_MODE и ENUM_SYMBOL_SWAP_MODE были бы необходимы при компиляции заголовочного файла в MQL4. Чтобы включить эти перечисления, нам нужно просто объявить их в заголовочном файле класса:

enum ENUM_SYMBOL_CALC_MODE
  {
   SYMBOL_CALC_MODE_FOREX,
   SYMBOL_CALC_MODE_FUTURES,
   SYMBOL_CALC_MODE_CFD,
   SYMBOL_CALC_MODE_CFDINDEX,
   SYMBOL_CALC_MODE_CFDLEVERAGE,
   SYMBOL_CALC_MODE_EXCH_STOCKS,
   SYMBOL_CALC_MODE_EXCH_FUTURES,
   SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS
  };
enum ENUM_SYMBOL_SWAP_MODE
  {
   SYMBOL_SWAP_MODE_DISABLED,
   SYMBOL_SWAP_MODE_POINTS,
   SYMBOL_SWAP_MODE_CURRENCY_SYMBOL,
   SYMBOL_SWAP_MODE_CURRENCY_MARGIN,
   SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT,
   SYMBOL_SWAP_MODE_INTEREST_CURRENT,
   SYMBOL_SWAP_MODE_INTEREST_OPEN,
   SYMBOL_SWAP_MODE_REOPEN_CURRENT,
   SYMBOL_SWAP_MODE_REOPEN_BID
  };

Обратите внимание, что были закомментированы не все неподдерживаемые в MQL4 функции. До сих пор мы предприняли минимальные шаги, необходимые для того, чтобы заголовочный файл мог быть обработан компилятором MQL4. Если вы не уверены в том, действительно ли конкретный метод или функция не поддерживается обеими версиями, рекомендуется обратиться к документации по обоим языкам.

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

#ifdef __MQL5__
   #include <Trade\SymbolInfo.mqh>
#else
   #include "..\..\MQL4\Lib\SymbolInfo.mqh"
#endif

Заголовочный файл MQL5 будет указывать на оригинальное расположение CSymbolInfo внутри Стандартной библиотеки MQL5. В свою очередь, заголовочный файл MQL4 будет указывать на заголовочный файл CSymbolInfo, который является доработанной копией заголовочного файла MQL5 CSymbolInfo, находящегося в Lib.


CSymbolManager

Для мультивалютных советников потребуется коллекция экземпляров CSymbolInfo. Ее создание станет задачей CSymbolManager. Класс расширяет CArrayObj и позволяет ему хранить различные экземпляры объектов информации по символам, наряду с некоторыми дополнительными методами. Его "взаимоотношения" с экземплярами CSymbolinfo проиллюстрированы нижеследующим рисунком:

То есть, он ведет себя подобно CArrayObj, хотя класс лучше использовать для хранения объектов конкретного типа (экземпляры CSymbolinfo). Поскольку CSymbolinfo основан на CArrayObj, который, в свою очередь, доступен и в MQL4, и в MQL5, то мы можем ограничить большую часть кода основным заголовочным файлом, после чего ссылаться на пустые файлы классов для двух разных версий языка (в зависимости от того, какой из компиляторов используется). В итоге мы получим файловую структуру, сходную с описанной ранее для класса CSymbolInfo:


Добавление символов

Экземпляры Csymbolnfo добавляются аналогично методу Add класса CArrayObj. Однако, в отличие от CArrayObj, добавляемый экземпляр должен быть уникальным. Иными словами, не должно быть двух элементов с одним и тем же названием символа.

bool CSymbolManagerBase::Add(CSymbolInfo *node)
  {
   if(Search(node.Name())==-1)
      return CArrayObj::Add(node);       
   return false;
  }

Производный класс будет использовать метод Search, который является перегрузкой аналогичного метода класса CArrayObj. В свою очередь, последний не будет использоваться, поскольку это означало бы использовать метод Compare класса CSymbolInfo, который по факту является методом класса CObject (CSymbolInfo не имеет явного метода Compare). Использование метода Compare потребует расширить класс CSymbolInfo. Это может быть более сложным решением, поскольку у нас уже есть отдельные копии этого класса. Новый метод Search будет сравнивать объекты с именем отслеживаемых ими символов:

int CSymbolManagerBase::Search(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *item=At(i);
      if(StringCompare(item.Name(),symbol)==0)
         return i;
     }
   return -1;
  }
Для мультивалютных советников в классе CSymbolManager заведен указатель на приоритетный символ. Это может быть как текущий символ, так и любой другой.  Обращаться к нему мы будем чаще, чем к остальным. В данном классе мы можем разрешить эксперту установить приоритетный символ во время инициализации. Если же этого не сделать, то приоритетным символом станет первый обработанный экземпляр CSymbolInfo:
void CSymbolManagerBase::SetPrimary(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   m_symbol_primary=Get(symbol);
  }

CSymbolInfo *CSymbolManagerBase::GetPrimary(void)
  {
   if (!CheckPointer(m_symbol_primary) && Total()>0)
      SetPrimary(0);
   return m_symbol_primary;
  }


Обновление символов

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

bool CSymbolManagerBase::RefreshRates(void)
  {
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *symbol=At(i);
      if(!CheckPointer(symbol))
         continue;
      if(!symbol.RefreshRates())
         return false;
     }
   return true;
  }

Обратите внимание на то, что методы Refresh и RefreshRates в CSymbolInfo различаются. Первый используется во время инициализации, второй — для обновления информации по символу на последнем обработанном тике.

Ниже продемонстрирован полный код базовой имплементации класса CSymbolManager:

(SymbolManagerBase.mqh)

#include <Arrays\ArrayObj.mqh>
#include "..\Lib\SymbolInfo.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSymbolManagerBase : public CArrayObj
  {
protected:
   CSymbolInfo      *m_symbol_primary;
   CObject          *m_container;
public:
                     CSymbolManagerBase(void);
                    ~CSymbolManagerBase(void);
   virtual bool      Add(CSymbolInfo*);
   virtual void      Deinit(void);
   CSymbolInfo      *Get(string);
   virtual bool      RefreshRates(void);
   virtual int       Search(string);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual void      SetPrimary(string);
   virtual void      SetPrimary(const int);
   virtual CSymbolInfo *GetPrimary(void);
   virtual string    GetPrimaryName(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::CSymbolManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::~CSymbolManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CObject *CSymbolManagerBase::GetContainer(void)
  {
   return m_container;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::SetContainer(CObject *container)
  {
   m_container=container;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::Deinit(void)
  {
   Shutdown();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSymbolManagerBase::Add(CSymbolInfo *node)
  {
   if(Search(node.Name())==-1)
      return CArrayObj::Add(node);       
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolInfo *CSymbolManagerBase::Get(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *item=At(i);
      if(!CheckPointer(item))
         continue;
      if(StringCompare(item.Name(),symbol)==0)
         return item;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSymbolManagerBase::RefreshRates(void)
  {
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *symbol=At(i);
      if(!CheckPointer(symbol))
         continue;
      if(!symbol.RefreshRates())
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CSymbolManagerBase::Search(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *item=At(i);
      if(StringCompare(item.Name(),symbol)==0)
         return i;
     }
   return -1;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSymbolManagerBase::SetPrimary(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   m_symbol_primary=Get(symbol);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSymbolManagerBase::SetPrimary(const int idx)
  {
   m_symbol_primary=At(idx);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolInfo *CSymbolManagerBase::GetPrimary(void)
  {
   if (!CheckPointer(m_symbol_primary) && Total()>0)
      SetPrimary(0);
   return m_symbol_primary;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string CSymbolManagerBase::GetPrimaryName(void)
  {
   if(CheckPointer(m_symbol_primary))
      return m_symbol_primary.Name();
   return NULL;
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\Symbol\SymbolManager.mqh"
#else
#include "..\..\MQL4\Symbol\SymbolManager.mqh"
#endif
//+------------------------------------------------------------------+

Все методы совместимы с обеими платформами. Таким образом, классы, специфичные для платформы, будут одинаковыми и не будут содержать никаких дополнительных методов.

(SymbolManager.mqh, MQL4 and MQL5)

class CSymbolManager : public CSymbolManagerBase
  {
public:
                     CSymbolManager(void);
                    ~CSymbolManager(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManager::CSymbolManager(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManager::~CSymbolManager(void)
  {
  }
//+------------------------------------------------------------------+

CAccountInfo

Аналогично тому, как это было сделано с CSymbolInfo, большинство неподдерживаемых функций надо будет закомментировать. Неподдерживаемые функции — к примеру, манипуляции с режимом расчета маржи (только в MetaTrader 5), функции OrderCalcMargin и OrderCalcProfit, которые не имеют прямых аналогов в MQL4. Таким образом, как и в случае с CSymbolinfo, мы создадим отдельные копии одного и того же класса для двух платформ, а затем изменим версию класса для MQL4 так, чтоб он мог быть обработан соответствующим компилятором. Файловая структура будет такой же, как и для других обсуждавшихся ранее классов:


Переработанный класс показан ниже.

//+------------------------------------------------------------------+
//|                                                  AccountInfo.mqh |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Class CAccountInfo.                                              |
//| Назначение: класс для доступа к информации об учетной записи A                  |
//|              Производная от класса CObject.                         |
//+------------------------------------------------------------------+
/class CAccountInfo : public CObject
  {
public:
                     CAccountInfo(void);
                    ~CAccountInfo(void);
   //--- методы быстрого доступа к целочисленным свойствам учетной записи
   long              Login(void) const;
   ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const;
   string            TradeModeDescription(void) const;
   long              Leverage(void) const;
   ENUM_ACCOUNT_STOPOUT_MODE StopoutMode(void) const;
   string            StopoutModeDescription(void) const;
   //ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const;
   //string            MarginModeDescription(void) const;
   bool              TradeAllowed(void) const;
   bool              TradeExpert(void) const;
   int               LimitOrders(void) const;
   //--- методы быстрого доступа к нецелочисленным свойствам аккаунта
   double            Balance(void) const;
   double            Credit(void) const;
   double            Profit(void) const;
   double            Equity(void) const;
   double            Margin(void) const;
   double            FreeMargin(void) const;
   double            MarginLevel(void) const;
   double            MarginCall(void) const;
   double            MarginStopOut(void) const;
   //--- методы быстрого доступа к строковым свойствам аккаунта
   string            Name(void) const;
   string            Server(void) const;
   string            Currency(void) const;
   string            Company(void) const;
   //--- методы доступа к функциям API MQL5
   long              InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const;
   double            InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const;
   string            InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const;
   //--- проверки
   //double            OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                      //const double volume,const double price_open,const double price_close) const;
   //double            MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 //const double volume,const double price) const;
   //double            FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                     //const double volume,const double price) const;
   //double            MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 //const double price,const double percent=100) const;
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CAccountInfo::CAccountInfo(void)
  {
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CAccountInfo::~CAccountInfo(void)
  {
  }
//+------------------------------------------------------------------+
//| получим значение свойства "ACCOUNT_LOGIN"                           |
//+------------------------------------------------------------------+
long CAccountInfo::Login(void) const
  {
   return(AccountInfoInteger(ACCOUNT_LOGIN));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_TRADE_MODE"                      |
//+------------------------------------------------------------------+
ENUM_ACCOUNT_TRADE_MODE CAccountInfo::TradeMode(void) const
  {
   return((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_TRADE_MODE" as string            |
//+------------------------------------------------------------------+
string CAccountInfo::TradeModeDescription(void) const
  {
   string str;
//--- 
   switch(TradeMode())
     {
      case ACCOUNT_TRADE_MODE_DEMO   : str="Demo trading account";    break;
      case ACCOUNT_TRADE_MODE_CONTEST: str="Contest trading account"; break;
      case ACCOUNT_TRADE_MODE_REAL   : str="Real trading account";    break;
      default                        : str="Unknown trade account";
     }
//--- 
   return(str);
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_LEVERAGE"                        |
//+------------------------------------------------------------------+
long CAccountInfo::Leverage(void) const
  {
   return(AccountInfoInteger(ACCOUNT_LEVERAGE));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_SO_MODE"                  |
//+------------------------------------------------------------------+
ENUM_ACCOUNT_STOPOUT_MODE CAccountInfo::StopoutMode(void) const
  {
   return((ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_SO_MODE" как строку        |
//+------------------------------------------------------------------+
string CAccountInfo::StopoutModeDescription(void) const
  {
   string str;
//--- 
   switch(StopoutMode())
     {
      case ACCOUNT_STOPOUT_MODE_PERCENT: str="Level is specified in percentage"; break;
      case ACCOUNT_STOPOUT_MODE_MONEY  : str="Level is specified in money";      break;
      default                          : str="Unknown stopout mode";
     }
//--- 
   return(str);
  }
/*
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_MODE"                     |
//+------------------------------------------------------------------+
ENUM_ACCOUNT_MARGIN_MODE CAccountInfo::MarginMode(void) const
  {
   return((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
  }
*/
/*
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_MODE" как строку           |
//+------------------------------------------------------------------+
string CAccountInfo::MarginModeDescription(void) const
  {
   string str;
//--- 
   switch(MarginMode())
     {
      case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: str="Netting";  break;
      case ACCOUNT_MARGIN_MODE_EXCHANGE      : str="Exchange"; break;
      case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: str="Hedging";  break;
      default                                : str="Unknown margin mode";
     }
//--- 
   return(str);
  }
*/
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_TRADE_ALLOWED"                   |
//+------------------------------------------------------------------+
bool CAccountInfo::TradeAllowed(void) const
  {
   return((bool)AccountInfoInteger(ACCOUNT_TRADE_ALLOWED));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_TRADE_EXPERT"                    |
//+------------------------------------------------------------------+
bool CAccountInfo::TradeExpert(void) const
  {
   return((bool)AccountInfoInteger(ACCOUNT_TRADE_EXPERT));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_LIMIT_ORDERS"                    |
//+------------------------------------------------------------------+
int CAccountInfo::LimitOrders(void) const
  {
   return((int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_BALANCE"                         |
//+------------------------------------------------------------------+
double CAccountInfo::Balance(void) const
  {
   return(AccountInfoDouble(ACCOUNT_BALANCE));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_CREDIT"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Credit(void) const
  {
   return(AccountInfoDouble(ACCOUNT_CREDIT));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_PROFIT"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Profit(void) const
  {
   return(AccountInfoDouble(ACCOUNT_PROFIT));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_EQUITY"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Equity(void) const
  {
   return(AccountInfoDouble(ACCOUNT_EQUITY));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Margin(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_FREEMARGIN"                      |
//+------------------------------------------------------------------+
double CAccountInfo::FreeMargin(void) const
  {
   return(AccountInfoDouble(ACCOUNT_FREEMARGIN));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_LEVEL"                    |
//+------------------------------------------------------------------+
double CAccountInfo::MarginLevel(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_SO_CALL"                  |
//+------------------------------------------------------------------+
double CAccountInfo::MarginCall(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_MARGIN_SO_SO"                    |
//+------------------------------------------------------------------+
double CAccountInfo::MarginStopOut(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN_SO_SO));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_NAME"                            |
//+------------------------------------------------------------------+
string CAccountInfo::Name(void) const
  {
   return(AccountInfoString(ACCOUNT_NAME));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_SERVER"                          |
//+------------------------------------------------------------------+
string CAccountInfo::Server(void) const
  {
   return(AccountInfoString(ACCOUNT_SERVER));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_CURRENCY"                        |
//+------------------------------------------------------------------+
string CAccountInfo::Currency(void) const
  {
   return(AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+
//| Получим значение свойства "ACCOUNT_COMPANY"                         |
//+------------------------------------------------------------------+
string CAccountInfo::Company(void) const
  {
   return(AccountInfoString(ACCOUNT_COMPANY));
  }
//+------------------------------------------------------------------+
//| Функции доступа AccountInfoInteger(...)                         |
//+------------------------------------------------------------------+
long CAccountInfo::InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const
  {
   return(AccountInfoInteger(prop_id));
  }
//+------------------------------------------------------------------+
//| Функции доступа AccountInfoDouble(...)                          |
//+------------------------------------------------------------------+
double CAccountInfo::InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const
  {
   return(AccountInfoDouble(prop_id));
  }
//+------------------------------------------------------------------+
//| Функции доступа AccountInfoString(...)                          |
//+------------------------------------------------------------------+
string CAccountInfo::InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const
  {
   return(AccountInfoString(prop_id));
  }
/*
//+------------------------------------------------------------------+
//| Функции доступа OrderCalcProfit(...).                            |
//| INPUT:  name            - имя символа,                           |
//|         trade_operation - торговая операция,                       |
//|         volume          - объем открытой позиции,        |
//|         price_open      - цена открытия позиции,         |
//|         price_close     - цена закрытия позиции.         |
//+------------------------------------------------------------------+
double CAccountInfo::OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                      const double volume,const double price_open,const double price_close) const
  {
   double profit=EMPTY_VALUE;
//--- 
   if(!OrderCalcProfit(trade_operation,symbol,volume,price_open,price_close,profit))
      return(EMPTY_VALUE);
//--- 
   return(profit);
  }
*/
/*
//+------------------------------------------------------------------+
//| Функции доступа OrderCalcMargin(...).                           |
//| INPUT:  name            - имя символа,                           |
//|         trade_operation - торговая операция,                       |
//|         volume          - объем открытой позиции,        |
//|         price           - цена открытия позиции.         |
//+------------------------------------------------------------------+
double CAccountInfo::MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 const double volume,const double price) const
  {
   double margin=EMPTY_VALUE;
//--- 
   if(!OrderCalcMargin(trade_operation,symbol,volume,price,margin))
      return(EMPTY_VALUE);
//--- 
   return(margin);
  }
*/
/*
//+------------------------------------------------------------------+
//| Функции доступа OrderCalcMargin(...).                           |
//| INPUT:  name            - имя символа,                           |
//|         trade_operation - торговая операция,                       |
//|         volume          - объем открытой позиции,        |
//|         price           - цена открытия позиции.         |
//+------------------------------------------------------------------+
double CAccountInfo::FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                     const double volume,const double price) const
  {
   return(FreeMargin()-MarginCheck(symbol,trade_operation,volume,price));
  }
*/
/*
//+------------------------------------------------------------------+
//| Функции доступа OrderCalcMargin(...).                           |
//| INPUT:  name            - имя символа,                           |
//|         trade_operation - торговая операция,                       |
//|         price           - цена открытия позиции,         |
//|         percent         - свободная маржа [1-100%].  |
//+------------------------------------------------------------------+
double CAccountInfo::MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 const double price,const double percent) const
  {
   double margin=0.0;
//--- проверки
   if(symbol=="" || price<=0.0 || percent<1 || percent>100)
     {
      Print("CAccountInfo::MaxLotCheck invalid parameters");
      return(0.0);
     }
//--- calculate margin requirements for 1 lot
   if(!OrderCalcMargin(trade_operation,symbol,1.0,price,margin) || margin<0.0)
     {
      Print("CAccountInfo::MaxLotCheck margin calculation failed");
      return(0.0);
     }
//--- 
   if(margin==0.0) // для отложенных ордеров
      return(SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX));
//--- вычисление максимального объема
   double volume=NormalizeDouble(FreeMargin()*percent/100.0/margin,2);
//--- нормализация и проверка лимитов
   double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   if(stepvol>0.0)
      volume=stepvol*MathFloor(volume/stepvol);
//--- 
   double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
   if(volume<minvol)
      volume=0.0;
//--- 
   double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
   if(volume>maxvol)
      volume=maxvol;
//--- возвращается объем 
   return(volume);
  }
*/
//+------------------------------------------------------------------+

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

CExpertTrade

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

Этот класс будет эмулировать члены и методы класса CExpertTrade, находящегося в версии MQL5. Таким образом, при таком исполнении кода:

CExpertTrade trade;
trade.Buy(/*params*/);

обе версии имеют возможность "понять", что мы намерены войти в длинную позицию.

Минимальные требования состоят в том, чтобы эмулировать методы для входа в позицию. Это методы Buy и Sell в классе CExpertTrade. Для выхода из рынка наши имплементации будут расходиться: OrderClose для версии MQL4, в то время как в MQL5 будут использоваться "родные" методы PositionClose (при хеджинге) или Buy и Sell (при неттинге). Нижеследующий код показывает версию класса CExpertTrade для MQL4. Это один из моментов, где код может значительно различаться в исполнении разных программистов:

#include <Arrays\ArrayInt.mqh>
enum ENUM_ORDER_TYPE_TIME
  {
   ORDER_TIME_GTC,
   ORDER_TIME_DAY,
   ORDER_TIME_SPECIFIED,
   ORDER_TIME_SPECIFIED_DAY
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CExpertTrade : public CObject
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
  {
protected:
   int               m_magic;
   ulong             m_deviation;
   ENUM_ORDER_TYPE_TIME m_order_type_time;
   datetime          m_order_expiration;
   bool              m_async_mode;
   uint              m_retry;
   int               m_sleep;
   color             m_color_long;
   color             m_color_short;
   color             m_color_buystop;
   color             m_color_buylimit;
   color             m_color_sellstop;
   color             m_color_selllimit;
   color             m_color_modify;
   color             m_color_exit;
   CSymbolInfo      *m_symbol;
public:
                     CExpertTrade(void);
                    ~CExpertTrade(void);
   //--- методы установки и получения
   color             ArrowColor(const ENUM_ORDER_TYPE type);
   uint              Retry() {return m_retry;}
   void              Retry(uint retry){m_retry=retry;}
   int               Sleep() {return m_sleep;}
   void              Sleep(int sleep){m_sleep=sleep;}
   void              SetAsyncMode(const bool mode) {m_async_mode=mode;}
   void              SetExpertMagicNumber(const int magic) {m_magic=magic;}
   void              SetDeviationInPoints(const ulong deviation) {m_deviation=deviation;}
   void              SetOrderExpiration(const datetime expire) {m_order_expiration=expire;}
   bool              SetSymbol(CSymbolInfo *);
   //-- методы торговли   
   virtual ulong     Buy(const double,const double,const double,const double,const string);
   virtual ulong     Sell(const double,const double,const double,const double,const string);
   virtual bool      OrderDelete(const ulong);
   virtual bool      OrderClose(const ulong,const double,const double);
   virtual bool      OrderCloseAll(CArrayInt *,const bool);
   virtual bool      OrderModify(const ulong,const double,const double,const double,const ENUM_ORDER_TYPE_TIME,const datetime,const double);
   virtual ulong     OrderOpen(const string,const ENUM_ORDER_TYPE,const double,const double,const double,const double,const double,const ENUM_ORDER_TYPE_TIME,const datetime,const string);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTrade::CExpertTrade(void) : m_magic(0),
                                   m_deviation(10),
                                   m_order_type_time(0),
                                   m_symbol(NULL),
                                   m_async_mode(0),
                                   m_retry(3),
                                   m_sleep(100),
                                   m_color_long(clrGreen),
                                   m_color_buystop(clrGreen),
                                   m_color_buylimit(clrGreen),
                                   m_color_sellstop(clrRed),
                                   m_color_selllimit(clrRed),
                                   m_color_short(clrRed),
                                   m_color_modify(clrNONE),
                                   m_color_exit(clrNONE)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTrade::~CExpertTrade(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::SetSymbol(CSymbolInfo *symbol)
  {
   if(symbol!=NULL)
     {
      m_symbol=symbol;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderModify(const ulong ticket,const double price,const double sl,const double tp,
                               const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit=0.0)
  {
   return ::OrderModify((int)ticket,price,sl,tp,expiration,m_color_modify);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderDelete(const ulong ticket)
  {
   return ::OrderDelete((int)ticket);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong CExpertTrade::Buy(const double volume,const double price,const double sl,const double tp,const string comment="")
  {
   if(m_symbol==NULL)
      return false;
   m_symbol.RefreshRates();
   string symbol=m_symbol.Name();
   double stops_level=m_symbol.StopsLevel()*m_symbol.Point();
   double ask=m_symbol.Ask();
   if(symbol=="")
      return 0;
   if(price!=0)
     {
      if(price>ask+stops_level)
         return OrderOpen(symbol,ORDER_TYPE_BUY_STOP,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
      if(price<ask-stops_level)
         return OrderOpen(symbol,ORDER_TYPE_BUY_LIMIT,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
     }
   return OrderOpen(symbol,ORDER_TYPE_BUY,volume,0.0,ask,sl,tp,m_order_type_time,m_order_expiration,comment);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong CExpertTrade::Sell(const double volume,const double price,const double sl,const double tp,const string comment="")
  {
   if(m_symbol==NULL)
      return false;
   m_symbol.RefreshRates();
   string symbol=m_symbol.Name();
   double stops_level=m_symbol.StopsLevel()*m_symbol.Point();
   double bid=m_symbol.Bid();
   if(symbol=="")
      return 0;
   if(price!=0)
     {
      if(price>bid+stops_level)
         return OrderOpen(symbol,ORDER_TYPE_SELL_LIMIT,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
      if(price<bid-stops_level)
         return OrderOpen(symbol,ORDER_TYPE_SELL_STOP,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
     }
   return OrderOpen(symbol,ORDER_TYPE_SELL,volume,0.0,bid,sl,tp,m_order_type_time,m_order_expiration,comment);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong CExpertTrade::OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,
                              const double limit_price,const double price,const double sl,const double tp,
                              const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,
                              const string comment="")
  {
   bool res;
   ulong ticket=0;
   color arrowcolor=ArrowColor(order_type);
   datetime expire=0;
   if(order_type>1 && expiration>0) expire=expiration*1000+TimeCurrent();
   double stops_level=m_symbol.StopsLevel();
   for(uint i=0;i<m_retry;i++)
     {
      if(ticket>0)
         break;
      if(IsStopped())
         return 0;
      if(IsTradeContextBusy() || !IsConnected())
        {
         ::Sleep(m_sleep);
         continue;
        }
      if(stops_level==0 && order_type<=1)
        {
         ticket=::OrderSend(symbol,order_type,volume,price,(int)(m_deviation*m_symbol.Point()),0,0,comment,m_magic,expire,arrowcolor);
         ::Sleep(m_sleep);
         for(uint j=0;j<m_retry;j++)
           {
            if(res) break;
            if(ticket>0 && (sl>0 || tp>0))
              {
               if(OrderSelect((int)ticket,SELECT_BY_TICKET))
                 {
                  res=OrderModify((int)ticket,OrderOpenPrice(),sl,tp,OrderExpiration());
                  ::Sleep(m_sleep);
                 }
              }
           }
        }
      else
        {
         ticket=::OrderSend(symbol,order_type,volume,price,(int)m_deviation,sl,tp,comment,m_magic,expire,arrowcolor);
         ::Sleep(m_sleep);
        }
     }
   return ticket>0?ticket:0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderClose(const ulong ticket,const double lotsize=0.0,const double price=0.0)
  {
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
      return false;
   if(OrderCloseTime()>0)
      return true;
   double close_price=0.0;
   int deviation=0;
   if(OrderSymbol()==m_symbol.Name() && price>0.0)
     {
      close_price=NormalizeDouble(price,m_symbol.Digits());
      deviation=(int)(m_deviation*m_symbol.Point());
     }
   else
     {
      close_price=NormalizeDouble(OrderClosePrice(),(int)MarketInfo(OrderSymbol(),MODE_DIGITS));
      deviation=(int)(m_deviation*MarketInfo(OrderSymbol(),MODE_POINT));
     }
   double lots=(lotsize>0.0 || lotsize>OrderLots())?lotsize:OrderLots();
   return ::OrderClose((int)ticket,lots,close_price,deviation,m_color_exit);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderCloseAll(CArrayInt *other_magic,const bool restrict_symbol=true)
  {
   bool res=true;
   int total= OrdersTotal();
   for(int i=total-1;i>=0;i--)
     {
      double bid=0.0,ask=0.0;
      if(!OrderSelect(i,SELECT_BY_POS)) continue;
      if(OrderSymbol()!=m_symbol.Name() && restrict_symbol) continue;
      if(OrderMagicNumber()!=m_magic && other_magic.Search(OrderMagicNumber())<0) continue;
      m_symbol.RefreshRates();
      if(OrderSymbol()==m_symbol.Name())
        {
         bid = m_symbol.Bid();
         ask = m_symbol.Ask();
        }
      else
        {
         bid = MarketInfo(OrderSymbol(),MODE_BID);
         ask = MarketInfo(OrderSymbol(),MODE_ASK);
        }
      if(res) res=OrderClose(OrderTicket(),OrderLots(),OrderType()==ORDER_TYPE_BUY?bid:ask);
     }
   return res;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
color CExpertTrade::ArrowColor(const ENUM_ORDER_TYPE type)
  {
   switch(type)
     {
      case ORDER_TYPE_BUY:        return m_color_long;
      case ORDER_TYPE_SELL:       return m_color_short;
      case ORDER_TYPE_BUY_STOP:   return m_color_buystop;
      case ORDER_TYPE_BUY_LIMIT:  return m_color_buylimit;
      case ORDER_TYPE_SELL_STOP:  return m_color_sellstop;
      case ORDER_TYPE_SELL_LIMIT: return m_color_selllimit;
     }
   return clrNONE;
  }
//+------------------------------------------------------------------+

Обратите внимание на то, что в версии MQL5 класс CExpertTrade наследуется от класса CTrade (который, в свою очередь, расширяет CObject). С другой стороны, в версии MQL4 класс CExpertTrade напрямую наследуется от CObject. Это означает, что в версии MQL4 классы CTrade и CExpertTrade объединены в один объект.

Некоторые функции — такие, как ORDER_TYPE_TIME — не имеют прямых аналогов в MQL4. Тем не менее, эти торговые классы могут оказаться полезными в случае, если если расширить их и эмулировать время истечения ордера MQL5 в MQL4.

CTradeManager

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

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

Один из способов добиться этого - использование "торгового менеджера". Так же, как и менеджер символов, рассмотренный ранее, этот объект будет расширять класс CArrayObj, и фактически имеет тот же набор методов, что и CSymbolManager. Основной заголовочный файл будет ссылаться на корректный класс-наследник в соответствии с используемым компилятором. Как и менеджер символов, торговый менеджер будет заниматься хранением и извлечением данных. Таким образом, большая часть его кода будет находиться в базовом заголовочном файле. Файловая структура показана на нижеследующем рисунке.

Код базового класса нашего торгового менеджера показан ниже.

(TradeManagerBase.mqh)

#include <Arrays\ArrayObj.mqh>
#include "ExpertTradeXBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CTradeManagerBase : public CArrayObj
  {
public:
                     CTradeManagerBase(void);
                    ~CTradeManagerBase(void);
   virtual int       Search(string);
   virtual bool      Add(CExpertTradeX*);
   virtual void      Deinit(void);
   CExpertTrade     *Get(const string);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTradeManagerBase::CTradeManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTradeManagerBase::~CTradeManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTradeManagerBase::Deinit(void)
  {
   Shutdown();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CTradeManagerBase::Add(CExpertTradeX *node)
  {
   if(Search(node.Name())==-1)
      return CArrayObj::Add(node);
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CTradeManagerBase::Search(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CExpertTradeX *item=At(i);
      if(StringCompare(item.Name(),symbol)==0)
         return i;
     }
   return -1;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTrade *CTradeManagerBase::Get(const string name=NULL)
  {
   if(name==NULL && Total()>0)
      return At(0);
   for(int i=0;i<Total();i++)
     {
      CExpertTradeX *item=At(i);
      if(StringCompare(item.Name(),name)==0)
         return item;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\Trade\TradeManager.mqh"
#else
#include "..\..\MQL4\Trade\TradeManager.mqh"
#endif
//+------------------------------------------------------------------+

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

Подобно менеджеру символов, этот класс также использует пользовательский метод Search для сравнения двух финансовых инструментов (они должны быть уникальными). Сравнение производится с использованием имен финансовых инструментов, которые, в свою очередь, базируются на именах экземпляров класса CSymbolInfo, на которые они ссылаются. Тем не менее, для версии MQL5 объект не возвращает ни указателя на символ, ни имени символа. В этом случае нам нужно расширить CExpertTrade, чтобы позволить экземплярам этого объекта возможность возвращать имя символа, которое оно содержит. Дадим объекту имя CExpertTradeX. Как и остальные классы, описанные в этой статье, он имеет базовый заголовочный файл, который определяет, на какой из заголовочных файлов-наследников ссылаться в зависимости от используемого компилятора:

Нижеследующий фрагмент кода показывает базовую имплементацию вышеописанного класса:

(CExpertTradeXBase.mqh)

#include "ExpertTradeBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CExpertTradeXBase : public CExpertTrade
  {
public:
                     CExpertTradeXBase(void);
                    ~CExpertTradeXBase(void);
   string            Name(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeXBase::CExpertTradeXBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeXBase::~CExpertTradeXBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string CExpertTradeXBase::Name(void)
  {
   if (CheckPointer(m_symbol))
      return m_symbol.Name();
   return NULL;
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\Trade\ExpertTradeX.mqh"
#else 
#include "..\..\MQL4\Trade\ExpertTradeX.mqh"
#endif
//+------------------------------------------------------------------+

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

(CExpertTradeX.mqh, версия для MQL4 и MQL5)

class CExpertTradeX : public CExpertTradeXBase
  {
public:
                     CExpertTradeX(void);
                    ~CExpertTradeX(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeX::CExpertTradeX(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeX::~CExpertTradeX(void)
  {
  }
//+------------------------------------------------------------------+

Заключение

В этой статье мы продемонстрировали, как некоторые компоненты Стандартной библиотеки MQL5 могут быть преобразованы для возможности использования их в советниках, написанных для MQL4, что избавляет нас от необходимости кодировать эти классы с нуля для версии MQL4. Классы CSymbolInfo, CAccount и CExpertTrade были модифицированы, а менеджеры классов — такие как CSymbolManager и CTradeManager — разработаны до возможности позволить им управлять многочисленными экземплярами одного из вышеупомянутых объектов.