//+------------------------------------------------------------------+ //| Trade.mqh | //| Copyright 2009-2020, MetaQuotes Software Corp. | //| http://www.mql5.com | //| Copyright 2022, Yuriy Bykov | //| https://www.mql5.com/ru/code/38861 | //| https://www.mql5.com/en/code/39161 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, Yuriy Bykov" #property link "https://www.mql5.com/ru/users/antekov" #property version "1.02" #property strict #include #include "OrderInfo.mqh" #include "SymbolInfo.mqh" //#include "HistoryOrderInfo.mqh" #include "PositionInfo.mqh" //#include "DealInfo.mqh" //+------------------------------------------------------------------+ //| enumerations | //+------------------------------------------------------------------+ enum ENUM_LOG_LEVELS { LOG_LEVEL_NO = 0, LOG_LEVEL_ERRORS = 1, LOG_LEVEL_ALL = 2 }; enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, // Покупка DEAL_TYPE_SELL, // Продажа DEAL_TYPE_BALANCE, // Начисление баланса DEAL_TYPE_CREDIT, // Начисление кредита DEAL_TYPE_CHARGE, // Дополнительные сборы DEAL_TYPE_CORRECTION, // Корректирующая запись DEAL_TYPE_BONUS, // Перечисление бонусов DEAL_TYPE_COMMISSION, // Дополнительные комиссии DEAL_TYPE_COMMISSION_DAILY, // Комиссия, начисляемая в конце торгового дня DEAL_TYPE_COMMISSION_MONTHLY, // Комиссия, начисляемая в конце месяца DEAL_TYPE_COMMISSION_AGENT_DAILY, // Агентская комиссия, начисляемая в конце торгового дня DEAL_TYPE_COMMISSION_AGENT_MONTHLY, // Агентская комиссия, начисляемая в конце месяца DEAL_TYPE_INTEREST, // Начисления процентов на свободные средства DEAL_TYPE_BUY_CANCELED, // Отмененная сделка покупки. Возможная ситуация, когда ранее совершенная сделка на покупку отменяется. // В таком случае тип ранее совершенной сделки (DEAL_TYPE_BUY) меняется на DEAL_TYPE_BUY_CANCELED, // а ее прибыль/убыток обнуляется. Ранее полученная прибыль/убыток начисляется/списывается со счета отдельной балансовой операцией DEAL_TYPE_SELL_CANCELED, // Отмененная сделка продажи. Возможная ситуация, когда ранее совершенная сделка на продажу отменяется. // В таком случае тип ранее совершенной сделки (DEAL_TYPE_SELL) меняется на DEAL_TYPE_SELL_CANCELED, // а ее прибыль/убыток обнуляется. Ранее полученная прибыль/убыток начисляется/списывается со счета отдельной балансовой операцией DEAL_DIVIDEND, // Начисление дивиденда DEAL_DIVIDEND_FRANKED, // Начисление франкированного дивиденда (освобожденного от уплаты налога) DEAL_TAX, // Начисление налога }; enum ENUM_TRADE_RETCODE { TRADE_RETCODE_REQUOTE = 10004, // Реквота TRADE_RETCODE_REJECT = 10006, // Запрос отклонен TRADE_RETCODE_CANCEL = 10007, // Запрос отменен трейдером TRADE_RETCODE_PLACED = 10008, // Ордер размещен TRADE_RETCODE_DONE = 10009, // Заявка выполнена TRADE_RETCODE_DONE_PARTIAL = 10010, // Заявка выполнена частично TRADE_RETCODE_ERROR = 10011, // Ошибка обработки запроса TRADE_RETCODE_TIMEOUT = 10012, // Запрос отменен по истечению времени TRADE_RETCODE_INVALID = 10013, // Неправильный запрос TRADE_RETCODE_INVALID_VOLUME = 10014, // Неправильный объем в запросе TRADE_RETCODE_INVALID_PRICE = 10015, // Неправильная цена в запросе TRADE_RETCODE_INVALID_STOPS = 10016, // Неправильные стопы в запросе TRADE_RETCODE_TRADE_DISABLED = 10017, // Торговля запрещена TRADE_RETCODE_MARKET_CLOSED = 10018, // Рынок закрыт TRADE_RETCODE_NO_MONEY = 10019, // Нет достаточных денежных средств для выполнения запроса TRADE_RETCODE_PRICE_CHANGED = 10020, // Цены изменились TRADE_RETCODE_PRICE_OFF = 10021, // Отсутствуют котировки для обработки запроса TRADE_RETCODE_INVALID_EXPIRATION = 10022, // Неверная дата истечения ордера в запросе TRADE_RETCODE_ORDER_CHANGED = 10023, // Состояние ордера изменилось TRADE_RETCODE_TOO_MANY_REQUESTS = 10024, // Слишком частые запросы TRADE_RETCODE_NO_CHANGES = 10025, // В запросе нет изменений TRADE_RETCODE_SERVER_DISABLES_AT = 10026, // Автотрейдинг запрещен сервером TRADE_RETCODE_CLIENT_DISABLES_AT = 10027, // Автотрейдинг запрещен клиентским терминалом TRADE_RETCODE_LOCKED = 10028, // Запрос заблокирован для обработки TRADE_RETCODE_FROZEN = 10029, // Ордер или позиция заморожены TRADE_RETCODE_INVALID_FILL = 10030, // Указан неподдерживаемый тип исполнения ордера по остатку TRADE_RETCODE_CONNECTION = 10031, // Нет соединения с торговым сервером TRADE_RETCODE_ONLY_REAL = 10032, // Операция разрешена только для реальных счетов TRADE_RETCODE_LIMIT_ORDERS = 10033, // Достигнут лимит на количество отложенных ордеров TRADE_RETCODE_LIMIT_VOLUME = 10034, // Достигнут лимит на объем ордеров и позиций для данного символа TRADE_RETCODE_INVALID_ORDER = 10035, // Неверный или запрещённый тип ордера TRADE_RETCODE_POSITION_CLOSED = 10036, // Позиция с указанным POSITION_IDENTIFIER уже закрыта TRADE_RETCODE_INVALID_CLOSE_VOLUME = 10038, // Закрываемый объем превышает текущий объем позиции TRADE_RETCODE_CLOSE_ORDER_EXIST = 10039, // Для указанной позиции уже есть ордер на закрытие. Может возникнуть при работе в системе хеджинга: //•при попытке закрытия позиции встречной, если уже есть ордера на закрытие этой позиции //•при попытке полного или частичного закрытия, если суммарный объем уже имеющихся ордеров на закрытие и вновь выставляемого ордера превышает текущий объем позиции TRADE_RETCODE_LIMIT_POSITIONS = 10040, // Количество открытых позиций, которое можно одновременно иметь на счете, может быть ограничено настройками сервера. При достижении лимита в ответ на выставление ордера сервер вернет ошибку TRADE_RETCODE_LIMIT_POSITIONS. Ограничение работает по-разному в зависимости от типа учета позиций на счете: // •Неттинговая система — учитывается количество открытых позиции. При достижении лимита платформа не позволит выставлять новые ордера, в результате исполнения которых может увеличиться количество открытых позиций. Фактически, платформа позволит выставлять ордера только по тем символам, по которым уже есть открытые позиции. В неттинговой системе при проверке лимита не учитываются текущие отложенные ордера, поскольку их исполнение может привести к изменению текущих позиций, а не увеличению их количества. // •Хеджинговая система — помимо открытых позиций, учитываются выставленные отложенные ордера, поскольку их срабатывание всегда приводит к открытию новой позиции. При достижении лимита платформа не позволит выставлять рыночные ордера на открытие позиций, а также отложенные ордера. TRADE_RETCODE_REJECT_CANCEL = 10041, // Запрос на активацию отложенного ордера отклонен, а сам ордер отменен TRADE_RETCODE_LONG_ONLY = 10042, // Запрос отклонен, так как на символе установлено правило "Разрешены только длинные позиции" (POSITION_TYPE_BUY) TRADE_RETCODE_SHORT_ONLY = 10043, // Запрос отклонен, так как на символе установлено правило "Разрешены только короткие позиции" (POSITION_TYPE_SELL) TRADE_RETCODE_CLOSE_ONLY = 10044, // Запрос отклонен, так как на символе установлено правило "Разрешено только закрывать существующие позиции" TRADE_RETCODE_FIFO_CLOSE = 10045, // Запрос отклонен, так как для торгового счета установлено правило "Разрешено закрывать существующие позиции только по правилу FIFO" (ACCOUNT_FIFO_CLOSE=true) TRADE_RETCODE_HEDGE_PROHIBITED = 10046, // Запрос отклонен, так как для торгового счета установлено правило "Запрещено открывать встречные позиции по одному символу". Например, если на счете имеется позиция Buy, то пользователь не может открыть позицию Sell или выставить отложенный ордер на продажу. Правило может применяться только на счетах с хеджинговой системой учета (ACCOUNT_MARGIN_MODE=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING). }; enum ENUM_TRADE_REQUEST_ACTIONS { TRADE_ACTION_DEAL, // Установить торговый ордер на немедленное совершение сделки с указанными параметрами (поставить рыночный ордер) TRADE_ACTION_PENDING, // Установить торговый ордер на совершение сделки при указанных условиях (отложенный ордер) TRADE_ACTION_SLTP, // Изменить значения Stop Loss и Take Profit у открытой позиции TRADE_ACTION_MODIFY, // Изменить параметры ранее установленного торгового ордера TRADE_ACTION_REMOVE, // Удалить ранее выставленный отложенный торговый ордер TRADE_ACTION_CLOSE_BY, // Закрыть позицию встречной }; struct MqlTradeRequest { ENUM_TRADE_REQUEST_ACTIONS action; // Тип выполняемого действия ulong magic; // Штамп эксперта (идентификатор magic number) ulong order; // Тикет ордера string symbol; // Имя торгового инструмента double volume; // Запрашиваемый объем сделки в лотах double price; // Цена double stoplimit; // Уровень StopLimit ордера double sl; // Уровень Stop Loss ордера double tp; // Уровень Take Profit ордера ulong deviation; // Максимально приемлемое отклонение от запрашиваемой цены ENUM_ORDER_TYPE type; // Тип ордера ENUM_ORDER_TYPE_FILLING type_filling; // Тип ордера по исполнению ENUM_ORDER_TYPE_TIME type_time; // Тип ордера по времени действия datetime expiration; // Срок истечения ордера (для ордеров типа ORDER_TIME_SPECIFIED) string comment; // Комментарий к ордеру ulong position; // Тикет позиции ulong position_by; // Тикет встречной позиции }; struct MqlTradeResult { uint retcode; // Код результата операции ulong deal; // Тикет сделки, если она совершена ulong order; // Тикет ордера, если он выставлен double volume; // Объем сделки, подтверждённый брокером double price; // Цена в сделке, подтверждённая брокером double bid; // Текущая рыночная цена предложения (цены реквота) double ask; // Текущая рыночная цена спроса (цены реквота) string comment; // Комментарий брокера к операции (по умолчанию заполняется расшифровкой кода возврата торгового сервера) uint request_id; // Идентификатор запроса, устанавливается терминалом при отправке int retcode_external; // Код ответа внешней торговой системы }; struct MqlTradeCheckResult { uint retcode; // Код ответа double balance; // Баланс после совершения сделки double equity; // Эквити после совершения сделки double profit; // Плавающая прибыль double margin; // Маржевые требования double margin_free; // Свободная маржа double margin_level; // Уровень маржи string comment; // Комментарий к коду ответа (описание ошибки) }; //+------------------------------------------------------------------+ //| Class CTrade. | //| Appointment: Class trade operations. | //| Derives from class CObject. | //+------------------------------------------------------------------+ class CTrade : public CObject { protected: MqlTradeRequest m_request; // request data MqlTradeResult m_result; // result data MqlTradeCheckResult m_check_result; // result check data bool m_async_mode; // trade mode ulong m_magic; // expert magic number ulong m_deviation; // deviation default ENUM_ORDER_TYPE_FILLING m_type_filling; ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; //--- ENUM_LOG_LEVELS m_log_level; public: CTrade(void); ~CTrade(void); //--- methods of access to protected data void LogLevel(const ENUM_LOG_LEVELS log_level) { m_log_level = log_level; } void Request(MqlTradeRequest &request) const; ENUM_TRADE_REQUEST_ACTIONS RequestAction(void) const { return(m_request.action); } string RequestActionDescription(void) const; ulong RequestMagic(void) const { return(m_request.magic); } ulong RequestOrder(void) const { return(m_request.order); } ulong RequestPosition(void) const { return(m_request.position); } ulong RequestPositionBy(void) const { return(m_request.position_by); } string RequestSymbol(void) const { return(m_request.symbol); } double RequestVolume(void) const { return(m_request.volume); } double RequestPrice(void) const { return(m_request.price); } double RequestStopLimit(void) const { return(m_request.stoplimit); } double RequestSL(void) const { return(m_request.sl); } double RequestTP(void) const { return(m_request.tp); } ulong RequestDeviation(void) const { return(m_request.deviation); } ENUM_ORDER_TYPE RequestType(void) const { return(m_request.type); } string RequestTypeDescription(void) const; ENUM_ORDER_TYPE_FILLING RequestTypeFilling(void) const { return(m_request.type_filling); } string RequestTypeFillingDescription(void) const; ENUM_ORDER_TYPE_TIME RequestTypeTime(void) const { return(m_request.type_time); } string RequestTypeTimeDescription(void) const; datetime RequestExpiration(void) const { return(m_request.expiration); } string RequestComment(void) const { return(m_request.comment); } //--- void Result(MqlTradeResult &result) const; uint ResultRetcode(void) const { return(m_result.retcode); } string ResultRetcodeDescription(void) const; int ResultRetcodeExternal(void) const { return(m_result.retcode_external); } ulong ResultDeal(void) const { return(m_result.deal); } ulong ResultOrder(void) const { return(m_result.order); } double ResultVolume(void) const { return(m_result.volume); } double ResultPrice(void) const { return(m_result.price); } double ResultBid(void) const { return(m_result.bid); } double ResultAsk(void) const { return(m_result.ask); } string ResultComment(void) const { return(m_result.comment); } //--- void CheckResult(MqlTradeCheckResult &check_result) const; uint CheckResultRetcode(void) const { return(m_check_result.retcode); } string CheckResultRetcodeDescription(void) const; double CheckResultBalance(void) const { return(m_check_result.balance); } double CheckResultEquity(void) const { return(m_check_result.equity); } double CheckResultProfit(void) const { return(m_check_result.profit); } double CheckResultMargin(void) const { return(m_check_result.margin); } double CheckResultMarginFree(void) const { return(m_check_result.margin_free); } double CheckResultMarginLevel(void) const { return(m_check_result.margin_level); } string CheckResultComment(void) const { return(m_check_result.comment); } //--- trade methods void SetAsyncMode(const bool mode) { m_async_mode = false; } void SetExpertMagicNumber(const ulong magic) { m_magic = magic; } void SetDeviationInPoints(const ulong deviation) { m_deviation = deviation; } void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING filling) { m_type_filling = filling; } bool SetTypeFillingBySymbol(const string symbol); void SetMarginMode(void) { m_margin_mode = ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; } //--- methods for working with positions bool PositionSelectByTicket(const ulong ticket); bool PositionOpen(const string symbol, const ENUM_ORDER_TYPE order_type, const double volume, const double price, const double sl, const double tp, const string comment = ""); bool PositionModify(const string symbol, const double sl, const double tp); bool PositionModify(const ulong ticket, const double sl, const double tp); bool PositionClose(const string symbol, const ulong deviation = ULONG_MAX); bool PositionClose(const ulong ticket, const ulong deviation = ULONG_MAX); bool PositionCloseBy(const ulong ticket, const ulong ticket_by); bool PositionClosePartial(const string symbol, const double volume, const ulong deviation = ULONG_MAX); bool PositionClosePartial(const ulong ticket, const double volume, const ulong deviation = ULONG_MAX); //--- methods for working with pending orders bool OrderOpen(const string symbol, const ENUM_ORDER_TYPE order_type, const double volume, const double limit_price, const double price, const double sl, const double tp, ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = ""); bool OrderModify(const ulong ticket, const double price, const double sl, const double tp, const ENUM_ORDER_TYPE_TIME type_time, const datetime expiration, const double stoplimit = 0.0); bool OrderDelete(const ulong ticket); //--- additions methods bool Buy(const double volume, const string symbol = NULL, double price = 0.0, const double sl = 0.0, const double tp = 0.0, const string comment = ""); bool Sell(const double volume, const string symbol = NULL, double price = 0.0, const double sl = 0.0, const double tp = 0.0, const string comment = ""); bool BuyLimit(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = ""); bool BuyStop(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = ""); bool SellLimit(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = ""); bool SellStop(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = ""); //--- method check virtual double CheckVolume(const string symbol, double volume, double price, ENUM_ORDER_TYPE order_type); virtual bool OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &check_result); virtual bool OrderSend(const MqlTradeRequest &request, MqlTradeResult &result); //--- info methods void PrintRequest(void) const; void PrintResult(void) const; //--- positions string FormatPositionType(string &str, const uint type) const; //--- orders string FormatOrderType(string &str, const uint type) const; string FormatOrderStatus(string &str, const uint status) const; string FormatOrderTypeFilling(string &str, const uint type) const; string FormatOrderTypeTime(string &str, const uint type) const; string FormatOrderPrice(string &str, const double price_order, const double price_trigger, const uint digits) const; //--- trade request string FormatRequest(string &str, const MqlTradeRequest &request) const; string FormatRequestResult(string &str, const MqlTradeRequest &request, const MqlTradeResult &result) const; protected: bool FillingCheck(const string symbol); bool ExpirationCheck(const string symbol); bool OrderTypeCheck(const string symbol); void ClearStructures(void); bool IsStopped(const string function); bool IsHedging(void) const { return(m_margin_mode == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); } //--- position select depending on netting or hedging bool SelectPosition(const string symbol); ENUM_TRADE_RETCODE ErrorToRetcode(int lastError); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTrade::CTrade(void) : m_async_mode(false), m_magic(0), m_deviation(10), m_type_filling(ORDER_FILLING_FOK), m_log_level(LOG_LEVEL_ERRORS) { SetMarginMode(); //--- initialize protected data ClearStructures(); //--- check programm mode if(IsTesting()) m_log_level = LOG_LEVEL_ALL; if(IsOptimization()) m_log_level = LOG_LEVEL_NO; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTrade::~CTrade(void) { } //+------------------------------------------------------------------+ //| Get the request structure | //+------------------------------------------------------------------+ void CTrade::Request(MqlTradeRequest &request) const { request.action = m_request.action; request.magic = m_request.magic; request.order = m_request.order; request.symbol = m_request.symbol; request.volume = m_request.volume; request.price = m_request.price; request.stoplimit = m_request.stoplimit; request.sl = m_request.sl; request.tp = m_request.tp; request.deviation = m_request.deviation; request.type = m_request.type; request.type_filling = m_request.type_filling; request.type_time = m_request.type_time; request.expiration = m_request.expiration; request.comment = m_request.comment; request.position = m_request.position; request.position_by = m_request.position_by; } //+------------------------------------------------------------------+ //| Get the trade action as string | //+------------------------------------------------------------------+ string CTrade::RequestActionDescription(void) const { string str; //--- FormatRequest(str, m_request); //--- return(str); } //+------------------------------------------------------------------+ //| Get the order type as string | //+------------------------------------------------------------------+ string CTrade::RequestTypeDescription(void) const { string str; //--- FormatOrderType(str, (uint)m_request.order); //--- return(str); } //+------------------------------------------------------------------+ //| Get the order type filling as string | //+------------------------------------------------------------------+ string CTrade::RequestTypeFillingDescription(void) const { string str; //--- FormatOrderTypeFilling(str, m_request.type_filling); //--- return(str); } //+------------------------------------------------------------------+ //| Get the order type time as string | //+------------------------------------------------------------------+ string CTrade::RequestTypeTimeDescription(void) const { string str; //--- FormatOrderTypeTime(str, m_request.type_time); //--- return(str); } //+------------------------------------------------------------------+ //| Get the result structure | //+------------------------------------------------------------------+ void CTrade::Result(MqlTradeResult &result) const { result.retcode = m_result.retcode; result.deal = m_result.deal; result.order = m_result.order; result.volume = m_result.volume; result.price = m_result.price; result.bid = m_result.bid; result.ask = m_result.ask; result.comment = m_result.comment; result.request_id = m_result.request_id; result.retcode_external = m_result.retcode_external; } //+------------------------------------------------------------------+ //| Get the retcode value as string | //+------------------------------------------------------------------+ string CTrade::ResultRetcodeDescription(void) const { string str; //--- FormatRequestResult(str, m_request, m_result); //--- return(str); } //+------------------------------------------------------------------+ //| Get the check result structure | //+------------------------------------------------------------------+ void CTrade::CheckResult(MqlTradeCheckResult &check_result) const { //--- copy structure check_result.retcode = m_check_result.retcode; check_result.balance = m_check_result.balance; check_result.equity = m_check_result.equity; check_result.profit = m_check_result.profit; check_result.margin = m_check_result.margin; check_result.margin_free = m_check_result.margin_free; check_result.margin_level = m_check_result.margin_level; check_result.comment = m_check_result.comment; } //+------------------------------------------------------------------+ //| Get the check retcode value as string | //+------------------------------------------------------------------+ string CTrade::CheckResultRetcodeDescription(void) const { string str; MqlTradeResult result; //--- result.retcode = m_check_result.retcode; FormatRequestResult(str, m_request, result); //--- return(str); } //+------------------------------------------------------------------+ //| Open position | //+------------------------------------------------------------------+ bool CTrade::PositionOpen(const string symbol, const ENUM_ORDER_TYPE order_type, const double volume, const double price, const double sl, const double tp, const string comment) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- clean ClearStructures(); //--- check if(order_type != ORDER_TYPE_BUY && order_type != ORDER_TYPE_SELL) { m_result.retcode = TRADE_RETCODE_INVALID; m_result.comment = "Invalid order type"; return(false); } //--- setting request m_request.action = TRADE_ACTION_DEAL; m_request.symbol = symbol; m_request.magic = m_magic; m_request.volume = volume; m_request.type = order_type; m_request.price = price; m_request.sl = sl; m_request.tp = tp; m_request.deviation = m_deviation; //--- check order type if(!OrderTypeCheck(symbol)) return(false); //--- check filling if(!FillingCheck(symbol)) return(false); m_request.comment = comment; //--- action and return the result return(this.OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Modify specified opened position | //+------------------------------------------------------------------+ bool CTrade::PositionModify(const string symbol, const double sl, const double tp) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- check position existence if(!SelectPosition(symbol)) return(false); //--- clean ClearStructures(); //--- setting request m_request.action = TRADE_ACTION_SLTP; m_request.symbol = symbol; m_request.magic = m_magic; m_request.sl = sl; m_request.tp = tp; m_request.position = OrderMagicNumber(); //--- action and return the result return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Modify specified opened position | //+------------------------------------------------------------------+ bool CTrade::PositionModify(const ulong ticket, const double sl, const double tp) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- check position existence if(!PositionSelectByTicket(ticket)) return(false); //--- clean ClearStructures(); //--- setting request m_request.action = TRADE_ACTION_SLTP; m_request.price = OrderOpenPrice(); m_request.position = ticket; m_request.symbol = OrderSymbol(); m_request.magic = m_magic; m_request.sl = sl; m_request.tp = tp; //--- action and return the result return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Close specified opened position | //+------------------------------------------------------------------+ bool CTrade::PositionClose(const string symbol, const ulong deviation) { bool partial_close = false; int retry_count = 10; uint retcode = TRADE_RETCODE_REJECT; //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- clean ClearStructures(); //--- check filling if(!FillingCheck(symbol)) return(false); do { //--- check if(SelectPosition(symbol)) { if(OrderType() == POSITION_TYPE_BUY) { //--- prepare request for close BUY position m_request.type = ORDER_TYPE_SELL; m_request.price = SymbolInfoDouble(symbol, SYMBOL_BID); } else { //--- prepare request for close SELL position m_request.type = ORDER_TYPE_BUY; m_request.price = SymbolInfoDouble(symbol, SYMBOL_ASK); } } else { //--- position not found m_result.retcode = retcode; return(false); } //--- setting request m_request.action = TRADE_ACTION_DEAL; m_request.symbol = symbol; m_request.volume = OrderLots(); m_request.magic = m_magic; m_request.deviation = (deviation == ULONG_MAX) ? m_deviation : deviation; //--- check volume double max_volume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(m_request.volume > max_volume) { m_request.volume = max_volume; partial_close = true; } else partial_close = false; //--- hedging? just send order if(IsHedging()) { m_request.position = OrderTicket(); return(OrderSend(m_request, m_result)); } //--- WARNING. If position volume exceeds the maximum volume allowed for deal, //--- and when the asynchronous trade mode is on, for safety reasons, position is closed not completely, //--- but partially. It is decreased by the maximum volume allowed for deal. if(m_async_mode) break; retcode = TRADE_RETCODE_DONE_PARTIAL; if(partial_close) Sleep(1000); } while(partial_close); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Close specified opened position | //+------------------------------------------------------------------+ bool CTrade::PositionClose(const ulong ticket, const ulong deviation) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- check position existence if(!OrderSelect((int)ticket, SELECT_BY_TICKET, MODE_TRADES)) return(false); string symbol = OrderSymbol(); //--- clean ClearStructures(); //--- check filling if(!FillingCheck(symbol)) return(false); //--- check if((ENUM_POSITION_TYPE)OrderType() == POSITION_TYPE_BUY) { //--- prepare request for close BUY position m_request.type = ORDER_TYPE_SELL; m_request.price = SymbolInfoDouble(symbol, SYMBOL_BID); } else { //--- prepare request for close SELL position m_request.type = ORDER_TYPE_BUY; m_request.price = SymbolInfoDouble(symbol, SYMBOL_ASK); } //--- setting request m_request.action = TRADE_ACTION_CLOSE_BY; m_request.position = ticket; m_request.symbol = symbol; m_request.volume = OrderLots(); m_request.magic = m_magic; m_request.deviation = (deviation == ULONG_MAX) ? m_deviation : deviation; //--- close position return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Close one position by other | //+------------------------------------------------------------------+ bool CTrade::PositionCloseBy(const ulong ticket, const ulong ticket_by) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- check hedging mode if(!IsHedging()) return(false); //--- check position existence if(!PositionSelectByTicket(ticket)) return(false); string symbol = OrderSymbol(); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE) OrderType(); if(!PositionSelectByTicket(ticket_by)) return(false); string symbol_by = OrderSymbol(); ENUM_POSITION_TYPE type_by = (ENUM_POSITION_TYPE) OrderType(); //--- check positions if(type == type_by) return(false); if(symbol != symbol_by) return(false); //--- clean ClearStructures(); //--- check filling if(!FillingCheck(symbol)) return(false); //--- setting request m_request.action = TRADE_ACTION_CLOSE_BY; m_request.position = ticket; m_request.position_by = ticket_by; m_request.magic = m_magic; //--- close position return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Partial close specified opened position (for hedging mode only) | //+------------------------------------------------------------------+ bool CTrade::PositionClosePartial(const string symbol, const double volume, const ulong deviation) { uint retcode = TRADE_RETCODE_REJECT; //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- for hedging mode only if(!IsHedging()) return(false); //--- clean ClearStructures(); //--- check filling if(!FillingCheck(symbol)) return(false); //--- check if(SelectPosition(symbol)) { if(OrderType() == POSITION_TYPE_BUY) { //--- prepare request for close BUY position m_request.type = ORDER_TYPE_SELL; m_request.price = SymbolInfoDouble(symbol, SYMBOL_BID); } else { //--- prepare request for close SELL position m_request.type = ORDER_TYPE_BUY; m_request.price = SymbolInfoDouble(symbol, SYMBOL_ASK); } } else { //--- position not found m_result.retcode = retcode; return(false); } //--- check volume double position_volume = OrderLots(); if(position_volume > volume) position_volume = volume; //--- setting request m_request.action = TRADE_ACTION_CLOSE_BY; m_request.symbol = symbol; m_request.volume = position_volume; m_request.magic = m_magic; m_request.deviation = (deviation == ULONG_MAX) ? m_deviation : deviation; m_request.position = OrderTicket(); //--- hedging? just send order return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Partial close specified opened position (for hedging mode only) | //+------------------------------------------------------------------+ bool CTrade::PositionClosePartial(const ulong ticket, const double volume, const ulong deviation) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- for hedging mode only if(!IsHedging()) return(false); //--- check position existence if(!PositionSelectByTicket(ticket)) return(false); string symbol = OrderSymbol(); //--- clean ClearStructures(); //--- check filling if(!FillingCheck(symbol)) return(false); //--- check if(OrderType() == POSITION_TYPE_BUY) { //--- prepare request for close BUY position m_request.type = ORDER_TYPE_SELL; m_request.price = SymbolInfoDouble(symbol, SYMBOL_BID); } else { //--- prepare request for close SELL position m_request.type = ORDER_TYPE_BUY; m_request.price = SymbolInfoDouble(symbol, SYMBOL_ASK); } //--- check volume double position_volume = OrderLots(); if(position_volume > volume) position_volume = volume; //--- setting request m_request.action = TRADE_ACTION_CLOSE_BY; m_request.position = ticket; m_request.symbol = symbol; m_request.volume = position_volume; m_request.magic = m_magic; m_request.deviation = (deviation == ULONG_MAX) ? m_deviation : deviation; //--- close position return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Installation pending order | //+------------------------------------------------------------------+ bool CTrade::OrderOpen(const string symbol, const ENUM_ORDER_TYPE order_type, const double volume, const double limit_price, const double price, const double sl, const double tp, ENUM_ORDER_TYPE_TIME type_time, const datetime expiration, const string comment) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- clean ClearStructures(); //--- check filling if(!FillingCheck(symbol)) return(false); //--- check order type if(order_type == ORDER_TYPE_BUY || order_type == ORDER_TYPE_SELL) { m_result.retcode = TRADE_RETCODE_INVALID; m_result.comment = "Invalid order type"; return(false); } /* //--- check order expiration if(type_time == ORDER_TIME_GTC && expiration == 0) { int exp = (int)SymbolInfoInteger(symbol, SYMBOL_EXPIRATION_MODE); if((exp & SYMBOL_EXPIRATION_GTC) != SYMBOL_EXPIRATION_GTC) { //--- if you place order for an unlimited time and if placing of such orders is prohibited //--- try to place order with expiration at the end of the day if((exp & SYMBOL_EXPIRATION_DAY) != SYMBOL_EXPIRATION_DAY) { //--- if even this is not possible - error Print(__FUNCTION__, ": Error: Unable to place order without explicitly specified expiration time"); m_result.retcode = TRADE_RETCODE_INVALID_EXPIRATION; m_result.comment = "Invalid expiration type"; return(false); } type_time = ORDER_TIME_DAY; } } */ //--- setting request m_request.action = TRADE_ACTION_PENDING; m_request.symbol = symbol; m_request.magic = m_magic; m_request.volume = volume; m_request.type = order_type; m_request.stoplimit = limit_price; m_request.price = price; m_request.sl = sl; m_request.tp = tp; m_request.type_time = type_time; m_request.expiration = expiration; //--- check order type if(!OrderTypeCheck(symbol)) return(false); //--- check filling if(!FillingCheck(symbol)) { m_result.retcode = TRADE_RETCODE_INVALID_FILL; Print(__FUNCTION__ + ": Invalid filling type"); return(false); } //--- check expiration if(!ExpirationCheck(symbol)) { m_result.retcode = TRADE_RETCODE_INVALID_EXPIRATION; Print(__FUNCTION__ + ": Invalid expiration type"); return(false); } m_request.comment = comment; //--- action and return the result return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Modify specified pending order | //+------------------------------------------------------------------+ bool CTrade::OrderModify(const ulong ticket, const double price, const double sl, const double tp, const ENUM_ORDER_TYPE_TIME type_time, const datetime expiration, const double stoplimit) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- check order existence if(!OrderSelect((int)ticket, SELECT_BY_TICKET, MODE_TRADES)) return(false); //--- clean ClearStructures(); //--- setting request m_request.symbol = OrderGetString(ORDER_SYMBOL); m_request.action = TRADE_ACTION_MODIFY; m_request.magic = m_magic; m_request.order = ticket; m_request.price = price; m_request.stoplimit = stoplimit; m_request.sl = sl; m_request.tp = tp; m_request.type_time = type_time; m_request.expiration = expiration; //--- action and return the result return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Delete specified pending order | //+------------------------------------------------------------------+ bool CTrade::OrderDelete(const ulong ticket) { //--- check stopped if(IsStopped(__FUNCTION__)) return(false); //--- clean ClearStructures(); //--- setting request m_request.action = TRADE_ACTION_REMOVE; m_request.magic = m_magic; m_request.order = ticket; //--- action and return the result return(OrderSend(m_request, m_result)); } //+------------------------------------------------------------------+ //| Output full information of request to log | //+------------------------------------------------------------------+ void CTrade::PrintRequest(void) const { if(m_log_level < LOG_LEVEL_ALL) return; //--- string str; PrintFormat("%s", FormatRequest(str, m_request)); } //+------------------------------------------------------------------+ //| Output full information of result to log | //+------------------------------------------------------------------+ void CTrade::PrintResult(void) const { if(m_log_level < LOG_LEVEL_ALL) return; //--- string str; PrintFormat("%s", FormatRequestResult(str, m_request, m_result)); } //+------------------------------------------------------------------+ //| Clear structures m_request,m_result and m_check_result | //+------------------------------------------------------------------+ void CTrade::ClearStructures(void) { ZeroMemory(m_request); ZeroMemory(m_result); ZeroMemory(m_check_result); } //+------------------------------------------------------------------+ //| Checks forced shutdown of MQL5-program | //+------------------------------------------------------------------+ bool CTrade::IsStopped(const string function) { if(!::IsStopped()) return(false); //--- MQL5 program is stopped PrintFormat("%s: MQL5 program is stopped. Trading is disabled", function); m_result.retcode = TRADE_RETCODE_CLIENT_DISABLES_AT; return(true); } //+------------------------------------------------------------------+ //| Buy operation | //+------------------------------------------------------------------+ bool CTrade::Buy(const double volume, const string symbol = NULL, double price = 0.0, const double sl = 0.0, const double tp = 0.0, const string comment = "") { //--- check volume if(volume <= 0.0) { m_result.retcode = TRADE_RETCODE_INVALID_VOLUME; return(false); } //--- check symbol string symbol_name = (symbol == NULL) ? _Symbol : symbol; //--- check price if(price == 0.0) price = SymbolInfoDouble(symbol_name, SYMBOL_ASK); //--- return(PositionOpen(symbol_name, ORDER_TYPE_BUY, volume, price, sl, tp, comment)); } //+------------------------------------------------------------------+ //| Sell operation | //+------------------------------------------------------------------+ bool CTrade::Sell(const double volume, const string symbol = NULL, double price = 0.0, const double sl = 0.0, const double tp = 0.0, const string comment = "") { //--- check volume if(volume <= 0.0) { m_result.retcode = TRADE_RETCODE_INVALID_VOLUME; return(false); } //--- check symbol string symbol_name = (symbol == NULL) ? _Symbol : symbol; //--- check price if(price == 0.0) price = SymbolInfoDouble(symbol_name, SYMBOL_BID); //--- return(PositionOpen(symbol_name, ORDER_TYPE_SELL, volume, price, sl, tp, comment)); } //+------------------------------------------------------------------+ //| Send BUY_LIMIT order | //+------------------------------------------------------------------+ bool CTrade::BuyLimit(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = "") { string symbol_name; //--- check volume if(volume <= 0.0) { m_result.retcode = TRADE_RETCODE_INVALID_VOLUME; return(false); } //--- check symbol symbol_name = (symbol == NULL) ? Symbol() : symbol; //--- send "BUY_LIMIT" order return(OrderOpen(symbol_name, ORDER_TYPE_BUY_LIMIT, volume, 0.0, price, sl, tp, type_time, expiration, comment)); } //+------------------------------------------------------------------+ //| Send BUY_STOP order | //+------------------------------------------------------------------+ bool CTrade::BuyStop(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = "") { string symbol_name; //--- check volume if(volume <= 0.0) { m_result.retcode = TRADE_RETCODE_INVALID_VOLUME; return(false); } //--- check symbol symbol_name = (symbol == NULL) ? Symbol() : symbol; //--- send "BUY_STOP" order return(OrderOpen(symbol_name, ORDER_TYPE_BUY_STOP, volume, 0.0, price, sl, tp, type_time, expiration, comment)); } //+------------------------------------------------------------------+ //| Send SELL_LIMIT order | //+------------------------------------------------------------------+ bool CTrade::SellLimit(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = "") { string symbol_name; //--- check volume if(volume <= 0.0) { m_result.retcode = TRADE_RETCODE_INVALID_VOLUME; return(false); } //--- check symbol symbol_name = (symbol == NULL) ? Symbol() : symbol; //--- send "SELL_LIMIT" order return(OrderOpen(symbol_name, ORDER_TYPE_SELL_LIMIT, volume, 0.0, price, sl, tp, type_time, expiration, comment)); } //+------------------------------------------------------------------+ //| Send SELL_STOP order | //+------------------------------------------------------------------+ bool CTrade::SellStop(const double volume, const double price, const string symbol = NULL, const double sl = 0.0, const double tp = 0.0, const ENUM_ORDER_TYPE_TIME type_time = ORDER_TIME_GTC, const datetime expiration = 0, const string comment = "") { string symbol_name; //--- check volume if(volume <= 0.0) { m_result.retcode = TRADE_RETCODE_INVALID_VOLUME; return(false); } //--- check symbol symbol_name = (symbol == NULL) ? Symbol() : symbol; //--- send "SELL_STOP" order return(OrderOpen(symbol_name, ORDER_TYPE_SELL_STOP, volume, 0.0, price, sl, tp, type_time, expiration, comment)); } //+------------------------------------------------------------------+ //| Converts the position type to text | //+------------------------------------------------------------------+ string CTrade::FormatPositionType(string &str, const uint type) const { //--- see the type switch(type) { case POSITION_TYPE_BUY: str = "buy"; break; case POSITION_TYPE_SELL: str = "sell"; break; default: str = "unknown position type " + (string)type; } //--- return the result return(str); } //+------------------------------------------------------------------+ //| Converts the order type to text | //+------------------------------------------------------------------+ string CTrade::FormatOrderType(string &str, const uint type) const { //--- see the type switch(type) { case ORDER_TYPE_BUY: str = "buy"; break; case ORDER_TYPE_SELL: str = "sell"; break; case ORDER_TYPE_BUY_LIMIT: str = "buy limit"; break; case ORDER_TYPE_SELL_LIMIT: str = "sell limit"; break; case ORDER_TYPE_BUY_STOP: str = "buy stop"; break; case ORDER_TYPE_SELL_STOP: str = "sell stop"; break; default: str = "unknown order type " + (string)type; } //--- return the result return(str); } //+------------------------------------------------------------------+ //| Converts the order filling type to text | //+------------------------------------------------------------------+ string CTrade::FormatOrderTypeFilling(string &str, const uint type) const { //--- see the type switch(type) { case ORDER_FILLING_RETURN: str = "return remainder"; break; case ORDER_FILLING_IOC: str = "cancel remainder"; break; case ORDER_FILLING_FOK: str = "fill or kill"; break; default: str = "unknown type filling " + (string)type; break; } //--- return the result return(str); } //+------------------------------------------------------------------+ //| Converts the type of order by expiration to text | //+------------------------------------------------------------------+ string CTrade::FormatOrderTypeTime(string &str, const uint type) const { //--- see the type switch(type) { case ORDER_TIME_GTC: str = "gtc"; break; case ORDER_TIME_DAY: str = "day"; break; case ORDER_TIME_SPECIFIED: str = "specified"; break; case ORDER_TIME_SPECIFIED_DAY: str = "specified day"; break; default: str = "unknown type time " + (string)type; } //--- return the result return(str); } //+------------------------------------------------------------------+ //| Converts the order prices to text | //+------------------------------------------------------------------+ string CTrade::FormatOrderPrice(string &str, const double price_order, const double price_trigger, const uint digits) const { string price, trigger; //--- Is there its trigger price? if(price_trigger) { price = DoubleToString(price_order, digits); trigger = DoubleToString(price_trigger, digits); str = StringFormat("%s (%s)", price, trigger); } else str = DoubleToString(price_order, digits); //--- return the result return(str); } //+------------------------------------------------------------------+ //| Converts the parameters of a trade request to text | //+------------------------------------------------------------------+ string CTrade::FormatRequest(string &str, const MqlTradeRequest &request) const { string type, price, price_new; string tmp_string; long tmp_long; //--- clean str = ""; //--- set up string symbol_name = (request.symbol == NULL) ? _Symbol : request.symbol; int digits = _Digits; ENUM_SYMBOL_TRADE_EXECUTION trade_execution = 0; if(SymbolInfoInteger(symbol_name, SYMBOL_DIGITS, tmp_long)) digits = (int)tmp_long; if(SymbolInfoInteger(symbol_name, SYMBOL_TRADE_EXEMODE, tmp_long)) trade_execution = (ENUM_SYMBOL_TRADE_EXECUTION)tmp_long; //--- see what is wanted switch(request.action) { //--- instant execution of a deal case TRADE_ACTION_DEAL: switch(trade_execution) { //--- request execution case SYMBOL_TRADE_EXECUTION_REQUEST: if(IsHedging() && request.position != 0) str = StringFormat("request %s %s position #%I64u %s at %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.position, request.symbol, DoubleToString(request.price, digits)); else str = StringFormat("request %s %s %s at %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.symbol, DoubleToString(request.price, digits)); //--- Is there SL or TP? if(request.sl != 0.0) { tmp_string = StringFormat(" sl: %s", DoubleToString(request.sl, digits)); str += tmp_string; } if(request.tp != 0.0) { tmp_string = StringFormat(" tp: %s", DoubleToString(request.tp, digits)); str += tmp_string; } break; //--- instant execution case SYMBOL_TRADE_EXECUTION_INSTANT: if(IsHedging() && request.position != 0) str = StringFormat("instant %s %s position #%I64u %s at %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.position, request.symbol, DoubleToString(request.price, digits)); else str = StringFormat("instant %s %s %s at %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.symbol, DoubleToString(request.price, digits)); //--- Is there SL or TP? if(request.sl != 0.0) { tmp_string = StringFormat(" sl: %s", DoubleToString(request.sl, digits)); str += tmp_string; } if(request.tp != 0.0) { tmp_string = StringFormat(" tp: %s", DoubleToString(request.tp, digits)); str += tmp_string; } break; //--- market execution case SYMBOL_TRADE_EXECUTION_MARKET: if(IsHedging() && request.position != 0) str = StringFormat("market %s %s position #%I64u %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.position, request.symbol); else str = StringFormat("market %s %s %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.symbol); //--- Is there SL or TP? if(request.sl != 0.0) { tmp_string = StringFormat(" sl: %s", DoubleToString(request.sl, digits)); str += tmp_string; } if(request.tp != 0.0) { tmp_string = StringFormat(" tp: %s", DoubleToString(request.tp, digits)); str += tmp_string; } break; //--- exchange execution case SYMBOL_TRADE_EXECUTION_EXCHANGE: if(IsHedging() && request.position != 0) str = StringFormat("exchange %s %s position #%I64u %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.position, request.symbol); else str = StringFormat("exchange %s %s %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.symbol); //--- Is there SL or TP? if(request.sl != 0.0) { tmp_string = StringFormat(" sl: %s", DoubleToString(request.sl, digits)); str += tmp_string; } if(request.tp != 0.0) { tmp_string = StringFormat(" tp: %s", DoubleToString(request.tp, digits)); str += tmp_string; } break; } //--- end of TRADE_ACTION_DEAL processing break; //--- setting a pending order case TRADE_ACTION_PENDING: str = StringFormat("%s %s %s at %s", FormatOrderType(type, request.type), DoubleToString(request.volume, 2), request.symbol, FormatOrderPrice(price, request.price, request.stoplimit, digits)); //--- Is there SL or TP? if(request.sl != 0.0) { tmp_string = StringFormat(" sl: %s", DoubleToString(request.sl, digits)); str += tmp_string; } if(request.tp != 0.0) { tmp_string = StringFormat(" tp: %s", DoubleToString(request.tp, digits)); str += tmp_string; } break; //--- Setting SL/TP case TRADE_ACTION_SLTP: if(IsHedging() && request.position != 0) str = StringFormat("modify position #%I64u %s (sl: %s, tp: %s)", request.position, request.symbol, DoubleToString(request.sl, digits), DoubleToString(request.tp, digits)); else str = StringFormat("modify %s (sl: %s, tp: %s)", request.symbol, DoubleToString(request.sl, digits), DoubleToString(request.tp, digits)); break; //--- modifying a pending order case TRADE_ACTION_MODIFY: str = StringFormat("modify #%I64u at %s (sl: %s tp: %s)", request.order, FormatOrderPrice(price_new, request.price, request.stoplimit, digits), DoubleToString(request.sl, digits), DoubleToString(request.tp, digits)); break; //--- deleting a pending order case TRADE_ACTION_REMOVE: str = StringFormat("cancel #%I64u", request.order); break; //--- close by case TRADE_ACTION_CLOSE_BY: if(IsHedging() && request.position != 0) str = StringFormat("close position #%I64u by #%I64u", request.position, request.position_by); else str = StringFormat("wrong action close by (#%I64u by #%I64u)", request.position, request.position_by); break; default: str = "unknown action " + (string)request.action; break; } //--- return the result return(str); } //+------------------------------------------------------------------+ //| Converts the result of a request to text | //+------------------------------------------------------------------+ string CTrade::FormatRequestResult(string &str, const MqlTradeRequest &request, const MqlTradeResult &result) const { //--- set up string symbol_name = (request.symbol == NULL) ? _Symbol : request.symbol; int digits = _Digits; long tmp_long; ENUM_SYMBOL_TRADE_EXECUTION trade_execution = 0; if(SymbolInfoInteger(symbol_name, SYMBOL_DIGITS, tmp_long)) digits = (int)tmp_long; if(SymbolInfoInteger(symbol_name, SYMBOL_TRADE_EXEMODE, tmp_long)) trade_execution = (ENUM_SYMBOL_TRADE_EXECUTION)tmp_long; //--- see the response code switch(result.retcode) { case TRADE_RETCODE_REQUOTE: str = StringFormat("requote (%s/%s)", DoubleToString(result.bid, digits), DoubleToString(result.ask, digits)); break; case TRADE_RETCODE_DONE: if(request.action == TRADE_ACTION_DEAL && (trade_execution == SYMBOL_TRADE_EXECUTION_REQUEST || trade_execution == SYMBOL_TRADE_EXECUTION_INSTANT || trade_execution == SYMBOL_TRADE_EXECUTION_MARKET)) str = StringFormat("done at %s", DoubleToString(result.price, digits)); else str = "done"; break; case TRADE_RETCODE_DONE_PARTIAL: if(request.action == TRADE_ACTION_DEAL && (trade_execution == SYMBOL_TRADE_EXECUTION_REQUEST || trade_execution == SYMBOL_TRADE_EXECUTION_INSTANT || trade_execution == SYMBOL_TRADE_EXECUTION_MARKET)) str = StringFormat("done partially %s at %s", DoubleToString(result.volume, 2), DoubleToString(result.price, digits)); else str = StringFormat("done partially %s", DoubleToString(result.volume, 2)); break; case TRADE_RETCODE_REJECT: str = "rejected"; break; case TRADE_RETCODE_CANCEL: str = "canceled"; break; case TRADE_RETCODE_PLACED: str = "placed"; break; case TRADE_RETCODE_ERROR: str = "common error"; break; case TRADE_RETCODE_TIMEOUT: str = "timeout"; break; case TRADE_RETCODE_INVALID: str = "invalid request"; break; case TRADE_RETCODE_INVALID_VOLUME: str = "invalid volume"; break; case TRADE_RETCODE_INVALID_PRICE: str = "invalid price"; break; case TRADE_RETCODE_INVALID_STOPS: str = "invalid stops"; break; case TRADE_RETCODE_TRADE_DISABLED: str = "trade disabled"; break; case TRADE_RETCODE_MARKET_CLOSED: str = "market closed"; break; case TRADE_RETCODE_NO_MONEY: str = "not enough money"; break; case TRADE_RETCODE_PRICE_CHANGED: str = "price changed"; break; case TRADE_RETCODE_PRICE_OFF: str = "off quotes"; break; case TRADE_RETCODE_INVALID_EXPIRATION: str = "invalid expiration"; break; case TRADE_RETCODE_ORDER_CHANGED: str = "order changed"; break; case TRADE_RETCODE_TOO_MANY_REQUESTS: str = "too many requests"; break; case TRADE_RETCODE_NO_CHANGES: str = "no changes"; break; case TRADE_RETCODE_SERVER_DISABLES_AT: str = "auto trading disabled by server"; break; case TRADE_RETCODE_CLIENT_DISABLES_AT: str = "auto trading disabled by client"; break; case TRADE_RETCODE_LOCKED: str = "locked"; break; case TRADE_RETCODE_FROZEN: str = "frozen"; break; case TRADE_RETCODE_INVALID_FILL: str = "invalid fill"; break; case TRADE_RETCODE_CONNECTION: str = "no connection"; break; case TRADE_RETCODE_ONLY_REAL: str = "only real"; break; case TRADE_RETCODE_LIMIT_ORDERS: str = "limit orders"; break; case TRADE_RETCODE_LIMIT_VOLUME: str = "limit volume"; break; case TRADE_RETCODE_POSITION_CLOSED: str = "position closed"; break; case TRADE_RETCODE_INVALID_ORDER: str = "invalid order"; break; case TRADE_RETCODE_CLOSE_ORDER_EXIST: str = "close order already exists"; break; case TRADE_RETCODE_LIMIT_POSITIONS: str = "limit positions"; break; default: str = "unknown retcode " + (string)result.retcode; } //--- return the result return(str); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CTrade::CheckVolume(const string symbol, double volume, double price, ENUM_ORDER_TYPE order_type) { //--- check if(order_type != ORDER_TYPE_BUY && order_type != ORDER_TYPE_SELL) return(0.0); double free_margin = AccountInfoDouble(ACCOUNT_FREEMARGIN); if(free_margin <= 0.0) return(0.0); //--- clean ClearStructures(); //--- setting request m_request.action = TRADE_ACTION_DEAL; m_request.symbol = symbol; m_request.volume = volume; m_request.type = order_type; m_request.price = price; //--- action and return the result if(!OrderCheck(m_request, m_check_result) && m_check_result.margin_free < 0.0) { double coeff = free_margin / (free_margin - m_check_result.margin_free); double lots = NormalizeDouble(volume * coeff, 2); if(lots < volume) { //--- normalize and check limits double stepvol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(stepvol > 0.0) volume = stepvol * (MathFloor(lots / stepvol) - 1); //--- double minvol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); if(volume < minvol) volume = 0.0; } } return(volume); } //+------------------------------------------------------------------+ //| Checks if the m_request structure is filled correctly | //+------------------------------------------------------------------+ bool CTrade::OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &check_result) { //--- action and return the result return true; //return(::OrderCheck(request, check_result)); } //+------------------------------------------------------------------+ //| Set order filling type according to symbol filling mode | //+------------------------------------------------------------------+ bool CTrade::SetTypeFillingBySymbol(const string symbol) { //--- get possible filling policy types by symbol uint filling = (uint)SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) { m_type_filling = ORDER_FILLING_FOK; return(true); } if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) { m_type_filling = ORDER_FILLING_IOC; return(true); } //--- return(false); } //+------------------------------------------------------------------+ //| Checks and corrects type of filling policy | //+------------------------------------------------------------------+ bool CTrade::FillingCheck(const string symbol) { //--- get execution mode of orders by symbol ENUM_SYMBOL_TRADE_EXECUTION exec = (ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(symbol, SYMBOL_TRADE_EXEMODE); //--- check execution mode if(exec == SYMBOL_TRADE_EXECUTION_REQUEST || exec == SYMBOL_TRADE_EXECUTION_INSTANT) { //--- neccessary filling type will be placed automatically return(true); } //--- get possible filling policy types by symbol // uint filling = (uint)SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); //--- check execution mode again if(exec == SYMBOL_TRADE_EXECUTION_MARKET) { //--- for the MARKET execution mode //--- analyze order /* if(m_request.action != TRADE_ACTION_PENDING) { //--- in case of instant execution order //--- if the required filling policy is supported, add it to the request if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) { m_type_filling = ORDER_FILLING_FOK; m_request.type_filling = m_type_filling; return(true); } if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) { m_type_filling = ORDER_FILLING_IOC; m_request.type_filling = m_type_filling; return(true); } //--- wrong filling policy, set error code m_result.retcode = TRADE_RETCODE_INVALID_FILL; return(false); } */ return(true); } /* //--- EXCHANGE execution mode switch(m_type_filling) { case ORDER_FILLING_FOK: //--- analyze order if(m_request.action == TRADE_ACTION_PENDING) { //--- in case of pending order //--- add the expiration mode to the request if(!ExpirationCheck(symbol)) m_request.type_time = ORDER_TIME_DAY; //--- stop order? if(m_request.type == ORDER_TYPE_BUY_STOP || m_request.type == ORDER_TYPE_SELL_STOP || m_request.type == ORDER_TYPE_BUY_LIMIT || m_request.type == ORDER_TYPE_SELL_LIMIT) { //--- in case of stop order //--- add the corresponding filling policy to the request m_request.type_filling = ORDER_FILLING_RETURN; return(true); } } //--- in case of limit order or instant execution order //--- if the required filling policy is supported, add it to the request if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) { m_request.type_filling = m_type_filling; return(true); } //--- wrong filling policy, set error code m_result.retcode = TRADE_RETCODE_INVALID_FILL; return(false); case ORDER_FILLING_IOC: //--- analyze order if(m_request.action == TRADE_ACTION_PENDING) { //--- in case of pending order //--- add the expiration mode to the request if(!ExpirationCheck(symbol)) m_request.type_time = ORDER_TIME_DAY; //--- stop order? if(m_request.type == ORDER_TYPE_BUY_STOP || m_request.type == ORDER_TYPE_SELL_STOP || m_request.type == ORDER_TYPE_BUY_LIMIT || m_request.type == ORDER_TYPE_SELL_LIMIT) { //--- in case of stop order //--- add the corresponding filling policy to the request m_request.type_filling = ORDER_FILLING_RETURN; return(true); } } //--- in case of limit order or instant execution order //--- if the required filling policy is supported, add it to the request if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) { m_request.type_filling = m_type_filling; return(true); } //--- wrong filling policy, set error code m_result.retcode = TRADE_RETCODE_INVALID_FILL; return(false); case ORDER_FILLING_RETURN: //--- add filling policy to the request m_request.type_filling = m_type_filling; return(true); } */ //--- unknown execution mode, set error code m_result.retcode = TRADE_RETCODE_ERROR; return(false); } //+------------------------------------------------------------------+ //| Check expiration type of pending order | //+------------------------------------------------------------------+ bool CTrade::ExpirationCheck(const string symbol) { /* //--- check symbol string symbol_name = (symbol == NULL) ? _Symbol : symbol; //--- get flags long tmp_long; int flags = 0; if(SymbolInfoInteger(symbol_name, SYMBOL_EXPIRATION_MODE, tmp_long)) flags = (int)tmp_long; //--- check type switch(m_request.type_time) { case ORDER_TIME_GTC: if((flags & SYMBOL_EXPIRATION_GTC) != 0) return(true); break; case ORDER_TIME_DAY: if((flags & SYMBOL_EXPIRATION_DAY) != 0) return(true); break; case ORDER_TIME_SPECIFIED: if((flags & SYMBOL_EXPIRATION_SPECIFIED) != 0) return(true); break; case ORDER_TIME_SPECIFIED_DAY: if((flags & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) return(true); break; default: Print(__FUNCTION__ + ": Unknown expiration type"); } //--- failed return(false); */ return(true); } //+------------------------------------------------------------------+ //| Checks order | //+------------------------------------------------------------------+ bool CTrade::OrderTypeCheck(const string symbol) { bool res = false; //--- check symbol string symbol_name = (symbol == NULL) ? _Symbol : symbol; //--- get flags of allowed trade orders long tmp_long; int flags = 0; ENUM_SYMBOL_TRADE_MODE trade_mode = SYMBOL_TRADE_MODE_DISABLED; if(SymbolInfoInteger(symbol_name, SYMBOL_TRADE_MODE, tmp_long)) trade_mode = (ENUM_SYMBOL_TRADE_MODE) tmp_long; //--- depending on the type of order in request if(trade_mode == SYMBOL_TRADE_MODE_FULL) { res = true; } else { switch(m_request.type) { case ORDER_TYPE_SELL: case ORDER_TYPE_SELL_LIMIT: case ORDER_TYPE_BUY_STOP: //--- check possibility of execution if(trade_mode == SYMBOL_TRADE_MODE_LONGONLY) { m_result.retcode = TRADE_RETCODE_INVALID_ORDER; res = false; } break; } //--- check res if(res) { //--- trading order is valid //--- check if we need and able to set protective orders /* if(m_request.sl != 0.0 || m_request.tp != 0.0) { if((flags & SYMBOL_ORDER_SL) == 0) m_request.sl = 0.0; if((flags & SYMBOL_ORDER_TP) == 0) m_request.tp = 0.0; } */ } else { //--- trading order is not valid //--- set error m_result.retcode = TRADE_RETCODE_INVALID_ORDER; } } if(!res) { Print(__FUNCTION__ + ": Invalid order type"); } //--- result return(res); } //+------------------------------------------------------------------+ //| Send order | //+------------------------------------------------------------------+ bool CTrade::OrderSend(const MqlTradeRequest &request, MqlTradeResult &result) { bool res = false; int ticket; string action = ""; string fmt = ""; ResetLastError(); result.retcode = TRADE_RETCODE_ERROR; //--- action switch(request.action) { case TRADE_ACTION_DEAL: ticket = ::OrderSend(request.symbol, request.type, request.volume, request.price, (int) request.deviation, request.sl, request.tp, request.comment, (int)request.magic, request.expiration); if(ticket != -1) { if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) { result.retcode = TRADE_RETCODE_DONE; result.deal = ticket; result.price = OrderOpenPrice(); result.comment = OrderComment(); res = true; } } break; case TRADE_ACTION_PENDING: ticket = ::OrderSend(request.symbol, request.type, request.volume, request.price, (int) request.deviation, request.sl, request.tp, request.comment, (int)request.magic, request.expiration); if(ticket != -1) { if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) { result.retcode = TRADE_RETCODE_PLACED; result.order = ticket; result.price = OrderOpenPrice(); result.comment = OrderComment(); res = true; } } break; case TRADE_ACTION_REMOVE: res = ::OrderDelete((int) request.order); break; case TRADE_ACTION_CLOSE_BY: res = ::OrderClose((int) m_request.position, m_request.volume, m_request.price, (int) m_request.deviation); break; case TRADE_ACTION_SLTP: res = ::OrderModify((int) request.position, request.price, request.sl, request.tp, 0); break; default: break; } //--- check if(res) { m_result.retcode = TRADE_RETCODE_DONE; if(m_log_level > LOG_LEVEL_ERRORS) PrintFormat(__FUNCTION__ + ": %s [%s]", FormatRequest(action, request), FormatRequestResult(fmt, request, result)); } else { m_result.retcode = ErrorToRetcode(_LastError); if(m_log_level > LOG_LEVEL_NO) PrintFormat(__FUNCTION__ + ": %s [%s]", FormatRequest(action, request), FormatRequestResult(fmt, request, result)); } //--- return the result return(res); } //+------------------------------------------------------------------+ //| Position select depending on netting or hedging | //+------------------------------------------------------------------+ bool CTrade::SelectPosition(const string symbol) { bool res = false; //--- if(IsHedging()) { uint total = PositionsTotal(); for(uint i = 0; i < total; i++) { if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if(OrderType() < 2) { if(OrderSymbol() == symbol && m_magic == OrderMagicNumber()) { res = true; break; } } } } } //--- return(res); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CTrade::PositionSelectByTicket(const ulong ticket) { return OrderSelect((int)ticket, SELECT_BY_TICKET, MODE_TRADES); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ENUM_TRADE_RETCODE CTrade::ErrorToRetcode(int lastError) { switch(lastError) { case ERR_NO_ERROR: return TRADE_RETCODE_DONE; case ERR_NO_RESULT: return TRADE_RETCODE_DONE_PARTIAL; case ERR_COMMON_ERROR: return TRADE_RETCODE_ERROR; case ERR_INVALID_TRADE_PARAMETERS: return TRADE_RETCODE_INVALID; case ERR_SERVER_BUSY: return TRADE_RETCODE_REJECT; case ERR_OLD_VERSION: return TRADE_RETCODE_ERROR; case ERR_NO_CONNECTION: return TRADE_RETCODE_CONNECTION; case ERR_NOT_ENOUGH_RIGHTS: return TRADE_RETCODE_REJECT; case ERR_TOO_FREQUENT_REQUESTS: return TRADE_RETCODE_TOO_MANY_REQUESTS; case ERR_MALFUNCTIONAL_TRADE: return TRADE_RETCODE_REJECT; case ERR_ACCOUNT_DISABLED: return TRADE_RETCODE_ERROR; case ERR_INVALID_ACCOUNT: return TRADE_RETCODE_ERROR; case ERR_TRADE_TIMEOUT: return TRADE_RETCODE_TIMEOUT; case ERR_INVALID_PRICE: return TRADE_RETCODE_INVALID_PRICE; case ERR_INVALID_STOPS: return TRADE_RETCODE_INVALID_STOPS; case ERR_INVALID_TRADE_VOLUME: return TRADE_RETCODE_INVALID_VOLUME; case ERR_MARKET_CLOSED: return TRADE_RETCODE_MARKET_CLOSED; case ERR_TRADE_DISABLED: return TRADE_RETCODE_TRADE_DISABLED; case ERR_NOT_ENOUGH_MONEY: return TRADE_RETCODE_NO_MONEY; case ERR_PRICE_CHANGED: return TRADE_RETCODE_PRICE_CHANGED; case ERR_OFF_QUOTES: return TRADE_RETCODE_PRICE_OFF; case ERR_BROKER_BUSY: return TRADE_RETCODE_REJECT; case ERR_REQUOTE: return TRADE_RETCODE_REQUOTE; case ERR_ORDER_LOCKED: return TRADE_RETCODE_LOCKED; case ERR_LONG_POSITIONS_ONLY_ALLOWED: return TRADE_RETCODE_LONG_ONLY; case ERR_TOO_MANY_REQUESTS: return TRADE_RETCODE_TOO_MANY_REQUESTS; case ERR_TRADE_MODIFY_DENIED: return TRADE_RETCODE_FROZEN; case ERR_TRADE_CONTEXT_BUSY: return TRADE_RETCODE_REJECT; case ERR_TRADE_EXPIRATION_DENIED: return TRADE_RETCODE_INVALID_EXPIRATION; case ERR_TRADE_HEDGE_PROHIBITED: return TRADE_RETCODE_HEDGE_PROHIBITED; case ERR_TRADE_PROHIBITED_BY_FIFO: return TRADE_RETCODE_FIFO_CLOSE; default: return TRADE_RETCODE_DONE; } } //+------------------------------------------------------------------+