LifeHack для трейдера: замешиваем ForEach на дефайнах (#define)
— В чем сила, брат?
—А сила, брат, в дефайнах
(с) 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.mqh, MarketInfo.mqh, Check.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":
В случае с 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, которая по всем параметрам гораздо удобнее.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
К примеру, вот здесь Я вообще дупля не отбиваю)
Было-бы не плохо описать вот такие вещи!
Это многостроковый макрос с пятью входными параметрами. Посмотрите по тексту, с какими параметрами он вызывается.
Он создает соответствующие методы с названиями MT4OrderTicket, MT4OrderClosePrice и т.д. Методы на 95% совпадают по тексту и их много. Чтобы не городить огромную копипасту, где может легко закрасться ошибка, делается макрос. Все компактно и сразу видно.
Более того, такой код подчеркивает, что функции на 95% одинаковые. Т.е. при чтении сразу об этом понимаешь. А вот если бы написать классически, то такой вывод можно было бе сделать только прочитав и проанализировав код 20-ти (там столько их) методов. А это нехилая головная боль. А так все 20 методов на одном экране и все подчеркнуто, что методы почти совпадают. И только входные параметры макросов говорят об отличиях. Вот на эти отличия, а не на мишуру при чтении и обращаешь внимание. Т.е. код сразу показывает главное - ядро, а не муторную оболочку.
Те штуки, которые он проделывает дают нестандартные фишки. Это ценные знания, которыми надо делиться с общественностью!
Ну так он делится. Некоторые его посты и описания к коду в КБ содержательнее половины здешних статей.
Тоже не понимаю, зачем искусственный справочник, если есть живые ответы от него на все задаваемые вопросы.
Лучше бы Ренат избавился от привычки банить его за каждое резкое высказывание.
Сделали бы разработчик в редактор к кнопке "Компилировать" дополнительную команду "сделать копию файла, заменить макросы без компиляции и открыть файл". Специально для изучения макросов от fxsaber.
Ну или же в контекстное меню команду. Ставим мышку на вызов макроса, выбираем команду, в буфер копируется код, получаемый после замены макроса.
Вообще макросы вот так можно понять:
1. Надо отделить имя макроса от его значения. Сначала в строке идет #define, потом пробел, после него это имя (оно с параметрами может быть), потом еще пробел или переход на новую строку и значение макроса.
2. Смотрим как макрос вызывается в коде, какие у него параметры. Делаем себе копию значения макроса и делаем замену параметров на те параметры, с которыми он вызывается где-то в коде.
3. Там, где в коде вызывается макрос заменяем его на то что получилось в п.2.
Примерно так.
Тут нужно кое-что знать: \ - это означает продолжение на новой строке. ## - это соединение строк.Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Особенности языка mql5, тонкости и приёмы работы
fxsaber, 2017.12.05 11:39
В дебаг-режиме нельзя узнать значение, которое возвращает функция или выражение.
Например
Например, что вернули выделенные функции.
Использую (не только в дебаг-режиме) такой способ
Результат
Оформил его в виде mqh.
В качестве примера кроссплатформенный скрипт
Ну и захотелось, допустим, узнать, что возвращает в этом коде OrderClosePrice(). Делаем так
А вот код, если захотелось узнать почти все (такое бывает, когда совсем не понимаешь, где проблема в своем или чужом коде)
Т.е. любое место, значение которого хочется увидеть, облачаем в _P(). Результат
Или, например, есть такое выражение
Нужно быстро врубиться, почему же 19602 выходит. Облачаем быстро интересуемые куски в наш макрос
И видим пошагово результат вычисления
ЗЫ За вместо куска статьи...