LifeHack для трейдера: замешиваем ForEach на дефайнах (#define)

Vladimir Karputov | 13 февраля, 2018

 — В чем сила, брат?                                
  —А сила, брат, в дефайнах                    

                                  (с)  fxsaber                  

Вы все ещё пишете на MQL4 и хотите перейти на MQL5, но не знаете с чего начать? Тогда мы идём к вам! Теперь появилась возможность комфортно работать в редакторе MetaEditor MQL5 и при этом использовать MQL4-нотацию (справедливости ради замечу, что появилась она чуть ранее, а в этой статье я хочу предоставить более полное и развёрнутое описание по переносу функций MQL4 на MQL5).


Хороший программист — ленивый программист

Создание советников или торговых роботов — это почти всегда много работы с циклами. Циклы окружают нас везде: перебор ордеров, сделок в истории, объектов на графике, символов в Обзоре рынка, баров в индикаторном буфере. И чтобы немного облегчить жизнь программиста, в MetaEditor добавлены сниппеты — это когда вы набираете начальные символы, и по нажатию Tab они автоматически разворачиваются в небольшой кусок кода. Вот как работает сниппет для цикла for:


Неплохо, но он не покрывает всех наших потребностей. Самый простой пример: пусть нам требуется перебрать все символы в Обзоре рынка.

   int total=SymbolsTotal(true);
   for(int i=0;i<total;i++)
     {
      string symbol=SymbolName(i,true);
      PrintFormat("%d. %s",i+1,symbol);
     }

Здорово было бы создать в MetaEditor свой сниппет, который начинался бы с fes (for_each_symbol) и разворачивался бы в такой блок:


Но в MetaEditor нет пользовательских сниппетов, поэтому мы пойдем другим путем — возьмемся за дефайны. Макроподстановка #define была придумана ленивыми умными программистами, которые преследовали несколько целей. Среди них —  простота чтения и удобство написания многократно повторяемого кода.

Во многих языках программирования, кроме классического цикла for , есть его разновидности вида for(<typename> element:Collection) или for each (type identifier in expression). Если бы можно было писать код вида

for(ulong order_id in History)
  {
   работа c order_id  
  }

, то жизнь программиста стала бы немного легче. В интернете вы найдете противников и сторонников такого подхода. Здесь я покажу, как сделать что-то подобное с помощью макросов #define.

Начнем с простой задачи — получим имена всех символов в Обзоре рынка. Пойдем в лоб и напишем такой макрос:

#define ForEachSymbol(s,i)  string s; int total=SymbolsTotal(true); for(int i=0;i<total;i++,s=SymbolName(i,true))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachSymbol(symbol,index)
     {
      PrintFormat("%d. %s",index,symbol);
     }
  }

Компилятор отлично понимает эту запись и не выдает ошибок. Запускаем отладку по F5 и видим, что что-то пошло не так:

1. (null)
2. GBPUSD
3. USDCHF
4. USDJPY
...

Проблема в том, что выражение s=SymbolName(i,true) в цикле for вычисляется после выполнения итерации, и наша переменная s никак не инициализирована на первой итерации, когда i=0. Оператор цикла for:

Оператор for состоит из трех выражений и выполняемого оператора:

for(выражение1; выражение2; выражение3)
   оператор;

Выражение1 описывает инициализацию цикла. Выражение2 — проверка условия завершения цикла. Если оно истинно, то выполняется оператор тела цикла for. Все повторяется, пока выражение2 не станет ложным. Если оно ложно, цикл заканчивается и управление передается следующему оператору. ВыражениеЗ вычисляется после каждой итерации.

Это решается просто. Делаем пару правок:

#define ForEachSymbol(s,i)  string s=SymbolName(0,true); int total=SymbolsTotal(true); for(int i=1;i<total;i++,s=SymbolName(i,true))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachSymbol(symbol,index)
     {
      PrintFormat("%d. %s",index,symbol); // вместо index+1 теперь стоит просто index
     }
  }

и получаем нужный результат. Мы написали макрос ForEachSymbol с параметрами symbol и index, как будто это обычный цикл for, и в теле псевдоцикла работали с этими переменными, как будто они уже объявлены и проинициализированы нужными значениями. Таким образом, мы можем получать нужные свойства символов из Обзора рынка c помощью функций вида SymbolInfoXXX(). Например, так:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachSymbol(symbol,index)
     {
      //--- готовим данные
      double spread=SymbolInfoDouble(symbol,SYMBOL_ASK)-SymbolInfoDouble(symbol,SYMBOL_BID);
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      long digits=SymbolInfoInteger(symbol,SYMBOL_DIGITS);
      string str_spread=DoubleToString(spread/point,0);
      string str_point=DoubleToString(point,digits);
      //--- выводим информацию
      Print(index,". ",symbol," spread=",str_spread," points (",
            digits," digits",", point=",str_point,")");
     }
/* Пример вывода
        1. EURUSD spread=3 points (5 digits, point=0.00001)
        2. USDCHF spread=8 points (5 digits, point=0.00001)
        3. USDJPY spread=5 points (3 digits, point=0.001)
        4. USDCAD spread=9 points (5 digits, point=0.00001)
        5. AUDUSD spread=5 points (5 digits, point=0.00001)
        6. NZDUSD spread=10 points (5 digits, point=0.00001)
        7. USDSEK spread=150 points (5 digits, point=0.00001)
*/
  }

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

#define ForEachObject(name,i)   string name=ObjectName(0,0); int total=ObjectsTotal(0); for(int i=1;i<=total;i++,name=ObjectName(0,i-1))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachObject(objectname,index)
     {
      Print(index,": objectname=\"",objectname,"\", objecttype=",
            EnumToString((ENUM_OBJECT)ObjectGetInteger(0,objectname,OBJPROP_TYPE)));
     }
/* Пример вывода
        1: objectname="H1 Arrow 61067", objecttype=OBJ_ARROW_UP
        2: objectname="H1 Rectangle 31152", objecttype=OBJ_RECTANGLE
        3: objectname="H1 StdDev Channel 56931", objecttype=OBJ_STDDEVCHANNEL
        4: objectname="H1 Trendline 6605", objecttype=OBJ_TREND
*/     
  }

Правда, строка определения макроса ForEachObject стала еще немного длиннее, да и понимать код замены, написанный в одну строку, становится не так просто. Но, оказывается, и эта проблема дано уже решена: определение макроса можно делить на строки с помощью обратного слеша '\'. Вот так:

#define ForEachObject(name,i)   string name=ObjectName(0,0);   \
   int ob_total=ObjectsTotal(0);                               \
   for(int i=1;i<=ob_total;i++,name=ObjectName(0,i-1))

Для компилятора все эти три строчки будут выглядеть как одна длинная строка, а вот программисту читать такой код гораздо проще. Осталось создать подобные макросы для работы с торговыми сущностями — ордерами, позициями и сделками.

Начнем с перебора ордеров. Здесь добавляется только функция выбора истории HistorySelect():

#define ForEachOrder(ticket,i)    HistorySelect(0,TimeCurrent());    \
  ulong ticket=OrderGetTicket(0);                                    \ 
  int or_total=OrdersTotal();                                        \
  for(int i=1;i<or_total;i++,ticket=OrderGetTicket(i))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachOrder(orderticket,index)
     {
      Print(index,": #",orderticket," ",OrderGetString(ORDER_SYMBOL)," ",
            EnumToString((ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE)));
     }
/* Пример вывода     
   1: #13965457 CADJPY ORDER_TYPE_SELL_LIMIT
   2: #14246567 AUDNZD ORDER_TYPE_SELL_LIMIT
*/ 
  }

Можно заметить, что в этом макросе (и двух предыдущих) нет обработки ошибок. Например, что если HistorySelect() вернет false? Можно ответить только тем, что в этом случае мы не сможем пройти в цикле по всем ордерам. И кроме того — кто из вас анализирует результат выполнения HistorySelect()? Получается, что в этом макросе нет ничего запрещенного и для обычного способа написания программы.

Но самой сильной критикой использования #define является тот факт, что использование макроподстановок не позволяет провести отладку кода. Я с этим согласен, хотя, как говорит fxsaber, "Хорошо зафиксированный пациент не требует анестезии  отлаженный макрос не требует отладки".

Дальше по аналогии делаем макрос для перебора позиций:

#define ForEachPosition(ticket,i) HistorySelect(0,TimeCurrent());    \
   ulong ticket=PositionGetTicket(0);                                \
   int po_total=PositionsTotal();                                    \
   for(int i=1;i<=po_total;i++,ticket=PositionGetTicket(i-1))        
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachPosition(positionid,index)
     {
      Print(index,": ",PositionGetString(POSITION_SYMBOL)," postionID #",positionid);
     }
/* Пример вывода 
   1: AUDCAD postionID #13234934
   2: EURNZD postionID #13443909
   3: AUDUSD postionID #14956799
   4: EURUSD postionID #14878673
*/ 

Перебор сделок в истории:

#define ForEachDeal(ticket,i) HistorySelect(0,TimeCurrent());        \
   ulong ticket=HistoryDealGetTicket(0);                             \
   int total=HistoryDealsTotal();                                    \
   for(int i=1;i<=total;i++,ticket=HistoryDealGetTicket(i-1))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   //---
   ForEachDeal(dealticket,index)
     {
      Print(index,": deal #",dealticket,",  order ticket=",
            HistoryDealGetInteger(dealticket,DEAL_ORDER));
     }
  }

Перебор ордеров в истории:

#define ForEachHistoryOrder(ticket,i) HistorySelect(0,TimeCurrent());\
   ulong ticket=HistoryOrderGetTicket(0);                            \
   int total=HistoryOrdersTotal();                                   \
   for(int i=1;i<=total;i++,ticket=HistoryOrderGetTicket(i-1))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachHistoryOrder(historyorderticket,index)
     {
      Print(index,": #",historyorderticket);
     }
  }

Соберем все макроподстановки в один файл ForEach.mqh4:

//+------------------------------------------------------------------+
//|                                                      ForEach.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Цикл перебора символов в Обзоре рынка                            |
//+------------------------------------------------------------------+
#define ForEachSymbol(symbol,i)  string symbol=SymbolName(0,true);   \
   int os_total=SymbolsTotal(true);                                  \
   for(int i=1;i<os_total;i++,symbol=SymbolName(i,true))
//+------------------------------------------------------------------+
//| Цикл перебора объектов на главном окне графика                   |
//+------------------------------------------------------------------+   
#define ForEachObject(name,i)   string name=ObjectName(0,0);         \
   int ob_total=ObjectsTotal(0);                                     \
   for(int i=1;i<=ob_total;i++,name=ObjectName(0,i-1))      
//+------------------------------------------------------------------+
//| Цикл перебора действующих ордеров                                |
//+------------------------------------------------------------------+
#define ForEachOrder(ticket,i)    HistorySelect(0,TimeCurrent());    \
   ulong ticket=OrderGetTicket(0);                                   \
   int or_total=OrdersTotal();                                       \
   for(int i=1;i<or_total;i++,ticket=OrderGetTicket(i))   
//+------------------------------------------------------------------+
//| Цикл перебора открытых позиций                                   |
//+------------------------------------------------------------------+
#define ForEachPosition(ticket,i) HistorySelect(0,TimeCurrent());    \
   ulong ticket=PositionGetTicket(0);                                \
   int po_total=PositionsTotal();                                    \
   for(int i=1;i<=po_total;i++,ticket=PositionGetTicket(i-1))      
//+------------------------------------------------------------------+
//| Цикл перебора сделок в истории                                   |
//+------------------------------------------------------------------+
#define ForEachDeal(ticket,i) HistorySelect(0,TimeCurrent());        \
   ulong ticket=HistoryDealGetTicket(0);                             \
   int dh_total=HistoryDealsTotal();                                 \
   for(int i=1;i<=dh_total;i++,ticket=HistoryDealGetTicket(i-1))        
//+------------------------------------------------------------------+
//| Цикл перебора ордеров в истории                                  |
//+------------------------------------------------------------------+
#define ForEachHistoryOrder(ticket,i) HistorySelect(0,TimeCurrent());\
   ulong ticket=HistoryOrderGetTicket(0);                            \
   int oh_total=HistoryOrdersTotal();                                \
   for(int i=1;i<=oh_total;i++,ticket=HistoryOrderGetTicket(i-1))   
//+------------------------------------------------------------------+

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

Кроме того, при использовании параметрического макроса компилятор не дает никаких подсказок, как он это делает для функций. Вам придется заучить эти 6 макросов, если хотите ими пользоваться. Хотя это и не так сложно — первый параметр всегда является сущностью, которую перебирают в цикле, а второй параметр — всегда индекс цикла, который начинается с 1 (единицы).

Напоследок добавим еще макросы для обхода в обратном порядке. В этом случае необходимо двигаться от конца списка к его началу. Добавим к имени макроса суффикс Back и сделаем небольшие правки. Вот как будет выглядеть макрос для обхода символов в обзоре рынка.

#define ForEachSymbolBack(symbol,i) int s_start=SymbolsTotal(true)-1;\
   string symbol=SymbolName(s_start,true);                           \
   for(int i=s_start;i>=0;i--,symbol=SymbolName(i,true))
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   ForEachSymbolBack(symbol,index)
     {
      //--- готовим данные
      double spread=SymbolInfoDouble(symbol,SYMBOL_ASK)-SymbolInfoDouble(symbol,SYMBOL_BID);
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      long digits=SymbolInfoInteger(symbol,SYMBOL_DIGITS);
      string str_spread=DoubleToString(spread/point,0);
      string str_point=DoubleToString(point,digits);
      //--- выводим информацию
      Print(index,". ",symbol," spread=",str_spread," points (",
            digits," digits",", point=",str_point,")");
     }
/* Пример вывода
   3. USDJPY spread=5 points (3 digits, point=0.001)
   2. USDCHF spread=8 points (5 digits, point=0.00001)
   1. GBPUSD spread=9 points (5 digits, point=0.00001)
   0. EURUSD spread=2 points (5 digits, point=0.00001)
*/
  }

Как видите, в этом случае значение переменной index меняется от size-1 до 0. На этом мы заканчиваем знакомство с #define и переходим к написанию функций для простого доступа в MQL4-стиле.


1. Какие группы MQL4 функций описаны в статье

Внимание: Такие переменные, как Point, Digits и Bar Вам нужно самостоятельно менять на Point(), Digits() и Bar(Symbol(),Period())

На MQL5 будут переведены группы MQL4 AccountXXXX, MQL4 MarketInfo, MQL4 Проверка состояния, MQL4 Предопределенные переменные. Таким образом, в папку [date folder]\MQL5\Include\SimpleCall\ добавятся четыре файла: AccountInfo.mqhMarketInfo.mqhCheck.mqh и Predefined.mqh. Всего в папке окажутся семь файлов для перевода MQL4 функций в MQL5: AccountInfo.mqh, Check.mqh, IndicatorsMQL4.mqh, IndicatorsMQL5.mqh, MarketInfo.mqh, Predefined.mqh и Series.mqh.

Для работы вам нужно будет самостоятельно подключать все эти файлы. При этом есть ограничение: файлы IndicatorsMQL4.mqh и IndicatorsMQL5.mqh вместе подключать нельзя — нужно выбирать какой-то один. Поэтому в папке есть два файла: SimpleCallMQL4.mqh — он подключает все файлы плюс IndicatorsMQL4.mqh и SimpleCallMQL5.mqh — он подключает все файлы плюс IndicatorsMQL5.mqh.

Пример подключения на базе MACD Sample.mq4

Копируем файл MACD Sample.mq4 в MQL5-папку с экспертами — например, в [data folder]\MQL5\Experts\, и меняем расширение файла на mq5. Таким образом получаем файл MACD Sample.mq5. Компилируем и сразу получаем 59 ошибок и одно предупреждение.

Теперь подключаем файл SimpleCallMQL4.mqh:

//+------------------------------------------------------------------+
//|                                                  MACD Sample.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
//---
#include <SimpleCall\SimpleCallMQL4.mqh>
//---
input double TakeProfit    =50;

Снова компилируем и получаем уже 39 ошибок и одно предупреждение. Теперь нужна ручная работа по замене (при помощи Ctrl+H) Bars на Bars(Symbol(),Period()) и Point на Point(). Компилируем. Остается 35 ошибок, все они относятся к торговым функциям. Но про торговые функции мы поговорим не в этой статье.

1.1. MQL4 AccountXXXX

Перевод MQL4 AccountXXXX функций выполнен в файле [date folder]\MQL5\Include\SimpleCall\AccountInfo.mqh

Таблица соответствия функций из MQL4 с функциями в MQL5 выглядит так:

MQL4 MQL5 AccountInfoXXXX MQL5 CAccountInfo  Примечания 
 AccountInfoDouble  AccountInfoDouble  CAccountInfo::InfoDouble или CAccountInfo::double методы  
 AccountInfoInteger  AccountInfoInteger  CAccountInfo::InfoInteger или CAccountInfo::целочисленные методы  
 AccountInfoString  AccountInfoString  CAccountInfo::InfoString или CAccountInfo::текстовые методы  
 AccountBalance  AccountInfoDouble(ACCOUNT_BALANCE) или   CAccountInfo::Balance  
 AccountCredit  AccountInfoDouble(ACCOUNT_CREDIT) или   CAccountInfo::Credit  
 AccountCompany  AccountInfoString(ACCOUNT_COMPANY) или   CAccountInfo::Company  
 AccountCurrency  AccountInfoString(ACCOUNT_CURRENCY) или   CAccountInfo::Currency  
 AccountEquity  AccountInfoDouble(ACCOUNT_EQUITY) или   CAccountInfo::Equity  
 AccountFreeMargin  AccountInfoDouble(ACCOUNT_FREEMARGIN) или   CAccountInfo::FreeMargin  
 AccountFreeMarginCheck  --- /---  CAccountInfo::FreeMarginCheck   
 AccountFreeMarginMode  Аналога нет  Аналога нет  
 AccountLeverage  AccountInfoInteger(ACCOUNT_LEVERAGE)   CAccountInfo::Leverage  В MQL4 имеет тип int, в MQL5: long
 AccountMargin  AccountInfoDouble(ACCOUNT_MARGIN)  CAccountInfo::Margin  
 AccountName  AccountInfoString(ACCOUNT_NAME)  CAccountInfo::Name  
 AccountNumber  AccountInfoInteger(ACCOUNT_LOGIN)  CAccountInfo::Login  В MQL4 имеет тип int, в MQL5: long
 AccountProfit  AccountInfoDouble(ACCOUNT_PROFIT)  CAccountInfo::Profit   
 AccountServer  AccountInfoString(ACCOUNT_SERVER)  CAccountInfo::Server  
 AccountStopoutLevel  AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)  CAccountInfo::MarginStopOut  В MQL4 имеет тип int, в MQL5: double
 AccountStopoutMode  AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE)  CAccountInfo::StopoutMode  

Обратите внимание, для MQL4 AccountFreeMarginMode аналога в MQL5 нет. Дальше вы используете MQL4 AccountFreeMarginMode на свой страх и риск. При встрече MQL4 AccountFreeMarginMode в лог будет печататься предупреждение и возвращаться NaN — "не число".

Для остальных MQL4 AccountXXXX функций аналоги есть, причем сразу в двух вариантах: через AccountInfoXXXX или через торговый класс CAccountInfo. Для AccountInfoDouble, AccountInfoInteger и AccountInfoString различий между MQL4 и MQL5 нет.

В шапку файла вынесен такой код. Как это работает?

//---
#define  OP_BUY                     ORDER_TYPE_BUY
#define  OP_SELL                    ORDER_TYPE_SELL
//--- returns balance value of the current account

Для начала заглянем в справку по #define:

Директива #define может быть использована для присвоения мнемонических имен выражениям. Существует две формы:

#define identifier expression                   // беспараметрическая форма 
#define identifier(par1,... par8) expression    // параметрическая форма

Директива #define подставляет expression вместо всех последующих найденных вхождений identifier в исходном тексте.

В случае с нашим кодом это описание будет выглядеть так (мы использовали беспараметрическую форму): директива #define подставляет ORDER_TYPE_BUY вместо всех последующих найденных вхождений OP_BUY в исходном тексте. Директива #define подставляет ORDER_TYPE_SELL вместо всех последующих найденных вхождений OP_SELL в исходном тексте. То есть после того, как вы подключите файл [date folder]\MQL5\Include\SimpleCall\AccountInfo.mqh к своему советнику MQL5, то сможете в нём привычным образом использовать MQL4 типы OP_BUY и OP_SELL. Компилятор не выдаст ошибку.

Итак, первая группа функций, которые мы приведем к MQL5-виду: AccountBalance(), AccountCredit(), AccountCompany(), AccountCurrency(), AccountEquity() и AccountFreeMargin():

//--- returns balance value of the current account
#define  AccountBalance(void)       AccountInfoDouble(ACCOUNT_BALANCE)
//--- returns credit value of the current account
#define  AccountCredit(void)        AccountInfoDouble(ACCOUNT_CREDIT)
//--- returns the brokerage company name where the current account was registered
#define  AccountCompany(void)       AccountInfoString(ACCOUNT_COMPANY)
//--- returns currency name of the current account 
#define  AccountCurrency(void)      AccountInfoString(ACCOUNT_CURRENCY)
//--- returns equity value of the current account
#define  AccountEquity(void)        AccountInfoDouble(ACCOUNT_EQUITY)
//--- returns free margin value of the current account
#define  AccountFreeMargin(void)    AccountInfoDouble(ACCOUNT_MARGIN_FREE)
Здесь в MQL4 XXXX(void) функциях используется уже параметрическая форма #define, где "void" выступает в качестве параметра. Это легко проверить: если убрать "void", то при компиляции получим ошибку "unexpected in macro format parameter list":


unexpected in macro format parameter list

В случае с MQL4 AccountFreeMarginCheck поступим иначе — оформим AccountFreeMarginCheck как обычную функцию, внутри которой используется только MQL5-код:

//--- returns free margin that remains after the specified order has been opened 
//---    at the current price on the current account
double   AccountFreeMarginCheck(string symbol,int cmd,double volume)
  {
   double price=0.0;
   ENUM_ORDER_TYPE trade_operation=(ENUM_ORDER_TYPE)cmd;
   if(trade_operation==ORDER_TYPE_BUY)
      price=SymbolInfoDouble(symbol,SYMBOL_ASK);
   if(trade_operation==ORDER_TYPE_SELL)
      price=SymbolInfoDouble(symbol,SYMBOL_BID);
//---
   double margin_check=EMPTY_VALUE;
   double margin=EMPTY_VALUE;
   margin_check=(!OrderCalcMargin(trade_operation,symbol,volume,price,margin))?EMPTY_VALUE:margin;
//---
   return(AccountInfoDouble(ACCOUNT_FREEMARGIN)-margin_check);
  }

Как было сказано выше, для AccountFreeMarginMode нет аналога в MQL5, поэтому при встрече в коде AccountFreeMarginMode будет выдано предупреждение и возвращено "не число":

//--- returns the calculation mode of free margin allowed to open orders on the current account
double AccountFreeMarginMode(void)
  {
   string text="MQL4 functions \"AccountFreeMarginMode()\" has no analogs in MQL5. Returned \"NAN - not a number\"";
   Alert(text);
   Print(text);
   return(double("nan"));
  }

Для остальных MQL4-функций поступаем по аналогии с предыдущими:

//--- returns leverage of the current account
#define  AccountLeverage(void)      (int)AccountInfoInteger(ACCOUNT_LEVERAGE)
//--- returns margin value of the current account
#define  AccountMargin(void)        AccountInfoDouble(ACCOUNT_MARGIN)
//--- returns the current account name
#define  AccountName(void)          AccountInfoString(ACCOUNT_NAME)
//--- returns the current account number
#define  AccountNumber(void)        (int)AccountInfoInteger(ACCOUNT_LOGIN)
//--- returns profit value of the current account
#define  AccountProfit(void)        AccountInfoDouble(ACCOUNT_PROFIT)
//--- returns the connected server name
#define  AccountServer(void)        AccountInfoString(ACCOUNT_SERVER)
//--- returns the value of the Stop Out level
#define  AccountStopoutLevel(void)  (int)AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)
//--- returns the calculation mode for the Stop Out level
int      AccountStopoutMode(void)
  {
   ENUM_ACCOUNT_STOPOUT_MODE stopout_mode=(ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
   if(stopout_mode==ACCOUNT_STOPOUT_MODE_PERCENT)
      return(0);
   return(1);
  }

1.2. MQL4 MarketInfo

Перевод MQL4 MarketInfotXXXX функций выполнен в файле [date folder]\MQL5\Include\SimpleCall\MarketInfo.mqh

MQL4 MarketInfo имеет тип double, но в MQL5 аналоги MarketInfo есть и в SymbolInfoInteger() (возвращает тип long), и в SymbolInfoDouble() (возвращает тип double). Здесь в глаза бросается вся неуклюжесть использования устаревшей функции MarketInfo: преобразование типов. На примере преобразования MQL5 SYMBOL_DIGITS:

MarketInfo(Symbol(),MODE_DIGITS) <= (double)SymbolInfoInteger(symbol,SYMBOL_DIGITS)

1.2.1 Неоднозначность с MQL4 MODE_TRADEALLOWED 

В MQL4 это простой флаг bool, тогда как в MQL5 для символа может быть несколько видов разрешений/запретов:

ENUM_SYMBOL_TRADE_MODE

Идентификатор Описание
 SYMBOL_TRADE_MODE_DISABLED  Торговля по символу запрещена
 SYMBOL_TRADE_MODE_LONGONLY  Разрешены только покупки
 SYMBOL_TRADE_MODE_SHORTONLY  Разрешены только продажи
 SYMBOL_TRADE_MODE_CLOSEONLY  Разрешены только операции закрытия позиций
 YMBOL_TRADE_MODE_FULL  Нет ограничений на торговые операции

Предлагаю отдавать false только в случае SYMBOL_TRADE_MODE_DISABLED, а для частичных ограничений или для полного доступа — true (при этом выдавать Alert и Print с предупреждением о частичном ограничении).

1.2.2. Неоднозначность с MQL4 MODE_SWAPTYPE

В MQL4 MODE_SWAPTYPE возвращает всего четыре значения (метод вычисления свопов. 0 — в пунктах; 1 — в базовой валюте инструмента; 2 — в процентах; 3 — в валюте залоговых средств), тогда как в MQL5 перечисление ENUM_SYMBOL_SWAP_MODE содержит девять значений и они пересекаются по значениям в MQL4:

MQL4 MODE_SWAPTYPE MQL5 ENUM_SYMBOL_SWAP_MODE
 Нет соответствия  SYMBOL_SWAP_MODE_DISABLED
 0 - в пунктах  SYMBOL_SWAP_MODE_POINTS
 1 - в базовой валюте инструмента  SYMBOL_SWAP_MODE_CURRENCY_SYMBOL
 3 - в валюте залоговых средств  SYMBOL_SWAP_MODE_CURRENCY_MARGIN
 Нет соответствия  SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT
 2 - в процентах  SYMBOL_SWAP_MODE_INTEREST_CURRENT
 2 - в процентах  SYMBOL_SWAP_MODE_INTEREST_OPEN
 Нет соответствия  SYMBOL_SWAP_MODE_REOPEN_CURRENT
 Нет соответствия  SYMBOL_SWAP_MODE_REOPEN_BID

В MQL5 для расчёта свопов в процентах может быть два варианта:

/*
SYMBOL_SWAP_MODE_INTEREST_CURRENT
Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа(банковский режим – 360 дней в году)
SYMBOL_SWAP_MODE_INTEREST_OPEN
Свопы начисляются в годовых процентах от цены открытия позиции по символу (банковский режим – 360 дней в году)
*/

Будем считать их за один тип. Для остальных MQL5-вариантов, которые не имеют аналога в MQL4, будет выдаваться предупреждение и возвращаться "не число".

1.2.3. Неоднозначность с MQL4 MODE_PROFITCALCMODE и MODE_MARGINCALCMODE

В MQL4 MODE_PROFITCALCMODE (способ расчета прибыли) возвращает всего три значения: 0 — Forex; 1 — CFD; 2 — Futures, а MODE_MARGINCALCMODE (способ расчета залоговых средств) — четыре: 0 — Forex; 1 — CFD; 2 — Futures; 3 — CFD на индексы. В MQL5 для инструмента можно определить способ расчёта залоговых средств (перечисление ENUM_SYMBOL_CALC_MODE), а вот способа расчёта прибыли нет. Предполагаю, что в MQL5 способ расчёта залоговых средств равен способу расчёта прибыли, поэтому для MQL4 MODE_PROFITCALCMODE  и MODE_MARGINCALCMODE будет возвращаться одинаковое значение с MQL5 ENUM_SYMBOL_CALC_MODE.

Перечисление MQL5 ENUM_SYMBOL_CALC_MODE содержит десять способов. Сопоставим MQL4 MODE_PROFITCALCMODE c MQL5 ENUM_SYMBOL_CALC_MODE:

MQL4 MODE_PROFITCALCMODE "Forex" <==> MQL5 SYMBOL_CALC_MODE_FOREX

MQL4 MODE_PROFITCALCMODE "CFD" <==> MQL5 SYMBOL_CALC_MODE_CFD

MQL4 MODE_PROFITCALCMODE "Futures" <==> MQL5 SYMBOL_CALC_MODE_FUTURES

Для остальных MQL5 вариантов, которые не имеют аналога в MQL4, будет выдаваться предупреждение и возвращаться "не число":

...
#define MODE_PROFITCALCMODE   1000//SYMBOL_TRADE_CALC_MODE
#define MODE_MARGINCALCMODE   1001//SYMBOL_TRADE_CALC_MODE
...
      case MODE_PROFITCALCMODE:
        {
         ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE);
         switch(profit_calc_mode)
           {
            case  SYMBOL_CALC_MODE_FOREX:
               return((double)0);
            case  SYMBOL_CALC_MODE_FUTURES:
               return((double)2);
            case  SYMBOL_CALC_MODE_CFD:
               return((double)1);
            default :
              {
               string text="MQL4 MODE_PROFITCALCMODE returned MQL5 "+EnumToString(profit_calc_mode);
               Alert(text);
               Print(text);
               return(double("nan"));
              }
           }
        }
      case MODE_MARGINCALCMODE:
        {
         ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE);
         switch(profit_calc_mode)
           {
            case  SYMBOL_CALC_MODE_FOREX:
               return((double)0);
            case  SYMBOL_CALC_MODE_FUTURES:
               return((double)2);
            case  SYMBOL_CALC_MODE_CFD:
               return((double)1);
            default :
              {
               string text="MQL4 MODE_MARGINCALCMODE returned MQL5 "+EnumToString(profit_calc_mode);
               Alert(text);
               Print(text);
               return(double("nan"));
              }
           }
        }

1.3. MQL4 Проверка состояния

Перевод MQL4-функций проверки состояния выполнен в файле [date folder]\MQL5\Include\SimpleCall\Check.mqh

Сюда входят такие элементы:

MQL4 MQL5 MQL5 classes Примечение
 Digits

 MQL4 позволяет одновременно использовать и Digits и Digits()
 Point

 MQL4 позволяет одновременно использовать и Point и Point()
 IsConnected  TerminalInfoInteger(TERMINAL_CONNECTED)  CTerminalInfo::IsConnected 
 IsDemo  AccountInfoInteger(ACCOUNT_TRADE_MODE)  CAccountInfo::TradeMode  Возвращается одно из значений из перечисления ENUM_ACCOUNT_TRADE_MODE
 IsDllsAllowed  TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) и MQLInfoInteger(MQL_DLLS_ALLOWED)  CTerminalInfo::IsDLLsAllowed   TERMINAL_DLLS_ALLOWED - имеет наивысший статус, и MQL_DLLS_ALLOWED можно игнорировать
 IsExpertEnabled  TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)   CTerminalInfo::IsTradeAllowed  Состояние кнопки "Автоторговля" в терминале
 IsLibrariesAllowed  MQLInfoInteger(MQL_DLLS_ALLOWED)  -/-  Проверка бессмысленна: если программа использует DLL и вы ей не разрешаете их использовать (вкладка "Зависимости" программы), то вы просто не запустите программу.
 IsOptimization  MQLInfoInteger(MQL_OPTIMIZATION)  -/-  MQL5-программа может иметь четыре режима: отладки, профилирования кода, тестера, оптимизации
 IsTesting  MQLInfoInteger(MQL_TESTER)  -/-  MQL5-программа может иметь четыре режима: отладки, профилирования кода, тестера, оптимизации
 IsTradeAllowed  MQLInfoInteger(MQL_TRADE_ALLOWED)  -/-  Состояние чекбокса "Разрешить автоматическую торговлю" в свойствах программы
 IsTradeContextBusy  -/-  -/-  Будет возвращено "false"
 IsVisualMode  MQLInfoInteger(MQL_VISUAL_MODE)
 MQL5-программа может иметь четыре режима: отладки, профилирования кода, тестера, оптимизации
 TerminalCompany  TerminalInfoString(TERMINAL_COMPANY)  CTerminalInfo::Company  
 TerminalName  TerminalInfoString(TERMINAL_NAME)  CTerminalInfo::Name  
 TerminalPath  TerminalInfoString(TERMINAL_PATH)  CTerminalInfo::Path  

Первые два пункта (Digits и Point) полностью нереализуемы из-за того, что здесь напрочь перепутаны MQL4 и MQL5. А именно: в MQL4 может одновременно встречаться и Digits и Digits() и Point и Point(). Например, лично я не знаю способа, каким образом через define можно Digits и Digits() свести к Digits(). Остальные пункты однозначно встречаются в виде XXXX() и могут быть заменены MQL5-аналогами.

И сама реализация:

//+------------------------------------------------------------------+
//|                                                        Check.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://wmua.ru/slesar/"
#property version   "1.003" 
//--- checks connection between client terminal and server
#define IsConnected        (bool)TerminalInfoInteger(TERMINAL_CONNECTED)
//--- checks if the Expert Advisor runs on a demo account
#define IsDemo             (bool)(AccountInfoInteger(ACCOUNT_TRADE_MODE)==(ENUM_ACCOUNT_TRADE_MODE)ACCOUNT_TRADE_MODE_DEMO)
//--- checks if the DLL function call is allowed for the Expert Advisor
#define IsDllsAllowed      (bool)TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) 
//--- checks if Expert Advisors are enabled for running
#define IsExpertEnabled    (bool)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 
//--- checks if the Expert Advisor can call library function
#define IsLibrariesAllowed (bool)MQLInfoInteger(MQL_DLLS_ALLOWED)
//--- checks if Expert Advisor runs in the Strategy Tester optimization mode
#define IsOptimization     (bool)MQLInfoInteger(MQL_OPTIMIZATION)
//--- checks if the Expert Advisor runs in the testing mode
#define IsTesting                (bool)MQLInfoInteger(MQL_TESTER)
//--- checks if the Expert Advisor is allowed to trade and trading context is not busy 
#define IsTradeAllowed     (bool)MQLInfoInteger(MQL_TRADE_ALLOWED)
//--- returns the information about trade context 
#define IsTradeContextBusy  false
//--- checks if the Expert Advisor is tested in visual mode 
#define IsVisualMode          (bool)MQLInfoInteger(MQL_VISUAL_MODE)
//--- returns the name of company owning the client terminal 
#define TerminalCompany    TerminalInfoString(TERMINAL_COMPANY)
//--- returns client terminal name
#define TerminalName          TerminalInfoString(TERMINAL_NAME)
//--- returns the directory, from which the client terminal was launched
#define TerminalPath          TerminalInfoString(TERMINAL_PATH)
//+------------------------------------------------------------------+

1.4. MQL4 Предопределенные переменные

Реализация в файле [data folder]\MQL5\Include\SimpleCall\Predefined.mqh

MQL4 предопределённые переменные:

Предопределённые переменные _XXXX переводятся в функции MQL5 при помощи беспараметрической формы #define:

//--- the _Digits variable stores number of digits after a decimal point,
#define _Digits         Digits()
//--- the _Point variable contains the point size of the current symbol in the quote currency
#define _Point          Point()
//--- the _LastError variable contains code of the last error
#define _LastError      GetLastError()
//--- the _Period variable contains the value of the timeframe of the current chart
#define _Period         Period()
//#define _RandomSeed
//--- the _StopFlag variable contains the flag of the program stop
#define _StopFlag       IsStopped()
//--- the _Symbol variable contains the symbol name of the current chart
#define _Symbol         Symbol()
//--- the _UninitReason variable contains the code of the program uninitialization reason
#define _UninitReason   UninitializeReason()
//#define Bars            Bars(Symbol(),Period());
//#define Digits
//#define Point

Исключение только для "_RandomSeed" — в этой переменной хранится текущее состояние генератора псевдослучайных целых чисел, и для Bars тоже нет перевода в MQL5 (точнее, Bars оставлен для ручной замены). Для Digits и Point решения нет. Как уже говорилось выше, в тексте могут встречаться одновременно и Digits и Digits(), и Point и Point().

MQL4 Ask и Bid заменяются на пользовательские (самописные) функции GetAsk() и GetBid():

//--- the latest known seller's price (ask price) for the current symbol
#define Ask             GetAsk()
//--- the latest known buyer's price (offer price, bid price) of the current symbol
#define Bid             GetBid()
...
//--- the latest known seller's price (ask price) for the current symbol                  
double GetAsk()
  {
   MqlTick tick;
   SymbolInfoTick(Symbol(),tick);
   return(tick.ask);
  }
//--- the latest known buyer's price (offer price, bid price) of the current symbol             
double GetBid()
  {
   MqlTick tick;
   SymbolInfoTick(Symbol(),tick);
   return(tick.bid);
  }

Мы, конечно, могли бы написать макрос для Ask проще:

#define Ask SymbolInfoDouble(__Symbol,SYMBOL_ASK)

Но в примечании к SymbolInfoDouble сказан:

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

В большинстве случаев достаточно использовать функцию SymbolInfoTick(), которая позволяет получить за один вызов значения Ask, Bid, Last, Volume и время прихода последнего тика.

А теперь кое-что новенькое: используем "operator"

Ключевое слово operator будем использовать для перегрузки (переназначения) оператора индексации []. Это понадобится для перевода MQL4 массивов-таймсерий (Open[], High[], Low[], Close[], Time[], Volume[]) в MQL5 форму.

Что нам говорит справка по "operator":

Перегрузка операций позволяет использовать операционную нотацию (запись в виде простых выражений) к сложным объектам - структурам и классам.

Исходя из справки, можно предположить, что для перегрузки оператора индексации [] нам понадобится создать класс.

Помня о #define:

Директива #define может быть использована для присвоения мнемонических имен выражениям. Существует две формы:

#define identifier expression                   // беспараметрическая форма 
#define identifier(par1,... par8) expression    // параметрическая форма

Директива #define подставляет expression вместо всех последующих найденных вхождений identifier в исходном тексте.

следующий код можно прочитать как: директива #define подставит 159 вместо всех последующих найденных вхождений в исходном тексте.

#define SeriesVolume(Volume,T) 159
//+-----------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   long a=SeriesVolume(Volume,long);
  }

То есть код в OnStart преобразовывается к виду:

   long a=159;

Шаг 2

Здесь внутрь #define поместили целый класс

//+------------------------------------------------------------------+
//|                                                      Test_en.mq5 |
//|                                      Copyright 2012, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#define SeriesVolume(Volume,T) class CVolume       \
  {                                                \
  public:                                          \
    T operator[](const int i) const                \
    {                                              \
    long val[1];                                   \
    if(CopyTickVolume(Symbol(),Period(),i,1,val)==1)\
      return(val[0]);                              \
    else                                           \
      return(-1);                                  \
    }                                              \
  };                                               \
CVolume Volume;
//---
SeriesVolume(Volume,long)
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   Print(Volume[4]);
  }
//+------------------------------------------------------------------+

Эту замену можно представить так:

//+------------------------------------------------------------------+
//|                                                      Test_en.mq5 |
//|                                      Copyright 2012, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
class CVolume      
  {                                                
  public:                                          
    long operator[](const int i) const                
    {                                              
    long val[1];                                   
    if(CopyTickVolume(Symbol(),Period(),i,1,val)==1)
      return(val[0]);                              
    else                                           
      return(-1);                                  
    }                                              
  };                                               
CVolume Volume;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   Print(Volume[4]);
  }

То есть в OnStart мы, по сути, обращаемся к объекту Volume класса CVolume, к методу [], в котором передаём индекс i. По такому же принципу пишем для MQL4-серий Open, High, Low, Close и Time.

И напоследок у нас остаются MQL4 iXXX функции: iOpen, iHigh, iLow, iClose, iTime и iVolume. Для них применим метод объявления пользовательской функции.

Пример для iClose:

//--- returns Close price value for the bar of specified symbol with timeframe and shift
double   iClose(
                string                    symbol,              // symbol
                ENUM_TIMEFRAMES           timeframe,           // timeframe
                int                       shift                // shift
                )
  {
   double result=0.0;
//---
   double val[1];
   ResetLastError();
   int copied=CopyClose(symbol,timeframe,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyClose error=",GetLastError());
//---
   return(result);
  }

2. Изменения в других файлах

В [data folder]\MQL5\Include\SimpleCall\IndicatorsMQL4.mqh все MQL4-названия линий теперь прописаны в шапке:

double NaN=double("nan");
#define MODE_MAIN          0   
#define MODE_SIGNAL        1
#define MODE_PLUSDI        1
#define MODE_MINUSDI       2  
#define MODE_GATORJAW      1
#define MODE_GATORTEETH    2
#define MODE_GATORLIPS     3 
#define MODE_UPPER         1
#define MODE_LOWER         2  
#define MODE_TENKANSEN     1     
#define MODE_KIJUNSEN      2                                   
#define MODE_SENKOUSPANA   3                                 
#define MODE_SENKOUSPANB   4                                 
#define MODE_CHIKOUSPAN    5   

В [data folder]\MQL5\Include\SimpleCall\Series.mqh убраны 

#define MODE_OPEN    0
//#define MODE_LOW     1
//#define MODE_HIGH    2
#define MODE_CLOSE   3
#define MODE_VOLUME  4
//#define MODE_TIME    5

Теперь они прописаны в [data folder]\MQL5\Include\SimpleCall\Header.mqh :

//+------------------------------------------------------------------+
//|                                                       Header.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://wmua.ru/slesar/"
//---
#define MODE_LOW     10001
#define MODE_HIGH    10002
#define MODE_TIME    10005

и iClose, iHigh, iLow, iOpen, iTime и iVolume — теперь они прописаны в [data folder]\MQL5\Include\SimpleCall\Predefined.mqh


Заключение

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

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

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

Минусы:

Плюсы

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