//+------------------------------------------------------------------+ //| TradeManager.mqh - Trade execution using CTrade class | //| Handles buy/sell orders with explicit SL/TP and risk controls | //+------------------------------------------------------------------+ #ifndef __TRADEMANAGER_MQH__ #define __TRADEMANAGER_MQH__ #include #include "Config.mqh" #include "Logger.mqh" #include "RiskManager.mqh" class CTradeManager { private: CTrade m_trade; CLogger *mp_logger; CRiskManager *mp_risk_manager; public: CTradeManager(CLogger *logger, CRiskManager *risk_manager) { mp_logger = logger; mp_risk_manager = risk_manager; m_trade.SetExpertMagicNumber((ulong)g_magic_number); m_trade.SetAsyncMode(false); m_trade.SetDeviationInPoints(10); } ~CTradeManager() { } bool OpenBuyTrade(const string symbol, double lot, double sl, double tp, double setupEntryPrice = 0.0) { if(lot <= 0.0) { if(mp_logger) mp_logger.Error(StringFormat("Invalid lot size: %.2f", lot)); return false; } // TP-less trades are intentionally unsupported until TradeSetup has an // explicit allowNoTakeProfit field; do not infer that from tp = 0. if(sl <= 0.0 || tp <= 0.0) { if(mp_logger) mp_logger.Error("Invalid SL or TP provided for BUY order"); return false; } double ask = mp_risk_manager.GetMarketData().GetAsk(); if(mp_logger && g_debug_mode) { double sl_distance = MathAbs(ask - sl); double tp_distance = MathAbs(tp - ask); double setup_diff = (setupEntryPrice > 0.0) ? MathAbs(setupEntryPrice - ask) : 0.0; string setup_info = (setupEntryPrice > 0.0) ? StringFormat("setupEntry=%.5f, execEntry=%.5f, diff=%.5f, ", setupEntryPrice, ask, setup_diff) : "execEntry=" + DoubleToString(ask, 5) + ", "; mp_logger.Info(StringFormat("Opening BUY: %s lot=%.2f, SL=%.5f, TP=%.5f, SL dist=%.5f, TP dist=%.5f", setup_info, lot, sl, tp, sl_distance, tp_distance)); } if(!m_trade.Buy(lot, symbol, ask, sl, tp)) { if(mp_logger) mp_logger.Error(StringFormat("Buy trade failed. Result code: %d, Error: %s", m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription())); return false; } if(mp_logger) mp_logger.Info(StringFormat("Buy trade opened. Ticket: %I64d, Volume: %.2f", m_trade.ResultOrder(), lot)); return true; } bool OpenSellTrade(const string symbol, double lot, double sl, double tp, double setupEntryPrice = 0.0) { if(lot <= 0.0) { if(mp_logger) mp_logger.Error(StringFormat("Invalid lot size: %.2f", lot)); return false; } // TP-less trades are intentionally unsupported until TradeSetup has an // explicit allowNoTakeProfit field; do not infer that from tp = 0. if(sl <= 0.0 || tp <= 0.0) { if(mp_logger) mp_logger.Error("Invalid SL or TP provided for SELL order"); return false; } double bid = mp_risk_manager.GetMarketData().GetBid(); if(mp_logger && g_debug_mode) { double sl_distance = MathAbs(sl - bid); double tp_distance = MathAbs(bid - tp); double setup_diff = (setupEntryPrice > 0.0) ? MathAbs(setupEntryPrice - bid) : 0.0; string setup_info = (setupEntryPrice > 0.0) ? StringFormat("setupEntry=%.5f, execEntry=%.5f, diff=%.5f, ", setupEntryPrice, bid, setup_diff) : "execEntry=" + DoubleToString(bid, 5) + ", "; mp_logger.Info(StringFormat("Opening SELL: %s lot=%.2f, SL=%.5f, TP=%.5f, SL dist=%.5f, TP dist=%.5f", setup_info, lot, sl, tp, sl_distance, tp_distance)); } if(!m_trade.Sell(lot, symbol, bid, sl, tp)) { if(mp_logger) mp_logger.Error(StringFormat("Sell trade failed. Result code: %d, Error: %s", m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription())); return false; } if(mp_logger) mp_logger.Info(StringFormat("Sell trade opened. Ticket: %I64d, Volume: %.2f", m_trade.ResultOrder(), lot)); return true; } bool ClosePosition(ulong ticket) { if(ticket == 0) return false; if(!PositionSelectByTicket(ticket)) return false; double volume = PositionGetDouble(POSITION_VOLUME); if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Closing position ticket %I64d, volume %.2f", ticket, volume)); if(!m_trade.PositionClose(ticket)) { if(mp_logger) mp_logger.Error(StringFormat("Close position failed. Ticket: %I64d, Result: %d", ticket, m_trade.ResultRetcode())); return false; } if(mp_logger) mp_logger.Info(StringFormat("Position closed. Ticket: %I64d", ticket)); return true; } bool ModifyPosition(ulong ticket, double sl, double tp) { if(ticket == 0) return false; if(!PositionSelectByTicket(ticket)) return false; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Modifying position %I64d: SL=%.5f, TP=%.5f", ticket, sl, tp)); if(!m_trade.PositionModify(ticket, sl, tp)) { if(mp_logger) mp_logger.Error(StringFormat("Position modify failed. Ticket: %I64d, Result: %d", ticket, m_trade.ResultRetcode())); return false; } if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Position modified. Ticket: %I64d", ticket)); return true; } uint GetResultRetcode() const { return m_trade.ResultRetcode(); } CTrade* GetTradeObject() { return &m_trade; } }; #endif //__TRADEMANAGER_MQH__