Содержание
Концепция
Ну что ж... В нашем торговом классе мы уже сделали контроль допустимых параметров
терминала, аккаунта и символа для торговли, автоматическую коррекцию ошибочно заданных
параметров торгового приказа, и теперь нам осталось сделать обработку ответов сервера на подготовленный и отправленный торговый
приказ.
После того как мы отправили торговый приказ на сервер, не стоит считать, что "дело сделано", а нужно поглядеть что ж мы там в
ответ на торговый запрос получили. Сервер нам возвращает коды ошибок, ну или отсутствие ошибок. И вот эти-то коды нам необходимо получить и
обработать в случае, если сервер вернул ошибку.
Обрабатывать будем точно таким же образом, как обрабатывали неверные параметры
торгового приказа:
- Ошибки нет — ордер успешно поставлен в очередь на исполнение,
- Запретить торговлю экспертом — например, полный запрет со стороны сервера на торговые операции,
- Выйти из торгового метода — например, нет никакой возможности добиться успешной отправки ордера на сервер, или
позиция уже закрыта или отложенный ордер удалён,
- Скорректировать параметры торгового запроса и повторить — есть некоторые ошибочные значения в параметрах
торгового приказа; скорее всего за время подготовки запроса на сервер, данные изменились, и теперь необходимо их скорректировать,
- Обновить данные и повторить — данные на сервере изменились, но корректировки значений торгового запроса не
требуется,
- Подождать и повторить — требуется некоторое ожидание, например, при близости цены к одному из стоп-уровней позиции
параметр FreezeLevel запрещает модификацию, так как стоп-приказ уже может сработать. Ожидание позволяет дождаться либо
срабатывания стоп-приказа и отмены торгового запроса, либо ухода цены из зоны заморозки и успешной отправки ордера на сервер,
- Создать отложенный запрос — об этом в следующей статье.
Только кодов возврата больше, чем мы делали ранее при исправлении возможных ошибок в торговом приказе, и не каждый код можно скорректировать и
повторить запрос. Но для исключения исправимых ошибок, мы будем стараться их обработать и отослать заново торговый приказ.
В методах отправки торговых запросов, после предварительной проверки ограничений и ошибок в торговом приказе, организуем цикл для
повторных отправок торгового приказа на сервер. Т.е. если после первого запроса на сервер получили ошибку, то будем отправлять торговый
приказ столько раз, сколько установлено торговых попыток для торгового класса — либо до успешной отправки приказа на сервер, либо до
окончания количества попыток.
После неудачного завершения всех попыток отправки ордера на сервер из торгового метода будем
возвращать false, и в вызывающей программе в данном случае сможем посмотреть код
последней ошибки, возвращённой торговым сервером для самостоятельного принятия решения по обработке данной ошибки.
Закончим с теорией и начнём.
Реализация
В класс аккаунта CAccount в файле Account.mqh в раздел упрощённого доступа к свойствам объекта-аккаунта
добавим
метод, возвращающий флаг работы на счёте с типом хедж:
ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const { return (ENUM_ACCOUNT_TRADE_MODE)this.GetProperty(ACCOUNT_PROP_TRADE_MODE); }
ENUM_ACCOUNT_STOPOUT_MODE MarginSOMode(void) const { return (ENUM_ACCOUNT_STOPOUT_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_SO_MODE); }
ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const { return (ENUM_ACCOUNT_MARGIN_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_MODE); }
long Login(void) const { return this.GetProperty(ACCOUNT_PROP_LOGIN); }
long Leverage(void) const { return this.GetProperty(ACCOUNT_PROP_LEVERAGE); }
long LimitOrders(void) const { return this.GetProperty(ACCOUNT_PROP_LIMIT_ORDERS); }
long TradeAllowed(void) const { return this.GetProperty(ACCOUNT_PROP_TRADE_ALLOWED); }
long TradeExpert(void) const { return this.GetProperty(ACCOUNT_PROP_TRADE_EXPERT); }
long CurrencyDigits(void) const { return this.GetProperty(ACCOUNT_PROP_CURRENCY_DIGITS); }
long ServerType(void) const { return this.GetProperty(ACCOUNT_PROP_SERVER_TYPE); }
long FIFOClose(void) const { return this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE); }
bool IsHedge(void) const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; }
В файл Defines.mqh добавим макроподстановку для указания количества
торговых попыток по умолчанию для торгового класса.
Так как сегодня дополнительно подготовим базу для создания отложенных
запросов, и нам потребуется таймер для торгового класса,
то сразу же впишем
параметры таймера торгового класса:
#define DFUN_ERR_LINE (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN (__FUNCTION__+": ")
#define COUNTRY_LANG ("Russian")
#define END_TIME (D'31.12.3000 23:59:59')
#define TIMER_FREQUENCY (16)
#define TOTAL_TRY (5)
#define SND_ALERT "alert.wav"
#define SND_ALERT2 "alert2.wav"
#define SND_CONNECT "connect.wav"
#define SND_DISCONNECT "disconnect.wav"
#define SND_EMAIL "email.wav"
#define SND_EXPERT "expert.wav"
#define SND_NEWS "news.wav"
#define SND_OK "ok.wav"
#define SND_REQUEST "request.wav"
#define SND_STOPS "stops.wav"
#define SND_TICK "tick.wav"
#define SND_TIMEOUT "timeout.wav"
#define SND_WAIT "wait.wav"
#define COLLECTION_ORD_PAUSE (250)
#define COLLECTION_ORD_COUNTER_STEP (16)
#define COLLECTION_ORD_COUNTER_ID (1)
#define COLLECTION_ACC_PAUSE (1000)
#define COLLECTION_ACC_COUNTER_STEP (16)
#define COLLECTION_ACC_COUNTER_ID (2)
#define COLLECTION_SYM_PAUSE1 (100)
#define COLLECTION_SYM_COUNTER_STEP1 (16)
#define COLLECTION_SYM_COUNTER_ID1 (3)
#define COLLECTION_SYM_PAUSE2 (300)
#define COLLECTION_SYM_COUNTER_STEP2 (16)
#define COLLECTION_SYM_COUNTER_ID2 (4)
#define COLLECTION_REQ_PAUSE (300)
#define COLLECTION_REQ_COUNTER_STEP (16)
#define COLLECTION_REQ_COUNTER_ID (5)
#define COLLECTION_HISTORY_ID (0x7779)
#define COLLECTION_MARKET_ID (0x777A)
#define COLLECTION_EVENTS_ID (0x777B)
#define COLLECTION_ACCOUNT_ID (0x777C)
#define COLLECTION_SYMBOLS_ID (0x777D)
#define DIRECTORY ("DoEasy\\")
#define RESOURCE_DIR ("DoEasy\\Resource\\")
#define CLR_DEFAULT (0xFF000000)
#define SYMBOLS_COMMON_TOTAL (1000)
В список флагов методов обработки ошибок торгового сервера добавим два флага — флаг
ошибки в цене отложенного ордера и флаг ошибки в цене стоп-лимитного
ордера, а в методы обработки ошибок и кодов возврата торгового сервера добавим метод
коррекции параметров торгового приказа:
enum ENUM_TRADE_REQUEST_ERR_FLAGS
{
TRADE_REQUEST_ERR_FLAG_NO_ERROR = 0,
TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = 1,
TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = 2,
TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = 4,
TRADE_REQUEST_ERR_FLAG_PRICE_ERROR = 8,
TRADE_REQUEST_ERR_FLAG_LIMIT_ERROR = 16,
};
enum ENUM_ERROR_CODE_PROCESSING_METHOD
{
ERROR_CODE_PROCESSING_METHOD_OK,
ERROR_CODE_PROCESSING_METHOD_DISABLE,
ERROR_CODE_PROCESSING_METHOD_EXIT,
ERROR_CODE_PROCESSING_METHOD_CORRECT,
ERROR_CODE_PROCESSING_METHOD_REFRESH,
ERROR_CODE_PROCESSING_METHOD_PENDING,
ERROR_CODE_PROCESSING_METHOD_WAIT,
};
В файле Datas.mqh впишем индексы новых сообщений:
MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED,
MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED,
MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED,
MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED,
MSG_LIB_TEXT_REQUEST_REJECTED_DUE,
MSG_LIB_TEXT_INVALID_REQUEST,
MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR,
MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED,
MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME,
MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME,
MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED,
MSG_LIB_TEXT_INVALID_VOLUME_STEP,
MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL,
MSG_LIB_TEXT_SL_LESS_STOP_LEVEL,
MSG_LIB_TEXT_TP_LESS_STOP_LEVEL,
MSG_LIB_TEXT_PRICE_LESS_STOP_LEVEL,
MSG_LIB_TEXT_LIMIT_LESS_STOP_LEVEL,
MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL,
MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL,
MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL,
MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE,
MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE,
MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE,
MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE,
MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ,
MSG_LIB_TEXT_TRADING_DISABLE,
MSG_LIB_TEXT_TRADING_OPERATION_ABORTED,
MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST,
MSG_LIB_TEXT_CREATE_PENDING_REQUEST,
MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT,
MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ,
MSG_LIB_TEXT_TRY_N,
};
и тексты этих сообщений:
{"Дистанция установки ордера в пунктах меньше разрешённой параметром StopLevel символа","The distance to place an order in points is less than the symbol allowed by the StopLevel parameter"},
{"Дистанция установки лимит-ордера относительно стоп-ордера меньше разрешённой параметром StopLevel символа","The distance to place the limit order relative to the stop order is less than the symbol allowed by the StopLevel parameter"},
{"Дистанция от цены до StopLoss меньше разрешённой параметром FreezeLevel символа","The distance from the price to StopLoss is less than the symbol allowed by the FreezeLevel parameter"},
{"Дистанция от цены до TakeProfit меньше разрешённой параметром FreezeLevel символа","The distance from the price to TakeProfit is less than the symbol allowed by the FreezeLevel parameter"},
{"Дистанция от цены до цены срабатывания ордера меньше разрешённой параметром FreezeLevel символа","The distance from the price to the order triggering price is less than the symbol allowed by the FreezeLevel parameter"},
{"Неподдерживаемый тип параметра StopLoss (необходимо int или double)","Unsupported StopLoss parameter type (int or double required)"},
{"Неподдерживаемый тип параметра TakeProfit (необходимо int или double)","Unsupported TakeProfit parameter type (int or double required)"},
{"Неподдерживаемый тип параметра цены (необходимо int или double)","Unsupported price parameter type (int or double required)"},
{"Неподдерживаемый тип параметра цены limit-ордера (необходимо int или double)","Unsupported type of price parameter for limit order (int or double required)"},
{"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"},
{"Торговля отключена для эксперта до устранения причины запрета","Trading for the expert is disabled until this ban is eliminated"},
{"Торговая операция прервана","Trading operation aborted"},
{"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."},
{"Создание отложенного запроса","Create a pending request"},
{"Нет возможности скорректировать лот","There is no possibility to correct the lot"},
{"Не удалось создать отложенный запрос","Failed to create pending request"},
{"Торговая попытка #","Trading attempt #"},
};
В файл базового торгового объекта TradeObj.mqh внесены незначительные изменения.
В метод установки
отложенного ордера добавлен параметр типа ордера по исполнению
(почему-то забыл про него и сразу не сделал — использовался установленный по умолчанию):
bool SetOrder(const ENUM_ORDER_TYPE type,
const double volume,
const double price,
const double sl=0,
const double tp=0,
const double price_stoplimit=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
Теперь, если передано значение больше, чем -1, то будет
использовано переданное в метод значение, иначе — значение
параметра по умолчанию:
bool CTradeObj::SetOrder(const ENUM_ORDER_TYPE type,
const double volume,
const double price,
const double sl=0,
const double tp=0,
const double price_stoplimit=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
::ResetLastError();
if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY
#ifdef __MQL4__ || type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT #endif )
{
this.m_result.retcode=MSG_LIB_SYS_INVALID_ORDER_TYPE;
this.m_result.comment=CMessage::Text(this.m_result.retcode);
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),OrderTypeDescription(type));
return false;
}
::ZeroMemory(this.m_request);
::ZeroMemory(this.m_result);
this.m_request.action = TRADE_ACTION_PENDING;
this.m_request.symbol = this.m_symbol;
this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic);
this.m_request.volume = volume;
this.m_request.type = type;
this.m_request.stoplimit = price_stoplimit;
this.m_request.price = price;
this.m_request.sl = sl;
this.m_request.tp = tp;
this.m_request.expiration = expiration;
this.m_request.type_time = (type_time>WRONG_VALUE ? type_time : this.m_type_time);
this.m_request.type_filling= (type_filling>WRONG_VALUE ? type_filling : this.m_type_filling);
this.m_request.comment = (comment==NULL ? this.m_comment : comment);
#ifdef __MQL5__
return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result));
#else
::ResetLastError();
int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE);
::SymbolInfoTick(this.m_symbol,this.m_tick);
if(ticket!=WRONG_VALUE)
{
this.m_result.retcode=::GetLastError();
this.m_result.ask=this.m_tick.ask;
this.m_result.bid=this.m_tick.bid;
this.m_result.order=ticket;
this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price);
this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume);
this.m_result.comment=CMessage::Text(this.m_result.retcode);
return true;
}
else
{
this.m_result.retcode=::GetLastError();
this.m_result.ask=this.m_tick.ask;
this.m_result.bid=this.m_tick.bid;
this.m_result.comment=CMessage::Text(this.m_result.retcode);
return false;
}
#endif
}
Также были исправлены цены в торговых приказах — ранее было так, что если график строится по ценам Last, то и цена в торговом приказе
устанавливалась Ask и Last. Теперь всегда Ask и Bid — независимо от цен построения графика.
Остальные мелкие изменения можно увидеть в прикреплённых в конце статьи файлах — они незначительны, и останавливаться на них здесь не имеет
смысла.
В файле Trading.mqh торгового класса CTrading в его приватную секцию впишем список
отложенных запросов и переменную для хранения количества торговых попыток:
class CTrading
{
private:
CAccount *m_account;
CSymbolsCollection *m_symbols;
CMarketCollection *m_market;
CHistoryCollection *m_history;
CArrayObj m_list_request;
CArrayInt m_list_errors;
bool m_is_trade_disable;
bool m_use_sound;
uchar m_total_try;
ENUM_LOG_LEVEL m_log_level;
MqlTradeRequest m_request;
ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags;
ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior;
В списке торговых запросов в последующем будем хранить объекты класса отложенного запроса, а в
переменную m_total_try впишем количество торговых попыток, которое задано
по умолчанию для торгового класса в его конструкторе:
CTrading::CTrading()
{
this.m_list_errors.Clear();
this.m_list_errors.Sort();
this.m_list_request.Clear();
this.m_list_request.Sort();
this.m_total_try=TOTAL_TRY;
this.m_log_level=LOG_LEVEL_ALL_MSG;
this.m_is_trade_disable=false;
this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT;
::ZeroMemory(this.m_request);
}
Здесь же очищаем список отложенных запросов и устанавливаем ему флаг
сортированного списка.
В параметры метода проверки цены относительно уровня StopLevel добавим
цену установки лимит-ордера для ордера с типом StopLimit:
bool CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0);
И в сам метод добавим проверку:
bool CTrading::CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0)
{
double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point();
double pr=(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid());
return
(limit==0 ?
(
order_type==ORDER_TYPE_SELL_STOP ||
order_type==ORDER_TYPE_SELL_STOP_LIMIT ||
order_type==ORDER_TYPE_BUY_LIMIT ? price<(pr-lv) :
order_type==ORDER_TYPE_BUY_STOP ||
order_type==ORDER_TYPE_BUY_STOP_LIMIT ||
order_type==ORDER_TYPE_SELL_LIMIT ? price>(pr+lv) :
true
) :
(
order_type==ORDER_TYPE_BUY_STOP_LIMIT ? limit<(price-lv) :
order_type==ORDER_TYPE_SELL_STOP_LIMIT ? limit>(price+lv) :
true
)
);
}
Здесь: если цена лимит-ордера равна нулю, значит проверяем
цены стоп- и лимит-ордеров, иначе — проверяем цены стоплимит-ордеров
(цену установки лимитного ордера относительно цены установки стоп-ордера, по которой срабатывает стоплимит-ордер).
В метод, возвращающий способ обработки ошибок будем передавать код
ошибки, и в метод корректировки ошибок добавим дополнительно
указатель на торговый объект:
ENUM_ERROR_CODE_PROCESSING_METHOD ResultProccessingMethod(const uint result_code);
ENUM_ERROR_CODE_PROCESSING_METHOD RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj);
Так как у нас множество методов открытия позиций и установки ордеров, то все они получились практически одинаковыми. Разница лишь в типах
открываемых позиций и устанавливаемых ордеров.
Для того, чтобы не писать один и тот же код для каждого метода,
объявим
и далее реализуем два приватных метода — для открытия позиции и для
установки отложенных ордеров:
template<typename SL,typename TP>
bool OpenPosition(const ENUM_POSITION_TYPE type,
const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
const SL sl=0,
const TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX);
template<typename PS,typename PL,typename SL,typename TP>
bool PlaceOrder( const ENUM_ORDER_TYPE order_type,
const double volume,
const string symbol,
const PS price_stop,
const PL price_limit=0,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
public:
В публичной секции класса объявим таймер, который нам
потребуется для работы с классом отложенных запросов, метод, возвращающий список
отложенных запросов, и метод, устанавливающий количество торговых
попыток:
public:
CTrading();
void OnTimer(void);
void OnInit(CAccount *account,CSymbolsCollection *symbols,CMarketCollection *market,CHistoryCollection *history)
{
this.m_account=account;
this.m_symbols=symbols;
this.m_market=market;
this.m_history=history;
}
CArrayInt *GetListErrors(void) { return &this.m_list_errors; }
CArrayObj *GetListRequests(void) { return &this.m_list_request;}
void SetTotalTry(const uchar number) { this.m_total_try=number; }
Дополним спецификацию метода для закрытия позиций закрываемым объёмом,
по умолчанию WRONG_VALUE — полное
закрытие позиции, иначе — частичное закрытие на указанный объём:
bool ClosePosition(const ulong ticket,const double volume=WRONG_VALUE,const string comment=NULL,const ulong deviation=ULONG_MAX);
В спецификациях методов установки отложенных ордеров допишем типы
исполнения ордеров по остатку — ранее всегда использовалось значение, заданное по умолчанию для класса. Теперь же будет
выбираться значение типа исполнения ордера исходя из переданного в метод значения — если WRONG_VALUE,
то по умолчанию заданное значение, иначе — переданное в метод:
template<typename PS,typename SL,typename TP>
bool PlaceBuyStop(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PS,typename SL,typename TP>
bool PlaceBuyLimit(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PS,typename PL,typename SL,typename TP>
bool PlaceBuyStopLimit(const double volume,
const string symbol,
const PS price_stop,
const PL price_limit,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PS,typename SL,typename TP>
bool PlaceSellStop(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PS,typename SL,typename TP>
bool PlaceSellLimit(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PS,typename PL,typename SL,typename TP>
bool PlaceSellStopLimit(const double volume,
const string symbol,
const PS price_stop,
const PL price_limit,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PS,typename PL,typename SL,typename TP>
bool ModifyOrder(const ulong ticket,
const PS price=WRONG_VALUE,
const SL sl=WRONG_VALUE,
const TP tp=WRONG_VALUE,
const PL limit=WRONG_VALUE,
datetime expiration=WRONG_VALUE,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
Напишем реализацию таймера — пока лишь просто заготовку обработки списка отложенных запросов:
void CTrading::OnTimer(void)
{
int total=this.m_list_request.Total();
for(int i=total-1;i>WRONG_VALUE;i--)
{
}
}
Реализация метода, возвращающего способы обработки кодов возврата торгового сервера:
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code)
{
switch(result_code)
{
#ifdef __MQL4__
case 9 :
case 64 :
case 65 : return ERROR_CODE_PROCESSING_METHOD_DISABLE;
case 1 :
case 2 :
case 5 :
case 7 :
case 132 :
case 133 :
case 139 :
case 140 :
case 148 :
case 149 :
case 150 : return ERROR_CODE_PROCESSING_METHOD_EXIT;
case 3 :
case 129 :
case 130 :
case 131 :
case 134 :
case 147 : return ERROR_CODE_PROCESSING_METHOD_CORRECT;
case 4 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 6 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 8 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;
case 136 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 137 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 141 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;
case 145 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 146 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000;
case 128 :
case 135 :
case 138 : return ERROR_CODE_PROCESSING_METHOD_REFRESH;
#else
case 10026 : return ERROR_CODE_PROCESSING_METHOD_DISABLE;
case 10007 :
case 10012 :
case 10017 :
case 10018 :
case 10023 :
case 10025 :
case 10028 :
case 10032 :
case 10033 :
case 10034 :
case 10035 :
case 10036 :
case 10039 :
case 10040 :
case 10041 :
case 10042 :
case 10043 :
case 10044 :
case 10045 : return ERROR_CODE_PROCESSING_METHOD_EXIT;
case 10004 :
case 10006 :
case 10020 : return ERROR_CODE_PROCESSING_METHOD_REFRESH;
case 10013 :
case 10014 :
case 10015 :
case 10016 :
case 10019 :
case 10022 :
case 10030 :
case 10038 : return ERROR_CODE_PROCESSING_METHOD_CORRECT;
case 10021 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 10024 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;
case 10029 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;
case 10011 : return ERROR_CODE_PROCESSING_METHOD_PENDING;
case 10027 : return ERROR_CODE_PROCESSING_METHOD_PENDING;
case 10031 : return ERROR_CODE_PROCESSING_METHOD_PENDING;
case 10008 :
case 10009 :
case 10010 :
#endif
default:
break;
}
return ERROR_CODE_PROCESSING_METHOD_OK;
}
Здесь всё просто: в метод передаётся код, полученный от сервера после отправки на
него торгового запроса, и далее те коды, при получении которых есть возможность исправить ошибку, будут обрабатывться методом
исправления ошибки, коды, треюущие обновления данных и повторной отправки запроса, будут обрабатываться соответственно, и т.д.
Так как MQL5- и MQL4-серверы возвращают разные коды ошибок, то и в методе организована условная компиляция для MQL4
и MQL5.
Все коды, требующие однотипной обработки, сгруппированы
в единый case оператора switch, и
возвращают единый для них метод обработки кода возврата торгового сервера.
Реализация метода обработки ошибок торгового сервера:
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request,
const ENUM_ORDER_TYPE order_type,
const uint spread_multiplier,
CSymbol *symbol_obj,
CTradeObj *trade_obj)
{
int total=this.m_list_errors.Total();
if(total==0)
return ERROR_CODE_PROCESSING_METHOD_OK;
if(this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY))
{
trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_CLOSEONLY);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_MARKET_ORDER_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_LIMIT_ORDER_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_STOP_ORDER_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY))
{
trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_SHORTONLY);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY))
{
trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_LONGONLY);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_CLOSE_BY_ORDER_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(10033))
{
trade_obj.SetResultRetcode(10033);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
if(this.IsPresentErorCode(10034))
{
trade_obj.SetResultRetcode(10034);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
double price_set=(this.IsPresentErrorFlag(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR) ? request.price : request.stoplimit);
if(this.IsPresentErorCode(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL))
request.sl=this.CorrectStopLoss(order_type,price_set,request.sl,symbol_obj,spread_multiplier);
if(this.IsPresentErorCode(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL))
request.tp=this.CorrectTakeProfit(order_type,price_set,request.tp,symbol_obj,spread_multiplier);
double shift=0;
if(this.IsPresentErrorFlag(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR))
{
price_set=request.price;
request.price=this.CorrectPricePending(order_type,price_set,0,symbol_obj,spread_multiplier);
shift=request.price-price_set;
if(request.stoplimit==0)
{
if(request.sl>0)
request.sl=this.CorrectStopLoss(order_type,request.price,request.sl+shift,symbol_obj,spread_multiplier);
if(request.tp>0)
request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp+shift,symbol_obj,spread_multiplier);
}
}
if(this.IsPresentErorCode(10030))
request.type_filling=symbol_obj.GetCorrectTypeFilling();
if(this.IsPresentErorCode(10022))
{
if(!symbol_obj.IsExpirationModeSpecified() && request.expiration>0)
request.expiration=0;
}
for(int i=0;i<total;i++)
{
int err=this.m_list_errors.At(i);
if(err==NULL)
continue;
switch(err)
{
case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME :
case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME :
case MSG_LIB_TEXT_INVALID_VOLUME_STEP : request.volume=symbol_obj.NormalizedLot(request.volume); break;
case MSG_SYM_SL_ORDER_DISABLED : request.sl=0; break;
case MSG_SYM_TP_ORDER_DISABLED : request.tp=0; break;
case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR : request.volume=this.CorrectVolume(request.price,order_type,symbol_obj,DFUN);
if(request.volume==0)
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_EXIT; break;
}
case 10021 : trade_obj.SetResultRetcode(10021);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case 10031 : trade_obj.SetResultRetcode(10031);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL :
case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL :
case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;
default:
break;
}
}
trade_obj.SetResultRetcode(0);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
return ERROR_CODE_PROCESSING_METHOD_OK;
}
В листинге метода в комментариях к коду расписаны все действия по обработке ошибок, возвращаемых торговым сервером.
Реализация приватного метода для открытия позиции:
template<typename SL,typename TP>
bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type,
const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
const SL sl=0,
const TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX)
{
bool res=true;
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type;
ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type;
CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
if(symbol_obj==NULL)
{
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
return false;
}
CTradeObj *trade_obj=symbol_obj.GetTradeObj();
if(trade_obj==NULL)
{
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
return false;
}
if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj))
{
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
trade_obj.SetResultRetcode(10021);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(10021));
return false;
}
this.m_request.volume=volume;
ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp);
if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
{
if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
if(this.IsUseSounds())
trade_obj.PlaySoundError(action,order_type);
return false;
}
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
{
int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
if(code!=NULL)
{
trade_obj.SetResultRetcode(code);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
}
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
if(this.IsUseSounds())
trade_obj.PlaySoundError(action,order_type);
return false;
}
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
{
int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
if(code!=NULL)
{
trade_obj.SetResultRetcode(code);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
}
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
::Sleep(method);
symbol_obj.Refresh();
}
if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
}
}
for(int i=0;i<this.m_total_try;i++)
{
res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);
if(res || trade_obj.IsAsyncMode())
{
if(this.IsUseSounds())
trade_obj.PlaySoundSuccess(action,order_type);
return true;
}
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
if(this.IsUseSounds())
trade_obj.PlaySoundError(action,order_type);
method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
{
this.SetTradingDisableFlag(true);
break;
}
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
{
break;
}
if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT)
{
this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj);
continue;
}
if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
{
symbol_obj.Refresh();
continue;
}
if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
{
::Sleep(method);
continue;
}
if(method==ERROR_CODE_PROCESSING_METHOD_PENDING)
{
break;
}
}
}
return res;
}
Данный метод подробно прокомментирован прямо в листинге и будет использоваться для открытия позиций
Buy
и
Sell:
template<typename SL,typename TP>
bool CTrading::OpenBuy(const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
const SL sl=0,
const TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX)
{
return this.OpenPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,comment,deviation);
}
template<typename SL,typename TP>
bool CTrading::OpenSell(const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
const SL sl=0,
const TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX)
{
return this.OpenPosition(POSITION_TYPE_SELL,volume,symbol,magic,sl,tp,comment,deviation);
}
В этих методах просто вызывается общий приватный метод для открытия позиции с
указанием типа открываемой позиции.
Реализация приватного метода для установки отложенных ордеров:
template<typename PS,typename PL,typename SL,typename TP>
bool CTrading::PlaceOrder(const ENUM_ORDER_TYPE order_type,
const double volume,
const string symbol,
const PS price_stop,
const PL price_limit=0,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
bool res=true;
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type;
CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
if(symbol_obj==NULL)
{
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
return false;
}
CTradeObj *trade_obj=symbol_obj.GetTradeObj();
if(trade_obj==NULL)
{
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
return false;
}
if(!this.SetPrices(order_type,price_stop,sl,tp,price_limit,DFUN,symbol_obj))
{
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
trade_obj.SetResultRetcode(10021);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(10021));
return false;
}
this.m_request.volume=volume;
this.m_request.type_filling=type_filling;
this.m_request.type_time=type_time;
this.m_request.expiration=expiration;
ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,
this.m_request.price,
action,
order_type,
symbol_obj,
trade_obj,
DFUN,
this.m_request.stoplimit,
this.m_request.sl,
this.m_request.tp);
if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
{
if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
if(this.IsUseSounds())
trade_obj.PlaySoundError(action,order_type);
return false;
}
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
{
int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
if(code!=NULL)
{
trade_obj.SetResultRetcode(code);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
}
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
if(this.IsUseSounds())
trade_obj.PlaySoundError(action,order_type);
return false;
}
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
{
int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
if(code!=NULL)
{
trade_obj.SetResultRetcode(code);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
}
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
::Sleep(method);
symbol_obj.Refresh();
}
if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
}
}
for(int i=0;i<this.m_total_try;i++)
{
res=trade_obj.SetOrder(order_type,
this.m_request.volume,
this.m_request.price,
this.m_request.sl,
this.m_request.tp,
this.m_request.stoplimit,
magic,
comment,
this.m_request.expiration,
this.m_request.type_time,
this.m_request.type_filling);
if(res || trade_obj.IsAsyncMode())
{
if(this.IsUseSounds())
trade_obj.PlaySoundSuccess(action,order_type);
return true;
}
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
if(this.IsUseSounds())
trade_obj.PlaySoundError(action,order_type);
method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
{
this.SetTradingDisableFlag(true);
break;
}
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
{
break;
}
if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT)
{
this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj);
continue;
}
if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
{
symbol_obj.Refresh();
continue;
}
if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
{
Sleep(method);
continue;
}
if(method==ERROR_CODE_PROCESSING_METHOD_PENDING)
{
break;
}
}
}
return res;
}
Данный метод подробно прокомментирован прямо в листинге и будет использоваться для установки различных
типов отложенных ордеров:
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceBuyStop(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.PlaceOrder(ORDER_TYPE_BUY_STOP,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceBuyLimit(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.PlaceOrder(ORDER_TYPE_BUY_LIMIT,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PS,typename PL,typename SL,typename TP>
bool CTrading::PlaceBuyStopLimit(const double volume,
const string symbol,
const PS price_stop,
const PL price_limit,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
#ifdef __MQL5__
return this.PlaceOrder(ORDER_TYPE_BUY_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling);
#else
return true;
#endif
}
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceSellStop(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.PlaceOrder(ORDER_TYPE_SELL_STOP,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceSellLimit(const double volume,
const string symbol,
const PS price,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.PlaceOrder(ORDER_TYPE_SELL_LIMIT,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PS,typename PL,typename SL,typename TP>
bool CTrading::PlaceSellStopLimit(const double volume,
const string symbol,
const PS price_stop,
const PL price_limit,
const SL sl=0,
const TP tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const datetime expiration=0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
#ifdef __MQL5__
return this.PlaceOrder(ORDER_TYPE_SELL_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling);
#else
return true;
#endif
}
Остальные методы для закрытия позиций и удаления отложенных ордеров, и методы модификации позиций и ордеров сделаны аналогично приватным
методам для открытия позиций/установки отложенных ордеров. Все коды методов подробно прокомментированы, и их можно будет изучить
самостоятельно — все файлы приложены в конце статьи.
С торговым классом на данном этапе мы завершили.
Теперь необходимо внести некоторые изменения в класс основного объекта библиотеки CEngine.
При плавающей величине минимального уровня установки стоп-приказов и отложенных ордеров (StopLevel) нам нужно задавать множитель
спреда — потому что часто при таком положении вещей используется спред, помноженный на некоторую величину, для указания разрешённой
дистанции установки стопов. Исходя из этого, нам необходим метод, который позволит задать множитель спреда для его указания в торговом
классе.
В публичной секции класса объявим такой метод:
void SetSpreadMultiplier(const uint value=1,const string symbol=NULL) { this.m_trading.SetSpreadMultiplier(value,symbol); }
Метод просто вызывает одноимённый метод торгового класса, рассмотренный нами ранее в
прошлой статье, и позволяет задать как один общий множитель для всех используемых символов, так и индивидуальные множители для
указанных символов.
Так как торговый класс в скором времени будет использовать таймер для работы с отложенными запросами, то
в
конструкторе класса CEngine создадим новый счётчик таймера для торгового класса:
CEngine::CEngine() : m_first_start(true),
m_last_trade_event(TRADE_EVENT_NO_EVENT),
m_last_account_event(WRONG_VALUE),
m_last_symbol_event(WRONG_VALUE),
m_global_error(ERR_SUCCESS)
{
this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
this.m_list_counters.Sort();
this.m_list_counters.Clear();
this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
::ResetLastError();
#ifdef __MQL5__
if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
{
::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
this.m_global_error=::GetLastError();
}
#else
if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
{
::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
this.m_global_error=::GetLastError();
}
#endif
}
В таймер класса CEngine впишем блок работы с таймером торгового класса:
void CEngine::OnTimer(void)
{
int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL)
{
if(!this.IsTester())
{
if(counter.IsTimeDone())
this.TradeEventsControl();
}
else
this.TradeEventsControl();
}
}
index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL)
{
if(!this.IsTester())
{
if(counter.IsTimeDone())
this.AccountEventsControl();
}
else
this.AccountEventsControl();
}
}
index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL)
{
if(!this.IsTester())
{
if(counter.IsTimeDone())
this.m_symbols.RefreshRates();
}
else
this.m_symbols.RefreshRates();
}
}
index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL)
{
if(!this.IsTester())
{
if(counter.IsTimeDone())
{
this.SymbolEventsControl();
if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
this.MarketWatchEventsControl();
}
}
else
this.SymbolEventsControl();
}
}
index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL)
{
if(!this.IsTester())
{
if(counter.IsTimeDone())
this.m_trading.OnTimer();
}
else
this.m_trading.OnTimer();
}
}
}
Чуть изменим метод для полного закрытия позиции:
bool CEngine::ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX)
{
return this.m_trading.ClosePosition(ticket,WRONG_VALUE,comment,deviation);
}
Так как метод для закрытия позиции у нас теперь един для полного и частичного закрытия, то для полного закрытия позиции нам необходимо в
качестве объёма закрываемой позиции передать -1, что мы тут и делаем.
Это все необходимые изменения и доработки для реализации обработки кодов возврата торгового сервера.
Тестирование
Чтобы проверить обработку ошибок, возвращаемых торговым сервером нам желательно задать такие условия торговли, которые будут вызывать
ошибки, например — задержку исполнения. За время задержки цены изменятся, что вызовет возврат ошибки "цены изменились".
Для тестирования возьмём советник из прошлой статьи и сохраним
его в новой папке \MQL5\Experts\TestDoEasy\ Part25\ под новым именем TestDoEasyPart25.mq5.
В принципе, нам достаточно сразу же запустить советник без каких-либо изменений, и всё будет работать. Но всё же немного подправим.
В
блоке входных параметров советника изменим проскальзывание по умолчанию с нуля до
пяти пунктов и добавим множитель спреда:
input ulong InpMagic = 123;
input double InpLots = 0.1;
input uint InpStopLoss = 50;
input uint InpTakeProfit = 50;
input uint InpDistance = 50;
input uint InpDistanceSL = 50;
input uint InpSlippage = 5;
input uint InpSpreadMultiplier = 1;
sinput double InpWithdrawal = 10;
sinput uint InpButtShiftX = 40;
sinput uint InpButtShiftY = 10;
input uint InpTrailingStop = 50;
input uint InpTrailingStep = 20;
input uint InpTrailingStart = 0;
input uint InpStopLossModify = 20;
input uint InpTakeProfitModify = 60;
sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT;
sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";
sinput bool InpUseSounds = true;
В функции инициализации библиотеки установим множитель спреда для всех торговых
объектов всех используемых символов, и закомментируем блок установки контроля
увеличения значений параметров символа — чтобы исключить их отслеживание и, соответственно — лишние записи в журнале тестера:
void OnInitDoEasy()
{
used_symbols_mode=InpModeUsedSymbols;
if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
{
int total=SymbolsTotal(false);
string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
string en_n="\nThe number of symbols on server "+(string)total+".\nMaximal number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
string caption=TextByLanguage("Внимание!","Attention!");
string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
string message=TextByLanguage(ru,en);
int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
int mb_res=MessageBox(message,caption,flags);
switch(mb_res)
{
case IDNO :
used_symbols_mode=SYMBOLS_MODE_CURRENT;
break;
default:
break;
}
}
used_symbols=InpUsedSymbols;
CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols);
engine.SetUsedSymbols(array_used_symbols);
Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Количество используемых символов: ",". The number of symbols used: "),engine.GetSymbolsCollectionTotal());
engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"),sound_array_coin_01);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Sound fallen coins"),sound_array_coin_02);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Sound of coins"),sound_array_coin_03);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"),sound_array_coin_04);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Click on the button sound 1"),sound_array_click_01);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Click on the button sound 1"),sound_array_click_02);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Click on the button sound 1"),sound_array_click_03);
engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","The sound of the cash machine"),sound_array_cash_machine_01);
engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);
engine.TradingOnInit();
engine.TradingSetAsyncMode(false);
engine.SetSoundsStandart();
engine.SetUseSounds(InpUseSounds);
engine.SetSpreadMultiplier(InpSpreadMultiplier);
CArrayObj *list=engine.GetListAllUsedSymbols();
if(list!=NULL && list.Total()!=0)
{
}
CAccount* account=engine.GetAccountCurrent();
if(account!=NULL)
{
account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
}
}
Установим в тестере стратегий задержку исполнения в 4 секунды.
Для этого в выпадающем меню выберем "Пользовательская
задержка..."
... и в появившемся поле ввода введём 4000 милисекунд:

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

Как видим, здесь не всегда удаётся открыть позицию с первого раза — получаем реквоту. Советник делает нужное количество торговых попыток,
но не более пяти (задано по умолчанию), и это видно по записям "Trading attempt" с номером попытки и пояснением ошибки "Requote". При
одновременном закрытии позиций тоже получили реквоты, и последняя позиция по истечению пяти попыток так и не была закрыта. Далее уже
вручную её удалось закрыть — так же не с первой попытки. Но советник "честно" отработал заложенный в библиотеку алгоритм с установленным
количеством повторных торговых попыток.
В последних версиях MetaTrader 5, начиная с билда 2201 в тестере появилась возможность установить параметры символа, на котором
проводится тестирование. Таким образом, возможно задать ограничения для торговли на символе и протестировать поведение
библиотеки при обнаружении установленных для символа ограничениях.
Для вызова окна настроек символа нужно кликнуть по кнопке, находящейся справа от выбора тестируемого таймфрейма:

Установим для символа разрешение торговли только длинными позициями, и поставим ограничение объёма одновременно открытых позиций и
установленных отложенных ордеров в одну сторону равным 0.5.

Таким образом мы сможем торговать долько длинными позициями, и иметь в рынке максимальный совокупный объём позиций и ордеров на покупку не
более 0.5 лота. Т.е. при открытии позиций лотом 0.1, мы сможем открыть только пять позиций, или установить один отложенный ордер на покупку
и открыть четыре позиции:

Здесь для чистоты эксперимента, конечно же, нужно было отключить автоматическое закрытие позиций при превышении прибыли на заданную в
настройках величину. Но, в принципе, и так видно, что открыть короткую позицию в самом начале мы не смогли — получили предупреждение о том,
что на символе разрешены только покупки, а далее, при попытках открыть количество позиций, совокупный объём которых превысит 0.5 лота, мы
получаем сообщение о невозможности открыть позицию, так как будет превышен максимальный совокупный объём позиций и ордеров в одном
направлении.
Всё это, и многое другое, связанное с параметрами символа, возможно протестировать в тестере бета-версий терминала, начиная с билда
2201.
Чтобы получить последнюю бета-версию терминала, необходимо просто подключиться к серверу MetaQuotes-Demo и в меню
"Помощь" выбрать пункт "Проверить последнюю бета-версию":

Что дальше
В следующей статье реализуем отложенные торговые запросы.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового советника. Их можно скачать и протестировать всё
самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.
К содержанию
Статьи этой серии:
Часть 1. Концепция, организация данных
Часть
2. Коллекция исторических ордеров и сделок
Часть 3. Коллекция рыночных ордеров и
позиций, организация поиска
Часть 4. Торговые события. Концепция
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу
Часть 6. События на счёте с типом неттинг
Часть
7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций
Часть
8. События модификации ордеров и позиций
Часть 9. Совместимость с MQL4 -
Подготовка данных
Часть 10. Совместимость с MQL4 - События открытия позиций и
активации отложенных ордеров
Часть 11. Совместимость с MQL4 - События закрытия
позиций
Часть 12. Класс объекта "аккаунт", коллекция объектов-аккаунтов
Часть 13. События объекта "аккаунт"
Часть
14. Объект "Символ"
Часть 15. Коллекция объектов-символов
Часть
16. События коллекции символов
Часть 17. Интерактивность объектов библиотеки
Часть 18. Интерактивность объекта-аккаунт и любых других объектов библиотеки
Часть
19. Класс сообщений библиотеки
Часть 20. Создание и хранение ресурсов программы
Часть 21. Торговые классы - Базовый кроссплатформенный торговый объект
Часть 22. Торговые классы - Основной торговый класс, контроль ограничений
Часть 23. Торговые классы - Основной торговый класс, контроль допустимых параметров
Часть 24. Торговые классы - Основной торговый класс, автоматическая коррекция ошибочных
параметров