// MQL4&5-code // Параллельное использование MT4 и MT5 ордерных систем. // https://www.mql5.com/ru/code/16006 // Данный mqh-файл после соответствующего #include позволяет работать с ордерами в MQL5 (MT5-Hedge) точно так же, как в MQL4. // Т.е. ордерная языковая система (ОЯС) становится идентичной MQL4. При этом сохраняется возможность ПАРАЛЛЕЛЬНО использовать // MQL5-ордерную систему. В частности, стандартная MQL5-библиотека будет продолжать полноценно работать. // Выбор между ордерными системами делать не нужно. Используйте их параллельно! // При переводе MQL4 -> MQL5 ордерную систему трогать СОВСЕМ не требуется. // Достаточно внести только одну строчку в начале (если исходник способен компилироваться в MT4 при #property strict): // #include // если есть #include , вставить эту строчку ПОСЛЕ // Аналогично действуя (добавление одной строки) в своих MQL5-кодах, можно добавить к MT5-ОЯС еще и MT4-ОЯС, либо полностью заменить ее. // Автор создавал такую возможность для себя, поэтому намеренно не проводил подобную же идею перехода "одной строкой" // для таймсерий, графических объектов, индикаторов и т.д. // Данная работа затрагивает ТОЛЬКО ордерную систему. // Вопрос возможности создания подобной полной библиотеки, когда MQL4-код может работать в MT5 БЕЗ ИЗМЕНЕНИЙ, не решался. // Что не реализовано: // CloseBy-моменты - пока было не до этого. Возможно, в будущем, когда понадобится. // Определение TP и SL закрытых позиций - на данный момент (build 1470) MQL5 этого делать не умеет. // Учет DEAL_ENTRY_INOUT и DEAL_ENTRY_OUT_BY сделок. // Особенности: // В MT4 OrderSelect в режиме SELECT_BY_TICKET выбирает тикет независимо от MODE_TRADES/MODE_HISTORY, // т.к. "Номер тикета является уникальным идентификатором ордера". // В MT5 номер тикета НЕ уникален, // поэтому OrderSelect в режиме SELECT_BY_TICKET имеет следующие приоритеты выбора при совпадающих тикетах: // MODE_TRADES: существующая позиция > существующий ордер > сделка > отмененный ордер // MODE_HISTORY: сделка > отмененный ордер > существующая позиция > существующий ордер // // Соответственно, OrderSelect в режиме SELECT_BY_TICKET в MT5 в редких случаях (в тестере) может выбрать не то, что задумывалось в MT4. // // Если вызвать OrdersTotal() со входным параметром, то возвращаемое значение будет соответствовать MT5-варианту. // Список изменений: // 03.08.2016: // Релиз - писался и проверялся только на оффлайн-тестере. // 29.09.2016: // Add: возможность работы на бирже (SYMBOL_TRADE_EXECUTION_EXCHANGE). Учитывайте, что биржа - Netto(не Hedge)-mode. // Add: Требование "если есть #include , вставить эту строчку ПОСЛЕ" // заменено на "если есть #include , вставить эту строчку ПОСЛЕ". // Fix: OrderSend маркет-ордеров возвращает тикет позиции, а не сделки. // 13.11.2016: // Add: Полная синхронизация OrderSend, OrderModify, OrderClose, OrderDelete с торговым окружением (реал-тайм и история) - как в MT4. // Максимальное время синхронизации можно задать через MT4ORDERS::OrderSend_MaxPause в мкс. Среднее время синхронизации в MT5 ~1 мс. // По-умолчанию максимальное время синхронизации равно одной секунде. MT4ORDERS::OrderSend_MaxPause = 0 - отсутствие синхронизации. // Add: Поскольку параметр SlipPage (OrderSend, OrderClose) влияет на исполнение маркет-ордеров только в Instant-режиме, // то через него теперь при желании можно задавать тип исполнения по остатку - ENUM_ORDER_TYPE_FILLING: // ORDER_FILLING_FOK, ORDER_FILLING_IOC или ORDER_FILLING_RETURN. // В случае ошибочного задания или не поддержки символом заданного типа исполнения автоматически будет выбран рабочий режим. // Примеры: // OrderSend(Symb, Type, Lots, Price, ORDER_FILLING_FOK, SL, TP) - отправить соответствующий ордер с типом исполнения ORDER_FILLING_FOK // OrderSend(Symb, Type, Lots, Price, ORDER_FILLING_IOC, SL, TP) - отправить соответствующий ордер с типом исполнения ORDER_FILLING_IOC // OrderClose(Ticket, Lots, Price, ORDER_FILLING_RETURN) - отправить соответствующий маркет-ордер с типом исполнения ORDER_FILLING_RETURN // Add: OrdersHistoryTotal() и OrderSelect(Pos, SELECT_BY_POS, MODE_HISTORY) закешированы - работают максимально быстро. // В библиотеке не осталось медленных реализаций. // 08.02.2017: // Add: Переменные MT4ORDERS::LastTradeRequest и MT4ORDERS::LastTradeResult содержат соответствующие данные MT5-OrderSend. // 14.06.2017: // Add: Включена изначально заложенная реализация определения SL/TP закрытых позиций (закрытых через OrderClose). // Add: MagicNumber теперь имеет тип long - 8 байт (раньше был int - 4 байта). // Add: Если в OrderSend, OrderClose или OrderModify цветовой входной параметр (самый последний) задать равным INT_MAX, то будет сформирован // соответствующий торговый MT5-запрос (MT4ORDERS::LastTradeRequest), но отправлен он НЕ будет. Вместо этого будет проведена его MT5-проверка, // результат которой станет доступен в MT4ORDERS::LastTradeCheckResult. // В случае успешной проверки OrderModify и OrderClose вернут true, иначе - false. // OrderSend вернет 0 в случае успеха, иначе - -1. // // Если же соответствующий цветовой входной параметр задать раным INT_MIN, то ТОЛЬКО в случае успешной MT5-проверки сформированного // торгового запроса(как в случае с INT_MAX) он БУДЕТ отправлен. // Add: Добавлены асинхронные аналоги MQL4-торговым функциям: OrderSendAsync, OrderModifyAsync, OrderCloseAsync, OrderDeleteAsync. // Возвращают соответствующий Result.request_id в случае удачи, иначе - 0. // 03.08.2017: // Add: Добавлена OrderCloseBy. // Add: Ускорена работа OrderSelect в MODE_TRADES-режиме. Теперь есть возможность получать данные выбранного ордера через // соответствующие MT4-Order-функции, даже если MT5-позиция/ордер(не в истории) выбраны не через MT4Orders. // Например, через MT5-PositionSelect*-функции или MT5-OrderSelect. // Add: Добавлены OrderOpenPriceRequest() и OrderClosePriceRequest() - возращают цену торгового запроса при открытии/закрытии позиции. // С помощью данных функций возможно вычислять соответствующие проскальзывания ордеров. // 26.08.2017: // Add: Добавлены OrderOpenTimeMsc() и OrderCloseTimeMsc() - соответствующее время в миллисекундах. // Fix: Раньше все торговые тикеты имели тип int, как в MT4. Из-за появления случаев выхода за пределы int-типа в MT5, тип тикетов изменен на long. // Соответственно, OrderTicket и OrderSend возвращают long-значения. Режим возврата того же типа, что и в MT4 (int), включается через // прописывание следующей строки перед #include // #define MT4_TICKET_TYPE // Обязываем OrderSend и OrderTicket возвращать значение такого же типа, как в MT4 - int. // 03.09.2017: // Add: Добавлены OrderTicketOpen() - тикет MT5-сделки открытия позиции // OrderOpenReason() - причина проведения MT5-сделки открытия (причина открытия позиции) // OrderCloseReason() - причина проведения MT5-сделки закрытия (причина закрытия позиции) // 14.09.2017: // Fix: Теперь библиотека не видит текущие MT5-ордера, которые не имеют состояние ORDER_STATE_PLACED. // Чтобы библиотека видела все открытые MT5-ордера нужно ДО библиотеки прописать строку // // #define MT4ORDERS_SELECTFILTER_OFF // Обязываем MT4Orders.mqh видеть все текущие MT5-ордера // 16.10.2017: // Fix: OrdersHistoryTotal() реагирует на смену номера торгового счета во время выполнения. // 13.02.2018 // Add: Добавлено логирование ошибочного выполнения MT5-OrderSend. // Fix: Теперь "невидимы" только закрывающие MT5-ордера (SL/TP/SO, partial/full close). // Fix: Механизм определения SL/TP закрытых позиций после OrderClose скорректирован - работает, если позволяет StopLevel. // 15.02.2018 // Fix: Проверка синхронизации MT5-OrderSend теперь учитывает возможные особенности реализации ECN/STP. // 06.03.2018 // Add: Добавлены TICKET_TYPE и MAGIC_TYPE, чтобы можно было писать единый кроссплатформенный код // без предупреждений компиляторов (включая strict-режим MQL4). // 30.05.2018 // Add: Ускорена работа с историей торговли, выбрана золотая середина реализаций между производительностью и // потреблением памяти - важно для VPS. Используется стандартная Generic-библиотека. // Если не хочется использовать Generic-библиотеку, то доступен старый режим работы с историей. // Для этого нужно ДО MT4Orders-библиотеки прописать строку // // #define MT4ORDERS_FASTHISTORY_OFF // Выключаем быструю реализацию истории торговли - не используем Generic-библиотеку. // 02.11.2018 // Fix: Цена открытия MT4-позиции до ее срабатывания теперь не может быть нулевой. // Fix: Учтены редкие особенности исполнения некоторых торговых серверов. // 26.11.2018 // Fix: Мэджик и комментарий закрытой MT4-позиции: приоритет у соответствующих полей открывающих сделок выше, чем у закрывающих. // Fix: Учитывается редкое изменение MT5-OrdersTotal и MT5-PositionsTotal во время расчета MT4-OrdersTotal и MT4-OrderSelect. // Fix: Ордера, которые открыли позицию, но не успели удалиться MT5, библиотекой больше не учитываются. // 17.01.2019 // Fix: Исправлена досадная ошибка при выборе отложенных ордеров. // 08.02.2019 // Add: Комментарий позиции сохраняется при частичном закрытии через OrderClose. // Если нужно сменить комментарий открытой позиции при частичном закрытии, в OrderClose можно его задать. // 20.02.2019 // Fix: В случае отсутствия MT5-ордера от существующей MT5-сделки библиотека будет ожидать синхронизации истории. В случае неудачи сообщит об этом. // 13.03.2019 // Add: Добавлена OrderTicketID() - PositionID MT5-сделки или MT5-позиции, тикет отложенного MT4-ордера. // Add: SELECT_BY_TICKET действует для всех MT5-тикетов (и MT5-PositionID). // 02.11.2019 // Fix: Скорректированы лот, комиссия и цена закрытия для CloseBy-позиций. // 12.01.2020 // Fix: OrderTicketID() для балансовых сделок возвращает теперь правильное значение. // Fix: Скорректирован SELECT_BY_TICKET-выбор по OrderTicketID() (MT5-PositionID). // Fix: Изменено название внутреннего метода библиотеки для большей совместимости с макросами. // 10.04.2020 // Fix: Частично исполненный живой отложенный ордер не попадал в OrdersTotal(). // 09.06.2020 // Add: Лучше определяются цены StopLoss/TakeProfit/ClosePriceRequest для закрытых позиций. // 10.06.2020 // Add: Добавлены миллисекунды и убрано округление цен, объемов в OrderPrint(). // 13.08.2020 // Add: Добавлена возможность проверки производительности частей библиотеки через макрос MT4ORDERS_BENCHMARK_MINTIME. // 20.08.2020 // Fix: Учет выявленных особенностей частичного исполнения ордеров. // 29.08.2020 // Fix: Ускорена работа с историей торгов. // 24.09.2020 // Add: Если нужно при выборе живого MT4-ордера по SELECT_BY_TICKET повысить приоритет выбора MT5-ордера над MT5-позицией (тикеты совпадают), // то это можно сделать через изменение знака тикета на отрицательный: OrderSelect(-Ticket, SELECT_BY_TICKET). // Add: Если нужно при модификации живого MT4-ордера указать выбор только MT5-ордера (тикеты совпадают), // то это можно сделать через изменение знака тикета на отрицательный: OrderModify(-Ticket, ...). // Add: OrderSelect(INT_MAX, SELECT_BY_POS) - переход на MT5-позицию без проверки существования и обновления. // OrderSelect(INT_MIN, SELECT_BY_POS) - переход на живой MT5-ордер без проверки существования и обновления. // Fix: Ускорена работа с историей торгов. // 30.09.2020 // Fix: Ускорена работа с историей торгов. // 08.10.2020 // Fix: OrderSend маркет-ордера мог дольше выполняться из-за недоработки при поиске свежей MT5-сделки. // 21.10.2020 // Add: С целью совместимости для MT4 добавлена OrderTicketID() - возвращает OrderTicket(). // 11.11.2020 // Fix: OrderTicketID() и OrderTicketOpen() возвращают заданный в TICKET_TYPE тип значения. // 06.12.2020 // Fix: Случаи некорректных записей исполнения SL/TP в истории торгов MT5 теперь учитываются. // Add: Макрос MT4ORDERS_TESTER_SELECT_BY_TICKET принуждает SELECT_BY_TICKET работать в Тестере только через OrderTicketID(). // 04.05.2021 // Fix: Не успевающие исчезнуть открывающие позиции MT5-ордера больше не попадают в список MT4-ордеров. // Fix: MT5-ордера CloseBy-типа больше не попадают в список MT4-ордеров. // 12.05.2021 // Add: Макрос MT4ORDERS_BYPASS_MAXTIME исправляет постоянно возникающие в MT5 рассинхронизации торгового окружения. // 13.05.2021 // Fix: Исправлена ошибка в OrderOpenReason(). // 14.05.2021 // Fix: BYPASS-механизм больше не влияет на OrderSelect(INT_MAX, SELECT_BY_POS) и OrderSelect(INT_MIN, SELECT_BY_POS). // 01.06.2021 // Fix: Совместимость с компилятором b2449+. // Fix: Улучшена синхронизация. ByPass.mqh должен быть последней версии. // Add: OrderLots(true) - синхронизированный размер выбранной позиции с учетом всех закрывающих ее ордеров. // 09.12.2021 // Fix: Исторические маркет-ордера с нулевым PositionID правильно обрабатываются. // Add: Задействован штатный механизм определения SL/TP закрытых позиций для MT5build 3081+. // Add: Учет возможности удаления истории MT5-ордеров со стороны брокера. // 28.12.2021 // Fix: OrderSelect(Index, SELECT_BY_POS, MODE_TRADES) учитывает еще одну рассинхронизацию со стороны MT5. // Fix: OrderLots(true) учитывает еще одну рассинхронизацию со стороны MT5. // 29.12.2021 // Fix: Улучшена синхронизация в ByPass-режиме. // 06.01.2022 // Fix: Точное определение данных открытия позиций и размера комиссии. Корректно работает только в ByPass-режиме. // Add: Добавлена OrderDealsAmount() - количество MT5-сделок, участвующих в формировании позиции. Корректно работает только в ByPass-режиме. // 12.01.2022 // Fix: OrderPrint() выводит корректные данные редких типов MT5-ордеров/сделок. // 14.01.2022 // Fix: Значительно ускорена OrderSelect(index, SELECT_BY_POS, MODE_HISTORY) в ByPass-режиме. // 08.02.2022 // Fix: Учитывается возможность исполнения без промежуточных шагов MT5 отложенных ордеров во время их установки/модификации. // 20.07.2022 // Add: Добавлена OrderLotsOpen() - полный объем позиции/ордера перед закрытием/удалением. Корректно работает только в ByPass-режиме. // 21.07.2023 // Fix: Исправлена OrderLotsOpen() для удаленных отложенных ордеров. // Add: Обрабатывается ситуация, когда торговая MT5-сделка не имеет MT5-ордера. // Fix: Корректное определение OrderClosePriceRequest() при отсутствии цены у SL/TP-ордера. // Add: Макрос MT4ORDERS_AUTO_VALIDATION позволяет автоматически проходить проверку Маркет-продуктов. // Add: Макрос MT4ORDERS_ORDERS_SORT формирует сортированную по времени закрытия/удаления историю MT4-ордеров. #ifdef __MQL5__ #ifndef __MT4ORDERS__ // #define MT4ORDERS_BYPASS_MAXTIME 1000000 // Максимальное время (в мкс.) на ожидание синхронизации торгового окружения // #define MT4ORDERS_AUTO_VALIDATION // Торговые приказы отправляются только в случае успешной проверки на корректность // #define MT4ORDERS_ORDERS_SORT // Формирование сортированной по времени закрытия/удаления истории MT4-ордеров. #ifdef MT4ORDERS_BYPASS_MAXTIME #include // https://www.mql5.com/ru/code/34173 #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME // #define MT4ORDERS_BENCHMARK_MINTIME 1000 // Минимальное время срабатывания Алерта-производительности. #ifdef MT4ORDERS_BENCHMARK_MINTIME #include // https://www.mql5.com/ru/code/31279 #define _B2(A) _B(A, MT4ORDERS_BENCHMARK_MINTIME) #define _B3(A) _B(A, 1) #define _BV2(A) _BV(A, MT4ORDERS_BENCHMARK_MINTIME) #else // MT4ORDERS_BENCHMARK_MINTIME #define _B2(A) (A) #define _B3(A) (A) #define _BV2(A) { A; } #endif // MT4ORDERS_BENCHMARK_MINTIME #define __MT4ORDERS__ "2023.07.21" // #define MT4ORDERS_TESTER_SELECT_BY_TICKET // Принуждает SELECT_BY_TICKET работать в Тестере только через OrderTicketID(). #ifdef MT4_TICKET_TYPE #define TICKET_TYPE int #define MAGIC_TYPE int #undef MT4_TICKET_TYPE #else // MT4_TICKET_TYPE #define TICKET_TYPE long // Нужны и отрицательные значения для OrderSelectByTicket. #define MAGIC_TYPE long #endif // MT4_TICKET_TYPE struct MT4_ORDER { long Ticket; int Type; long TicketOpen; long TicketID; double Lots; string Symbol; string Comment; double OpenPriceRequest; double OpenPrice; long OpenTimeMsc; datetime OpenTime; ENUM_DEAL_REASON OpenReason; double StopLoss; double TakeProfit; double ClosePriceRequest; double ClosePrice; long CloseTimeMsc; datetime CloseTime; ENUM_DEAL_REASON CloseReason; ENUM_ORDER_STATE State; datetime Expiration; long MagicNumber; double Profit; double Commission; double Swap; int DealsAmount; double LotsOpen; #define POSITION_SELECT (-1) #define ORDER_SELECT (-2) static int GetDigits( double Price ) { int Res = 0; while ((bool)(Price = ::NormalizeDouble(Price - (int)Price, 8))) { Price *= 10; Res++; } return(Res); } static string DoubleToString( const double Num, const int digits ) { return(::DoubleToString(Num, ::MathMax(digits, MT4_ORDER::GetDigits(Num)))); } static string TimeToString( const long time ) { return((string)(datetime)(time / 1000) + "." + ::IntegerToString(time % 1000, 3, '0')); } static const MT4_ORDER GetPositionData( void ) { MT4_ORDER Res = {}; // Обнуление полей. Res.Ticket = ::PositionGetInteger(POSITION_TICKET); Res.Type = (int)::PositionGetInteger(POSITION_TYPE); Res.Lots = ::PositionGetDouble(POSITION_VOLUME); Res.Symbol = ::PositionGetString(POSITION_SYMBOL); // Res.Comment = NULL; // MT4ORDERS::CheckPositionCommissionComment(); Res.OpenPrice = ::PositionGetDouble(POSITION_PRICE_OPEN); Res.OpenTimeMsc = (datetime)::PositionGetInteger(POSITION_TIME_MSC); Res.StopLoss = ::PositionGetDouble(POSITION_SL); Res.TakeProfit = ::PositionGetDouble(POSITION_TP); Res.ClosePrice = ::PositionGetDouble(POSITION_PRICE_CURRENT); Res.MagicNumber = ::PositionGetInteger(POSITION_MAGIC); Res.Profit = ::PositionGetDouble(POSITION_PROFIT); Res.Swap = ::PositionGetDouble(POSITION_SWAP); // Res.Commission = UNKNOWN_COMMISSION; // MT4ORDERS::CheckPositionCommissionComment(); return(Res); } static const MT4_ORDER GetOrderData( void ) { MT4_ORDER Res = {}; // Обнуление полей. Res.Ticket = ::OrderGetInteger(ORDER_TICKET); Res.Type = (int)::OrderGetInteger(ORDER_TYPE); Res.Lots = ::OrderGetDouble(ORDER_VOLUME_CURRENT); Res.Symbol = ::OrderGetString(ORDER_SYMBOL); Res.Comment = ::OrderGetString(ORDER_COMMENT); Res.OpenPrice = ::OrderGetDouble(ORDER_PRICE_OPEN); Res.OpenTimeMsc = (datetime)::OrderGetInteger(ORDER_TIME_SETUP_MSC); Res.StopLoss = ::OrderGetDouble(ORDER_SL); Res.TakeProfit = ::OrderGetDouble(ORDER_TP); Res.ClosePrice = ::OrderGetDouble(ORDER_PRICE_CURRENT); Res.Expiration = (datetime)::OrderGetInteger(ORDER_TIME_EXPIRATION); Res.MagicNumber = ::OrderGetInteger(ORDER_MAGIC); if (!Res.OpenPrice) Res.OpenPrice = Res.ClosePrice; return(Res); } static string GetAddType( const int Type, const bool FlagOrder ) { ::ResetLastError(); string Str = FlagOrder ? ::EnumToString((ENUM_ORDER_TYPE)Type) : ::EnumToString((ENUM_DEAL_TYPE)Type); if (!::_LastError && ::StringToLower(Str)) { Str = FlagOrder ? ::StringSubstr(Str, 11) // "order_type_" : (!::StringFind(Str, "deal_type_") ? ::StringSubstr(Str, 10) // "deal_type_" : (!::StringFind(Str, "deal_") ? ::StringSubstr(Str, 5) // "deal_" : Str)); ::StringReplace(Str, "_", " "); } else Str = "unknown(" + (string)Type + ")"; return(Str); } string ToString( void ) const { static const string Types[] = {"buy", "sell", "buy limit", "sell limit", "buy stop", "sell stop", "balance"}; const int digits = (int)::SymbolInfoInteger(this.Symbol, SYMBOL_DIGITS); MT4_ORDER TmpOrder = {}; if (this.Ticket == POSITION_SELECT) { TmpOrder = MT4_ORDER::GetPositionData(); TmpOrder.Comment = this.Comment; TmpOrder.Commission = this.Commission; } else if (this.Ticket == ORDER_SELECT) TmpOrder = MT4_ORDER::GetOrderData(); return(((this.Ticket == POSITION_SELECT) || (this.Ticket == ORDER_SELECT)) ? TmpOrder.ToString() : ("#" + (string)this.Ticket + " " + MT4_ORDER::TimeToString(this.OpenTimeMsc) + " " + (((this.Type < ::ArraySize(Types)) && ((this.Type <= ORDER_TYPE_SELL_STOP) || !this.OpenPrice)) ? Types[this.Type] : MT4_ORDER::GetAddType(this.Type, this.OpenPrice)) + " " + MT4_ORDER::DoubleToString(this.Lots, 2) + " " + (::StringLen(this.Symbol) ? this.Symbol + " " : NULL) + MT4_ORDER::DoubleToString(this.OpenPrice, digits) + " " + MT4_ORDER::DoubleToString(this.StopLoss, digits) + " " + MT4_ORDER::DoubleToString(this.TakeProfit, digits) + " " + ((this.CloseTimeMsc > 0) ? (MT4_ORDER::TimeToString(this.CloseTimeMsc) + " ") : "") + MT4_ORDER::DoubleToString(this.ClosePrice, digits) + " " + MT4_ORDER::DoubleToString(::NormalizeDouble(this.Commission, 3), 2) + " " + // Больше трех цифр после запятой не выводим. MT4_ORDER::DoubleToString(this.Swap, 2) + " " + MT4_ORDER::DoubleToString(this.Profit, 2) + " " + ((this.Comment == "") ? "" : (this.Comment + " ")) + (string)this.MagicNumber + (((this.Expiration > 0) ? (" expiration " + (string)this.Expiration): "")))); } }; #define RESERVE_SIZE 1000 #define DAY (24 * 3600) #define HISTORY_PAUSE (MT4HISTORY::IsTester ? 0 : 5) #define END_TIME D'31.12.3000 23:59:59' #define THOUSAND 1000 #define LASTTIME(A) \ if (Time##A >= LastTimeMsc) \ { \ const datetime TmpTime = (datetime)(Time##A / THOUSAND); \ \ if (TmpTime > this.LastTime) \ { \ this.LastTotalOrders = 0; \ this.LastTotalDeals = 0; \ \ this.LastTime = TmpTime; \ LastTimeMsc = this.LastTime * THOUSAND; \ } \ \ this.LastTotal##A##s++; \ } #ifndef MT4ORDERS_FASTHISTORY_OFF #include #endif // MT4ORDERS_FASTHISTORY_OFF class MT4HISTORY { private: static const bool MT4HISTORY::IsTester; // static long MT4HISTORY::AccountNumber; #ifndef MT4ORDERS_FASTHISTORY_OFF CHashMap DealsIn; // По PositionID возвращает DealIn. CHashMap DealsOut; // По PositionID возвращает DealOut. #endif // MT4ORDERS_FASTHISTORY_OFF long Tickets[]; uint Amount; int LastTotalDeals; int LastTotalOrders; bool TicketValid; double TicketCommission; double TicketPrice; double TicketLots; int TicketDeals; #ifdef MT4ORDERS_HISTORY_OLD datetime LastTime; datetime LastInitTime; int PrevDealsTotal; int PrevOrdersTotal; // https://www.mql5.com/ru/forum/93352/page50#comment_18040243 bool IsChangeHistory( void ) { bool Res = !_B2(::HistorySelect(0, INT_MAX)); if (!Res) { const int iDealsTotal = ::HistoryDealsTotal(); const int iOrdersTotal = ::HistoryOrdersTotal(); if (Res = (iDealsTotal != this.PrevDealsTotal) || (iOrdersTotal != this.PrevOrdersTotal)) { this.PrevDealsTotal = iDealsTotal; this.PrevOrdersTotal = iOrdersTotal; } } return(Res); } bool RefreshHistory( void ) { bool Res = !MT4HISTORY::IsChangeHistory(); if (!Res) { const datetime LastTimeCurrent = ::TimeCurrent(); if (!MT4HISTORY::IsTester && ((LastTimeCurrent >= this.LastInitTime + DAY)/* || (MT4HISTORY::AccountNumber != ::AccountInfoInteger(ACCOUNT_LOGIN))*/)) { // MT4HISTORY::AccountNumber = ::AccountInfoInteger(ACCOUNT_LOGIN); this.LastTime = 0; this.LastTotalOrders = 0; this.LastTotalDeals = 0; this.Amount = 0; ::ArrayResize(this.Tickets, this.Amount, RESERVE_SIZE); this.LastInitTime = LastTimeCurrent; #ifndef MT4ORDERS_FASTHISTORY_OFF this.DealsIn.Clear(); this.DealsOut.Clear(); #endif // MT4ORDERS_FASTHISTORY_OFF } const datetime LastTimeCurrentLeft = LastTimeCurrent - HISTORY_PAUSE; // Если LastTime равен нулю, то HistorySelect уже сделан в MT4HISTORY::IsChangeHistory(). if (!this.LastTime || _B2(::HistorySelect(this.LastTime, END_TIME))) // https://www.mql5.com/ru/forum/285631/page79#comment_9884935 // if (_B2(::HistorySelect(this.LastTime, INT_MAX))) // Возможно, INT_MAX быстрее END_TIME { const int TotalOrders = ::HistoryOrdersTotal(); const int TotalDeals = ::HistoryDealsTotal(); Res = ((TotalOrders > this.LastTotalOrders) || (TotalDeals > this.LastTotalDeals)); if (Res) { int iOrder = this.LastTotalOrders; int iDeal = this.LastTotalDeals; ulong TicketOrder = 0; ulong TicketDeal = 0; long TimeOrder = (iOrder < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = ::HistoryOrderGetTicket(iOrder)), ORDER_TIME_DONE_MSC) : LONG_MAX; long TimeDeal = (iDeal < TotalDeals) ? ::HistoryDealGetInteger((TicketDeal = ::HistoryDealGetTicket(iDeal)), DEAL_TIME_MSC) : LONG_MAX; if (this.LastTime < LastTimeCurrentLeft) { this.LastTotalOrders = 0; this.LastTotalDeals = 0; this.LastTime = LastTimeCurrentLeft; } long LastTimeMsc = this.LastTime * THOUSAND; while ((iDeal < TotalDeals) || (iOrder < TotalOrders)) if (TimeOrder < TimeDeal) { LASTTIME(Order) if (MT4HISTORY::IsMT4Order(TicketOrder)) { this.Amount = ::ArrayResize(this.Tickets, this.Amount + 1, RESERVE_SIZE); this.Tickets[this.Amount - 1] = -(long)TicketOrder; } iOrder++; TimeOrder = (iOrder < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = ::HistoryOrderGetTicket(iOrder)), ORDER_TIME_DONE_MSC) : LONG_MAX; } else { LASTTIME(Deal) if (MT4HISTORY::IsMT4Deal(TicketDeal)) { this.Amount = ::ArrayResize(this.Tickets, this.Amount + 1, RESERVE_SIZE); this.Tickets[this.Amount - 1] = (long)TicketDeal; #ifndef MT4ORDERS_FASTHISTORY_OFF _B2(this.DealsOut.Add(::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID), TicketDeal)); // Запомнится только первая OUT-сделка. #endif // MT4ORDERS_FASTHISTORY_OFF } #ifndef MT4ORDERS_FASTHISTORY_OFF else if ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) _B2(this.DealsIn.Add(::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID), TicketDeal)); #endif // MT4ORDERS_FASTHISTORY_OFF iDeal++; TimeDeal = (iDeal < TotalDeals) ? ::HistoryDealGetInteger((TicketDeal = ::HistoryDealGetTicket(iDeal)), DEAL_TIME_MSC) : LONG_MAX; } } else if (LastTimeCurrentLeft > this.LastTime) { this.LastTime = LastTimeCurrentLeft; this.LastTotalOrders = 0; this.LastTotalDeals = 0; } } } return(Res); } #else // #ifdef MT4ORDERS_HISTORY_OLD bool RefreshHistory( void ) { if (_B2(::HistorySelect(0, INT_MAX))) { const int TotalOrders = ::HistoryOrdersTotal(); const int TotalDeals = ::HistoryDealsTotal(); if ((TotalOrders > this.LastTotalOrders) || (TotalDeals > this.LastTotalDeals)) { ulong TicketOrder = 0; ulong TicketDeal = 0; // https://www.mql5.com/ru/forum/1111/page3329#comment_47299480 #ifdef MT4ORDERS_ORDERS_SORT ulong ArrayOrders[][2]; if (!MT4HISTORY::IsTester && (this.LastTotalOrders < TotalOrders)) { ::ArrayResize(ArrayOrders, TotalOrders); for (int i = 0; i < TotalOrders; i++) { const ulong Ticket = ::HistoryOrderGetTicket(i); ArrayOrders[i][0] = ::HistoryOrderGetInteger(Ticket, ORDER_TIME_DONE_MSC); ArrayOrders[i][1] = Ticket; } ::ArraySort(ArrayOrders); } long TimeOrder = (this.LastTotalOrders < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = MT4HISTORY::IsTester ? ::HistoryOrderGetTicket(this.LastTotalOrders) : ArrayOrders[this.LastTotalOrders][1]), ORDER_TIME_DONE_MSC) : LONG_MAX; #else // #ifdef MT4ORDERS_ORDERS_SORT long TimeOrder = (this.LastTotalOrders < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = ::HistoryOrderGetTicket(this.LastTotalOrders)), ORDER_TIME_DONE_MSC) : LONG_MAX; #endif // #ifdef MT4ORDERS_ORDERS_SORT #else #ifdef MT4ORDERS_ORDERS_SORT #else // #ifdef MT4ORDERS_ORDERS_SORT #endif // #ifdef MT4ORDERS_ORDERS_SORT #else long TimeDeal = (this.LastTotalDeals < TotalDeals) ? ::HistoryDealGetInteger((TicketDeal = ::HistoryDealGetTicket(this.LastTotalDeals)), DEAL_TIME_MSC) : LONG_MAX; while ((this.LastTotalDeals < TotalDeals) || (this.LastTotalOrders < TotalOrders)) if (TimeOrder < TimeDeal) { if (MT4HISTORY::IsMT4Order(TicketOrder)) { this.Amount = ::ArrayResize(this.Tickets, this.Amount + 1, RESERVE_SIZE); this.Tickets[this.Amount - 1] = -(long)TicketOrder; } this.LastTotalOrders++; #ifdef MT4ORDERS_ORDERS_SORT TimeOrder = (this.LastTotalOrders < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = MT4HISTORY::IsTester ? ::HistoryOrderGetTicket(this.LastTotalOrders) : ArrayOrders[this.LastTotalOrders][1]), ORDER_TIME_DONE_MSC) : LONG_MAX; #else // #ifdef MT4ORDERS_ORDERS_SORT TimeOrder = (this.LastTotalOrders < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = ::HistoryOrderGetTicket(this.LastTotalOrders)), ORDER_TIME_DONE_MSC) : LONG_MAX; #endif // #ifdef MT4ORDERS_ORDERS_SORT #else } else { if (MT4HISTORY::IsMT4Deal(TicketDeal)) { this.Amount = ::ArrayResize(this.Tickets, this.Amount + 1, RESERVE_SIZE); this.Tickets[this.Amount - 1] = (long)TicketDeal; _B2(this.DealsOut.Add(::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID), TicketDeal)); } else if ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) _B2(this.DealsIn.Add(::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID), TicketDeal)); this.LastTotalDeals++; TimeDeal = (this.LastTotalDeals < TotalDeals) ? ::HistoryDealGetInteger((TicketDeal = ::HistoryDealGetTicket(this.LastTotalDeals)), DEAL_TIME_MSC) : LONG_MAX; } } } return(true); } ulong GetPositionDealIn2( const ulong PositionID, const ulong DealStop = LONG_MAX ) { ulong Ticket = 0; // UNKNOWN_TICKET #ifdef MT4ORDERS_BYPASS_MAXTIME static TRADESID TradesID; ulong Deals[]; const int Size = _B2(TradesID.GetDealsByID(PositionID, Deals)); // Будет выполнен HistorySelect(0, INT_MAX) this.TicketValid = (DealStop == LONG_MAX) ? (Size >= 2) : (Size > 2); if (this.TicketValid) { this.TicketCommission = 0; this.TicketPrice = 0; this.TicketLots = 0; for (int i = 0; (i < Size) && (Deals[i] < DealStop); i++) { const ulong DealTicket = Deals[i]; const ENUM_DEAL_ENTRY Entry = (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(DealTicket, DEAL_ENTRY); const double Commission = ::HistoryDealGetDouble(DealTicket, DEAL_COMMISSION); const double Volume = ::HistoryDealGetDouble(DealTicket, DEAL_VOLUME); if (this.TicketLots < 1e-8) { Ticket = DealTicket; this.TicketLots = 0; this.TicketDeals = 0; } if (Entry == DEAL_ENTRY_IN) { this.TicketPrice = (this.TicketPrice * this.TicketLots + ::HistoryDealGetDouble(DealTicket, DEAL_PRICE) * Volume) / (this.TicketLots + Volume); this.TicketCommission += Commission; this.TicketLots += Volume; } else { this.TicketCommission -= this.TicketCommission * Volume / this.TicketLots; this.TicketLots -= Volume; } this.TicketDeals++; } } else if (Size) Ticket = Deals[0]; return(Ticket); #else // #ifdef MT4ORDERS_BYPASS_MAXTIME return((_B2(this.DealsIn.TryGetValue(PositionID, Ticket)) || _B2(this.RefreshHistory() && this.DealsIn.TryGetValue(PositionID, Ticket))) ? Ticket : 0); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME #else } #endif // #ifdef MT4ORDERS_HISTORY_OLD #else public: static bool IsMT4Deal( const ulong &Ticket ) { const ENUM_DEAL_TYPE DealType = (ENUM_DEAL_TYPE)::HistoryDealGetInteger(Ticket, DEAL_TYPE); const ENUM_DEAL_ENTRY DealEntry = (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(Ticket, DEAL_ENTRY); return(((DealType != DEAL_TYPE_BUY) && (DealType != DEAL_TYPE_SELL)) || // не торговая сделка ((DealEntry == DEAL_ENTRY_OUT) || (DealEntry == DEAL_ENTRY_OUT_BY))); // торговая } static bool IsMT4Order( const ulong &Ticket ) { // Если отложенный ордер исполнился, его ORDER_POSITION_ID заполняется. // https://www.mql5.com/ru/forum/170952/page70#comment_6543162 // https://www.mql5.com/ru/forum/93352/page19#comment_6646726 // Второе условие: когда лимитный ордер был частично исполнен, а затем удален. // Маркет-ордер может быть отменен и не иметь ORDER_POSITION_ID. return((::HistoryOrderGetInteger(Ticket, ORDER_TYPE) > ORDER_TYPE_SELL) &&(!::HistoryOrderGetInteger(Ticket, ORDER_POSITION_ID) || ::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_CURRENT))); } MT4HISTORY( void ) : Amount(::ArrayResize(this.Tickets, 0, RESERVE_SIZE)), LastTotalDeals(0), LastTotalOrders(0), TicketValid(false), TicketCommission(0), TicketPrice(0), TicketLots(0), TicketDeals(0) #ifdef MT4ORDERS_HISTORY_OLD , LastTime(0), LastInitTime(0), PrevDealsTotal(0), PrevOrdersTotal(0) #endif // #ifdef MT4ORDERS_HISTORY_OLD { // this.RefreshHistory(); // Если история не используется, незачем забивать ресурсы. } ulong GetPositionDealIn( const ulong PositionIdentifier = -1, const ulong DealOutTicket = LONG_MAX ) // ID = 0 - нельзя, т.к. балансовая сделка тестера имеет ноль { ulong Ticket = 0; this.TicketValid = false; if (PositionIdentifier == -1) { const ulong MyPositionIdentifier = ::PositionGetInteger(POSITION_IDENTIFIER); #ifndef MT4ORDERS_FASTHISTORY_OFF if (!(Ticket = this.GetPositionDealIn2(MyPositionIdentifier))) #endif // MT4ORDERS_FASTHISTORY_OFF { const datetime PosTime = (datetime)::PositionGetInteger(POSITION_TIME); if (_B3(::HistorySelect(PosTime, PosTime))) { const int Total = ::HistoryDealsTotal(); for (int i = 0; i < Total; i++) { const ulong TicketDeal = ::HistoryDealGetTicket(i); if ((::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID) == MyPositionIdentifier) /*&& ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) */) // Первое упоминание и так будет DEAL_ENTRY_IN { Ticket = TicketDeal; #ifndef MT4ORDERS_FASTHISTORY_OFF _B2(this.DealsIn.Add(MyPositionIdentifier, Ticket)); #endif // MT4ORDERS_FASTHISTORY_OFF break; } } } } } else if (PositionIdentifier && // PositionIdentifier балансовых сделок равен нулю #ifndef MT4ORDERS_FASTHISTORY_OFF !(Ticket = this.GetPositionDealIn2(PositionIdentifier, DealOutTicket)) && #endif // MT4ORDERS_FASTHISTORY_OFF _B3(::HistorySelectByPosition(PositionIdentifier)) && (::HistoryDealsTotal() > 1)) // > 1, а не > 0 - ищется DealIN для уже закрытой позиции. { Ticket = _B2(::HistoryDealGetTicket(0)); // Первое упоминание и так будет DEAL_ENTRY_IN /* const int Total = ::HistoryDealsTotal(); for (int i = 0; i < Total; i++) { const ulong TicketDeal = ::HistoryDealGetTicket(i); if (TicketDeal > 0) if ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) { Ticket = TicketDeal; break; } } */ #ifndef MT4ORDERS_FASTHISTORY_OFF _B2(this.DealsIn.Add(PositionIdentifier, Ticket)); #endif // MT4ORDERS_FASTHISTORY_OFF } return(Ticket); } ulong GetPositionDealOut( const ulong PositionIdentifier ) { ulong Ticket = 0; #ifndef MT4ORDERS_FASTHISTORY_OFF if (!_B2(this.DealsOut.TryGetValue(PositionIdentifier, Ticket)) && _B2(this.RefreshHistory())) _B2(this.DealsOut.TryGetValue(PositionIdentifier, Ticket)); #endif // MT4ORDERS_FASTHISTORY_OFF return(Ticket); } int GetAmount( void ) { _B2(this.RefreshHistory()); return((int)this.Amount); } int GetAmountPrev( void ) const { return((int)this.Amount); } long operator []( const uint &Pos ) { long Res = 0; if ((Pos >= this.Amount)/* || (!MT4HISTORY::IsTester && (MT4HISTORY::AccountNumber != ::AccountInfoInteger(ACCOUNT_LOGIN)))*/) { _B2(this.RefreshHistory()); if (Pos < this.Amount) Res = this.Tickets[Pos]; } else Res = this.Tickets[Pos]; return(Res); } bool GetTicketCommission( double &Commission, double &Lots ) const { if (this.TicketValid) { Commission = this.TicketCommission; Lots = this.TicketLots; } return(this.TicketValid); } bool GetTicketPrice( double &Price ) const { if (this.TicketValid) Price = this.TicketPrice; return(this.TicketValid); } int GetTicketDeals( void ) const { return(this.TicketValid ? this.TicketDeals : 1); } double GetTicketLots( void ) const { return(this.TicketValid ? ::NormalizeDouble(this.TicketLots, 8) : 0); } }; static const bool MT4HISTORY::IsTester = ::MQLInfoInteger(MQL_TESTER); // static long MT4HISTORY::AccountNumber = ::AccountInfoInteger(ACCOUNT_LOGIN); #undef LASTTIME #undef THOUSAND #undef END_TIME #undef HISTORY_PAUSE #undef DAY #undef RESERVE_SIZE #define OP_BUY ORDER_TYPE_BUY #define OP_SELL ORDER_TYPE_SELL #define OP_BUYLIMIT ORDER_TYPE_BUY_LIMIT #define OP_SELLLIMIT ORDER_TYPE_SELL_LIMIT #define OP_BUYSTOP ORDER_TYPE_BUY_STOP #define OP_SELLSTOP ORDER_TYPE_SELL_STOP #define OP_BALANCE 6 #define SELECT_BY_POS 0 #define SELECT_BY_TICKET 1 #define MODE_TRADES 0 #define MODE_HISTORY 1 class MT4ORDERS { private: static MT4_ORDER Order; static MT4HISTORY History; static const bool MT4ORDERS::IsTester; static const bool MT4ORDERS::IsHedging; static const bool MTBuildSLTP; static int OrderSendBug; // static bool HistorySelectOrder( const ulong &Ticket ) static bool HistorySelectOrder( const ulong Ticket ) { return(Ticket && ((::HistoryOrderGetInteger(Ticket, ORDER_TICKET) == Ticket) || (_B2(::HistorySelect(0, INT_MAX)) && (::HistoryOrderGetInteger(Ticket, ORDER_TICKET) == Ticket)))); } static bool HistorySelectDeal( const ulong &Ticket ) { return(Ticket && ((::HistoryDealGetInteger(Ticket, DEAL_TICKET) == Ticket) || (_B2(::HistorySelect(0, INT_MAX)) && (::HistoryDealGetInteger(Ticket, DEAL_TICKET) == Ticket)))); } #define UNKNOWN_COMMISSION DBL_MIN #define UNKNOWN_REQUEST_PRICE DBL_MIN #define UNKNOWN_TICKET 0 // #define UNKNOWN_REASON (-1) static bool CheckNewTicket( void ) { return(false); // Ни к чему этот функционал - есть INT_MIN/INT_MAX с SELECT_BY_POS + MODE_TRADES static long PrevPosTimeUpdate = 0; static long PrevPosTicket = 0; const long PosTimeUpdate = ::PositionGetInteger(POSITION_TIME_UPDATE_MSC); const long PosTicket = ::PositionGetInteger(POSITION_TICKET); // На случай, если пользователь сделал выбор позиции не через MT4Orders // Перегружать MQL5-PositionSelect* и MQL5-OrderSelect нерезонно. // Этой проверки достаточно, т.к. несколько изменений позиции + PositionSelect в одну миллисекунду возможно только в тестере const bool Res = ((PosTimeUpdate != PrevPosTimeUpdate) || (PosTicket != PrevPosTicket)); if (Res) { MT4ORDERS::GetPositionData(); PrevPosTimeUpdate = PosTimeUpdate; PrevPosTicket = PosTicket; } return(Res); } static bool CheckPositionTicketOpen( void ) { if ((MT4ORDERS::Order.TicketOpen == UNKNOWN_TICKET) || MT4ORDERS::CheckNewTicket()) { MT4ORDERS::Order.TicketOpen = (long)_B2(MT4ORDERS::History.GetPositionDealIn()); // Все из-за этой очень дорогой функции MT4ORDERS::Order.DealsAmount = MT4ORDERS::History.GetTicketDeals(); MT4ORDERS::Order.LotsOpen = MT4ORDERS::History.GetTicketLots(); } return(true); } static bool CheckPositionCommissionComment( void ) { if ((MT4ORDERS::Order.Commission == UNKNOWN_COMMISSION) || MT4ORDERS::CheckNewTicket()) { MT4ORDERS::Order.Commission = 0; // ::PositionGetDouble(POSITION_COMMISSION); // Всегда ноль MT4ORDERS::Order.Comment = ::PositionGetString(POSITION_COMMENT); if (!MT4ORDERS::Order.Commission || (MT4ORDERS::Order.Comment == "")) { MT4ORDERS::CheckPositionTicketOpen(); const ulong Ticket = MT4ORDERS::Order.TicketOpen; if ((Ticket > 0) && _B2(MT4ORDERS::HistorySelectDeal(Ticket))) { double LotsIn; if (!MT4ORDERS::Order.Commission && !MT4ORDERS::History.GetTicketCommission(MT4ORDERS::Order.Commission, LotsIn)) { LotsIn = ::HistoryDealGetDouble(Ticket, DEAL_VOLUME); if (LotsIn > 0) MT4ORDERS::Order.Commission = ::HistoryDealGetDouble(Ticket, DEAL_COMMISSION) * ::PositionGetDouble(POSITION_VOLUME) / LotsIn; } if (MT4ORDERS::Order.Comment == "") MT4ORDERS::Order.Comment = ::HistoryDealGetString(Ticket, DEAL_COMMENT); } } } return(true); } /* static bool CheckPositionOpenReason( void ) { if ((MT4ORDERS::Order.OpenReason == UNKNOWN_REASON) || MT4ORDERS::CheckNewTicket()) { MT4ORDERS::CheckPositionTicketOpen(); const ulong Ticket = MT4ORDERS::Order.TicketOpen; if ((Ticket > 0) && (MT4ORDERS::IsTester || MT4ORDERS::HistorySelectDeal(Ticket))) MT4ORDERS::Order.OpenReason = (ENUM_DEAL_REASON)::HistoryDealGetInteger(Ticket, DEAL_REASON); } return(true); } */ static bool CheckPositionOpenPriceRequest( void ) { const long PosTicket = ::PositionGetInteger(POSITION_TICKET); if (((MT4ORDERS::Order.OpenPriceRequest == UNKNOWN_REQUEST_PRICE) || MT4ORDERS::CheckNewTicket()) && !(MT4ORDERS::Order.OpenPriceRequest = (_B2(MT4ORDERS::HistorySelectOrder(PosTicket)) && (MT4ORDERS::IsTester || (::PositionGetInteger(POSITION_TIME_MSC) == ::HistoryOrderGetInteger(PosTicket, ORDER_TIME_DONE_MSC)))) // А нужна ли эта проверка? ? ::HistoryOrderGetDouble(PosTicket, ORDER_PRICE_OPEN) : ::PositionGetDouble(POSITION_PRICE_OPEN))) MT4ORDERS::Order.OpenPriceRequest = ::PositionGetDouble(POSITION_PRICE_OPEN); // На случай, если цена ордера нулевая return(true); } static void GetPositionData( void ) { MT4ORDERS::Order.Ticket = POSITION_SELECT; MT4ORDERS::Order.Commission = UNKNOWN_COMMISSION; // MT4ORDERS::CheckPositionCommissionComment(); MT4ORDERS::Order.OpenPriceRequest = UNKNOWN_REQUEST_PRICE; // MT4ORDERS::CheckPositionOpenPriceRequest() MT4ORDERS::Order.TicketOpen = UNKNOWN_TICKET; // MT4ORDERS::Order.OpenReason = UNKNOWN_REASON; // const bool AntoWarning = ::OrderSelect(0); // Обнуляет данные выбранной позиции - может быть нужно для OrderModify return; } // #undef UNKNOWN_REASON #undef UNKNOWN_TICKET #undef UNKNOWN_REQUEST_PRICE #undef UNKNOWN_COMMISSION static void GetOrderData( void ) { MT4ORDERS::Order.Ticket = ORDER_SELECT; // ::PositionSelectByTicket(0); // Обнуляет данные выбранной позиции - может быть нужно для OrderModify return; } static void GetHistoryOrderData( const ulong Ticket ) { MT4ORDERS::Order.Ticket = ::HistoryOrderGetInteger(Ticket, ORDER_TICKET); MT4ORDERS::Order.Type = (int)::HistoryOrderGetInteger(Ticket, ORDER_TYPE); MT4ORDERS::Order.TicketOpen = MT4ORDERS::Order.Ticket; MT4ORDERS::Order.TicketID = MT4ORDERS::Order.Ticket; // Удаленная отложка может иметь ненулевой POSITION_ID. MT4ORDERS::Order.Lots = ::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_CURRENT); if (!MT4ORDERS::Order.Lots) MT4ORDERS::Order.Lots = ::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_INITIAL); MT4ORDERS::Order.Symbol = ::HistoryOrderGetString(Ticket, ORDER_SYMBOL); MT4ORDERS::Order.Comment = ::HistoryOrderGetString(Ticket, ORDER_COMMENT); MT4ORDERS::Order.OpenTimeMsc = ::HistoryOrderGetInteger(Ticket, ORDER_TIME_SETUP_MSC); MT4ORDERS::Order.OpenTime = (datetime)(MT4ORDERS::Order.OpenTimeMsc / 1000); MT4ORDERS::Order.OpenPrice = ::HistoryOrderGetDouble(Ticket, ORDER_PRICE_OPEN); MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; MT4ORDERS::Order.OpenReason = (ENUM_DEAL_REASON)::HistoryOrderGetInteger(Ticket, ORDER_REASON); MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(Ticket, ORDER_SL); MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(Ticket, ORDER_TP); MT4ORDERS::Order.CloseTimeMsc = ::HistoryOrderGetInteger(Ticket, ORDER_TIME_DONE_MSC); MT4ORDERS::Order.CloseTime = (datetime)(MT4ORDERS::Order.CloseTimeMsc / 1000); MT4ORDERS::Order.ClosePrice = ::HistoryOrderGetDouble(Ticket, ORDER_PRICE_CURRENT); MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; MT4ORDERS::Order.CloseReason = MT4ORDERS::Order.OpenReason; MT4ORDERS::Order.State = (ENUM_ORDER_STATE)::HistoryOrderGetInteger(Ticket, ORDER_STATE); MT4ORDERS::Order.Expiration = (datetime)::HistoryOrderGetInteger(Ticket, ORDER_TIME_EXPIRATION); MT4ORDERS::Order.MagicNumber = ::HistoryOrderGetInteger(Ticket, ORDER_MAGIC); MT4ORDERS::Order.Profit = 0; MT4ORDERS::Order.Commission = 0; MT4ORDERS::Order.Swap = 0; MT4ORDERS::Order.LotsOpen = ::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_INITIAL); return; } static string GetTickFlag( uint tickflag ) { string flag = " " + (string)tickflag; #define TICKFLAG_MACRO(A) flag += ((bool)(tickflag & TICK_FLAG_##A)) ? " TICK_FLAG_" + #A : ""; \ tickflag -= tickflag & TICK_FLAG_##A; TICKFLAG_MACRO(BID) TICKFLAG_MACRO(ASK) TICKFLAG_MACRO(LAST) TICKFLAG_MACRO(VOLUME) TICKFLAG_MACRO(BUY) TICKFLAG_MACRO(SELL) #undef TICKFLAG_MACRO if (tickflag) flag += " FLAG_UNKNOWN (" + (string)tickflag + ")"; return(flag); } #define TOSTR(A) " " + #A + " = " + (string)Tick.A #define TOSTR2(A) " " + #A + " = " + ::DoubleToString(Tick.A, digits) #define TOSTR3(A) " " + #A + " = " + (string)(A) static string TickToString( const string &Symb, const MqlTick &Tick ) { const int digits = (int)::SymbolInfoInteger(Symb, SYMBOL_DIGITS); return(TOSTR3(Symb) + TOSTR(time) + "." + ::IntegerToString(Tick.time_msc % 1000, 3, '0') + TOSTR2(bid) + TOSTR2(ask) + TOSTR2(last)+ TOSTR(volume) + MT4ORDERS::GetTickFlag(Tick.flags)); } static string TickToString( const string &Symb ) { MqlTick Tick = {}; return(TOSTR3(::SymbolInfoTick(Symb, Tick)) + MT4ORDERS::TickToString(Symb, Tick)); } #undef TOSTR3 #undef TOSTR2 #undef TOSTR static void AlertLog( void ) { ::Alert("Please send the logs to the coauthor - https://www.mql5.com/en/users/fxsaber"); string Str = ::TimeToString(::TimeLocal(), TIME_DATE); ::StringReplace(Str, ".", NULL); ::Alert(::TerminalInfoString(TERMINAL_PATH) + "\\MQL5\\Logs\\" + Str + ".log"); return; } static long GetTimeCurrent( void ) { long Res = 0; MqlTick Tick = {}; for (int i = ::SymbolsTotal(true) - 1; i >= 0; i--) { const string SymbName = ::SymbolName(i, true); if (!::SymbolInfoInteger(SymbName, SYMBOL_CUSTOM) && ::SymbolInfoTick(SymbName, Tick) && (Tick.time_msc > Res)) Res = Tick.time_msc; } return(Res); } static string TimeToString( const long time ) { return((string)(datetime)(time / 1000) + "." + ::IntegerToString(time % 1000, 3, '0')); } #define WHILE(A) while ((!(Res = (A))) && MT4ORDERS::Waiting()) #define TOSTR(A) #A + " = " + (string)(A) + "\n" #define TOSTR2(A) #A + " = " + ::EnumToString(A) + " (" + (string)(A) + ")\n" static ulong GetFirstOrderTicket( void ) { static ulong FirstOrderTicket = ULONG_MAX; static uint PrevTime = 0; const uint NewTime = ::GetTickCount(); if (NewTime - PrevTime > 1000) { if ((FirstOrderTicket != ::HistoryOrderGetTicket(0)) && ::HistorySelect(0, INT_MAX)) FirstOrderTicket = ::HistoryOrdersTotal() ? ::HistoryOrderGetTicket(0) : ULONG_MAX; PrevTime = NewTime; } return(FirstOrderTicket); } static bool IsHistoryFull( const ulong &OrderTicket ) { return(MT4ORDERS::IsTester || (OrderTicket >= MT4ORDERS::GetFirstOrderTicket())); // Если был живой ордер во время удаления истории брокером - плохо. } static void GetHistoryPositionData( const ulong Ticket ) { MT4ORDERS::Order.Ticket = (long)Ticket; MT4ORDERS::Order.TicketID = ::HistoryDealGetInteger(MT4ORDERS::Order.Ticket, DEAL_POSITION_ID); MT4ORDERS::Order.Type = (int)::HistoryDealGetInteger(Ticket, DEAL_TYPE); if ((MT4ORDERS::Order.Type > OP_SELL)) MT4ORDERS::Order.Type += (OP_BALANCE - OP_SELL - 1); else MT4ORDERS::Order.Type = 1 - MT4ORDERS::Order.Type; MT4ORDERS::Order.Lots = ::HistoryDealGetDouble(Ticket, DEAL_VOLUME); MT4ORDERS::Order.Symbol = ::HistoryDealGetString(Ticket, DEAL_SYMBOL); MT4ORDERS::Order.Comment = ::HistoryDealGetString(Ticket, DEAL_COMMENT); MT4ORDERS::Order.CloseTimeMsc = ::HistoryDealGetInteger(Ticket, DEAL_TIME_MSC); MT4ORDERS::Order.CloseTime = (datetime)(MT4ORDERS::Order.CloseTimeMsc / 1000); // (datetime)::HistoryDealGetInteger(Ticket, DEAL_TIME); MT4ORDERS::Order.ClosePrice = ::HistoryDealGetDouble(Ticket, DEAL_PRICE); MT4ORDERS::Order.CloseReason = (ENUM_DEAL_REASON)::HistoryDealGetInteger(Ticket, DEAL_REASON); MT4ORDERS::Order.Expiration = 0; MT4ORDERS::Order.MagicNumber = ::HistoryDealGetInteger(Ticket, DEAL_MAGIC); MT4ORDERS::Order.Profit = ::HistoryDealGetDouble(Ticket, DEAL_PROFIT); MT4ORDERS::Order.Commission = ::HistoryDealGetDouble(Ticket, DEAL_COMMISSION); MT4ORDERS::Order.Swap = ::HistoryDealGetDouble(Ticket, DEAL_SWAP); MT4ORDERS::Order.StopLoss = MT4ORDERS::MTBuildSLTP ? ::HistoryDealGetDouble(Ticket, DEAL_SL) : 0; MT4ORDERS::Order.TakeProfit = MT4ORDERS::MTBuildSLTP ? ::HistoryDealGetDouble(Ticket, DEAL_TP) : 0; MT4ORDERS::Order.DealsAmount = 0; MT4ORDERS::Order.LotsOpen = MT4ORDERS::Order.Lots; const ulong OrderTicket = (MT4ORDERS::Order.Type < OP_BALANCE) ? ::HistoryDealGetInteger(Ticket, DEAL_ORDER) : 0; // Торговый DEAL_ORDER может быть нулевым. const ulong PosTicket = MT4ORDERS::Order.TicketID; const ulong OpenTicket = ((OrderTicket > 0) || (MT4ORDERS::Order.Type < OP_BALANCE)) ? _B2(MT4ORDERS::History.GetPositionDealIn(PosTicket, Ticket)) : 0; const bool IsOrderTicket = MT4ORDERS::IsHistoryFull(OrderTicket); // Не обрезана ли брокером история до этого тикета? if (OpenTicket > 0) { MT4ORDERS::Order.DealsAmount = MT4ORDERS::History.GetTicketDeals(); MT4ORDERS::Order.LotsOpen = MT4ORDERS::History.GetTicketLots(); const ENUM_DEAL_REASON Reason = MT4ORDERS::Order.CloseReason; const ENUM_DEAL_ENTRY DealEntry = (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(Ticket, DEAL_ENTRY); // История (OpenTicket и OrderTicket) подгружена, благодаря GetPositionDealIn, - HistorySelectByPosition #ifdef MT4ORDERS_FASTHISTORY_OFF const bool Res = true; #else // MT4ORDERS_FASTHISTORY_OFF // Частичное исполнение породит нужный ордер - https://www.mql5.com/ru/forum/227423/page2#comment_6543129 bool Res = MT4ORDERS::IsTester ? MT4ORDERS::HistorySelectOrder(OrderTicket) : (!IsOrderTicket || MT4ORDERS::Waiting(true)); // Можно долго ждать в этой ситуации: https://www.mql5.com/ru/forum/170952/page184#comment_17913645 if (!Res) WHILE(_B2(MT4ORDERS::HistorySelectOrder(OrderTicket))) // https://www.mql5.com/ru/forum/304239#comment_10710403 ; if (_B2(MT4ORDERS::HistorySelectDeal(OpenTicket))) // Обязательно сработает, т.к. OpenTicket гарантированно в истории. #endif // MT4ORDERS_FASTHISTORY_OFF { MT4ORDERS::Order.TicketOpen = (long)OpenTicket; MT4ORDERS::Order.OpenReason = (ENUM_DEAL_REASON)HistoryDealGetInteger(OpenTicket, DEAL_REASON); if (!MT4ORDERS::History.GetTicketPrice(MT4ORDERS::Order.OpenPrice)) MT4ORDERS::Order.OpenPrice = ::HistoryDealGetDouble(OpenTicket, DEAL_PRICE); MT4ORDERS::Order.OpenTimeMsc = ::HistoryDealGetInteger(OpenTicket, DEAL_TIME_MSC); MT4ORDERS::Order.OpenTime = (datetime)(MT4ORDERS::Order.OpenTimeMsc / 1000); double OpenLots; double Commission; if (!MT4ORDERS::History.GetTicketCommission(Commission, OpenLots)) { Commission = ::HistoryDealGetDouble(OpenTicket, DEAL_COMMISSION); OpenLots = ::HistoryDealGetDouble(OpenTicket, DEAL_VOLUME); MT4ORDERS::Order.LotsOpen = OpenLots; } if (OpenLots > 0) MT4ORDERS::Order.Commission += Commission * MT4ORDERS::Order.Lots / OpenLots; // if (!MT4ORDERS::Order.MagicNumber) // Мэджик закрытой позиции всегда должен быть равен мэджику открывающей сделки. const long Magic = ::HistoryDealGetInteger(OpenTicket, DEAL_MAGIC); if (Magic) MT4ORDERS::Order.MagicNumber = Magic; // if (MT4ORDERS::Order.Comment == "") // Комментарий закрытой позиции всегда должен быть равен комментарию открывающей сделки. const string StrComment = ::HistoryDealGetString(OpenTicket, DEAL_COMMENT); if (Res && (IsOrderTicket || !OrderTicket)) // OrderTicket может не быть в истории, но может оказаться среди еще живых. Возможно, резонно оттуда выудить нужную инфу. { double OrderPriceOpen = OrderTicket ? ::HistoryOrderGetDouble(OrderTicket, ORDER_PRICE_OPEN) : 0; if (!MT4ORDERS::MTBuildSLTP) { if (Reason == DEAL_REASON_TP) { if (!OrderPriceOpen) // https://www.mql5.com/ru/forum/1111/page2820#comment_17749873 OrderPriceOpen = (double)::StringSubstr(MT4ORDERS::Order.Comment, MT4ORDERS::IsTester ? 3 : (::StringFind(MT4ORDERS::Order.Comment, "tp ") + 3)); MT4ORDERS::Order.TakeProfit = OrderPriceOpen; MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(OrderTicket, ORDER_TP); } else if (Reason == DEAL_REASON_SL) { if (!OrderPriceOpen) // https://www.mql5.com/ru/forum/1111/page2820#comment_17749873 OrderPriceOpen = (double)::StringSubstr(MT4ORDERS::Order.Comment, MT4ORDERS::IsTester ? 3 : (::StringFind(MT4ORDERS::Order.Comment, "sl ") + 3)); MT4ORDERS::Order.StopLoss = OrderPriceOpen; MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(OrderTicket, ORDER_SL); } else if (!MT4ORDERS::IsTester &&::StringLen(MT4ORDERS::Order.Comment) > 3) { const string PartComment = ::StringSubstr(MT4ORDERS::Order.Comment, 0, 3); if (PartComment == "[tp") { MT4ORDERS::Order.CloseReason = DEAL_REASON_TP; if (!OrderPriceOpen) // https://www.mql5.com/ru/forum/1111/page2820#comment_17749873 OrderPriceOpen = (double)::StringSubstr(MT4ORDERS::Order.Comment, MT4ORDERS::IsTester ? 3 : (::StringFind(MT4ORDERS::Order.Comment, "tp ") + 3)); MT4ORDERS::Order.TakeProfit = OrderPriceOpen; MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(OrderTicket, ORDER_TP); } else if (PartComment == "[sl") { MT4ORDERS::Order.CloseReason = DEAL_REASON_SL; if (!OrderPriceOpen) // https://www.mql5.com/ru/forum/1111/page2820#comment_17749873 OrderPriceOpen = (double)::StringSubstr(MT4ORDERS::Order.Comment, MT4ORDERS::IsTester ? 3 : (::StringFind(MT4ORDERS::Order.Comment, "sl ") + 3)); MT4ORDERS::Order.StopLoss = OrderPriceOpen; MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(OrderTicket, ORDER_SL); } else { // Перевернуто - не ошибка: см. OrderClose. MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(OrderTicket, ORDER_TP); MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(OrderTicket, ORDER_SL); } } else { // Перевернуто - не ошибка: см. OrderClose. MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(OrderTicket, ORDER_TP); MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(OrderTicket, ORDER_SL); } } else if (!OrderPriceOpen) { if (Reason == DEAL_REASON_TP) OrderPriceOpen = MT4ORDERS::Order.TakeProfit; else if (Reason == DEAL_REASON_SL) OrderPriceOpen = MT4ORDERS::Order.StopLoss; else if (MT4ORDERS::Order.Comment[0] == '[') { if ((MT4ORDERS::Order.Comment[1] == 't') && (MT4ORDERS::Order.Comment[2] == 'p')) { OrderPriceOpen = MT4ORDERS::Order.TakeProfit; MT4ORDERS::Order.CloseReason = DEAL_REASON_TP; } else if ((MT4ORDERS::Order.Comment[1] == 's') && (MT4ORDERS::Order.Comment[2] == 'l')) { OrderPriceOpen = MT4ORDERS::Order.StopLoss; MT4ORDERS::Order.CloseReason = DEAL_REASON_SL; } } } MT4ORDERS::Order.State = OrderTicket ? (ENUM_ORDER_STATE)::HistoryOrderGetInteger(OrderTicket, ORDER_STATE) : ORDER_STATE_FILLED; if (!(MT4ORDERS::Order.ClosePriceRequest = (DealEntry == DEAL_ENTRY_OUT_BY) ? MT4ORDERS::Order.ClosePrice : OrderPriceOpen)) MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; if (!(MT4ORDERS::Order.OpenPriceRequest = _B2(MT4ORDERS::HistorySelectOrder(PosTicket) && // При частичном исполнении только последняя сделка полностью исполненного ордера имеет это условие для взятия цены запроса. (MT4ORDERS::IsTester || (::HistoryDealGetInteger(OpenTicket, DEAL_TIME_MSC) == ::HistoryOrderGetInteger(PosTicket, ORDER_TIME_DONE_MSC)))) ? ::HistoryOrderGetDouble(PosTicket, ORDER_PRICE_OPEN) : MT4ORDERS::Order.OpenPrice)) MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; } else { MT4ORDERS::Order.State = ORDER_STATE_FILLED; MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; } // Выше комментарий используется для нахождения SL/TP. if (StrComment != "") MT4ORDERS::Order.Comment = StrComment; } if (!Res) { ::Alert("HistoryOrderSelect(" + (string)OrderTicket + ") - BUG! MT4ORDERS - not Sync with History!"); MT4ORDERS::AlertLog(); ::Print(__FILE__ + "\nVersion = " + __MT4ORDERS__ + "\nCompiler = " + (string)__MQLBUILD__ + "\n" + TOSTR(__DATE__) + TOSTR(::AccountInfoString(ACCOUNT_SERVER)) + TOSTR2((ENUM_ACCOUNT_TRADE_MODE)::AccountInfoInteger(ACCOUNT_TRADE_MODE)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_CONNECTED)) + TOSTR(::TerminalInfoInteger(TERMINAL_PING_LAST)) + TOSTR(::TerminalInfoDouble(TERMINAL_RETRANSMISSION)) + TOSTR(::TerminalInfoInteger(TERMINAL_BUILD)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_X64)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_VPS)) + TOSTR2((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)) + TOSTR(::TimeCurrent()) + TOSTR(::TimeTradeServer()) + TOSTR(MT4ORDERS::TimeToString(MT4ORDERS::GetTimeCurrent())) + TOSTR(::SymbolInfoString(MT4ORDERS::Order.Symbol, SYMBOL_PATH)) + TOSTR(::SymbolInfoString(MT4ORDERS::Order.Symbol, SYMBOL_DESCRIPTION)) + "CurrentTick =" + MT4ORDERS::TickToString(MT4ORDERS::Order.Symbol) + "\n" + TOSTR(MT4ORDERS::HistorySelectOrder(OrderTicket)) + TOSTR(::OrderSelect(OrderTicket)) + // Влияют ли на результат вызовы функций ниже. TOSTR(::PositionsTotal()) + TOSTR(::OrdersTotal()) + TOSTR(::HistorySelect(0, INT_MAX)) + TOSTR(::HistoryDealsTotal()) + TOSTR(::HistoryOrdersTotal()) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_AVAILABLE)) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_PHYSICAL)) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_TOTAL)) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_USED)) + TOSTR(::MQLInfoInteger(MQL_MEMORY_LIMIT)) + TOSTR(::MQLInfoInteger(MQL_MEMORY_USED)) + TOSTR(::MQLInfoInteger(MQL_HANDLES_USED)) + TOSTR(Ticket) + TOSTR(OrderTicket) + TOSTR(OpenTicket) + TOSTR(PosTicket) + TOSTR(MT4ORDERS::TimeToString(MT4ORDERS::Order.CloseTimeMsc)) + TOSTR(MT4ORDERS::HistorySelectOrder(OrderTicket)) + TOSTR(::OrderSelect(OrderTicket)) + // Влияют ли на результат вызовы функций выше. TOSTR(MT4ORDERS::GetFirstOrderTicket()) + (::OrderSelect(OrderTicket) ? TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE)) : NULL) + (::HistoryDealsTotal() ? TOSTR(::HistoryDealGetTicket(::HistoryDealsTotal() - 1)) + "DEAL_ORDER = " + (string)::HistoryDealGetInteger(::HistoryDealGetTicket(::HistoryDealsTotal() - 1), DEAL_ORDER) + "\n" "DEAL_TIME_MSC = " + MT4ORDERS::TimeToString(::HistoryDealGetInteger(::HistoryDealGetTicket(::HistoryDealsTotal() - 1), DEAL_TIME_MSC)) + "\n" : NULL) + (::HistoryOrdersTotal() ? TOSTR(::HistoryOrderGetTicket(::HistoryOrdersTotal() - 1)) + "ORDER_TIME_DONE_MSC = " + MT4ORDERS::TimeToString(::HistoryOrderGetInteger(::HistoryOrderGetTicket(::HistoryOrdersTotal() - 1), ORDER_TIME_DONE_MSC)) + "\n" : NULL) + #ifdef MT4ORDERS_BYPASS_MAXTIME "MT4ORDERS::ByPass: " + MT4ORDERS::ByPass.ToString() + "\n" + #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME TOSTR(MT4ORDERS::OrderSend_MaxPause) + TOSTR(MT4ORDERS::OrderSendBug)); } } else { MT4ORDERS::Order.TicketOpen = MT4ORDERS::Order.Ticket; if (!MT4ORDERS::Order.TicketID && (MT4ORDERS::Order.Type <= OP_SELL)) // ID балансовых сделок должен оставаться нулевым. MT4ORDERS::Order.TicketID = MT4ORDERS::Order.Ticket; MT4ORDERS::Order.OpenPrice = MT4ORDERS::Order.ClosePrice; // ::HistoryDealGetDouble(Ticket, DEAL_PRICE); MT4ORDERS::Order.OpenTimeMsc = MT4ORDERS::Order.CloseTimeMsc; MT4ORDERS::Order.OpenTime = MT4ORDERS::Order.CloseTime; // (datetime)::HistoryDealGetInteger(Ticket, DEAL_TIME); MT4ORDERS::Order.OpenReason = MT4ORDERS::Order.CloseReason; MT4ORDERS::Order.State = ORDER_STATE_FILLED; MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; } if (OrderTicket && IsOrderTicket) { bool Res = MT4ORDERS::IsTester ? MT4ORDERS::HistorySelectOrder(OrderTicket) : MT4ORDERS::Waiting(true); if (!Res) WHILE(_B2(MT4ORDERS::HistorySelectOrder(OrderTicket))) // https://www.mql5.com/ru/forum/304239#comment_10710403 ; if ((ENUM_ORDER_TYPE)::HistoryOrderGetInteger(OrderTicket, ORDER_TYPE) == ORDER_TYPE_CLOSE_BY) { const ulong PosTicketBy = ::HistoryOrderGetInteger(OrderTicket, ORDER_POSITION_BY_ID); if (PosTicketBy == PosTicket) // CloseBy-Slave не должен влиять на торговый оборот. Master_DealTicket < Slave_DealTicket { MT4ORDERS::Order.Lots = 0; MT4ORDERS::Order.Commission = 0; MT4ORDERS::Order.ClosePrice = MT4ORDERS::Order.OpenPrice; MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; } else // CloseBy-Master должен получить комиссию (но не свопы!) от CloseBy-Slave. { // Может быть несколько позиций с ID от CloseBy-Slave, поэтому во входных присутствует Master_DealTicket. const ulong OpenTicketBy = (OrderTicket > 0) ? _B2(MT4ORDERS::History.GetPositionDealIn(PosTicketBy, Ticket)) : 0; if ((OpenTicketBy > 0) && _B2(MT4ORDERS::HistorySelectDeal(OpenTicketBy))) { double OpenLots; double Commission; if (!MT4ORDERS::History.GetTicketCommission(Commission, OpenLots)) { Commission= ::HistoryDealGetDouble(OpenTicketBy, DEAL_COMMISSION) ; OpenLots = ::HistoryDealGetDouble(OpenTicketBy, DEAL_VOLUME); } if (OpenLots > 0) MT4ORDERS::Order.Commission += Commission * MT4ORDERS::Order.Lots / OpenLots; } } } } return; } static bool Waiting( const bool FlagInit = false ) { static ulong StartTime = 0; const bool Res = FlagInit ? false : (::GetMicrosecondCount() - StartTime < MT4ORDERS::OrderSend_MaxPause); if (FlagInit) { StartTime = ::GetMicrosecondCount(); MT4ORDERS::OrderSendBug = 0; } else if (Res) { // ::Sleep(0); // https://www.mql5.com/ru/forum/170952/page100#comment_8750511 MT4ORDERS::OrderSendBug++; } return(Res); } static bool EqualPrices( const double Price1, const double &Price2, const int &digits) { return(!::NormalizeDouble(Price1 - Price2, digits)); } static bool HistoryDealSelect2( MqlTradeResult &Result ) // В конце названия цифра для большей совместимости с макросами. { #ifdef MT4ORDERS_HISTORY_OLD // Заменить HistorySelectByPosition на HistorySelect(PosTime, PosTime) if (!Result.deal && Result.order && _B3(::HistorySelectByPosition(::HistoryOrderGetInteger(Result.order, ORDER_POSITION_ID)))) { #else // #ifdef MT4ORDERS_HISTORY_OLD if (!Result.deal && Result.order && _B2(MT4ORDERS::HistorySelectOrder(Result.order))) { const long OrderTimeFill = ::HistoryOrderGetInteger(Result.order, ORDER_TIME_DONE_MSC); #endif // #ifdef MT4ORDERS_HISTORY_OLD #else if (::HistorySelect(0, INT_MAX)) // Без этого сделку можно не обнаружить. for (int i = ::HistoryDealsTotal() - 1; i >= 0; i--) { const ulong DealTicket = ::HistoryDealGetTicket(i); if (Result.order == ::HistoryDealGetInteger(DealTicket, DEAL_ORDER)) { Result.deal = DealTicket; Result.price = ::HistoryDealGetDouble(DealTicket, DEAL_PRICE); break; } #ifndef MT4ORDERS_HISTORY_OLD else if (::HistoryDealGetInteger(DealTicket, DEAL_TIME_MSC) < OrderTimeFill) break; #endif // #ifndef MT4ORDERS_HISTORY_OLD } } return(_B2(MT4ORDERS::HistorySelectDeal(Result.deal))); } /* #define MT4ORDERS_BENCHMARK Alert(MT4ORDERS::LastTradeRequest.symbol + " " + \ (string)MT4ORDERS::LastTradeResult.order + " " + \ MT4ORDERS::LastTradeResult.comment); \ Print(ToString(MT4ORDERS::LastTradeRequest) + \ ToString(MT4ORDERS::LastTradeResult)); */ #define TMP_MT4ORDERS_BENCHMARK(A) \ static ulong Max##A = 0; \ \ if (Interval##A > Max##A) \ { \ MT4ORDERS_BENCHMARK \ \ Max##A = Interval##A; \ } static void OrderSend_Benchmark( const ulong &Interval1, const ulong &Interval2 ) { #ifdef MT4ORDERS_BENCHMARK TMP_MT4ORDERS_BENCHMARK(1) TMP_MT4ORDERS_BENCHMARK(2) #endif // MT4ORDERS_BENCHMARK return; } #undef TMP_MT4ORDERS_BENCHMARK static string ToString( const MqlTradeRequest &Request ) { return(TOSTR2(Request.action) + TOSTR(Request.magic) + TOSTR(Request.order) + TOSTR(Request.symbol) + TOSTR(Request.volume) + TOSTR(Request.price) + TOSTR(Request.stoplimit) + TOSTR(Request.sl) + TOSTR(Request.tp) + TOSTR(Request.deviation) + TOSTR2(Request.type) + TOSTR2(Request.type_filling) + TOSTR2(Request.type_time) + TOSTR(Request.expiration) + TOSTR(Request.comment) + TOSTR(Request.position) + TOSTR(Request.position_by)); } static string ToString( const MqlTradeResult &Result ) { return(TOSTR(Result.retcode) + TOSTR(Result.deal) + TOSTR(Result.order) + TOSTR(Result.volume) + TOSTR(Result.price) + TOSTR(Result.bid) + TOSTR(Result.ask) + TOSTR(Result.comment) + TOSTR(Result.request_id) + TOSTR(Result.retcode_external)); } static bool OrderSend( const MqlTradeRequest &Request, MqlTradeResult &Result ) { const bool FlagCalc = !MT4ORDERS::IsTester && MT4ORDERS::OrderSend_MaxPause; MqlTick PrevTick = {}; if (FlagCalc) ::SymbolInfoTick(Request.symbol, PrevTick); // Может тормозить. const long PrevTimeCurrent = FlagCalc ? _B2(MT4ORDERS::GetTimeCurrent()) : 0; const ulong StartTime1 = FlagCalc ? ::GetMicrosecondCount() : 0; bool Res = ::OrderSend(Request, Result); const ulong StartTime2 = FlagCalc ? ::GetMicrosecondCount() : 0; const ulong Interval1 = StartTime2 - StartTime1; if (FlagCalc && Res && (Result.retcode < TRADE_RETCODE_ERROR)) { Res = (Result.retcode == TRADE_RETCODE_DONE); MT4ORDERS::Waiting(true); // TRADE_ACTION_CLOSE_BY отсутствует в перечне проверок if (Request.action == TRADE_ACTION_DEAL) { if (!Result.deal) { WHILE(_B2(::OrderSelect(Result.order)) || _B2(MT4ORDERS::HistorySelectOrder(Result.order))) ; if (!Res) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(::OrderSelect(Result.order)) + TOSTR(MT4ORDERS::HistorySelectOrder(Result.order))); else if (::OrderSelect(Result.order) && !(Res = ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PLACED) || ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PARTIAL))) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(::OrderSelect(Result.order)) + TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE))); } // Если после частичного исполнения оставшаяся часть осталась висеть - false. if (Res) { const bool ResultDeal = (!Result.deal) && (!MT4ORDERS::OrderSendBug); if (MT4ORDERS::OrderSendBug && (!Result.deal)) ::Print("Line = " + (string)__LINE__ + "\n" + "Before ::HistoryOrderSelect(Result.order):\n" + TOSTR(MT4ORDERS::OrderSendBug) + TOSTR(Result.deal)); WHILE(_B2(MT4ORDERS::HistorySelectOrder(Result.order))) ; // Если ранее не было OrderSend-бага и был Result.deal == 0 if (ResultDeal) MT4ORDERS::OrderSendBug = 0; if (!Res) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(MT4ORDERS::HistorySelectOrder(Result.order)) + TOSTR(MT4ORDERS::HistorySelectDeal(Result.deal)) + TOSTR(::OrderSelect(Result.order)) + TOSTR(Result.deal)); // Если исторический ордер не исполнился (отклонили) - false else if (!(Res = ((ENUM_ORDER_STATE)::HistoryOrderGetInteger(Result.order, ORDER_STATE) == ORDER_STATE_FILLED) || ((ENUM_ORDER_STATE)::HistoryOrderGetInteger(Result.order, ORDER_STATE) == ORDER_STATE_PARTIAL))) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR2((ENUM_ORDER_STATE)::HistoryOrderGetInteger(Result.order, ORDER_STATE))); } if (Res) { const bool ResultDeal = (!Result.deal) && (!MT4ORDERS::OrderSendBug); if (MT4ORDERS::OrderSendBug && (!Result.deal)) ::Print("Line = " + (string)__LINE__ + "\n" + "Before MT4ORDERS::HistoryDealSelect(Result):\n" + TOSTR(MT4ORDERS::OrderSendBug) + TOSTR(Result.deal)); WHILE(MT4ORDERS::HistoryDealSelect2(Result)) ; // Если ранее не было OrderSend-бага и был Result.deal == 0 if (ResultDeal) MT4ORDERS::OrderSendBug = 0; if (!Res) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(MT4ORDERS::HistoryDealSelect2(Result))); } } else if (Request.action == TRADE_ACTION_PENDING) { if (Res) { WHILE(_B2(::OrderSelect(Result.order)) || _B2(MT4ORDERS::HistorySelectOrder(Result.order))) // History - может исполниться. ; if (!Res) { ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(::OrderSelect(Result.order))); ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(MT4ORDERS::HistorySelectOrder(Result.order))); } else if (::OrderSelect(Result.order) && (!(Res = ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PLACED) || ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PARTIAL)))) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE))); } else { WHILE(_B2(MT4ORDERS::HistorySelectOrder(Result.order))) ; ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(MT4ORDERS::HistorySelectOrder(Result.order))); Res = false; } } else if (Request.action == TRADE_ACTION_SLTP) { if (Res) { const int digits = (int)::SymbolInfoInteger(Request.symbol, SYMBOL_DIGITS); bool EqualSL = false; bool EqualTP = false; do if (Request.position ? _B2(::PositionSelectByTicket(Request.position)) : _B2(::PositionSelect(Request.symbol))) { EqualSL = MT4ORDERS::EqualPrices(::PositionGetDouble(POSITION_SL), Request.sl, digits); EqualTP = MT4ORDERS::EqualPrices(::PositionGetDouble(POSITION_TP), Request.tp, digits); } WHILE(EqualSL && EqualTP); if (!Res) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(::PositionGetDouble(POSITION_SL)) + TOSTR(::PositionGetDouble(POSITION_TP)) + TOSTR(EqualSL) + TOSTR(EqualTP) + TOSTR(Request.position ? ::PositionSelectByTicket(Request.position) : ::PositionSelect(Request.symbol))); } } else if (Request.action == TRADE_ACTION_MODIFY) { if (Res) { const int digits = (int)::SymbolInfoInteger(Request.symbol, SYMBOL_DIGITS); bool EqualSL = false; bool EqualTP = false; bool EqualPrice = false; do if (_B2(::OrderSelect(Result.order))) { // https://www.mql5.com/ru/forum/170952/page184#comment_17913645 if (((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) != ORDER_STATE_REQUEST_MODIFY)) { EqualSL = MT4ORDERS::EqualPrices(::OrderGetDouble(ORDER_SL), Request.sl, digits); EqualTP = MT4ORDERS::EqualPrices(::OrderGetDouble(ORDER_TP), Request.tp, digits); EqualPrice = MT4ORDERS::EqualPrices(::OrderGetDouble(ORDER_PRICE_OPEN), Request.price, digits); } } else if (_B2(MT4ORDERS::HistorySelectOrder(Result.order))) // History - может исполниться. { EqualSL = true; EqualTP = true; EqualPrice = true; } WHILE((EqualSL && EqualTP && EqualPrice)); if (!Res) { ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(::OrderSelect(Result.order))); ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(MT4ORDERS::HistorySelectOrder(Result.order))); ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(::OrderGetDouble(ORDER_SL)) + TOSTR(Request.sl)+ TOSTR(::OrderGetDouble(ORDER_TP)) + TOSTR(Request.tp) + TOSTR(::OrderGetDouble(ORDER_PRICE_OPEN)) + TOSTR(Request.price) + TOSTR(EqualSL) + TOSTR(EqualTP) + TOSTR(EqualPrice) + TOSTR(::OrderSelect(Result.order)) + TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE))); } } } else if (Request.action == TRADE_ACTION_REMOVE) { if (Res) WHILE(_B2(MT4ORDERS::HistorySelectOrder(Result.order))) ; if (!Res) ::Print("Line = " + (string)__LINE__ + "\n" + TOSTR(MT4ORDERS::HistorySelectOrder(Result.order))); } const ulong Interval2 = ::GetMicrosecondCount() - StartTime2; Result.comment += " " + ::DoubleToString(Interval1 / 1000.0, 3) + " + " + ::DoubleToString(Interval2 / 1000.0, 3) + " (" + (string)MT4ORDERS::OrderSendBug + ") ms."; if (!Res || MT4ORDERS::OrderSendBug) { ::Alert(Res ? "OrderSend(" + (string)Result.order + ") - BUG!" : "MT4ORDERS - not Sync with History!"); MT4ORDERS::AlertLog(); ::Print(__FILE__ + "\nVersion = " + __MT4ORDERS__ + "\nCompiler = " + (string)__MQLBUILD__ + "\n" + TOSTR(__DATE__) + TOSTR(::AccountInfoString(ACCOUNT_SERVER)) + TOSTR2((ENUM_ACCOUNT_TRADE_MODE)::AccountInfoInteger(ACCOUNT_TRADE_MODE)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_CONNECTED)) + TOSTR(::TerminalInfoInteger(TERMINAL_PING_LAST)) + TOSTR(::TerminalInfoDouble(TERMINAL_RETRANSMISSION)) + TOSTR(::TerminalInfoInteger(TERMINAL_BUILD)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_X64)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_VPS)) + TOSTR2((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)) + TOSTR(::TimeCurrent()) + TOSTR(::TimeTradeServer()) + TOSTR(MT4ORDERS::TimeToString(MT4ORDERS::GetTimeCurrent())) + TOSTR(MT4ORDERS::TimeToString(PrevTimeCurrent)) + "PrevTick =" + MT4ORDERS::TickToString(Request.symbol, PrevTick) + "\n" + "CurrentTick =" + MT4ORDERS::TickToString(Request.symbol) + "\n" + TOSTR(::SymbolInfoString(Request.symbol, SYMBOL_PATH)) + TOSTR(::SymbolInfoString(Request.symbol, SYMBOL_DESCRIPTION)) + TOSTR(::PositionsTotal()) + TOSTR(::OrdersTotal()) + TOSTR(::HistorySelect(0, INT_MAX)) + TOSTR(::HistoryDealsTotal()) + TOSTR(::HistoryOrdersTotal()) + (::HistoryDealsTotal() ? TOSTR(::HistoryDealGetTicket(::HistoryDealsTotal() - 1)) + "DEAL_ORDER = " + (string)::HistoryDealGetInteger(::HistoryDealGetTicket(::HistoryDealsTotal() - 1), DEAL_ORDER) + "\n" "DEAL_TIME_MSC = " + MT4ORDERS::TimeToString(::HistoryDealGetInteger(::HistoryDealGetTicket(::HistoryDealsTotal() - 1), DEAL_TIME_MSC)) + "\n" : NULL) + (::HistoryOrdersTotal() ? TOSTR(::HistoryOrderGetTicket(::HistoryOrdersTotal() - 1)) + "ORDER_TIME_DONE_MSC = " + MT4ORDERS::TimeToString(::HistoryOrderGetInteger(::HistoryOrderGetTicket(::HistoryOrdersTotal() - 1), ORDER_TIME_DONE_MSC)) + "\n" : NULL) + TOSTR(MT4ORDERS::GetFirstOrderTicket()) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_AVAILABLE)) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_PHYSICAL)) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_TOTAL)) + TOSTR(::TerminalInfoInteger(TERMINAL_MEMORY_USED)) + TOSTR(::MQLInfoInteger(MQL_HANDLES_USED)) + TOSTR(::MQLInfoInteger(MQL_MEMORY_LIMIT)) + TOSTR(::MQLInfoInteger(MQL_MEMORY_USED)) + TOSTR(MT4ORDERS::IsHedging) + TOSTR(Res) + TOSTR(MT4ORDERS::OrderSendBug) + MT4ORDERS::ToString(Request) + MT4ORDERS::ToString(Result) + #ifdef MT4ORDERS_BYPASS_MAXTIME "MT4ORDERS::ByPass: " + MT4ORDERS::ByPass.ToString() + "\n" + #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME TOSTR(MT4ORDERS::OrderSend_MaxPause)); } else MT4ORDERS::OrderSend_Benchmark(Interval1, Interval2); } else if (FlagCalc) { Result.comment += " " + ::DoubleToString(Interval1 / 1000.0, 3) + " ms"; ::Print(TOSTR(::TimeCurrent()) + TOSTR(::TimeTradeServer()) + TOSTR(MT4ORDERS::TimeToString(PrevTimeCurrent)) + MT4ORDERS::TickToString(Request.symbol, PrevTick) + "\n" + MT4ORDERS::TickToString(Request.symbol) + "\n" + MT4ORDERS::ToString(Request) + MT4ORDERS::ToString(Result)); // ExpertRemove(); } return(Res); } #undef TOSTR2 #undef TOSTR #undef WHILE static ENUM_DAY_OF_WEEK GetDayOfWeek( const datetime &time ) { return((ENUM_DAY_OF_WEEK)((time / (24 * 3600) + THURSDAY) % 7)); } static bool SessionTrade( const string &Symb ) { datetime TimeNow = ::TimeCurrent(); const ENUM_DAY_OF_WEEK DayOfWeek = MT4ORDERS::GetDayOfWeek(TimeNow); TimeNow %= 24 * 60 * 60; bool Res = false; datetime From, To; for (int i = 0; (!Res) && ::SymbolInfoSessionTrade(Symb, DayOfWeek, i, From, To); i++) Res = ((From <= TimeNow) && (TimeNow < To)); return(Res); } static bool SymbolTrade( const string &Symb ) { MqlTick Tick; return(::SymbolInfoTick(Symb, Tick) ? (Tick.bid && Tick.ask && MT4ORDERS::SessionTrade(Symb) /* && ((ENUM_SYMBOL_TRADE_MODE)::SymbolInfoInteger(Symb, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_FULL) */) : false); } static bool CorrectResult( void ) { ::ZeroMemory(MT4ORDERS::LastTradeResult); MT4ORDERS::LastTradeResult.retcode = MT4ORDERS::LastTradeCheckResult.retcode; MT4ORDERS::LastTradeResult.comment = MT4ORDERS::LastTradeCheckResult.comment; return(false); } static bool NewOrderCheck( void ) { return((::OrderCheck(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeCheckResult) && (MT4ORDERS::IsTester || MT4ORDERS::SymbolTrade(MT4ORDERS::LastTradeRequest.symbol))) || (!MT4ORDERS::IsTester && MT4ORDERS::CorrectResult())); } static bool NewOrderSend( const int &Check ) { return((Check == INT_MAX) ? MT4ORDERS::NewOrderCheck() : (( #ifndef MT4ORDERS_AUTO_VALIDATION (Check != INT_MIN) || #endif // #ifndef MT4ORDERS_AUTO_VALIDATION MT4ORDERS::NewOrderCheck()) && MT4ORDERS::OrderSend(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeResult) ? (MT4ORDERS::LastTradeResult.retcode < TRADE_RETCODE_ERROR) #ifdef MT4ORDERS_BYPASS_MAXTIME && _B2(MT4ORDERS::ByPass += MT4ORDERS::LastTradeResult.order) #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME : false)); } static bool ModifyPosition( const long &Ticket, MqlTradeRequest &Request ) { const bool Res = _B2(::PositionSelectByTicket(Ticket)); if (Res) { Request.action = TRADE_ACTION_SLTP; Request.position = Ticket; Request.symbol = ::PositionGetString(POSITION_SYMBOL); // указания одного тикета не достаточно! } return(Res); } static ENUM_ORDER_TYPE_FILLING GetFilling( const string &Symb, const uint Type = ORDER_FILLING_FOK ) { static ENUM_ORDER_TYPE_FILLING Res = ORDER_FILLING_FOK; static string LastSymb = NULL; static uint LastType = ORDER_FILLING_FOK; const bool SymbFlag = (LastSymb != Symb); if (SymbFlag || (LastType != Type)) // Можно немного ускорить, поменяв очередность проверки условия. { LastType = Type; if (SymbFlag) LastSymb = Symb; const ENUM_SYMBOL_TRADE_EXECUTION ExeMode = (ENUM_SYMBOL_TRADE_EXECUTION)::SymbolInfoInteger(Symb, SYMBOL_TRADE_EXEMODE); const int FillingMode = (int)::SymbolInfoInteger(Symb, SYMBOL_FILLING_MODE); Res = (!FillingMode || (Type >= ORDER_FILLING_RETURN) || ((FillingMode & (Type + 1)) != Type + 1)) ? (((ExeMode == SYMBOL_TRADE_EXECUTION_EXCHANGE) || (ExeMode == SYMBOL_TRADE_EXECUTION_INSTANT)) ? ORDER_FILLING_RETURN : ((FillingMode == SYMBOL_FILLING_IOC) ? ORDER_FILLING_IOC : ORDER_FILLING_FOK)) : (ENUM_ORDER_TYPE_FILLING)Type; } return(Res); } static ENUM_ORDER_TYPE_TIME GetExpirationType( const string &Symb, uint Expiration = ORDER_TIME_GTC ) { static ENUM_ORDER_TYPE_TIME Res = ORDER_TIME_GTC; static string LastSymb = NULL; static uint LastExpiration = ORDER_TIME_GTC; const bool SymbFlag = (LastSymb != Symb); if ((LastExpiration != Expiration) || SymbFlag) { LastExpiration = Expiration; if (SymbFlag) LastSymb = Symb; const int ExpirationMode = (int)::SymbolInfoInteger(Symb, SYMBOL_EXPIRATION_MODE); if ((Expiration > ORDER_TIME_SPECIFIED_DAY) || (!((ExpirationMode >> Expiration) & 1))) { if ((Expiration < ORDER_TIME_SPECIFIED) || (ExpirationMode < SYMBOL_EXPIRATION_SPECIFIED)) Expiration = ORDER_TIME_GTC; else if (Expiration > ORDER_TIME_DAY) Expiration = ORDER_TIME_SPECIFIED; uint i = 1 << Expiration; while ((Expiration <= ORDER_TIME_SPECIFIED_DAY) && ((ExpirationMode & i) != i)) { i <<= 1; Expiration++; } } Res = (ENUM_ORDER_TYPE_TIME)Expiration; } return(Res); } static bool ModifyOrder( const long Ticket, const double &Price, const datetime &Expiration, MqlTradeRequest &Request ) { const bool Res = _B2(::OrderSelect(Ticket)); if (Res) { Request.action = TRADE_ACTION_MODIFY; Request.order = Ticket; Request.price = Price; Request.symbol = ::OrderGetString(ORDER_SYMBOL); // https://www.mql5.com/ru/forum/1111/page1817#comment_4087275 // Request.type_filling = (ENUM_ORDER_TYPE_FILLING)::OrderGetInteger(ORDER_TYPE_FILLING); Request.type_filling = _B2(MT4ORDERS::GetFilling(Request.symbol)); Request.type_time = _B2(MT4ORDERS::GetExpirationType(Request.symbol, (uint)Expiration)); if (Expiration > ORDER_TIME_DAY) Request.expiration = Expiration; } return(Res); } static bool SelectByPosHistory( const int Index ) { const long Ticket = MT4ORDERS::History[Index]; const bool Res = (Ticket > 0) ? _B2(MT4ORDERS::HistorySelectDeal(Ticket)) : ((Ticket < 0) && _B2(MT4ORDERS::HistorySelectOrder(-Ticket))); if (Res) { if (Ticket > 0) _BV2(MT4ORDERS::GetHistoryPositionData(Ticket)) else _BV2(MT4ORDERS::GetHistoryOrderData(-Ticket)) } return(Res); } // https://www.mql5.com/ru/forum/227960#comment_6603506 static bool OrderVisible( void ) { // Если позиция закрылась при живой частично исполненной отложке, что ее породила. // А после оставшаяся часть отложки полностью исполнилась, но не успела исчезнуть. // То будет видна и новая позиция (правильно) и не исчезнувшая отложка (неправильно). const ulong PositionID = ::OrderGetInteger(ORDER_POSITION_ID); const ENUM_ORDER_TYPE Type = (ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE); ulong Ticket = 0; return(!((Type == ORDER_TYPE_CLOSE_BY) || (PositionID && // Partial-отложенник имеет ненулевой PositionID. (Type <= ORDER_TYPE_SELL) && // Закрывающие маркет-ордера игнорируем ((Ticket = ::OrderGetInteger(ORDER_TICKET)) != PositionID))) && // Открывающие частично исполненные маркет-ордера не игнорируем. // Открывающий/доливающий позицию ордер может не успеть исчезнуть. (!::PositionsTotal() || !(::PositionSelectByTicket(Ticket ? Ticket : ::OrderGetInteger(ORDER_TICKET)) && // (::PositionGetInteger(POSITION_TYPE) == (::OrderGetInteger(ORDER_TYPE) & 1)) && // (::PositionGetInteger(POSITION_TIME_MSC) >= ::OrderGetInteger(ORDER_TIME_SETUP_MSC)) && (::PositionGetDouble(POSITION_VOLUME) == ::OrderGetDouble(ORDER_VOLUME_INITIAL))))); } static ulong OrderGetTicket( const int Index ) { ulong Res; int PrevTotal; const long PrevTicket = ::OrderGetInteger(ORDER_TICKET); const long PositionTicket = ::PositionGetInteger(POSITION_TICKET); do { Res = 0; PrevTotal = ::OrdersTotal(); if ((Index >= 0) && (Index < PrevTotal)) { int Count = 0; for (int i = 0; i < PrevTotal; i++) { const int Total = ::OrdersTotal(); // Во время перебора может измениться количество ордеров if (Total != PrevTotal) { PrevTotal = -1; break; } else { const ulong Ticket = ::OrderGetTicket(i); if (Ticket && MT4ORDERS::OrderVisible()) { if (Count == Index) { Res = Ticket; break; } Count++; } } } #ifdef MT4ORDERS_BYPASS_MAXTIME _B2(MT4ORDERS::ByPass.Waiting()); // Изменяет ORDER_TICKET. #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME } } while (PrevTotal != ::OrdersTotal()); // Во время перебора может измениться количество ордеров if (!Res) { // При неудаче выбираем тот ордер, что был выбран ранее. if (PrevTicket && (::OrderGetInteger(ORDER_TICKET) != PrevTicket)) const bool AntiWarning = _B2(::OrderSelect(PrevTicket)); } #ifdef MT4ORDERS_BYPASS_MAXTIME else if (::OrderGetInteger(ORDER_TICKET) != Res) const bool AntiWarning = _B2(::OrderSelect(Res)); // MT4ORDERS::ByPass.Waiting() изменяет ORDER_TICKET. #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME // MT4ORDERS::OrderVisible() меняет выбор позиции. if (PositionTicket && (::PositionGetInteger(POSITION_TICKET) != PositionTicket)) ::PositionSelectByTicket(PositionTicket); return(Res); } // С одним и тем же тикетом приоритет выбора позиции выше ордера static bool SelectByPos( const int Index ) { bool Flag = (Index == INT_MAX); bool Res = Flag || (Index == INT_MIN); if (!Res) { if (MT4ORDERS::IsTester) { const int Total = ::PositionsTotal(); Flag = (Index < Total); Res = Flag ? ::PositionGetTicket(Index) : ::OrderGetTicket(Index - Total); } else { int Total; do { Total = ::PositionsTotal(); Flag = (Index < Total); if (Flag) Res = _B2(::PositionGetTicket(Index)); else { const int Index2 = Index - Total; const int Total2 = ::OrdersTotal(); if ((Index2 >= 0) && (Index2 < Total2)) { #ifdef MT4ORDERS_SELECTFILTER_OFF Res = ::OrderGetTicket(Index2); #else // MT4ORDERS_SELECTFILTER_OFF Res = _B2(MT4ORDERS::OrderGetTicket(Index2)); #endif //MT4ORDERS_SELECTFILTER_OFF } else Res = 0; } } while (Total != ::PositionsTotal()); // Во время перебора может измениться количество позиций. } } if (Res) { if (Flag) MT4ORDERS::GetPositionData(); // (Index == INT_MAX) - переход на MT5-позицию без проверки существования и обновления. else MT4ORDERS::GetOrderData(); // (Index == INT_MIN) - переход на живой MT5-ордер без проверки существования и обновления. } return(Res); } static bool SelectByHistoryTicket( const long &Ticket ) { bool Res = false; if (!Ticket) // Выбор по OrderTicketID (по нулевому значению - балансовые операции). { const ulong TicketDealOut = MT4ORDERS::History.GetPositionDealOut(Ticket); if (Res = _B2(MT4ORDERS::HistorySelectDeal(TicketDealOut))) _BV2(MT4ORDERS::GetHistoryPositionData(TicketDealOut)); } else if (_B2(MT4ORDERS::HistorySelectDeal(Ticket))) { #ifdef MT4ORDERS_TESTER_SELECT_BY_TICKET // В Тестере при поиске закрытой позиции нужно искать сначала по PositionID из-за близкой нумерации тикетов MT5-сделок/ордеров. if (MT4ORDERS::IsTester) { const ulong TicketDealOut = MT4ORDERS::History.GetPositionDealOut(HistoryOrderGetInteger(Ticket, ORDER_POSITION_ID)); if (Res = _B2(MT4ORDERS::HistorySelectDeal(TicketDealOut))) _BV2(MT4ORDERS::GetHistoryPositionData(TicketDealOut)); } if (!Res) #endif // #ifdef MT4ORDERS_TESTER_SELECT_BY_TICKET { if (Res = MT4HISTORY::IsMT4Deal(Ticket)) _BV2(MT4ORDERS::GetHistoryPositionData(Ticket)) else// DealIn { const ulong TicketDealOut = MT4ORDERS::History.GetPositionDealOut(HistoryDealGetInteger(Ticket, DEAL_POSITION_ID)); // Выбор по DealIn if (Res = _B2(MT4ORDERS::HistorySelectDeal(TicketDealOut))) _BV2(MT4ORDERS::GetHistoryPositionData(TicketDealOut)) } } } else if (_B2(MT4ORDERS::HistorySelectOrder(Ticket))) { if (Res = MT4HISTORY::IsMT4Order(Ticket)) _BV2(MT4ORDERS::GetHistoryOrderData(Ticket)) else { const ulong TicketDealOut = MT4ORDERS::History.GetPositionDealOut(HistoryOrderGetInteger(Ticket, ORDER_POSITION_ID)); if (Res = _B2(MT4ORDERS::HistorySelectDeal(TicketDealOut))) _BV2(MT4ORDERS::GetHistoryPositionData(TicketDealOut)); } } else { // Выбор по OrderTicketID или тикету исполненной отложки - актуально для Неттинга. const ulong TicketDealOut = MT4ORDERS::History.GetPositionDealOut(Ticket); if (Res = _B2(MT4ORDERS::HistorySelectDeal(TicketDealOut))) _BV2(MT4ORDERS::GetHistoryPositionData(TicketDealOut)); } return(Res); } static bool SelectByExistingTicket( const long &Ticket ) { bool Res = true; if (Ticket < 0) { if (_B2(::OrderSelect(-Ticket))) MT4ORDERS::GetOrderData(); else if (_B2(::PositionSelectByTicket(-Ticket))) MT4ORDERS::GetPositionData(); else Res = false; } else if (_B2(::PositionSelectByTicket(Ticket))) MT4ORDERS::GetPositionData(); else if (_B2(::OrderSelect(Ticket))) MT4ORDERS::GetOrderData(); else if (_B2(MT4ORDERS::HistorySelectDeal(Ticket))) { #ifdef MT4ORDERS_TESTER_SELECT_BY_TICKET // В Тестере при поиске закрытой позиции нужно искать сначала по PositionID из-за близкой нумерации тикетов MT5-сделок/ордеров. if (Res = !MT4ORDERS::IsTester) #endif // #ifdef MT4ORDERS_TESTER_SELECT_BY_TICKET { if (MT4HISTORY::IsMT4Deal(Ticket)) // Если сделан выбор по DealOut. _BV2(MT4ORDERS::GetHistoryPositionData(Ticket)) else if (_B2(::PositionSelectByTicket(::HistoryDealGetInteger(Ticket, DEAL_POSITION_ID)))) // Выбор по DealIn MT4ORDERS::GetPositionData(); else Res = false; } } else if (_B2(MT4ORDERS::HistorySelectOrder(Ticket)) && _B2(::PositionSelectByTicket(::HistoryOrderGetInteger(Ticket, ORDER_POSITION_ID)))) // Выбор по тикету MT5-ордера MT4ORDERS::GetPositionData(); else Res = false; return(Res); } // С одним и тем же тикетом приоритеты выбора: // MODE_TRADES: существующая позиция > существующий ордер > сделка > отмененный ордер // MODE_HISTORY: сделка > отмененный ордер > существующая позиция > существующий ордер static bool SelectByTicket( const long &Ticket, const int &Pool ) { return((Pool == MODE_TRADES) || (Ticket < 0) ? (_B2(MT4ORDERS::SelectByExistingTicket(Ticket)) || ((Ticket > 0) && _B2(MT4ORDERS::SelectByHistoryTicket(Ticket)))) : (_B2(MT4ORDERS::SelectByHistoryTicket(Ticket)) || _B2(MT4ORDERS::SelectByExistingTicket(Ticket)))); } static void CheckPrices( double &MinPrice, double &MaxPrice, const double Min, const double Max ) { if (MinPrice && (MinPrice >= Min)) MinPrice = 0; if (MaxPrice && (MaxPrice <= Max)) MaxPrice = 0; return; } static int OrdersTotal( void ) { int Res = 0; int PrevTotal = ::OrdersTotal(); if (PrevTotal) { const long PrevTicket = ::OrderGetInteger(ORDER_TICKET); const long PositionTicket = ::PositionGetInteger(POSITION_TICKET); do { PrevTotal = ::OrdersTotal(); for (int i = PrevTotal - 1; i >= 0; i--) { // Во время перебора может измениться количество ордеров if (PrevTotal != ::OrdersTotal()) { PrevTotal = -1; Res = 0; break; } else if (::OrderGetTicket(i) && MT4ORDERS::OrderVisible()) Res++; } #ifdef MT4ORDERS_BYPASS_MAXTIME if (PrevTotal) _B2(MT4ORDERS::ByPass.Waiting()); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME } while (PrevTotal && (PrevTotal != ::OrdersTotal())); // Во время перебора может измениться количество ордеров if (PrevTicket && (::OrderGetInteger(ORDER_TICKET) != PrevTicket)) const bool AntiWarning = _B2(::OrderSelect(PrevTicket)); // MT4ORDERS::OrderVisible() меняет выбор позиции. if (PositionTicket && (::PositionGetInteger(POSITION_TICKET) != PositionTicket)) ::PositionSelectByTicket(PositionTicket); } return(Res); } public: static uint OrderSend_MaxPause; // максимальное время на синхронизацию в мкс. #ifdef MT4ORDERS_BYPASS_MAXTIME static BYPASS ByPass; #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME static MqlTradeResult LastTradeResult; static MqlTradeRequest LastTradeRequest; static MqlTradeCheckResult LastTradeCheckResult; static bool MT4OrderSelect( const long &Index, const int &Select, const int &Pool ) { return( #ifdef MT4ORDERS_BYPASS_MAXTIME (MT4ORDERS::IsTester || ((Select == SELECT_BY_POS) && ((Index == INT_MIN) || (Index == INT_MAX) || ((Pool != MODE_TRADES) && (Index < MT4ORDERS::History.GetAmountPrev())))) || _B2(MT4ORDERS::ByPass.Waiting())) && #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME ((Select == SELECT_BY_POS) ? ((Pool == MODE_TRADES) ? _B2(MT4ORDERS::SelectByPos((int)Index)) : _B2(MT4ORDERS::SelectByPosHistory((int)Index))) : _B2(MT4ORDERS::SelectByTicket(Index, Pool)))); } static int MT4OrdersTotal( void ) { #ifdef MT4ORDERS_SELECTFILTER_OFF return(::OrdersTotal() + ::PositionsTotal()); #else // MT4ORDERS_SELECTFILTER_OFF int Res; if (MT4ORDERS::IsTester) return(::OrdersTotal() + ::PositionsTotal()); else { int PrevTotal; #ifdef MT4ORDERS_BYPASS_MAXTIME _B2(MT4ORDERS::ByPass.Waiting()); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME do { const int Total = ::OrdersTotal(); PrevTotal = ::PositionsTotal(); Res = Total ? _B2(MT4ORDERS::OrdersTotal()) + PrevTotal : PrevTotal; } while (PrevTotal != ::PositionsTotal()); // Отслеживаем только изменение позиций, т.к. ордера отслеживаются в MT4ORDERS::OrdersTotal() } return(Res); // https://www.mql5.com/ru/forum/290673#comment_9493241 #endif //MT4ORDERS_SELECTFILTER_OFF } // Такая "перегрузка" позволяет использоваться совместно и MT5-вариант OrdersTotal static int MT4OrdersTotal( const bool ) { return(::OrdersTotal()); } static int MT4OrdersHistoryTotal( void ) { #ifdef MT4ORDERS_BYPASS_MAXTIME _B2(MT4ORDERS::ByPass.Waiting()); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME return(MT4ORDERS::History.GetAmount()); } static long MT4OrderSend( const string &Symb, const int &Type, const double &dVolume, const double &Price, const int &SlipPage, const double &SL, const double &TP, const string &comment, const MAGIC_TYPE &magic, const datetime &dExpiration, const color &arrow_color ) { ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = (((Type == OP_BUY) || (Type == OP_SELL)) ? TRADE_ACTION_DEAL : TRADE_ACTION_PENDING); MT4ORDERS::LastTradeRequest.magic = magic; MT4ORDERS::LastTradeRequest.symbol = ((Symb == NULL) ? ::Symbol() : Symb); MT4ORDERS::LastTradeRequest.volume = dVolume; MT4ORDERS::LastTradeRequest.price = Price; MT4ORDERS::LastTradeRequest.tp = TP; MT4ORDERS::LastTradeRequest.sl = SL; MT4ORDERS::LastTradeRequest.deviation = SlipPage; MT4ORDERS::LastTradeRequest.type = (ENUM_ORDER_TYPE)Type; MT4ORDERS::LastTradeRequest.type_filling = _B2(MT4ORDERS::GetFilling(MT4ORDERS::LastTradeRequest.symbol, (uint)MT4ORDERS::LastTradeRequest.deviation)); if (MT4ORDERS::LastTradeRequest.action == TRADE_ACTION_PENDING) { MT4ORDERS::LastTradeRequest.type_time = _B2(MT4ORDERS::GetExpirationType(MT4ORDERS::LastTradeRequest.symbol, (uint)dExpiration)); if (dExpiration > ORDER_TIME_DAY) MT4ORDERS::LastTradeRequest.expiration = dExpiration; } if (comment != NULL) MT4ORDERS::LastTradeRequest.comment = comment; return((arrow_color == INT_MAX) ? (MT4ORDERS::NewOrderCheck() ? 0 : -1) : (( #ifndef MT4ORDERS_AUTO_VALIDATION ((int)arrow_color != INT_MIN) || #endif // #ifndef MT4ORDERS_AUTO_VALIDATION MT4ORDERS::NewOrderCheck()) && MT4ORDERS::OrderSend(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeResult) #ifdef MT4ORDERS_BYPASS_MAXTIME && (!MT4ORDERS::IsHedging || _B2(MT4ORDERS::ByPass += MT4ORDERS::LastTradeResult.order)) #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME ? (MT4ORDERS::IsHedging ? (long)MT4ORDERS::LastTradeResult.order : // PositionID == Result.order - особенность MT5-Hedge ((MT4ORDERS::LastTradeRequest.action == TRADE_ACTION_DEAL) ? (MT4ORDERS::IsTester ? (_B2(::PositionSelect(MT4ORDERS::LastTradeRequest.symbol)) ? ::PositionGetInteger(POSITION_TICKET) : 0) : // HistoryDealSelect в MT4ORDERS::OrderSend ::HistoryDealGetInteger(MT4ORDERS::LastTradeResult.deal, DEAL_POSITION_ID)) : (long)MT4ORDERS::LastTradeResult.order)) : -1)); } static bool MT4OrderModify( const long &Ticket, const double &Price, const double &SL, const double &TP, const datetime &Expiration, const color &Arrow_Color ) { ::ZeroMemory(MT4ORDERS::LastTradeRequest); // Учитывается случай, когда присутствуют ордер и позиция с одним и тем же тикетом bool Res = (Ticket < 0) ? MT4ORDERS::ModifyOrder(-Ticket, Price, Expiration, MT4ORDERS::LastTradeRequest) : ((MT4ORDERS::Order.Ticket != ORDER_SELECT) // Спорное решение. Проблема, когда нужно модифицировать позицию, а получается модификация ордера с тем же тикетом. // || (((::PositionGetInteger(POSITION_TICKET) == Ticket) && (::OrderGetInteger(ORDER_TICKET) != Ticket)) ? (MT4ORDERS::ModifyPosition(Ticket, MT4ORDERS::LastTradeRequest) || MT4ORDERS::ModifyOrder(Ticket, Price, Expiration, MT4ORDERS::LastTradeRequest)) : (MT4ORDERS::ModifyOrder(Ticket, Price, Expiration, MT4ORDERS::LastTradeRequest) || MT4ORDERS::ModifyPosition(Ticket, MT4ORDERS::LastTradeRequest))); // if (Res) // Игнорируем проверку - есть OrderCheck { MT4ORDERS::LastTradeRequest.tp = TP; MT4ORDERS::LastTradeRequest.sl = SL; Res = MT4ORDERS::NewOrderSend(Arrow_Color); } return(Res); } // Невозможно закрыть на весь объем определенную MT4-позицию - открывающий позицию MT5-маркет ордер: отсутствует вариант с вызовом OrderDelete. // Искусственно воспроизвести такую ситуацию не получилось. static bool MT4OrderClose( const long &Ticket, const double &dLots, const double &Price, const int &SlipPage, const color &Arrow_Color, const string &comment ) { // Есть MT4ORDERS::LastTradeRequest и MT4ORDERS::LastTradeResult, поэтому на результат не влияет, но нужно для PositionGetString ниже _B2(::PositionSelectByTicket(Ticket)); ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = TRADE_ACTION_DEAL; MT4ORDERS::LastTradeRequest.position = Ticket; MT4ORDERS::LastTradeRequest.symbol = ::PositionGetString(POSITION_SYMBOL); // Сохраняем комментарий при частичном закрытии позиции // if (dLots < ::PositionGetDouble(POSITION_VOLUME)) MT4ORDERS::LastTradeRequest.comment = (comment == NULL) ? ::PositionGetString(POSITION_COMMENT) : comment; // Правильно ли не задавать мэджик при закрытии? -Правильно! MT4ORDERS::LastTradeRequest.volume = dLots; MT4ORDERS::LastTradeRequest.price = Price; if (!MT4ORDERS::MTBuildSLTP) { // Нужно для определения SL/TP-уровней у закрытой позиции. Перевернуто - не ошибка // SYMBOL_SESSION_PRICE_LIMIT_MIN и SYMBOL_SESSION_PRICE_LIMIT_MAX проверять не требуется, т.к. исходные SL/TP уже установлены MT4ORDERS::LastTradeRequest.tp = ::PositionGetDouble(POSITION_SL); MT4ORDERS::LastTradeRequest.sl = ::PositionGetDouble(POSITION_TP); if (MT4ORDERS::LastTradeRequest.tp || MT4ORDERS::LastTradeRequest.sl) { const double StopLevel = ::SymbolInfoInteger(MT4ORDERS::LastTradeRequest.symbol, SYMBOL_TRADE_STOPS_LEVEL) * ::SymbolInfoDouble(MT4ORDERS::LastTradeRequest.symbol, SYMBOL_POINT); const bool FlagBuy = (::PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY); const double CurrentPrice = SymbolInfoDouble(MT4ORDERS::LastTradeRequest.symbol, FlagBuy ? SYMBOL_ASK : SYMBOL_BID); if (CurrentPrice) { if (FlagBuy) MT4ORDERS::CheckPrices(MT4ORDERS::LastTradeRequest.tp, MT4ORDERS::LastTradeRequest.sl, CurrentPrice - StopLevel, CurrentPrice + StopLevel); else MT4ORDERS::CheckPrices(MT4ORDERS::LastTradeRequest.sl, MT4ORDERS::LastTradeRequest.tp, CurrentPrice - StopLevel, CurrentPrice + StopLevel); } else { MT4ORDERS::LastTradeRequest.tp = 0; MT4ORDERS::LastTradeRequest.sl = 0; } } } MT4ORDERS::LastTradeRequest.deviation = SlipPage; MT4ORDERS::LastTradeRequest.type = (ENUM_ORDER_TYPE)(1 - ::PositionGetInteger(POSITION_TYPE)); MT4ORDERS::LastTradeRequest.type_filling = _B2(MT4ORDERS::GetFilling(MT4ORDERS::LastTradeRequest.symbol, (uint)MT4ORDERS::LastTradeRequest.deviation)); return(MT4ORDERS::NewOrderSend(Arrow_Color)); } static bool MT4OrderCloseBy( const long &Ticket, const long &Opposite, const color &Arrow_Color ) { ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = TRADE_ACTION_CLOSE_BY; MT4ORDERS::LastTradeRequest.position = Ticket; MT4ORDERS::LastTradeRequest.position_by = Opposite; if ((!MT4ORDERS::IsTester) && _B2(::PositionSelectByTicket(Ticket))) // нужен для MT4ORDERS::SymbolTrade() MT4ORDERS::LastTradeRequest.symbol = ::PositionGetString(POSITION_SYMBOL); return(MT4ORDERS::NewOrderSend(Arrow_Color)); } static bool MT4OrderDelete( const long &Ticket, const color &Arrow_Color ) { // bool Res = ::OrderSelect(Ticket); // Надо ли это, когда нужны MT4ORDERS::LastTradeRequest и MT4ORDERS::LastTradeResult ? ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = TRADE_ACTION_REMOVE; MT4ORDERS::LastTradeRequest.order = Ticket; if ((!MT4ORDERS::IsTester) && _B2(::OrderSelect(Ticket))) // нужен для MT4ORDERS::SymbolTrade() MT4ORDERS::LastTradeRequest.symbol = ::OrderGetString(ORDER_SYMBOL); return(MT4ORDERS::NewOrderSend(Arrow_Color)); } #define MT4_ORDERFUNCTION(NAME,T,A,B,C) \ static T MT4Order##NAME( void ) \ { \ return(POSITION_ORDER((T)(A), (T)(B), MT4ORDERS::Order.NAME, C)); \ } #define POSITION_ORDER(A,B,C,D) (((MT4ORDERS::Order.Ticket == POSITION_SELECT) && (D)) ? (A) : ((MT4ORDERS::Order.Ticket == ORDER_SELECT) ? (B) : (C))) MT4_ORDERFUNCTION(Ticket, long, ::PositionGetInteger(POSITION_TICKET), ::OrderGetInteger(ORDER_TICKET), true) MT4_ORDERFUNCTION(Type, int, ::PositionGetInteger(POSITION_TYPE), ::OrderGetInteger(ORDER_TYPE), true) MT4_ORDERFUNCTION(Lots, double, ::PositionGetDouble(POSITION_VOLUME), ::OrderGetDouble(ORDER_VOLUME_CURRENT), true) MT4_ORDERFUNCTION(OpenPrice, double, ::PositionGetDouble(POSITION_PRICE_OPEN), (::OrderGetDouble(ORDER_PRICE_OPEN) ? ::OrderGetDouble(ORDER_PRICE_OPEN) : ::OrderGetDouble(ORDER_PRICE_CURRENT)), true) MT4_ORDERFUNCTION(OpenTimeMsc, long, ::PositionGetInteger(POSITION_TIME_MSC), ::OrderGetInteger(ORDER_TIME_SETUP_MSC), true) MT4_ORDERFUNCTION(OpenTime, datetime, ::PositionGetInteger(POSITION_TIME), ::OrderGetInteger(ORDER_TIME_SETUP), true) MT4_ORDERFUNCTION(StopLoss, double, ::PositionGetDouble(POSITION_SL), ::OrderGetDouble(ORDER_SL), true) MT4_ORDERFUNCTION(TakeProfit, double, ::PositionGetDouble(POSITION_TP), ::OrderGetDouble(ORDER_TP), true) MT4_ORDERFUNCTION(ClosePrice, double, ::PositionGetDouble(POSITION_PRICE_CURRENT), ::OrderGetDouble(ORDER_PRICE_CURRENT), true) MT4_ORDERFUNCTION(CloseTimeMsc, long, 0, 0, true) MT4_ORDERFUNCTION(CloseTime, datetime, 0, 0, true) MT4_ORDERFUNCTION(Expiration, datetime, 0, ::OrderGetInteger(ORDER_TIME_EXPIRATION), true) MT4_ORDERFUNCTION(MagicNumber, long, ::PositionGetInteger(POSITION_MAGIC), ::OrderGetInteger(ORDER_MAGIC), true) MT4_ORDERFUNCTION(Profit, double, ::PositionGetDouble(POSITION_PROFIT), 0, true) MT4_ORDERFUNCTION(Swap, double, ::PositionGetDouble(POSITION_SWAP), 0, true) MT4_ORDERFUNCTION(Symbol, string, ::PositionGetString(POSITION_SYMBOL), ::OrderGetString(ORDER_SYMBOL), true) MT4_ORDERFUNCTION(Comment, string, MT4ORDERS::Order.Comment, ::OrderGetString(ORDER_COMMENT), MT4ORDERS::CheckPositionCommissionComment()) MT4_ORDERFUNCTION(Commission, double, MT4ORDERS::Order.Commission, 0, MT4ORDERS::CheckPositionCommissionComment()) MT4_ORDERFUNCTION(OpenPriceRequest, double, MT4ORDERS::Order.OpenPriceRequest, ::OrderGetDouble(ORDER_PRICE_OPEN), MT4ORDERS::CheckPositionOpenPriceRequest()) MT4_ORDERFUNCTION(ClosePriceRequest, double, ::PositionGetDouble(POSITION_PRICE_CURRENT), ::OrderGetDouble(ORDER_PRICE_CURRENT), true) MT4_ORDERFUNCTION(TicketOpen, long, MT4ORDERS::Order.TicketOpen, ::OrderGetInteger(ORDER_TICKET), MT4ORDERS::CheckPositionTicketOpen()) // MT4_ORDERFUNCTION(OpenReason, ENUM_DEAL_REASON, MT4ORDERS::Order.OpenReason, ::OrderGetInteger(ORDER_REASON), MT4ORDERS::CheckPositionOpenReason()) MT4_ORDERFUNCTION(OpenReason, ENUM_DEAL_REASON, ::PositionGetInteger(POSITION_REASON), ::OrderGetInteger(ORDER_REASON), true) MT4_ORDERFUNCTION(CloseReason, ENUM_DEAL_REASON, 0, ::OrderGetInteger(ORDER_REASON), true) MT4_ORDERFUNCTION(TicketID, long, ::PositionGetInteger(POSITION_IDENTIFIER), ::OrderGetInteger(ORDER_TICKET), true) MT4_ORDERFUNCTION(DealsAmount, int, MT4ORDERS::Order.DealsAmount, 0, MT4ORDERS::CheckPositionTicketOpen()) MT4_ORDERFUNCTION(LotsOpen, double, ::PositionGetDouble(POSITION_VOLUME), ::OrderGetDouble(ORDER_VOLUME_INITIAL), true) #undef POSITION_ORDER #undef MT4_ORDERFUNCTION static void MT4OrderPrint( void ) { if (MT4ORDERS::Order.Ticket == POSITION_SELECT) MT4ORDERS::CheckPositionCommissionComment(); ::Print(MT4ORDERS::Order.ToString()); return; } static double MT4OrderLots( const bool& ) { // На случай, если будет решение в пользу целесообразности проверок (OrderLots() != OrderLots(true)). // Такой вариант позволяет не порождать ошибки в OrderClose, но неоднозначен в удобстве во всех сценариях. double Res = /*((MT4ORDERS::Order.Ticket == ORDER_SELECT) && (::OrderGetInteger(ORDER_TYPE) <= OP_SELL)) ? 0 :*/ MT4ORDERS::MT4OrderLots(); if (Res && (MT4ORDERS::Order.Ticket == POSITION_SELECT)) { const ulong PositionID = ::PositionGetInteger(POSITION_IDENTIFIER); if (::PositionSelectByTicket(PositionID)) { const int Type = 1 - (int)::PositionGetInteger(POSITION_TYPE); double PrevVolume = Res; double NewVolume = 0; while (Res && (NewVolume != PrevVolume)) { #ifdef MT4ORDERS_BYPASS_MAXTIME _B2(MT4ORDERS::ByPass.Waiting()); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME if (::PositionSelectByTicket(PositionID)) { Res = ::PositionGetDouble(POSITION_VOLUME); PrevVolume = Res; for (int i = ::OrdersTotal() - 1; i >= 0; i--) if (!::OrderGetTicket(i)) // Случается при i == ::OrdersTotal() - 1. { PrevVolume = -1; break; } else if ((::OrderGetInteger(ORDER_POSITION_ID) == PositionID) && (::OrderGetInteger(ORDER_TYPE) == Type)) Res -= ::OrderGetDouble(ORDER_VOLUME_CURRENT); /* #ifdef MT4ORDERS_BYPASS_MAXTIME _B2(MT4ORDERS::ByPass.Waiting()); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME */ if (::PositionSelectByTicket(PositionID)) NewVolume = ::PositionGetDouble(POSITION_VOLUME); else Res = 0; } else Res = 0; } } else Res = 0; } return(Res); } #undef ORDER_SELECT #undef POSITION_SELECT }; // #define OrderToString MT4ORDERS::MT4OrderToString static MT4_ORDER MT4ORDERS::Order = {}; static MT4HISTORY MT4ORDERS::History; static const bool MT4ORDERS::IsTester = ::MQLInfoInteger(MQL_TESTER); // Если переключить счет, это значение у советников все равно пересчитается // https://www.mql5.com/ru/forum/170952/page61#comment_6132824 static const bool MT4ORDERS::IsHedging = ((ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); static const bool MT4ORDERS::MTBuildSLTP = (::TerminalInfoInteger(TERMINAL_BUILD) >= 3081); // https://www.mql5.com/ru/forum/378360 static int MT4ORDERS::OrderSendBug = 0; static uint MT4ORDERS::OrderSend_MaxPause = 1000000; // максимальное время на синхронизацию в мкс. #ifdef MT4ORDERS_BYPASS_MAXTIME static BYPASS MT4ORDERS::ByPass(MT4ORDERS_BYPASS_MAXTIME); #endif // #ifdef MT4ORDERS_BYPASS_MAXTIME static MqlTradeResult MT4ORDERS::LastTradeResult = {}; static MqlTradeRequest MT4ORDERS::LastTradeRequest = {}; static MqlTradeCheckResult MT4ORDERS::LastTradeCheckResult = {}; bool OrderClose( const long Ticket, const double dLots, const double Price, const int SlipPage, const color Arrow_Color = clrNONE, const string comment = NULL ) { return(MT4ORDERS::MT4OrderClose(Ticket, dLots, Price, SlipPage, Arrow_Color, comment)); } bool OrderModify( const long Ticket, const double Price, const double SL, const double TP, const datetime Expiration, const color Arrow_Color = clrNONE ) { return(MT4ORDERS::MT4OrderModify(Ticket, Price, SL, TP, Expiration, Arrow_Color)); } bool OrderCloseBy( const long Ticket, const long Opposite, const color Arrow_Color = clrNONE ) { return(MT4ORDERS::MT4OrderCloseBy(Ticket, Opposite, Arrow_Color)); } bool OrderDelete( const long Ticket, const color Arrow_Color = clrNONE ) { return(MT4ORDERS::MT4OrderDelete(Ticket, Arrow_Color)); } void OrderPrint( void ) { MT4ORDERS::MT4OrderPrint(); return; } #define MT4_ORDERGLOBALFUNCTION(NAME,T) \ T Order##NAME( void ) \ { \ return((T)MT4ORDERS::MT4Order##NAME()); \ } MT4_ORDERGLOBALFUNCTION(sHistoryTotal, int) MT4_ORDERGLOBALFUNCTION(Ticket, TICKET_TYPE) MT4_ORDERGLOBALFUNCTION(Type, int) MT4_ORDERGLOBALFUNCTION(Lots, double) MT4_ORDERGLOBALFUNCTION(OpenPrice, double) MT4_ORDERGLOBALFUNCTION(OpenTimeMsc, long) MT4_ORDERGLOBALFUNCTION(OpenTime, datetime) MT4_ORDERGLOBALFUNCTION(StopLoss, double) MT4_ORDERGLOBALFUNCTION(TakeProfit, double) MT4_ORDERGLOBALFUNCTION(ClosePrice, double) MT4_ORDERGLOBALFUNCTION(CloseTimeMsc, long) MT4_ORDERGLOBALFUNCTION(CloseTime, datetime) MT4_ORDERGLOBALFUNCTION(Expiration, datetime) MT4_ORDERGLOBALFUNCTION(MagicNumber, MAGIC_TYPE) MT4_ORDERGLOBALFUNCTION(Profit, double) MT4_ORDERGLOBALFUNCTION(Commission, double) MT4_ORDERGLOBALFUNCTION(Swap, double) MT4_ORDERGLOBALFUNCTION(Symbol, string) MT4_ORDERGLOBALFUNCTION(Comment, string) MT4_ORDERGLOBALFUNCTION(OpenPriceRequest, double) MT4_ORDERGLOBALFUNCTION(ClosePriceRequest, double) MT4_ORDERGLOBALFUNCTION(TicketOpen, TICKET_TYPE) MT4_ORDERGLOBALFUNCTION(OpenReason, ENUM_DEAL_REASON) MT4_ORDERGLOBALFUNCTION(CloseReason, ENUM_DEAL_REASON) MT4_ORDERGLOBALFUNCTION(TicketID, TICKET_TYPE) MT4_ORDERGLOBALFUNCTION(DealsAmount, int) MT4_ORDERGLOBALFUNCTION(LotsOpen, double) #undef MT4_ORDERGLOBALFUNCTION double OrderLots( const bool Value ) { return(MT4ORDERS::MT4OrderLots(Value)); } // Перегруженные стандартные функции #define OrdersTotal MT4ORDERS::MT4OrdersTotal // ПОСЛЕ Expert/Expert.mqh - идет вызов MT5-OrdersTotal() bool OrderSelect( const long Index, const int Select, const int Pool = MODE_TRADES ) { return(_B2(MT4ORDERS::MT4OrderSelect(Index, Select, Pool))); } TICKET_TYPE OrderSend( const string Symb, const int Type, const double dVolume, const double Price, const int SlipPage, const double SL, const double TP, const string comment = NULL, const MAGIC_TYPE magic = 0, const datetime dExpiration = 0, color arrow_color = clrNONE ) { return((TICKET_TYPE)MT4ORDERS::MT4OrderSend(Symb, Type, dVolume, Price, SlipPage, SL, TP, comment, magic, dExpiration, arrow_color)); } #define RETURN_ASYNC(A) return((A) && ::OrderSendAsync(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeResult) && \ (MT4ORDERS::LastTradeResult.retcode == TRADE_RETCODE_PLACED) ? MT4ORDERS::LastTradeResult.request_id : 0); uint OrderCloseAsync( const long Ticket, const double dLots, const double Price, const int SlipPage, const color Arrow_Color = clrNONE ) { RETURN_ASYNC(OrderClose(Ticket, dLots, Price, SlipPage, INT_MAX)) } uint OrderModifyAsync( const long Ticket, const double Price, const double SL, const double TP, const datetime Expiration, const color Arrow_Color = clrNONE ) { RETURN_ASYNC(OrderModify(Ticket, Price, SL, TP, Expiration, INT_MAX)) } uint OrderDeleteAsync( const long Ticket, const color Arrow_Color = clrNONE ) { RETURN_ASYNC(OrderDelete(Ticket, INT_MAX)) } uint OrderSendAsync( const string Symb, const int Type, const double dVolume, const double Price, const int SlipPage, const double SL, const double TP, const string comment = NULL, const MAGIC_TYPE magic = 0, const datetime dExpiration = 0, color arrow_color = clrNONE ) { RETURN_ASYNC(!OrderSend(Symb, Type, dVolume, Price, SlipPage, SL, TP, comment, magic, dExpiration, INT_MAX)) } #undef RETURN_ASYNC #undef _BV2 #undef _B3 #undef _B2 #ifdef MT4ORDERS_BENCHMARK_MINTIME #undef MT4ORDERS_BENCHMARK_MINTIME #endif // MT4ORDERS_BENCHMARK_MINTIME // #undef TICKET_TYPE #endif // __MT4ORDERS__ #else // __MQL5__ #define TICKET_TYPE int #define MAGIC_TYPE int TICKET_TYPE OrderTicketID( void ) { return(::OrderTicket()); } #endif // __MQL5__