Содержание
В прошлой статье мы начали реализацию отложенных торговых запросов и создали
первый отложенный запрос на открытие позиции в случае, если в торговом классе после отправки запроса на сервер была получена ошибка.
Сегодня продолжим развитие торгового класса по части работы с отложенными запросами, и реализуем создание отложенного запроса при
ошибках установки отложенных ордеров.
Во время тестирования торгового класса выявились некоторые недочёты допущенные при его проектировании. В частности — торговым объектам
символов при их инициализации в конструкторе класса устанавливались жёстко заданные умолчательные значения, которые могли не
поддерживаться в спецификации символа. Это приводило к ошибкам, получаемым от сервера при попытке выставить отложенные ордера — сервер
выдавал ошибку неподдерживаемого типа истечения ордера, и данная ошибка нигде не корректировалась, что в итоге приводило к
невозможности выставить отложенный ордер. При отсылке торгового запроса со значениями по умолчанию в торговый запрос тоже отправлялись
некоторые неподдерживаемые данные, которые далее нигде не корректировались. Для выхода из ситуации приходилось прямо в торговом
запросе указывать верные данные, соответствующие спецификации символа, на котором должна была проводиться торговая операция.
Это
неудобно, так как требует вместо автоматической корректировки значений самой библиотекой, обязательного знания спецификации символа
и ввода точных значений вручную прямо в коде программы. Поэтому мы внесём небольшие исправления в логику работы с торговым классом — в
советнике в обработчике OnInit() все торгуемые объекты символов — их торговые объекты будут инициализироваться автоматическим выбором
корректных значений. В торговые методы торгового класса по умолчанию будут передаваться значения -1 для типов заливки и экспирации
ордеров, что будет сигналом к использованию заранее установленных корректных умолчательных значений. Если же из программы в торговый
метод будет передано иное значение, то в этом случае будет использовано оно, и в случае, если значение окажется ошибочным, то оно будет
скорректировано при обработке ошибок в торговом классе.
Подготовка данных
Помимо исправления торгового класса допишем в класс объекта-отложенного запроса описание запроса — вывод в журнал всех его параметров. Это
облегчит нам в дальнейшем тестирование работы с объектами отложенных запросов.
Ну и в первую очередь добавим все необходимые сообщения в массив сообщений библиотеки.
Откроем файл Datas.mqh, и впишем в него индексы новых сообщений:
enum ENUM_MESSAGES_LIB
{
MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST,
MSG_LIB_PARAMS_LIST_END,
MSG_LIB_PROP_NOT_SUPPORTED,
MSG_LIB_PROP_NOT_SUPPORTED_MQL4,
MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155,
MSG_LIB_PROP_NOT_SUPPORTED_POSITION,
MSG_LIB_PROP_NOT_SUPPORTED_PENDING,
MSG_LIB_PROP_NOT_SUPPORTED_MARKET,
MSG_LIB_PROP_NOT_SUPPORTED_MARKET_HIST,
MSG_LIB_PROP_NOT_SET,
MSG_LIB_PROP_EMPTY,
MSG_LIB_PROP_AS_IN_ORDER,
MSG_LIB_SYS_ERROR,
...
MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ,
MSG_LIB_TEXT_TRY_N,
MSG_LIB_TEXT_RE_TRY_N,
MSG_LIB_TEXT_REQUEST_ACTION,
MSG_LIB_TEXT_REQUEST_MAGIC,
MSG_LIB_TEXT_REQUEST_ORDER,
MSG_LIB_TEXT_REQUEST_SYMBOL,
MSG_LIB_TEXT_REQUEST_VOLUME,
MSG_LIB_TEXT_REQUEST_PRICE,
MSG_LIB_TEXT_REQUEST_STOPLIMIT,
MSG_LIB_TEXT_REQUEST_SL,
MSG_LIB_TEXT_REQUEST_TP,
MSG_LIB_TEXT_REQUEST_DEVIATION,
MSG_LIB_TEXT_REQUEST_TYPE,
MSG_LIB_TEXT_REQUEST_TYPE_FILLING,
MSG_LIB_TEXT_REQUEST_TYPE_TIME,
MSG_LIB_TEXT_REQUEST_EXPIRATION,
MSG_LIB_TEXT_REQUEST_COMMENT,
MSG_LIB_TEXT_REQUEST_POSITION,
MSG_LIB_TEXT_REQUEST_POSITION_BY,
MSG_LIB_TEXT_REQUEST_ACTION_DEAL,
MSG_LIB_TEXT_REQUEST_ACTION_PENDING,
MSG_LIB_TEXT_REQUEST_ACTION_SLTP,
MSG_LIB_TEXT_REQUEST_ACTION_MODIFY,
MSG_LIB_TEXT_REQUEST_ACTION_REMOVE,
MSG_LIB_TEXT_REQUEST_ACTION_CLOSE_BY,
MSG_LIB_TEXT_REQUEST_ACTION_UNCNOWN,
MSG_LIB_TEXT_REQUEST_ORDER_FILLING_FOK,
MSG_LIB_TEXT_REQUEST_ORDER_FILLING_IOK,
MSG_LIB_TEXT_REQUEST_ORDER_FILLING_RETURN,
MSG_LIB_TEXT_REQUEST_ORDER_TIME_GTC,
MSG_LIB_TEXT_REQUEST_ORDER_TIME_DAY,
MSG_LIB_TEXT_REQUEST_ORDER_TIME_SPECIFIED,
MSG_LIB_TEXT_REQUEST_ORDER_TIME_SPECIFIED_DAY,
MSG_LIB_TEXT_REQUEST_DATAS,
MSG_LIB_TEXT_PEND_REQUEST_DATAS,
MSG_LIB_TEXT_PEND_REQUEST_CREATED,
MSG_LIB_TEXT_PEND_REQUEST_DELETED,
MSG_LIB_TEXT_PEND_REQUEST_PRICE_CREATE,
MSG_LIB_TEXT_PEND_REQUEST_TIME_CREATE,
MSG_LIB_TEXT_PEND_REQUEST_TIME_ACTIVATE,
MSG_LIB_TEXT_PEND_REQUEST_WAITING,
MSG_LIB_TEXT_PEND_REQUEST_CURRENT_ATTEMPT,
MSG_LIB_TEXT_PEND_REQUEST_TOTAL_ATTEMPTS,
MSG_LIB_TEXT_PEND_REQUEST_ID,
MSG_LIB_TEXT_PEND_REQUEST_RETCODE,
MSG_LIB_TEXT_PEND_REQUEST_TYPE,
MSG_LIB_TEXT_PEND_REQUEST_BY_ERROR,
MSG_LIB_TEXT_PEND_REQUEST_BY_REQUEST,
MSG_LIB_TEXT_PEND_REQUEST_WAITING_ONSET,
};
и соответствующие индексам тексты сообщений:
string messages_library[][TOTAL_LANG]=
{
{"Начало списка параметров","The beginning of the event parameter list"},
{"Конец списка параметров","End of the parameter list"},
{"Свойство не поддерживается","Property is not support"},
{"Свойство не поддерживается в MQL4","Property is not supported in MQL4"},
{"Свойство не поддерживается в MetaTrader5 версии ниже 2155","The property is not supported in MetaTrader5, build lower than 2155"},
{"Свойство не поддерживается у позиции","Property not supported for position"},
{"Свойство не поддерживается у отложенного ордера","The property is not supported for a pending order"},
{"Свойство не поддерживается у маркет-ордера","The property is not supported for a market-order"},
{"Свойство не поддерживается у исторического маркет-ордера","The property is not supported for a history market-order"},
{"Значение не задано","Value not set"},
{"Отсутствует","Not set"},
{"В соответствии с режимом истечения ордера","In accordance with the order expiration mode"},
{"Ошибка ","Error"},
...
{"Не удалось создать отложенный запрос","Failed to create pending request"},
{"Торговая попытка #","Trading attempt #"},
{"Повторная торговая попытка #","Retry trading attempt #"},
{"Тип выполняемого действия","Trade operation type"},
{"Штамп эксперта (magic number)","Expert Advisor ID (magic number)"},
{"Тикет ордера","Order ticket"},
{"Имя торгового инструмента","Trade symbol"},
{"Запрашиваемый объем сделки в лотах","Requested volume for a deal in lots"},
{"Цена","Price"},
{"Уровень StopLimit ордера","StopLimit level of the order"},
{"Уровень Stop Loss ордера","Stop Loss level of the order"},
{"Уровень Take Profit ордера","Take Profit level of the order"},
{"Максимальное отклонение от цены","Maximal deviation from the price"},
{"Тип ордера","Order type"},
{"Тип ордера по исполнению","Order execution type"},
{"Тип ордера по времени действия","Order expiration type"},
{"Срок истечения ордера","Order expiration time"},
{"Комментарий к ордеру","Order comment"},
{"Тикет позиции","Position ticket"},
{"Тикет встречной позиции","Opposite position ticket"},
{"Поставить рыночный ордер","Place market order"},
{"Установить отложенный ордер","Place pending order"},
{"Изменить значения Stop Loss и Take Profit у открытой позиции","Modify Stop Loss and Take Profit values of an opened position"},
{"Изменить параметры ранее установленного торгового ордера","Modify the parameters of the order placed previously"},
{"Удалить ранее выставленный отложенный ордер","Delete the pending order placed previously"},
{"Закрыть позицию встречной","Close a position by an opposite one"},
{"Неизвестный тип торговой операции","Unknown trade action type"},
{"Ордер исполняется исключительно в указанном объеме, иначе отменяется (FOK)","The order is executed exclusively in the specified volume, otherwise it is canceled (FOK)"},
{"Ордер исполняется на доступный объем, неисполненный отменяется (IOK)","The order is executed on the available volume, the unfulfilled is canceled (IOK)"},
{"Ордер исполняется на доступный объем, неисполненный остаётся (Return)","The order is executed at an available volume, unfulfilled remains in the market (Return)"},
{"Ордер действителен до явной отмены","Good till cancel order"},
{"Ордер действителен только в течение текущего торгового дня","Good till current trade day order"},
{"Ордер действителен до даты истечения","Good till expired order"},
{"Ордер действителен до 23:59:59 указанного дня","The order will be effective till 23:59:59 of the specified day"},
{"Параметры торгового запроса","Trade request's parameters"},
{"Параметры отложенного торгового запроса","Pending trade request's parameters"},
{"Создан отложенный запрос","Pending request created"},
{"Отложенный запрос удалён в связи с окончанием времени его действия","Pending request deleted due to expiration"},
{"Цена в момент создания запроса: ","Price at time of request create: "},
{"Время создания запроса: ","Request creation time: "},
{"Время активации запроса: ","Request activation time: "},
{"Время ожидания между торговыми попытками: ","Waiting time between trading attempts: "},
{"Текущая торговая попытка: ","Current trading attempt: "},
{"Общее количество торговых попыток: ","Total trade attempts: "},
{"Идентификатор торгового запроса: ","Trade request ID: "},
{"Код возврата, на основании которого создан запрос: ","Return code based on which the request was created: "},
{"Тип отложенного запроса: ","Pending request type: "},
{"Отложенный запрос, созданный по коду возврата сервера","Pending request that was created as a result of the server code"},
{"Отложенный запрос, созданный по запросу","Pending request created by request"},
{"Ожидание наступления времени первой торговой попытки","Waiting for the onset time of the first trading attempt"},
};
Помимо описанных выше недочётов, я заметил, что у нас есть пересечение значений идентификаторов коллекций с идентификаторами типов объектов
в стандартной библиотеке. В частности — идентификатор коллекции исторических ордеров и сделок COLLECTION_HISTORY_ID имеет значение
0x7779, которое соответствует значению типа
списка класса динамического списка экземпляров
класса CObject и его наследников CList стандартной библиотеки. Это неправильно — то что идентификаторы объектов
имели одинаковое значение.
Вот, скорее всего, неполный список идентификаторов объектов стандартной библиотеки и соответствующие им шестнадцатеричные значения:
Как видим, тип объекта-списка совпадает с заданным для библиотеки
идентификатором коллекции исторических ордеров и сделок.
Исправим эту коллизию данных в файле Defines.mqh, увеличив
значения всех идентификаторов коллекций на 1:
#define COLLECTION_HISTORY_ID (0x777A)
#define COLLECTION_MARKET_ID (0x777B)
#define COLLECTION_EVENTS_ID (0x777C)
#define COLLECTION_ACCOUNT_ID (0x777D)
#define COLLECTION_SYMBOLS_ID (0x777E)
Так как в дальнейшем мы реализуем возможность торговли при помощи отложенных запросов, то у нас будут два типа отложенных запросов:
- запрос, созданный по коду ошибки, полученной от торгового сервера (реализацией таких запросов мы занимаемся на данном этапе);
- отложенный запрос, созданный по запросу от программы (торговля отложенными запросами, которую мы реализуем позже).
Поэтому, для разделения типов запросов мы введём понятие "тип запроса" и соответствующие
типу идентификаторы:
#define CLR_DEFAULT (0xFF000000)
#define SYMBOLS_COMMON_TOTAL (1000)
#define PENDING_REQUEST_ID_TYPE_ERR (1)
#define PENDING_REQUEST_ID_TYPE_REQ (2)
И в самом конце файла Defines.mqh впишем перечисление типов отложенных запросов:
enum ENUM_PENDING_REQUEST_TYPE
{
PENDING_REQUEST_TYPE_ERROR=PENDING_REQUEST_ID_TYPE_ERR,
PENDING_REQUEST_TYPE_REQUEST=PENDING_REQUEST_ID_TYPE_REQ,
};
Для вывода описания торговых запросов в журнал нам необходимо подготовить функции, которые будут это делать.
В файле сервисных функций
DELib.mqh пропишем все необходимые функции, которые просто будут создавать сообщение из предопределённых текстов,
прописанных в файле Datas.mqh и значений выводимых функциями свойств.
Функции для вывода описания режима заливки ордера и типа
экспирации:
string OrderTypeFillingDescription(const ENUM_ORDER_TYPE_FILLING type)
{
return
(
type==ORDER_FILLING_FOK ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_FOK) :
type==ORDER_FILLING_IOC ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_IOK) :
type==ORDER_FILLING_RETURN ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_RETURN):
type==WRONG_VALUE ? "WRONG_VALUE" : EnumToString(type)
);
}
string OrderTypeTimeDescription(const ENUM_ORDER_TYPE_TIME type)
{
return
(
type==ORDER_TIME_GTC ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_TIME_GTC) :
type==ORDER_TIME_DAY ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_TIME_DAY) :
type==ORDER_TIME_SPECIFIED ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_TIME_SPECIFIED) :
type==ORDER_TIME_SPECIFIED_DAY ? CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_TIME_SPECIFIED_DAY) :
type==WRONG_VALUE ? "WRONG_VALUE" : EnumToString(type)
);
}
Функции для вывода описания структуры торгового
запроса MqlTradeRequest:
void PrintRequestDescription(const MqlTradeRequest &request)
{
string datas=
(
" - "+RequestActionDescription(request)+"\n"+
" - "+RequestMagicDescription(request)+"\n"+
" - "+RequestOrderDescription(request)+"\n"+
" - "+RequestSymbolDescription(request)+"\n"+
" - "+RequestVolumeDescription(request)+"\n"+
" - "+RequestPriceDescription(request)+"\n"+
" - "+RequestStopLimitDescription(request)+"\n"+
" - "+RequestStopLossDescription(request)+"\n"+
" - "+RequestTakeProfitDescription(request)+"\n"+
" - "+RequestDeviationDescription(request)+"\n"+
" - "+RequestTypeDescription(request)+"\n"+
" - "+RequestTypeFillingDescription(request)+"\n"+
" - "+RequestTypeTimeDescription(request)+"\n"+
" - "+RequestExpirationDescription(request)+"\n"+
" - "+RequestCommentDescription(request)+"\n"+
" - "+RequestPositionDescription(request)+"\n"+
" - "+RequestPositionByDescription(request)
);
Print("================== ",CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)," ==================\n",datas,"\n");
}
string RequestActionDescription(const MqlTradeRequest &request)
{
int code_descr=
(
request.action==TRADE_ACTION_DEAL ? MSG_LIB_TEXT_REQUEST_ACTION_DEAL :
request.action==TRADE_ACTION_PENDING ? MSG_LIB_TEXT_REQUEST_ACTION_PENDING :
request.action==TRADE_ACTION_SLTP ? MSG_LIB_TEXT_REQUEST_ACTION_SLTP :
request.action==TRADE_ACTION_MODIFY ? MSG_LIB_TEXT_REQUEST_ACTION_MODIFY :
request.action==TRADE_ACTION_REMOVE ? MSG_LIB_TEXT_REQUEST_ACTION_REMOVE :
request.action==TRADE_ACTION_CLOSE_BY ? MSG_LIB_TEXT_REQUEST_ACTION_CLOSE_BY :
MSG_LIB_TEXT_REQUEST_ACTION_UNCNOWN
);
return CMessage::Text(MSG_LIB_TEXT_REQUEST_ACTION)+": "+CMessage::Text(code_descr);
}
string RequestMagicDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_ORD_MAGIC)+": "+(string)request.magic;
}
string RequestOrderDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER)+": "+(request.order>0 ? (string)request.order : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestSymbolDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_SYMBOL)+": "+request.symbol;
}
string RequestVolumeDescription(const MqlTradeRequest &request)
{
int dg=(int)DigitsLots(request.symbol);
int dgl=(dg==0 ? 1 : dg);
return CMessage::Text(MSG_LIB_TEXT_REQUEST_VOLUME)+": "+(request.volume>0 ? DoubleToString(request.volume,dgl) : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestPriceDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_PRICE)+": "+(request.price>0 ? DoubleToString(request.price,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS)) : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestStopLimitDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_STOPLIMIT)+": "+(request.stoplimit>0 ? DoubleToString(request.stoplimit,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS)) : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestStopLossDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_SL)+": "+(request.sl>0 ? DoubleToString(request.sl,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS)) : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestTakeProfitDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_TP)+": "+(request.tp>0 ? DoubleToString(request.tp,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS)) : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestDeviationDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_DEVIATION)+": "+(string)request.deviation;
}
string RequestTypeDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_TYPE)+": "+OrderTypeDescription(request.type);
}
string RequestTypeFillingDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_TYPE_FILLING)+": "+OrderTypeFillingDescription(request.type_filling);
}
string RequestTypeTimeDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_TYPE_TIME)+": "+OrderTypeTimeDescription(request.type_time);
}
string RequestExpirationDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_EXPIRATION)+": "+(request.expiration>0 ? TimeToString(request.expiration) : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestCommentDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_COMMENT)+": "+(request.comment!="" && request.comment!=NULL ? "\""+request.comment+"\"" : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestPositionDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_POSITION)+": "+(request.position>0 ? (string)request.position : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
string RequestPositionByDescription(const MqlTradeRequest &request)
{
return CMessage::Text(MSG_LIB_TEXT_REQUEST_POSITION_BY)+": "+(request.position_by>0 ? (string)request.position_by : CMessage::Text(MSG_LIB_PROP_NOT_SET));
}
Займёмся доработкой торгового класса по исправлению выявленных недочётов, и одновременно напишем всё необходимое для создания
объекта-отложенного запроса на выставление отложенных ордеров..
Устраняем недочёты торгового класса и создаём отложенный запрос на выставление ордеров
Заметил интересную особенность некоторых символов, график которых строится по ценам Last. Иногда у них отсутствуют цены Ask и Bid, или одна из
них. Чтобы в любом случае получить цену, пришлось сделать дополнительные методы (и изменить уже существующие) в классе объекта
абстрактного символа
CSymbol, которые проверяют тип построения графика, и если он строится по ценам Last, то проводится дополнительная проверка на
присутствие цен Ask и Bid, и если они есть, то они и используются, иначе — используется цена Last.
В файле Symbol.mqh, в блоке методов упрощённого доступа к свойствам объекта-символа изменим
тип метода, возвращающего время. Так как время возвращается в милисекундах, то и тип должен быть ulong вместо datetime.
Также объявим
три дополнительных метода, о назначении которых говорилось выше:
long Status(void) const { return this.GetProperty(SYMBOL_PROP_STATUS); }
int IndexInMarketWatch(void) const { return (int)this.GetProperty(SYMBOL_PROP_INDEX_MW); }
bool IsCustom(void) const { return (bool)this.GetProperty(SYMBOL_PROP_CUSTOM); }
color ColorBackground(void) const { return (color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR); }
ENUM_SYMBOL_CHART_MODE ChartMode(void) const { return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE); }
bool IsExist(void) const { return (bool)this.GetProperty(SYMBOL_PROP_EXIST); }
bool IsExist(const string name) const { return this.SymbolExists(name); }
bool IsSelect(void) const { return (bool)this.GetProperty(SYMBOL_PROP_SELECT); }
bool IsVisible(void) const { return (bool)this.GetProperty(SYMBOL_PROP_VISIBLE); }
long SessionDeals(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); }
long SessionBuyOrders(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS); }
long SessionSellOrders(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS); }
long Volume(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME); }
long VolumeHigh(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH); }
long VolumeLow(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW); }
long Time(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_TIME); }
int Digits(void) const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS); }
int DigitsLot(void) const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS); }
int Spread(void) const { return (int)this.GetProperty(SYMBOL_PROP_SPREAD); }
bool IsSpreadFloat(void) const { return (bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT); }
int TicksBookdepth(void) const { return (int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH); }
ENUM_SYMBOL_CALC_MODE TradeCalcMode(void) const { return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE); }
ENUM_SYMBOL_TRADE_MODE TradeMode(void) const { return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE); }
datetime StartTime(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_START_TIME); }
datetime ExpirationTime(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME); }
int TradeStopLevel(void) const { return (int)this.GetProperty(SYMBOL_PROP_TRADE_STOPS_LEVEL); }
int TradeFreezeLevel(void) const { return (int)this.GetProperty(SYMBOL_PROP_TRADE_FREEZE_LEVEL); }
ENUM_SYMBOL_TRADE_EXECUTION TradeExecutionMode(void) const { return (ENUM_SYMBOL_TRADE_EXECUTION)this.GetProperty(SYMBOL_PROP_TRADE_EXEMODE); }
ENUM_SYMBOL_SWAP_MODE SwapMode(void) const { return (ENUM_SYMBOL_SWAP_MODE)this.GetProperty(SYMBOL_PROP_SWAP_MODE); }
ENUM_DAY_OF_WEEK SwapRollover3Days(void) const { return (ENUM_DAY_OF_WEEK)this.GetProperty(SYMBOL_PROP_SWAP_ROLLOVER3DAYS); }
bool IsMarginHedgedUseLeg(void) const { return (bool)this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED_USE_LEG); }
int ExpirationModeFlags(void) const { return (int)this.GetProperty(SYMBOL_PROP_EXPIRATION_MODE); }
int FillingModeFlags(void) const { return (int)this.GetProperty(SYMBOL_PROP_FILLING_MODE); }
int OrderModeFlags(void) const { return (int)this.GetProperty(SYMBOL_PROP_ORDER_MODE); }
ENUM_SYMBOL_ORDER_GTC_MODE OrderModeGTC(void) const { return (ENUM_SYMBOL_ORDER_GTC_MODE)this.GetProperty(SYMBOL_PROP_ORDER_GTC_MODE); }
ENUM_SYMBOL_OPTION_MODE OptionMode(void) const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); }
ENUM_SYMBOL_OPTION_RIGHT OptionRight(void) const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); }
double Bid(void) const { return this.GetProperty(SYMBOL_PROP_BID); }
double BidHigh(void) const { return this.GetProperty(SYMBOL_PROP_BIDHIGH); }
double BidLow(void) const { return this.GetProperty(SYMBOL_PROP_BIDLOW); }
double Ask(void) const { return this.GetProperty(SYMBOL_PROP_ASK); }
double AskHigh(void) const { return this.GetProperty(SYMBOL_PROP_ASKHIGH); }
double AskLow(void) const { return this.GetProperty(SYMBOL_PROP_ASKLOW); }
double Last(void) const { return this.GetProperty(SYMBOL_PROP_LAST); }
double LastHigh(void) const { return this.GetProperty(SYMBOL_PROP_LASTHIGH); }
double LastLow(void) const { return this.GetProperty(SYMBOL_PROP_LASTLOW); }
double VolumeReal(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_REAL); }
double VolumeHighReal(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL); }
double VolumeLowReal(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL); }
double OptionStrike(void) const { return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE); }
double Point(void) const { return this.GetProperty(SYMBOL_PROP_POINT); }
double TradeTickValue(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE); }
double TradeTickValueProfit(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT); }
double TradeTickValueLoss(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS); }
double TradeTickSize(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_SIZE); }
double TradeContractSize(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_CONTRACT_SIZE); }
double TradeAccuredInterest(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST); }
double TradeFaceValue(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE); }
double TradeLiquidityRate(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_LIQUIDITY_RATE); }
double LotsMin(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_MIN); }
double LotsMax(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_MAX); }
double LotsStep(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_STEP); }
double VolumeLimit(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT); }
double SwapLong(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_LONG); }
double SwapShort(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_SHORT); }
double MarginInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_INITIAL); }
double MarginMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_MAINTENANCE); }
double MarginLongInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_INITIAL); }
double MarginBuyStopInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL); }
double MarginBuyLimitInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL); }
double MarginBuyStopLimitInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL); }
double MarginLongMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE); }
double MarginBuyStopMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE); }
double MarginBuyLimitMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE); }
double MarginBuyStopLimitMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE); }
double MarginShortInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_INITIAL); }
double MarginSellStopInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL); }
double MarginSellLimitInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL); }
double MarginSellStopLimitInitial(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL); }
double MarginShortMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE); }
double MarginSellStopMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE); }
double MarginSellLimitMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE); }
double MarginSellStopLimitMaintenance(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE); }
double SessionVolume(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_VOLUME); }
double SessionTurnover(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_TURNOVER); }
double SessionInterest(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_INTEREST); }
double SessionBuyOrdersVolume(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME); }
double SessionSellOrdersVolume(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME); }
double SessionOpen(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_OPEN); }
double SessionClose(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_CLOSE); }
double SessionAW(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_AW); }
double SessionPriceSettlement(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT); }
double SessionPriceLimitMin(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN); }
double SessionPriceLimitMax(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX); }
double MarginHedged(void) const { return this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED); }
double NormalizedPrice(const double price) const;
double NormalizedLot(const double volume) const;
double BidLast(void) const;
double BidLastHigh(void) const;
double BidLastLow(void) const;
double AskLast(void) const;
double AskLastHigh(void) const;
double AskLastLow(void) const;
string Name(void) const { return this.GetProperty(SYMBOL_PROP_NAME); }
string Basis(void) const { return this.GetProperty(SYMBOL_PROP_BASIS); }
string CurrencyBase(void) const { return this.GetProperty(SYMBOL_PROP_CURRENCY_BASE); }
string CurrencyProfit(void) const { return this.GetProperty(SYMBOL_PROP_CURRENCY_PROFIT); }
string CurrencyMargin(void) const { return this.GetProperty(SYMBOL_PROP_CURRENCY_MARGIN); }
string Bank(void) const { return this.GetProperty(SYMBOL_PROP_BANK); }
string Description(void) const { return this.GetProperty(SYMBOL_PROP_DESCRIPTION); }
string Formula(void) const { return this.GetProperty(SYMBOL_PROP_FORMULA); }
string ISIN(void) const { return this.GetProperty(SYMBOL_PROP_ISIN); }
string Page(void) const { return this.GetProperty(SYMBOL_PROP_PAGE); }
string Path(void) const { return this.GetProperty(SYMBOL_PROP_PATH); }
string Category(void) const { return this.GetProperty(SYMBOL_PROP_CATEGORY); }
string Exchange(void) const { return this.GetProperty(SYMBOL_PROP_EXCHANGE); }
Три таких же метода для цены Bid у нас уже были сделаны ранее, но в них не было проверки наличия цен. Внесём в них изменения:
double CSymbol::BidLast(void) const
{
return
(
this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BID) :
(this.GetProperty(SYMBOL_PROP_BID)==0 ? this.GetProperty(SYMBOL_PROP_LAST) : this.GetProperty(SYMBOL_PROP_BID))
);
}
double CSymbol::BidLastHigh(void) const
{
return
(
this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BIDHIGH) :
(this.GetProperty(SYMBOL_PROP_BIDHIGH)==0 ? this.GetProperty(SYMBOL_PROP_LASTHIGH) : this.GetProperty(SYMBOL_PROP_BIDHIGH))
);
}
double CSymbol::BidLastLow(void) const
{
return
(
this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BIDLOW) :
(this.GetProperty(SYMBOL_PROP_BIDLOW)==0 ? this.GetProperty(SYMBOL_PROP_LASTLOW) : this.GetProperty(SYMBOL_PROP_BIDLOW))
);
}
Здесь всё, как и говорилось выше — проверяем тип постороения графика, и если график строится по ценам Bid, то возвращаем соответствующие цены
Bid, если же график строится по ценам Last, то делаем дополнительную проверку на нулевую цену Bid возвращаемого свойства символа. Если она
равна нулю, то используем соответствующую цену Last, иначе — соответствующую цену Bid.
Напишем точно такую же реализацию для методов, возвращающих цены Ask:
double CSymbol::AskLast(void) const
{
return
(
this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_ASK) :
(this.GetProperty(SYMBOL_PROP_ASK)==0 ? this.GetProperty(SYMBOL_PROP_LAST) : this.GetProperty(SYMBOL_PROP_ASK))
);
}
double CSymbol::AskLastHigh(void) const
{
return
(
this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_ASKHIGH) :
(this.GetProperty(SYMBOL_PROP_ASKHIGH)==0 ? this.GetProperty(SYMBOL_PROP_LASTHIGH) : this.GetProperty(SYMBOL_PROP_ASKHIGH))
);
}
double CSymbol::AskLastLow(void) const
{
return
(
this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_ASKLOW) :
(this.GetProperty(SYMBOL_PROP_ASKLOW)==0 ? this.GetProperty(SYMBOL_PROP_LASTLOW) : this.GetProperty(SYMBOL_PROP_ASKLOW))
);
}
Теперь именно эти методы будем использовать в библиотеке при расчёте уровней цен для получения цен Ask или Last и Bid или Last.
При выводе сообщений о торговых событиях в журнал у нас сейчас выводятся дополнительные обозначения для идентификаторов магика и двух
групп при условии, что в значении магического номера хранятся дополнительные данные (это мы
реализовали в прошлой статье). Но если в магическом номере содержится ещё
и идентификатор отложенного запроса, то его мы не выводили в журнал. Поправим это упущение. Просто добавим пару строчек в каждый из классов
событий в файлах
EventModify.mqh, EventOrderPlaced.mqh, EventOrderRemoved.mqh, EventPositionClose.mqh и EventPositionOpen.mqh
в их методы краткого описания события.
Заменим одну такую строку в каждом из файлов:
string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : "");
на две такие:
string pend_req_id=(this.GetPendReqID()>0 ? ", ID: "+(string)this.GetPendReqID() : "");
string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2+pend_req_id : "");
Тем самым мы добавили описание идентификатора отложенного события (если таковой есть) к описанию магика.
В публичную секцию класса торгового объекта символа CTradeObj в файле TradeObj.mqh добавим методы, возвращающие описания полей
структуры торгового запроса
MqlTradeRequest:
string GetRequestActionDescription(void) const { return RequestActionDescription(this.m_request); }
string GetRequestMagicDescription(void) const { return RequestMagicDescription(this.m_request); }
string GetRequestOrderDescription(void) const { return RequestOrderDescription(this.m_request); }
string GetRequestSymbolDescription(void) const { return RequestSymbolDescription(this.m_request); }
string GetRequestVolumeDescription(void) const { return RequestVolumeDescription(this.m_request); }
string GetRequestPriceDescription(void) const { return RequestPriceDescription(this.m_request); }
string GetRequestStopLimitDescription(void) const { return RequestStopLimitDescription(this.m_request); }
string GetRequestStopLossDescription(void) const { return RequestStopLossDescription(this.m_request); }
string GetRequestTakeProfitDescription(void) const { return RequestTakeProfitDescription(this.m_request); }
string GetRequestDeviationDescription(void) const { return RequestDeviationDescription(this.m_request); }
string GetRequestTypeDescription(void) const { return RequestTypeDescription(this.m_request); }
string GetRequestTypeFillingDescription(void) const { return RequestTypeFillingDescription(this.m_request); }
string GetRequestTypeTimeDescription(void) const { return RequestTypeTimeDescription(this.m_request); }
string GetRequestExpirationDescription(void) const { return RequestExpirationDescription(this.m_request); }
string GetRequestCommentDescription(void) const { return RequestCommentDescription(this.m_request); }
string GetRequestPositionDescription(void) const { return RequestPositionDescription(this.m_request); }
string GetRequestPositionByDescription(void) const { return RequestPositionByDescription(this.m_request); }
Методы просто вызывают соответствующие функции, ранее созданные нами в файле сервисных функций библиотеки.
По какой-то причине я ранее упустил из виду передачу типа заливки ордера в методы открытия позиции.
Добавим этот
параметр в объявлении метода открытия позиций класса:
bool OpenPosition(const ENUM_POSITION_TYPE type,
const double volume,
const double sl=0,
const double tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const ulong deviation=ULONG_MAX,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
и в реализацию метода:
bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type,
const double volume,
const double sl=0,
const double tp=0,
const ulong magic=ULONG_MAX,
const string comment=NULL,
const ulong deviation=ULONG_MAX,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
Так как в будущем у нас будет организована возможность торговли отложенными запросами, то мы ввели понятие типа отложенного запроса.
В файле торгового класса Trading.mqh в классе отложенного запроса CPendingReq
добавим
в приватную секцию переменную-член класса для хранения типа отложенного запроса:
class CPendingReq : public CObject
{
private:
MqlTradeRequest m_request;
uchar m_id;
int m_type;
int m_retcode;
double m_price_create;
ulong m_time_create;
ulong m_time_activate;
ulong m_waiting_msc;
uchar m_current_attempt;
uchar m_total_attempts;
В публичную секцию класса добавим метод, возвращающий код возврата
сервера, на основании которого был создан отложенный запрос, методы,
возвращающие описания свойств отложенного запроса, и тип запроса,
а также
метод, выводящий в журнал все данные торгового запроса:
public:
MqlTradeRequest MqlRequest(void) const { return this.m_request; }
double PriceCreate(void) const { return this.m_price_create; }
ulong TimeCreate(void) const { return this.m_time_create; }
ulong TimeActivate(void) const { return this.m_time_activate; }
ulong WaitingMSC(void) const { return this.m_waiting_msc; }
uchar CurrentAttempt(void) const { return this.m_current_attempt; }
uchar TotalAttempts(void) const { return this.m_total_attempts; }
uchar ID(void) const { return this.m_id; }
int Retcode(void) const { return this.m_retcode; }
void SetPriceCreate(const double price) { this.m_price_create=price; }
void SetTimeCreate(const ulong time) { this.m_time_create=time; }
void SetTimeActivate(const ulong time) { this.m_time_activate=time; }
void SetWaitingMSC(const ulong miliseconds) { this.m_waiting_msc=miliseconds;}
void SetCurrentAttempt(const uchar number) { this.m_current_attempt=number; }
void SetTotalAttempts(const uchar number) { this.m_total_attempts=number; }
void SetID(const uchar id) { this.m_id=id; }
string MqlRequestDescription(void) const { return RequestActionDescription(this.m_request); }
string TypeDescription(void) const
{
return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TYPE) +
(this.Type()==PENDING_REQUEST_ID_TYPE_ERR ?
CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_BY_ERROR) :
CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_BY_REQUEST)
);
}
string PriceCreateDescription(void) const
{
return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_PRICE_CREATE)+": "+
::DoubleToString(this.PriceCreate(),(int)::SymbolInfoInteger(this.m_request.symbol,SYMBOL_DIGITS));
}
string TimeCreateDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TIME_CREATE)+TimeMSCtoString(this.TimeCreate()); }
string TimeActivateDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TIME_ACTIVATE)+TimeMSCtoString(this.TimeActivate());}
string WaitingMSCDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_WAITING)+(string)this.WaitingMSC(); }
string CurrentAttemptDescription(void) const
{
return
(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CURRENT_ATTEMPT)+
(this.CurrentAttempt()==0 ? CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_WAITING_ONSET) : (string)this.CurrentAttempt())
);
}
string TotalAttemptsDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TOTAL_ATTEMPTS)+(string)this.TotalAttempts(); }
string IdDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ID)+(string)this.ID(); }
string RetcodeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_RETCODE)+(string)this.Retcode(); }
string ReasonDescription(void) const { return CMessage::Text(this.m_retcode); }
virtual int Type(void) const { return this.m_type; }
void Print(void);
CPendingReq(void){;}
CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode);
};
Методы, возвращающие описания свойств объекта отложенного запроса просто создают составное описание из заголовка, описывающего свойство, и
его значения. Метод Type(), возвращающий тип отложенного запроса, сделан виртуальным, так как у базового объекта CObject, наследником
которого является объект отложенного запроса,
такой виртуальный метод уже определён. И для
реализации возврата типа объекта-наследника метод нужно переопределить в наследнике, что мы и сделали — теперь этот метод возвращает
значение переменной
m_type, определённой в классе отложенного запроса.
В конструкторе класса установим значение для типа отложенного запроса:
CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price),
m_time_create(time),
m_id(id),
m_retcode(retcode)
{
this.CopyRequest(request);
this.m_type=(retcode>0 ? PENDING_REQUEST_ID_TYPE_ERR : PENDING_REQUEST_ID_TYPE_REQ);
}
Так как отложенные запросы у нас будут создаваться по двум причинам: по коду возарата сервера и по запросу от программы, то для определения
типа отложенного запроса достаточно знать код ответа сервера. Именно это тут мы и делаем:
если значение кода возврата больше ноля (сервер вернул ошибку) — значит
этот запрос был создан по коду возврата сервера, иначе — если код
нулевой, то объект отложенного запроса создан по запросу из программы.
Доработаем виртуальный метод Compare() объекта отложенного запроса.
Ранее он у нас всегда сравнивал объекты только по
одному свойству —
по идентификатору запроса:
int CPendingReq::Compare(const CObject *node,const int mode=0) const
{
const CPendingReq *compared_req=node;
return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0);
return 0;
}
Сейчас же, после введения типов отложенных запросов, нам необходимо сравнивать объекты по двум свойствам — по идентификатору и типу.
Для
этого изменим метод сравнения двух объектов:
int CPendingReq::Compare(const CObject *node,const int mode=0) const
{
const CPendingReq *compared_req=node;
return
(
mode==0 ?
(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0) :
(this.Type()>compared_req.Type() ? 1 : this.Type()<compared_req.Type() ? -1 : 0)
);
}
Здесь происходит выбор сравниваемых свойств от значения переменной mode.
При нулевом значении объекты сравниваются по
идентификаторам, при отличном от нуля — по типам
объектов.
Также за пределами тела класса напишем метод, выводящий в журнал полное описание всех свойств объекта-отложенного запроса:
void CPendingReq::Print(void)
{
string action=" - "+RequestActionDescription(this.m_request)+"\n";
string symbol="",order="",volume="",price="",stoplimit="",sl="",tp="",deviation="",type="",type_filling="";
string type_time="",expiration="",position="",position_by="",magic="",comment="",request_data="";
string type_req=" - "+this.TypeDescription()+"\n";
if(this.m_request.action==TRADE_ACTION_DEAL)
{
symbol=" - "+RequestSymbolDescription(this.m_request)+"\n";
volume=" - "+RequestVolumeDescription(this.m_request)+"\n";
price=" - "+RequestPriceDescription(this.m_request)+"\n";
sl=" - "+RequestStopLossDescription(this.m_request)+"\n";
tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n";
deviation=" - "+RequestDeviationDescription(this.m_request)+"\n";
type=" - "+RequestTypeDescription(this.m_request)+"\n";
type_filling=" - "+RequestTypeFillingDescription(this.m_request)+"\n";
magic=" - "+RequestMagicDescription(this.m_request)+"\n";
comment=" - "+RequestCommentDescription(this.m_request)+"\n";
request_data=
("================== "+
CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+
action+symbol+volume+price+sl+tp+deviation+type+type_filling+magic+comment+
" ==================\n"
);
}
else if(this.m_request.action==TRADE_ACTION_SLTP)
{
symbol=" - "+RequestSymbolDescription(this.m_request)+"\n";
sl=" - "+RequestStopLossDescription(this.m_request)+"\n";
tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n";
position=" - "+RequestPositionDescription(this.m_request)+"\n";
request_data=
("================== "+
CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+
action+symbol+sl+tp+position+
" ==================\n"
);
}
else if(this.m_request.action==TRADE_ACTION_PENDING)
{
symbol=" - "+RequestSymbolDescription(this.m_request)+"\n";
volume=" - "+RequestVolumeDescription(this.m_request)+"\n";
price=" - "+RequestPriceDescription(this.m_request)+"\n";
stoplimit=" - "+RequestStopLimitDescription(this.m_request)+"\n";
sl=" - "+RequestStopLossDescription(this.m_request)+"\n";
tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n";
type=" - "+RequestTypeDescription(this.m_request)+"\n";
type_filling=" - "+RequestTypeFillingDescription(this.m_request)+"\n";
type_time=" - "+RequestTypeTimeDescription(this.m_request)+"\n";
expiration=" - "+RequestExpirationDescription(this.m_request)+"\n";
magic=" - "+RequestMagicDescription(this.m_request)+"\n";
comment=" - "+RequestCommentDescription(this.m_request)+"\n";
request_data=
("================== "+
CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+
action+symbol+volume+price+stoplimit+sl+tp+type+type_filling+type_time+expiration+magic+comment+
" ==================\n"
);
}
else if(this.m_request.action==TRADE_ACTION_MODIFY)
{
order=" - "+RequestOrderDescription(this.m_request)+"\n";
price=" - "+RequestPriceDescription(this.m_request)+"\n";
sl=" - "+RequestStopLossDescription(this.m_request)+"\n";
tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n";
type_time=" - "+RequestTypeTimeDescription(this.m_request)+"\n";
expiration=" - "+RequestExpirationDescription(this.m_request)+"\n";
request_data=
("================== "+
CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+
action+order+price+sl+tp+type_time+expiration+
" ==================\n"
);
}
else if(this.m_request.action==TRADE_ACTION_REMOVE)
{
order=" - "+RequestOrderDescription(this.m_request)+"\n";
request_data=
("================== "+
CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+
action+order+
" ==================\n"
);
}
else if(this.m_request.action==TRADE_ACTION_CLOSE_BY)
{
position=" - "+RequestPositionDescription(this.m_request)+"\n";
position_by=" - "+RequestPositionByDescription(this.m_request)+"\n";
magic=" - "+RequestMagicDescription(this.m_request)+"\n";
request_data=
("================== "+
CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+
action+position+position_by+magic+
" ==================\n"
);
}
string datas=
(
" - "+this.TypeDescription()+"\n"+
" - "+this.IdDescription()+"\n"+
" - "+this.RetcodeDescription()+" \""+this.ReasonDescription()+"\"\n"+
" - "+this.TimeCreateDescription()+"\n"+
" - "+this.PriceCreateDescription()+"\n"+
" - "+this.TimeActivateDescription()+"\n"+
" - "+this.WaitingMSCDescription()+" ("+TimeToString(this.WaitingMSC()/1000,TIME_MINUTES|TIME_SECONDS)+")"+"\n"+
" - "+this.CurrentAttemptDescription()+"\n"+
" - "+this.TotalAttemptsDescription()+"\n"
);
::Print("================== ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_DATAS)," ==================\n",datas,request_data);
}
В методе в строковые переменные собираются описания всех свойств объекта, включая описания полей структуры торгового запроса,
входящей в состав объекта. Количество выводимых данных зависит от типа выполняемого действия в торговом запросе — так как при разных
действиях используется разное количество полей структуры торгового запроса. Поэтому проверяется значение поля action и выводятся
только соответствующие ему поля. Сначала выводится описание переменных объекта, затем — описание полей структуры запроса. Таким
образом, полностью все свойства объекта-отложенного запроса выводятся в журнал в соответствии с типом торгового действия запроса
(action).
Ранее мы добавили в метод открытия позиции в классе CTradeObj дополнительное свойство — тип заливки ордера.
Теперь
добавим это же свойство и в определение приватного метода открытия позиции в классе CTrading:
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,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
И такие же свойства добавим в определение публичных методов открытия позиции Buy и Sell:
template<typename SL,typename TP>
bool 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,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename SL,typename TP>
bool 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,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
Соответственно, в реализации этих методов за пределами тела класса так же добавим эти параметры:
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,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
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,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.OpenPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,comment,deviation,type_filling);
}
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,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.OpenPosition(POSITION_TYPE_SELL,volume,symbol,magic,sl,tp,comment,deviation,type_filling);
}
В приватной секции класса объявим метод, возвращающий индекс
объекта-запроса в списке по идентификатору:
int GetFreeID(void);
int GetIndexPendingRequestByID(const uchar id);
public:
Реализация таймера класса претерпела небольшие изменения.
Полный код реализации таймера с описанием логики в комментариях:
void CTrading::OnTimer(void)
{
int total=this.m_list_request.Total();
for(int i=total-1;i>WRONG_VALUE;i--)
{
CPendingReq *req_obj=this.m_list_request.At(i);
if(req_obj==NULL)
continue;
MqlTradeRequest request=req_obj.MqlRequest();
CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol);
if(symbol_obj==NULL || !symbol_obj.RefreshRates())
continue;
if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX ||
(long)symbol_obj.Time()>long(req_obj.TimeCreate()+req_obj.WaitingMSC()*req_obj.TotalAttempts()))
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
{
::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_DELETED));
req_obj.Print();
}
this.m_list_request.Delete(i);
continue;
}
uchar id=this.GetPendReqID((uint)request.magic);
CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL);
if(::CheckPointer(list)==POINTER_INVALID)
continue;
if(list.Total()>0)
{
this.m_list_request.Delete(i);
continue;
}
req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1));
if((long)symbol_obj.Time()<(long)req_obj.TimeActivate())
continue;
req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1));
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(CMessage::Text(MSG_LIB_TEXT_RE_TRY_N)+(string)req_obj.CurrentAttempt());
switch(request.action)
{
case TRADE_ACTION_DEAL :
this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation,request.type_filling);
break;
case TRADE_ACTION_PENDING :
this.PlaceOrder(request.type,request.volume,request.symbol,request.price,request.stoplimit,request.sl,request.tp,request.magic,request.comment,request.expiration,request.type_time,request.type_filling);
break;
default:
break;
}
}
}
Надеюсь, здесь всё понятно расписано для самостоятельного изучения.
В методе корректировки ошибок RequestErrorsCorrecting() добавим
корректировку типа экспирации при получении ошибки "неправильная дата
истечения ордера" (лишь часть кода с исправлениями):
if(this.IsPresentErorCode(10030))
request.type_filling=symbol_obj.GetCorrectTypeFilling();
if(this.IsPresentErorCode(10022))
{
request.type_time=symbol_obj.GetCorrectTypeExpiration();
if(!symbol_obj.IsExpirationModeSpecified() && request.expiration>0)
request.expiration=0;
}
Ранее мы добавили в объект-символ новые методы для получения цен Ask и поправили методы для получения цен Bid. Теперь нам нужно заменить все
вхождения строк "Ask()" и "Bid()" во всём листинге на "AskLast()" и "BidLast()" соответственно. Для этого удобно воспользоваться
функцией поиска с заменой (Ctrl+H) и, пройдясь поиском по всему коду, заменить все нужные строки. Таким образом, мы везде, где нам
требуется использование цен Ask и Bid объекта-символа, будем использовать автоматический выбор подходящих цен.
Например, метод установки цены торгового запроса теперь будет таким с
заменёнными ценами:
template <typename PS,typename SL,typename TP,typename PL>
bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PS price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj)
{
::ZeroMemory(this.m_request);
if(!symbol_obj.RefreshRates())
{
this.AddErrorCodeToList(10021);
return false;
}
if(price>0)
{
if(typename(price)=="double")
this.m_request.price=::NormalizeDouble(price,symbol_obj.Digits());
else if(typename(price)=="int" || typename(price)=="uint" || typename(price)=="long" || typename(price)=="ulong")
{
switch((int)action)
{
case ORDER_TYPE_BUY_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.AskLast()-price*symbol_obj.Point(),symbol_obj.Digits()); break;
case ORDER_TYPE_BUY_STOP :
case ORDER_TYPE_BUY_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.AskLast()+price*symbol_obj.Point(),symbol_obj.Digits()); break;
case ORDER_TYPE_SELL_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits()); break;
case ORDER_TYPE_SELL_STOP :
case ORDER_TYPE_SELL_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits()); break;
default : this.m_request.price=
(
this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.AskLast(),symbol_obj.Digits()) :
::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
); break;
}
}
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE));
return false;
}
}
else
{
this.m_request.price=
(
this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ?
::NormalizeDouble(symbol_obj.AskLast(),symbol_obj.Digits()) :
::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
);
}
if(limit>0)
{
if(typename(limit)=="double")
this.m_request.stoplimit=::NormalizeDouble(limit,symbol_obj.Digits());
else if(typename(limit)=="int" || typename(limit)=="uint" || typename(limit)=="long" || typename(limit)=="ulong")
{
if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
this.m_request.stoplimit=::NormalizeDouble(this.m_request.price-limit*symbol_obj.Point(),symbol_obj.Digits());
else
this.m_request.stoplimit=::NormalizeDouble(this.m_request.price+limit*symbol_obj.Point(),symbol_obj.Digits());
}
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE));
return false;
}
}
double price_open=
(
(action==ORDER_TYPE_BUY_STOP_LIMIT || action==ORDER_TYPE_SELL_STOP_LIMIT) && limit>0 ? this.m_request.stoplimit : this.m_request.price
);
if(sl>0)
{
if(typename(sl)=="double")
this.m_request.sl=::NormalizeDouble(sl,symbol_obj.Digits());
else if(typename(sl)=="int" || typename(sl)=="uint" || typename(sl)=="long" || typename(sl)=="ulong")
{
if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
this.m_request.sl=::NormalizeDouble(price_open-sl*symbol_obj.Point(),symbol_obj.Digits());
else
this.m_request.sl=::NormalizeDouble(price_open+sl*symbol_obj.Point(),symbol_obj.Digits());
}
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE));
return false;
}
}
if(tp>0)
{
if(typename(tp)=="double")
this.m_request.tp=::NormalizeDouble(tp,symbol_obj.Digits());
else if(typename(tp)=="int" || typename(tp)=="uint" || typename(tp)=="long" || typename(tp)=="ulong")
{
if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
this.m_request.tp=::NormalizeDouble(price_open+tp*symbol_obj.Point(),symbol_obj.Digits());
else
this.m_request.tp=::NormalizeDouble(price_open-tp*symbol_obj.Point(),symbol_obj.Digits());
}
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE));
return false;
}
}
return true;
}
Не будем здесь приводить все коды с проведёнными заменами — они все уже исправлены, и прилагаются в прикреплённых файлах в конце статьи.
В реализации приватного метода установки отложенного ордера добавим блок
создания отложенного торгового запроса при ошибке, полученной от сервера:
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_WAIT)
{
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_REFRESH)
{
if(this.GetPendReqID((uint)magic)==0)
{
ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : 0);
int id=this.GetFreeID();
if(id<1 || !symbol_obj.RefreshRates())
return false;
uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic);
this.SetPendReqID((uchar)id,mn);
this.m_request.magic=mn;
this.m_request.symbol=symbol_obj.Name();
this.m_request.action=TRADE_ACTION_PENDING;
this.m_request.type=order_type;
this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment);
this.m_request.type_time=(type_time>WRONG_VALUE ? type_time : trade_obj.GetTypeExpiration());
this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling());
uchar attempts=(this.m_total_try < 1 ? 1 : this.m_total_try);
this.CreatePendingRequest((uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj);
break;
}
}
}
}
return res;
}
Все действия по созданию отложенного запроса описаны в комментариях к коду, и думаю, понятны. В любом случае, что непонятно, то можно
обсудить в обсуждениях к статье.
Для упрощения отладки — чтобы можно было видеть результат создания отложенного запроса, в методе создания
объекта-отложенного запроса
добавим вывод свойств
вновь созданного запроса в журнал:
bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj)
{
CPendingReq *req_obj=new CPendingReq(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode);
if(req_obj==NULL)
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
return false;
}
if(!this.m_list_request.Add(req_obj))
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
delete req_obj;
return false;
}
req_obj.SetTimeActivate(symbol_obj.Time()+wait);
req_obj.SetWaitingMSC(wait);
req_obj.SetCurrentAttempt(0);
req_obj.SetTotalAttempts(attempts);
if(this.m_log_level>LOG_LEVEL_NO_MSG)
{
::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CREATED)," #",req_obj.ID(),":");
req_obj.Print();
}
return true;
}
В самом конце листинга торгового класса напишем реализацию метода, возвращающего индекс объекта-запроса в списке по
идентификатору:
int CTrading::GetIndexPendingRequestByID(const uchar id)
{
CPendingReq *req=new CPendingReq();
if(req==NULL)
return WRONG_VALUE;
req.SetID(id);
this.m_list_request.Sort();
int index=this.m_list_request.Search(req);
delete req;
return index;
}
В метод передаётся искомый идентификатор, создаётся
временный объект-запрос и устанавливается для него переданный в метод
идентификатор.
Затем списку, содержащему объекты-запросы устанавливается флаг сортированного
списка. По умолчанию режим сортировки равен нулю, и в таком режиме в виртуальном методе Compare() класса CPendingReq у
нас как раз организовано сравнение объектов по идентификатору. Поэтому мы спокойно можем воспользоваться
методом поиска объекта Search()
в динамическом массиве указателей на объекты.
Метод возвращает индекс найденного объекта в списке или -1, если объект не найден. Перед выходом из метода
удаляем временный объект-запрос, и
возвращаем полученный индекс найденного объекта, или -1.
Всё, что нам осталось сделать, это добавить в класс базового объекта библиотеки CEngine в определения его методов отправки торговых
запросов дополнительный
параметр с указанием типа заливки ордеров.
template<typename SL,typename TP>
bool OpenBuy(const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
SL sl=0,
TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename SL,typename TP>
bool OpenSell(const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
SL sl=0,
TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
template<typename PR,typename SL,typename TP>
bool PlaceBuyStop(const double volume,
const string symbol,
const PR 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 PR,typename SL,typename TP>
bool PlaceBuyLimit(const double volume,
const string symbol,
const PR 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 PR,typename PL,typename SL,typename TP>
bool PlaceBuyStopLimit(const double volume,
const string symbol,
const PR 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 PR,typename SL,typename TP>
bool PlaceSellStop(const double volume,
const string symbol,
const PR 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 PR,typename SL,typename TP>
bool PlaceSellLimit(const double volume,
const string symbol,
const PR 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 PR,typename PL,typename SL,typename TP>
bool PlaceSellStopLimit(const double volume,
const string symbol,
const PR 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);
При значении по умолчанию равным -1 из торговых объектов символов, на которых будет проводиться торговая операция, будут браться
корректные значения типов заливки ордеров.
Допишем те же параметры и в коды реализации этих торговых методов:
template<typename SL,typename TP>
bool CEngine::OpenBuy(const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
SL sl=0,TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.m_trading.OpenBuy(volume,symbol,magic,sl,tp,comment,deviation,type_filling);
}
template<typename SL,typename TP>
bool CEngine::OpenSell(const double volume,
const string symbol,
const ulong magic=ULONG_MAX,
SL sl=0,TP tp=0,
const string comment=NULL,
const ulong deviation=ULONG_MAX,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
{
return this.m_trading.OpenSell(volume,symbol,magic,sl,tp,comment,deviation,type_filling);
}
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceBuyStop(const double volume,
const string symbol,
const PR price,
const SL sl=0,
const TP tp=0,
const ulong magic=WRONG_VALUE,
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.m_trading.PlaceBuyStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceBuyLimit(const double volume,
const string symbol,
const PR price,
const SL sl=0,
const TP tp=0,
const ulong magic=WRONG_VALUE,
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.m_trading.PlaceBuyLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PR,typename PL,typename SL,typename TP>
bool CEngine::PlaceBuyStopLimit(const double volume,
const string symbol,
const PR price_stop,
const PL price_limit,
const SL sl=0,
const TP tp=0,
const ulong magic=WRONG_VALUE,
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.m_trading.PlaceBuyStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceSellStop(const double volume,
const string symbol,
const PR price,
const SL sl=0,
const TP tp=0,
const ulong magic=WRONG_VALUE,
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.m_trading.PlaceSellStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceSellLimit(const double volume,
const string symbol,
const PR price,
const SL sl=0,
const TP tp=0,
const ulong magic=WRONG_VALUE,
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.m_trading.PlaceSellLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling);
}
template<typename PR,typename PL,typename SL,typename TP>
bool CEngine::PlaceSellStopLimit(const double volume,
const string symbol,
const PR price_stop,
const PL price_limit,
const SL sl=0,
const TP tp=0,
const ulong magic=WRONG_VALUE,
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.m_trading.PlaceSellStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling);
}
На момент текущей реализации это все необходимые доработки и изменения.
Тестирование
Для тестирования отложенных запросов на выставление отложенных ордеров возьмём советник
из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part27\ под новым именем TestDoEasyPart27.mq5.
В советнике, в функции инициализации библиотеки добавим установку
корректных значений типов заливки ордеров и типов их экспирации для всех торговых объектов всех используемых в советнике символов:
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.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
engine.TradingSetAsyncMode(false);
engine.TradingSetTotalTry(InpTotalAttempts);
engine.TradingSetCorrectTypeExpiration();
engine.TradingSetCorrectTypeFilling();
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);
}
}
И, как ни странно, это все изменения в советнике. Всё остальное уже сегодня нами сделано в кодах библиотеки.
Для тестирования работы отложенных торговых запросов по установке отложенных ордеров сделаем точно так же, как и в прошлый раз — отключим
интернет, попытаемся установить отложенный ордер, получим ошибку отсутствия связи с торговым сервером, получим сообщение в журнал о
создании отложенного запроса и распечатку его параметров. Затем подключим обратно интернет и в результате должны получить срабатывание
отложенного запроса и установку запрошенного отложенного ордера.
Проверим.
Скомпилируем и запустим советник. Отключим любым способом интернет и дождёмся такого значка в правом нижнем углу терминала:


![]()
После отключения интернета и нажатия на кнопку Sell торговый сервер возвращает нам ошибку, и в журнал выводятся записи об ошибке и создании
отложенного запроса.
Затем подключаем интернет, тем самым восстанавливая связь с торговым сервером:

Как только связь восстанавливается, библиотека начинает обрабатывать отложенный запрос, отсылая его на сервер.
В
результате мы имеем установленный отложенный ордер и записи в журнале:
2019.12.05 16:38:32.591 CTrading::PlaceOrder<uint,int,uint,uint>: Invalid request:
2019.12.05 16:38:32.591 No connection with the trade server
2019.12.05 16:38:32.591 Correction of trade request parameters ...
2019.12.05 16:38:32.610 Trading attempt #1. Error: No connection with the trade server
2019.12.05 16:38:32.610 Pending request created #1:
2019.12.05 16:38:32.610 ================== Pending trade request's parameters ==================
2019.12.05 16:38:32.610 - Pending request type: Pending request that was created as a result of the server code
2019.12.05 16:38:32.610 - Trade request ID: 1
2019.12.05 16:38:32.610 - Return code based on which the request was created: 10031 "No connection with the trade server"
2019.12.05 16:38:32.610 - Request creation time: 2019.12.05 11:37:39.054
2019.12.05 16:38:32.610 - Price at time of request create: : 1.10913
2019.12.05 16:38:32.610 - Request activation time: 2019.12.05 11:37:59.054
2019.12.05 16:38:32.610 - Waiting time between trading attempts: 20000 (00:00:20)
2019.12.05 16:38:32.610 - Current trading attempt: Waiting for the onset time of the first trading attempt
2019.12.05 16:38:32.610 - Total trade attempts: 5
2019.12.05 16:38:32.610 ================== Trade request's parameters ==================
2019.12.05 16:38:32.610 - Trade operation type: Place pending order
2019.12.05 16:38:32.610 - Trade symbol: EURUSD
2019.12.05 16:38:32.610 - Requested volume for a deal in lots: 0.10
2019.12.05 16:38:32.610 - Price: 1.10963
2019.12.05 16:38:32.610 - StopLimit level of the order: Value not set
2019.12.05 16:38:32.610 - Stop Loss level of the order: 1.11113
2019.12.05 16:38:32.610 - Take Profit level of the order: 1.10813
2019.12.05 16:38:32.610 - Order type: Pending order Sell Limit
2019.12.05 16:38:32.610 - Order execution type: The order is executed exclusively in the specified volume, otherwise it is canceled (FOK)
2019.12.05 16:38:32.610 - Order expiration type: Good till cancel order
2019.12.05 16:38:32.610 - Order expiration time: Value not set
2019.12.05 16:38:32.610 - Magic number: 24379515
2019.12.05 16:38:32.610 - Order comment: "Pending order SellLimit"
2019.12.05 16:38:32.610 ==================
2019.12.05 16:38:32.610
2019.12.05 16:38:45.185 Retry trading attempt #1
2019.12.05 16:38:45.185 CTrading::PlaceOrder<double,double,double,double>: Invalid request:
2019.12.05 16:38:45.185 Trading is prohibited for the current account
2019.12.05 16:38:45.185 Correction of trade request parameters ...
2019.12.05 16:38:45.185 Trading operation aborted
2019.12.05 16:38:45.512 Retry trading attempt #2
2019.12.05 16:38:45.512 CTrading::PlaceOrder<double,double,double,double>: Invalid request:
2019.12.05 16:38:45.512 Trading is prohibited for the current account
2019.12.05 16:38:45.512 Correction of trade request parameters ...
2019.12.05 16:38:45.512 Trading operation aborted
2019.12.05 16:38:45.852 Retry trading attempt #3
2019.12.05 16:38:46.405 - Pending order placed: 2019.12.05 11:38:45.933 -
2019.12.05 16:38:46.405 EURUSD Placed 0.10 Pending order Sell Limit #491179168 at price 1.10963, sl 1.11113, tp 1.10813, Magic number 24379515 (123), G1: 4, G2: 7, ID: 1
2019.12.05 16:38:46.472 OnDoEasyEvent: Pending order placed
Что мы тут видим:
Сначала получаем ошибку от торгового сервера "Нет соединения с торговым сервером".
Затем получаем сообщение о создании отложенного торгового запроса с
идентификатором #1, в котором прописаны все
параметры объекта и параметры
структуры запроса в этом объекте.
После чего выполняются две
повторные торговые попытки #1 и #2, отсылаемые из отложенного торгового запроса, на что возвращается ошибка отсутствия разрешения
торговли на счёте (после восстановления связи не успело включиться разрешение для торговли на счёте).
И с
третьей попытки, отсылаемой из объекта-отложенного торгового запроса, отложенный ордер всё же установить удалось.
В описании магического номера выставленного отложенного ордера у нас присутствует магический номер 24379515, затем идентификатор
магика, установленный в параметрах советника (123), идентификатор первой группы "G1: 4", идентификатор второй группы "G2: 7" и
идентификатор отложенного запроса "ID: 1"
Пожалуйста, имейте в виду:
Настоятельно
обращаю ваше внимание на то, что результат торгового класса с отложенными запросами, описанные в данной статье и тестовый советник,
прилагаемый к статье, ни в коем случае не стоит пытаться использовать в своих наработках для реальной торговли!
Данная статья, её материалы и результат являются лишь продолжением тестовой концепции отложенных запросов, и в данном виде не
являются законченным продуктом для использования на реальных счетах — только для
использования в демо-режиме или тестере.
Что дальше
В следующей статье продолжим работу над базовыми возможностями отложенных запросов — модификация, удаление и закрытие
ордеров/позиций.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового советника. Их можно скачать и протестировать всё
самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.
К содержанию
Статьи этой серии:
Часть 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. Торговые классы —
Основной торговый класс, автоматическая коррекция ошибочных параметров
Часть
25. Торговые классы — Основной торговый класс, обработка ошибок, возвращаемых торговым сервером
Часть
26. Работа с отложенными торговыми запросами - первая реализация