//+------------------------------------------------------------------+ //| VirtualOrder.mqh | //| Copyright 2019-2025, Yuriy Bykov | //| https://www.mql5.com/ru/users/antekov | //+------------------------------------------------------------------+ #property copyright "Copyright 2019-2025, Yuriy Bykov" #property link "https://www.mql5.com/ru/users/antekov" #property version "1.09" #include //+------------------------------------------------------------------+ //| Предварительные определения классов | //+------------------------------------------------------------------+ class CVirtualOrder; class CVirtualReceiver; class CVirtualStrategy; #include "../Utils/SymbolsMonitor.mqh" #include "VirtualReceiver.mqh" #include "VirtualStrategy.mqh" // Структура для чтения/записи из БД // основных свойств виртуального ордера/позиции struct VirtualOrderStruct { string strategyHash; int strategyIndex; ulong ticket; string symbol; double lot; ENUM_ORDER_TYPE type; datetime openTime; double openPrice; double stopLoss; double takeProfit; datetime closeTime; double closePrice; datetime expiration; string comment; double point; }; //+------------------------------------------------------------------+ //| Класс виртуальных ордеров и позиций | //+------------------------------------------------------------------+ class CVirtualOrder { private: //--- Статические поля static ulong s_count; // Счётчик всех созданных объектов CVirtualOrder static ulong s_ticket; CSymbolInfo *m_symbolInfo; // Объект для получения свойств символов //--- Связанные объекты получателя и стратегии CSymbolsMonitor *m_symbols; CVirtualReceiver *m_receiver; CVirtualStrategy *m_strategy; //--- Свойства ордера (позиции) ulong m_id; // ID ulong m_ticket; // Тикет string m_symbol; // Символ double m_lot; // Объем ENUM_ORDER_TYPE m_type; // Тип double m_openPrice; // Цена открытия double m_stopLoss; // Уровень StopLoss double m_takeProfit; // Уровень TakeProfit string m_comment; // Комментарий datetime m_expiration; // Время истечения datetime m_openTime; // Время открытия //--- Свойства закрытого ордера (позиции) double m_closePrice; // Цена закрытия datetime m_closeTime; // Время закрытия string m_closeReason; // Причина закрытия double m_point; // Величина пункта bool m_isStopLoss; // Признак срабатывания StopLoss bool m_isTakeProfit; // Признак срабатывания TakeProfit bool m_isExpired; // Признак истечения времени //--- Частные методы bool CheckClose(); // Проверка условий закрытия bool CheckTrigger(); // Проверка срабатывания отложенного ордера public: CVirtualOrder( CVirtualStrategy *p_strategy ); // Конструктор ~CVirtualOrder() { if(!!m_symbolInfo) delete m_symbolInfo; } //--- Методы проверки состояния позиции (ордера) bool IsOpen() { // Ордер открыт? return(this.m_openTime > 0 && this.m_closeTime == 0); }; bool IsClosed() { // Ордер закрыт? return(this.m_openTime > 0 && this.m_closeTime > 0); }; bool IsMarketOrder() { // Это рыночная позиция? return IsOpen() && (m_type == ORDER_TYPE_BUY || m_type == ORDER_TYPE_SELL); } bool IsPendingOrder() {// Это отложенный ордер? return IsOpen() && (m_type == ORDER_TYPE_BUY_LIMIT || m_type == ORDER_TYPE_BUY_STOP || m_type == ORDER_TYPE_SELL_LIMIT || m_type == ORDER_TYPE_SELL_STOP); } bool IsBuyOrder() { // Это открытая позиция BUY? return IsOpen() && (m_type == ORDER_TYPE_BUY || m_type == ORDER_TYPE_BUY_LIMIT || m_type == ORDER_TYPE_BUY_STOP); } bool IsSellOrder() { // Это открытая позиция SELL? return IsOpen() && (m_type == ORDER_TYPE_SELL || m_type == ORDER_TYPE_SELL_LIMIT || m_type == ORDER_TYPE_SELL_STOP); } bool IsStopOrder() { // Это отложенный STOP-ордер? return IsOpen() && (m_type == ORDER_TYPE_BUY_STOP || m_type == ORDER_TYPE_SELL_STOP); } bool IsLimitOrder() { // Это отложенный LIMIT-ордер? return IsOpen() && (m_type == ORDER_TYPE_BUY_LIMIT || m_type == ORDER_TYPE_SELL_LIMIT); } //--- Методы получения свойств позиции (ордера) ulong Id() { // ID return m_id; } ulong Ticket() { // Тикет return m_ticket; } CStrategy *Strategy() { return m_strategy; } ENUM_ORDER_TYPE Type() { return m_type; } string TypeName() { string s = StringSubstr(EnumToString(m_type), 11); StringReplace(s, "_", " "); return s; } double Lot() { return m_lot; } double Volume() { // Объем с направлением return IsBuyOrder() ? m_lot : (IsSellOrder() ? -m_lot : 0); } double Profit(); // Текущая прибыль double ClosedProfit(); string Symbol() { // Символ return m_symbol; } double OpenPrice() { return m_openPrice; } datetime OpenTime() { return m_openTime; } double ClosePrice() { return m_closePrice; } datetime CloseTime() { return m_closeTime; } double FittedBalance() { return m_strategy.FittedBalance(); } double StopLoss() { return m_stopLoss; } void StopLoss(double value) { m_stopLoss = value; } double TakeProfit() { return m_takeProfit; } void TakeProfit(double value) { m_takeProfit = value; } //--- Методы обработки позиций (ордеров) bool CVirtualOrder::Open(string symbol, ENUM_ORDER_TYPE type, double lot, double price, double sl = 0, double tp = 0, string comment = "", datetime expiration = 0, bool inPoints = false); // Открытие позиции (ордера) void Expiration(datetime p_expiration) { if(IsOpen()) { m_expiration = p_expiration; } } void Tick(); // Обработка тика для позиции (ордера) void Close(); // Закрытие позиции (ордера) void Clear() { m_lot = 0; } virtual void Load(const VirtualOrderStruct &o); // Загрузка состояния virtual void Save(VirtualOrderStruct &o); // Сохранение состояния static void Reset() { s_count = 0; } // Есть ли открытые рыночные виртуальные позиции? static bool HasMarket(CVirtualOrder* &p_orders[]); string operator~(); // Преобразование объекта в строку }; // Инициализация статических полей класса ulong CVirtualOrder::s_count = 0; ulong CVirtualOrder::s_ticket = 0; //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CVirtualOrder::CVirtualOrder(CVirtualStrategy *p_strategy) : // Список инициализации m_id(++s_count), // Новый идентификатор = счётчик объектов + 1 m_ticket(0), m_receiver(CVirtualReceiver::Instance()), m_strategy(p_strategy), m_symbol(""), m_lot(0), m_type(-1), m_openPrice(0), m_stopLoss(0), m_takeProfit(0), m_openTime(0), m_comment(""), m_expiration(0), m_closePrice(0), m_closeTime(0), m_closeReason(""), m_point(0) { PrintFormat(__FUNCTION__ + "#%d | CREATED VirtualOrder", m_id); m_symbolInfo = NULL; m_symbols = CSymbolsMonitor::Instance(); } //+------------------------------------------------------------------+ //| Открытие виртуальной позиции (ордера) | //+------------------------------------------------------------------+ bool CVirtualOrder::Open(string symbol, // Символ ENUM_ORDER_TYPE type, // Тип (BUY или SELL) double lot, // Объём double price = 0, // Цена открытия double sl = 0, // Уровень StopLoss (цена или пункты) double tp = 0, // Уровень TakeProfit (цена или пункты) string comment = "", // Комментарий datetime expiration = 0, // Время истечения bool inPoints = false // Уровни SL и TP заданы в пунктах? ) { if(IsOpen()) { // Если позиция уже открыта, то ничего не делаем PrintFormat(__FUNCTION__ "#%d | ERROR: Order is opened already!", m_id); return false; } // Получаем от монитора символов указатель на информационный объект для нужного символа m_symbolInfo = m_symbols[symbol]; if(!!m_symbolInfo) { //m_symbolInfo.RefreshRates(); // Обновляем информацию о текущих ценах // Инициализируем свойства позиции m_ticket = ++s_ticket; m_openPrice = price; m_symbol = symbol; m_lot = lot; m_openTime = TimeCurrent(); m_closeTime = 0; m_type = type; m_comment = comment; m_expiration = expiration; // Открываемая позиция (ордер) не является закрытой по SL, TP или истечению m_isStopLoss = false; m_isTakeProfit = false; m_isExpired = false; m_point = m_symbolInfo.Point(); double bid = m_symbolInfo.Bid(); double ask = m_symbolInfo.Ask(); double spread = ((ask - bid) / m_point); //m_symbolInfo.Spread(); // В зависимости от направления устанавливаем цену открытия и уровни SL и TP. // Если SL и TP заданы в пунктах, то предварительно вычисляем их ценовые уровни // относительно цены открытия if(IsBuyOrder()) { if(type == ORDER_TYPE_BUY) { m_openPrice = ask; } m_stopLoss = (sl > 0 ? (inPoints ? m_openPrice - sl * m_point - spread * m_point : sl) : 0); m_takeProfit = (tp > 0 ? (inPoints ? m_openPrice + tp * m_point : tp) : 0); } else if(IsSellOrder()) { if(type == ORDER_TYPE_SELL) { m_openPrice = bid; } m_stopLoss = (sl > 0 ? (inPoints ? m_openPrice + sl * m_point : sl) : 0); m_takeProfit = (tp > 0 ? (inPoints ? m_openPrice - tp * m_point - spread * m_point : tp) : 0); } // Оповещаем получатель и стратегию, что позиция (ордер) открыта m_receiver.OnOpen(&this); m_strategy.OnOpen(&this); PrintFormat(__FUNCTION__"#%d | OPEN %s: %s %s %.2f | Price=%.5f | SL=%.5f | TP=%.5f | %s | %s", m_id, (IsMarketOrder() ? "Market" : "Pending"), StringSubstr(EnumToString(type), 11), m_symbol, m_lot, m_openPrice, m_stopLoss, m_takeProfit, m_comment, (m_expiration ? TimeToString(m_expiration) : "-")); return true; } else { PrintFormat(__FUNCTION__"#%d | ERROR: Can't find symbol %s for " "OPEN %s: %s %s %.2f | Price=%.5f | SL=%.5f | TP=%.5f | %s | %s", m_id, m_symbol, (IsMarketOrder() ? "Market" : "Pending"), StringSubstr(EnumToString(type), 11), m_symbol, m_lot, m_openPrice, m_stopLoss, m_takeProfit, m_comment, (m_expiration ? TimeToString(m_expiration) : "-")); return false; } } //+------------------------------------------------------------------+ //| Закрытие позиции | //+------------------------------------------------------------------+ void CVirtualOrder::Close() { if(IsOpen()) { // Если позиция открыта // Определяем причину закрытия для вывода в лог string closeReason = ""; if(m_isStopLoss) { closeReason += "[SL]"; } else if(m_isTakeProfit) { closeReason += "[TP]"; } else if(m_isExpired) { closeReason += "[EX]"; } else { closeReason += "[CL]"; } PrintFormat(__FUNCTION__ + "#%d | CLOSE %s: %s %s %.2f | Profit=%.2f %s | %s", m_id, (IsMarketOrder() ? "Market" : "Pending"), StringSubstr(EnumToString(m_type), 11), m_symbol, m_lot, Profit(), closeReason, m_comment); m_closeTime = TimeCurrent(); // Время закрытия позиции // Запоминаем цену закрытия в зависимости от типа if(m_type == ORDER_TYPE_BUY) { m_closePrice = m_symbolInfo.Bid(); } else if(m_type == ORDER_TYPE_SELL) { m_closePrice = m_symbolInfo.Ask(); } else { m_closePrice = 0; } // Оповещаем получатель и стратегию, что позиция (ордер) закрыта m_receiver.OnClose(&this); m_strategy.OnClose(&this); } } //+------------------------------------------------------------------+ //| Расчет текущей прибыли позиции | //+------------------------------------------------------------------+ double CVirtualOrder::Profit() { double profit = 0; if(IsMarketOrder()) { // Если это открытая рыночная виртуальная позиция //m_symbolInfo.Name(m_symbol); // Выбираем нужный символ //m_symbolInfo.RefreshRates(); // Обновляем информацию о текущих ценах // Текущая цена, по которой можно закрыть позицию double closePrice = (m_type == ORDER_TYPE_BUY) ? m_symbolInfo.Bid() : m_symbolInfo.Ask(); // Прибыль в виде разности цен открытия и закрытия if(m_type == ORDER_TYPE_BUY) { profit = closePrice - m_openPrice; } else { profit = m_openPrice - closePrice; } if(m_point > 1e-10) { // Если известен размер пункта, то // Пересчитываем прибыль из разности цен в денежное выражение для объёма в 1 лот if(profit > 0) { profit = profit / m_point * m_symbolInfo.TickValueProfit(); } else { profit = profit / m_point * m_symbolInfo.TickValueLoss(); } } else { PrintFormat(__FUNCTION__ + "#%d | ERROR: Point for %s is undefined", m_id, m_symbol); m_point = m_symbolInfo.Point(); } // Пересчитываем прибыль для объёма позиции profit *= m_lot; } return profit; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CVirtualOrder::ClosedProfit() { double profit = 0; if(IsClosed()) { // Если это открытая рыночная виртуальная позиция //s_symbolInfo.Name(m_symbol); // Выбираем нужный символ //s_symbolInfo.RefreshRates(); // Обновляем информацию о текущих ценах // Текущая цена, по которой можно закрыть позицию double closePrice = m_closePrice; // Прибыль в виде разности цен открытия и закрытия if(m_type == ORDER_TYPE_BUY) { profit = closePrice - m_openPrice; } else if(m_type == ORDER_TYPE_SELL) { profit = m_openPrice - closePrice; } if(m_point > 1e-10) { // Если известен размер пункта, то // Пересчитываем прибыль из разности цен в денежное выражение для объёма в 1 лот if(profit > 0) { profit = profit / m_point * m_symbolInfo.TickValueProfit(); } else { profit = profit / m_point * m_symbolInfo.TickValueLoss(); } } else { PrintFormat(__FUNCTION__ + "#%d | ERROR: Point for %s is undefined", m_id, m_symbol); m_point = m_symbolInfo.Point(); } // Пересчитываем прибыль для объёма позиции profit *= m_lot; } return profit; } //+------------------------------------------------------------------+ //| Проверка необходимости закрытия по SL, TP или EX | //+------------------------------------------------------------------+ bool CVirtualOrder::CheckClose() { if(IsMarketOrder()) { // Если это открытая рыночная виртуальная позиция, то //s_symbolInfo.Name(m_symbol); // Выбираем нужный символ //s_symbolInfo.RefreshRates(); // Обновляем информацию о текущих ценах // Текущая цена, по которой можно закрыть позицию double closePrice = (m_type == ORDER_TYPE_BUY) ? m_symbolInfo.Bid() : m_symbolInfo.Ask(); //double spread = m_symbolInfo.Spread(); //double lastHigh = iHigh(m_symbol, PERIOD_M1, 1); //double lastLow = iLow(m_symbol, PERIOD_M1, 1) + spread; bool res = false; // Проверяем, что цена достигла SL или TP if(m_type == ORDER_TYPE_BUY) { m_isStopLoss = (m_stopLoss > 0 && closePrice <= m_stopLoss); m_isTakeProfit = (m_takeProfit > 0 && (closePrice >= m_takeProfit //|| lastHigh >= m_takeProfit )); } else if(m_type == ORDER_TYPE_SELL) { m_isStopLoss = (m_stopLoss > 0 && closePrice >= m_stopLoss); m_isTakeProfit = (m_takeProfit > 0 && (closePrice <= m_takeProfit // || lastLow <= m_takeProfit )); } // Был ли достигнут SL или TP? res = (m_isStopLoss || m_isTakeProfit); if(res) { PrintFormat(__FUNCTION__ + "#%d | %s REACHED at %.5f: %.5f | %.5f, Profit=%.2f | %s", m_id, (m_isStopLoss ? "SL" : "TP"), closePrice, m_stopLoss, m_takeProfit, Profit(), m_comment); return true; } } else if(IsPendingOrder()) { // Если это виртуальный отложенный ордер // Проверяем, было ли достигнуто время истечения, если оно задано if(m_expiration > 0 && m_expiration < TimeCurrent()) { m_isExpired = true; return true; } } return false; } //+------------------------------------------------------------------+ //| Обработка тика одного виртуального ордера (позиции) | //+------------------------------------------------------------------+ void CVirtualOrder::Tick() { if(IsOpen()) { // Если это открытая виртуальная позиция или ордер if(CheckClose()) { // Проверяем, достигнуты ли уровни SL или TP или время истечения Close(); // Закрываем при достижении } else if (IsPendingOrder()) { // Если это отложенный ордер CheckTrigger(); // Проверяем его срабатывание } } } //+------------------------------------------------------------------+ //| Проверка срабатывания отложенного ордера | //+------------------------------------------------------------------+ bool CVirtualOrder::CheckTrigger() { if(IsPendingOrder()) { //m_symbolInfo.Name(m_symbol); // Выбираем нужный символ //m_symbolInfo.RefreshRates(); // Обновляем информацию о текущих ценах double bid = m_symbolInfo.Bid(); double ask = m_symbolInfo.Ask(); double spread = ((ask - bid) / m_point); double price = (IsBuyOrder()) ? ask : bid; // Если цена дошла до уровней открытия, то превращаем ордер в позицию if(false || (m_type == ORDER_TYPE_BUY_LIMIT && price <= m_openPrice) || (m_type == ORDER_TYPE_BUY_STOP && price >= m_openPrice) ) { PrintFormat(__FUNCTION__"#%d | OPEN %s at %.5f -> BUY at %.5f (Spread: %.0f)", m_id, StringSubstr(EnumToString(m_type), 11), m_openPrice, price, spread); m_type = ORDER_TYPE_BUY; } else if(false || (m_type == ORDER_TYPE_SELL_LIMIT && price >= m_openPrice) || (m_type == ORDER_TYPE_SELL_STOP && price <= m_openPrice) ) { PrintFormat(__FUNCTION__"#%d | OPEN %s at %.5f -> SELL at %.5f (Spread: %.0f)", m_id, StringSubstr(EnumToString(m_type), 11), m_openPrice, price, spread); m_type = ORDER_TYPE_SELL; } // Если ордер превратился в позицию if(IsMarketOrder()) { m_openPrice = price; // Запоминаем цену открытия // Оповещаем получатель и стратегию, что открыта позиция m_receiver.OnOpen(&this); m_strategy.OnOpen(&this); return true; } } return false; } //+------------------------------------------------------------------+ //| Сохранение состояния | //+------------------------------------------------------------------+ void CVirtualOrder::Save(VirtualOrderStruct &o) { o.ticket = m_ticket; o.symbol = m_symbol; o.lot = m_lot; o.type = m_type; o.openPrice = m_openPrice; o.stopLoss = m_stopLoss; o.takeProfit = m_takeProfit; o.openTime = m_openTime; o.closePrice = m_closePrice; o.closeTime = m_closeTime; o.expiration = m_expiration; o.comment = m_comment; o.point = m_point; } //+------------------------------------------------------------------+ //| Загрузка состояния | //+------------------------------------------------------------------+ void CVirtualOrder::Load(const VirtualOrderStruct &o) { m_ticket = o.ticket; m_symbol = o.symbol; m_lot = o.lot; m_type = o.type; m_openPrice = o.openPrice; m_stopLoss = o.stopLoss; m_takeProfit = o.takeProfit; m_openTime = o.openTime; m_closePrice = o.closePrice; m_closeTime = o.closeTime; m_expiration = o.expiration; m_comment = o.comment; m_point = o.point; PrintFormat(__FUNCTION__" | %s", ~this); s_ticket = MathMax(s_ticket, m_ticket); m_symbolInfo = m_symbols[m_symbol]; // Оповещаем получатель и стратегию, что позиция (ордер) открыта if(IsOpen()) { m_receiver.OnOpen(&this); m_strategy.OnOpen(&this); } else { m_receiver.OnClose(&this); m_strategy.OnClose(&this); } } //+------------------------------------------------------------------+ //| Есть ли открытые рыночные виртуальные позиции? | //+------------------------------------------------------------------+ bool CVirtualOrder::HasMarket(CVirtualOrder *&p_orders[]) { FOREACH(p_orders) if (p_orders[i].IsMarketOrder()) return true; return false; } //+------------------------------------------------------------------+ //| Преобразование объекта в строку | //+------------------------------------------------------------------+ string CVirtualOrder::operator~() { if(IsOpen()) { return StringFormat("#%d %s %s %.2f in %s at %.5f (%.5f, %.5f). %s, %f", m_id, TypeName(), m_symbol, m_lot, TimeToString(m_openTime), m_openPrice, m_stopLoss, m_takeProfit, TimeToString(m_closeTime), m_closePrice); } else { return StringFormat("#%d --- ", m_id); } } //+------------------------------------------------------------------+