English 中文 Español Deutsch 日本語 Português
LifeHack для трейдера: замешиваем ForEach на дефайнах (#define)

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

MetaTrader 5Примеры | 13 февраля 2018, 14:02
4 696 56
Vladimir Karputov
Vladimir Karputov

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

                                  (с)  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

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

  • Digits
  • Point
  • IsConnected
  • IsDemo
  • IsDllsAllowed
  • IsExpertEnabled
  • IsLibrariesAllowed
  • IsOptimization
  • IsTesting
  • IsTradeAllowed
  • IsTradeContextBusy
  • IsVisualMode
  • TerminalCompany
  • TerminalName
  • TerminalPath
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 предопределённые переменные:

  • _Digits
  • _Point
  • _LastError
  • _Period
  • _RandomSeed
  • _StopFlag
  • _Symbol
  • _UninitReason
  • Ask
  • Bars
  • Bid
  • Close
  • Digits
  • High
  • Low
  • Open
  • Point
  • Time
  • Volume

Предопределённые переменные _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. Но и этот вопрос при желании решается. Подводя итоги, повторим еще раз плюсы и минусы этого подхода:

Минусы:

  • ограничение в обработке возвращаемой ошибки при доступе к индикаторам;
  • падение скорости тестирования при одновременном доступе более чем к одному индикатору;
  • необходимость правильно указывать линии индикаторов в зависимости от подключения "IndicatorsMQL5.mqh" или "IndicatorsMQL4.mqh";
  • невозможность отладки макроподстановки #define;
  • нет всплывающей подсказки об аргументах параметрического #define;
  • потенциальные коллизии переменных, спрятанными за макросами.
Плюсы
  • простота написания кода — одна строка вместо нескольких;
  • наглядность и краткость — чем меньше кода, тем проще его понимать;
  • макроподстановки подсвечиваются в редакторе красным цветом — легче видеть пользовательские идентификаторы и функции;
  • можно создавать свои суррогаты сниппетов.

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



Прикрепленные файлы |
ForEachSymbol.mq5 (3.74 KB)
ForEachObject.mq5 (2.82 KB)
ForEachOrder.mq5 (2.59 KB)
ForEachDeal.mq5 (1.24 KB)
SimpleCall.zip (25.38 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (56)
fxsaber
fxsaber | 28 авг. 2018 в 21:42
Vitaly Muzichenko:

К примеру, вот здесь Я вообще дупля не отбиваю)

Было-бы не плохо описать вот такие вещи!

Это многостроковый макрос с пятью входными параметрами. Посмотрите по тексту, с какими параметрами он вызывается.

Он создает соответствующие методы с названиями MT4OrderTicket, MT4OrderClosePrice и т.д. Методы на 95% совпадают по тексту и их много. Чтобы не городить огромную копипасту, где может легко закрасться ошибка, делается макрос. Все компактно и сразу видно.

Более того, такой код подчеркивает, что функции на 95% одинаковые. Т.е. при чтении сразу об этом понимаешь. А вот если бы написать классически, то такой вывод можно было бе сделать только прочитав и проанализировав код 20-ти (там столько их) методов. А это нехилая головная боль. А так все 20 методов на одном экране и все подчеркнуто, что методы почти совпадают. И только входные параметры макросов говорят об отличиях. Вот на эти отличия, а не на мишуру при чтении и обращаешь внимание. Т.е. код сразу показывает главное - ядро, а не муторную оболочку.

Andrey Khatimlianskii
Andrey Khatimlianskii | 28 авг. 2018 в 21:43
Vasiliy Sokolov:

Те штуки, которые он проделывает дают нестандартные фишки. Это ценные знания, которыми надо делиться с общественностью! 

Ну так он делится. Некоторые его посты и описания к коду в КБ содержательнее половины здешних статей.

Тоже не понимаю, зачем искусственный справочник, если есть живые ответы от него на все задаваемые вопросы.

Лучше бы Ренат избавился от привычки банить его за каждое резкое высказывание.

Dmitry Fedoseev
Dmitry Fedoseev | 29 авг. 2018 в 00:54

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

Ну или же в контекстное меню команду. Ставим мышку на вызов макроса, выбираем команду, в буфер копируется код, получаемый после замены макроса. 

Dmitry Fedoseev
Dmitry Fedoseev | 29 авг. 2018 в 01:01

Вообще макросы вот так можно понять:

1. Надо отделить имя макроса от его значения. Сначала в строке идет #define, потом пробел, после него это имя (оно с параметрами может быть), потом еще пробел или переход на новую строку и значение макроса.

2. Смотрим как макрос вызывается в коде, какие у него параметры. Делаем себе копию значения макроса и делаем замену параметров на те параметры, с которыми он вызывается где-то в коде.

3. Там, где в коде вызывается макрос заменяем его на то что получилось в п.2.

Примерно так.

Тут нужно кое-что знать: \ - это означает продолжение на новой строке. ## - это соединение строк.
fxsaber
fxsaber | 29 авг. 2018 в 09:16
Самый часто применяемый мною макрос

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Особенности языка mql5, тонкости и приёмы работы

fxsaber, 2017.12.05 11:39

В дебаг-режиме нельзя узнать значение, которое возвращает функция или выражение.

Например

void OnStart()
{
  double Angle = 1;
  double d = MathSin(Angle / 2) * MathSin(Angle * 2);
}

Например, что вернули выделенные функции.


Использую (не только в дебаг-режиме) такой способ

template <typename T>
T MyPrint( const T Value, const string Str )
{
  static const bool IsDebug = MQLInfoInteger(MQL_DEBUG);

//  if (IsDebug)
  {
//    DebugBreak(); // если хочется посмотреть средствами дебага

    Print(Str + " = " + (string)Value);
  }
  
  return(Value);
}

#define _P(A) MyPrint(A, __FUNCSIG__ ", Line = " + (string)__LINE__ + ": " + #A)

void OnStart()
{
  double Angle = 1;
  double d = _P(MathSin(Angle / 2)) * _P(MathSin(Angle * 2));
}


Результат

void OnStart(), Line = 21: MathSin(Angle/2) = 0.479425538604203
void OnStart(), Line = 21: MathSin(Angle*2) = 0.9092974268256817

Оформил его в виде mqh.

В качестве примера кроссплатформенный скрипт

#include <MT4Orders.mqh>

#define Ask SymbolInfoDouble(_Symbol, SYMBOL_ASK)

void OnStart()
{
  // Открывает и закрывает BUY-позицию
  if (OrderSelect(OrderSend(_Symbol, OP_BUY, 1, Ask, 100, 0, 0), SELECT_BY_TICKET))
    OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 100);
}


Ну и захотелось, допустим, узнать, что возвращает в этом коде OrderClosePrice(). Делаем так

OrderClose(OrderTicket(), OrderLots(), _P(OrderClosePrice()), 100);


А вот код, если захотелось узнать почти все (такое бывает, когда совсем не понимаешь, где проблема в своем или чужом коде)

#include <MT4Orders.mqh>

#define Ask SymbolInfoDouble(_Symbol, SYMBOL_ASK)

#include <Debug.mqh> // https://c.mql5.com/3/173/Debug.mqh

void OnStart()
{
  // Открывает и закрывает BUY-позицию
  if (_P(OrderSelect(_P(OrderSend(_P(_Symbol), _P(OP_BUY), 1, _P(Ask), 100, 0, 0)), _P(SELECT_BY_TICKET))))
    _P(OrderClose(_P(OrderTicket()), _P(OrderLots()), _P(OrderClosePrice()), 100));
}

Т.е. любое место, значение которого хочется увидеть, облачаем в _P(). Результат

void OnStart(), Line = 10: SELECT_BY_TICKET = 1
void OnStart(), Line = 10: Ask = 1.16688
void OnStart(), Line = 10: OP_BUY = 0
void OnStart(), Line = 10: _Symbol = EURUSD
void OnStart(), Line = 10: OrderSend(_P(_Symbol),_P(OP_BUY),1,_P(Ask),100,0,0) = 293785198
void OnStart(), Line = 10: OrderSelect(_P(OrderSend(_P(_Symbol),_P(OP_BUY),1,_P(Ask),100,0,0)),_P(SELECT_BY_TICKET)) = true
void OnStart(), Line = 11: OrderClosePrice() = 1.16678
void OnStart(), Line = 11: OrderLots() = 1.0
void OnStart(), Line = 11: OrderTicket() = 293785198
void OnStart(), Line = 11: OrderClose(_P(OrderTicket()),_P(OrderLots()),_P(OrderClosePrice()),100) = true


Или, например, есть такое выражение

void OnStart()
{
  int a = 137;
  double b = 1.37;
  
  int Num = ((a = (int)(a / b)) << 1) * a; // 19602
}

Нужно быстро врубиться, почему же 19602 выходит. Облачаем быстро интересуемые куски в наш макрос

int Num = _P(_P(((a = _P((int)(_P(a / b)))) << 1)) * _P(a));


И видим пошагово результат вычисления

void OnStart(), Line = 8: a/b = 99.99999999999999
void OnStart(), Line = 8: (int)(_P(a/b)) = 99
void OnStart(), Line = 8: ((a=_P((int)(_P(a/b))))<<1) = 198
void OnStart(), Line = 8: a = 99
void OnStart(), Line = 8: _P(((a=_P((int)(_P(a/b))))<<1))*_P(a) = 19602


ЗЫ За вместо куска статьи...

ZUP - зигзаг универсальный с паттернами Песавенто. Поиск паттернов ZUP - зигзаг универсальный с паттернами Песавенто. Поиск паттернов
Индикаторная платформа ZUP позволяет производить поиск множества известных паттернов, параметры которых уже заданы. Но можно также и подстраивать эти параметры в соответствии со своими требованиями. Есть и возможность создавать новые паттерны с помощью графического интерфейса ZUP и сохранять их параметры в файл. После этого можно быстро проверить, встречаются ли новые паттерны на графиках.
Глубокие нейросети (Часть V). Байесовская  оптимизация гиперпараметров DNN Глубокие нейросети (Часть V). Байесовская оптимизация гиперпараметров DNN
В статье рассматриваются возможности байесовской оптимизации гиперпараметров глубоких нейросетей, полученных различными вариантами обучения. Сравнивается качество классификации DNN с оптимальными гиперпараметрами при различных вариантах обучения. Форвард-тестами проверена глубина эффективности оптимальных гиперпараметров DNN. Определены возможные направления улучшения качества классификации.
Визуализируем оптимизацию торговой стратегии в MetaTrader 5 Визуализируем оптимизацию торговой стратегии в MetaTrader 5
В статье реализовано MQL-приложение с графическим интерфейсом для расширенной визуализации процесса оптимизации. Графический интерфейс создан с помощью последней версии библиотеки EasyAndFast. У многих пользователей возникает вопрос, зачем нужны графические интерфейсы в MQL-приложениях. В настоящей статье продемонстрирован один из множества случаев, когда они могут быть полезными для трейдеров.
LifeHack для трейдера: готовим фастфуд из индикаторов LifeHack для трейдера: готовим фастфуд из индикаторов
Если вы переходите на MQL5 только сейчас, то эта статья вам пригодится: с одной стороны, доступ к данным индикаторов и к сериям выполнен в привычном вам MQL4-стиле, с другой — вся реализация этой простоты написана на MQL5. Все функции максимально понятны и отлично подходят для пошаговой отладки.