//+------------------------------------------------------------------+ //| TradeExecutor.mqh | //| Universal Buffer Reader EA v2.0 | //+------------------------------------------------------------------+ #property copyright "Copyright 2025" #property link "" #property version "1.00" #property strict #include #include //+------------------------------------------------------------------+ //| CTradeExecutor - Executes trades and manages orders | //+------------------------------------------------------------------+ class CTradeExecutor { private: int m_slippage_points; int m_magic_number; string m_symbol; int m_digits; bool m_take_screenshot; long m_chart_id; string m_indicator_name; CTrade *m_trade; CPositionInfo *m_position_info; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Trade result structure | //+------------------------------------------------------------------+ struct TradeResult { bool success; ulong ticket; string error_message; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeExecutor() { m_slippage_points = 30; m_magic_number = 24680; m_symbol = ""; m_digits = 0; m_take_screenshot = true; m_chart_id = 0; m_indicator_name = ""; m_enable_debug = false; m_trade = new CTrade(); m_position_info = new CPositionInfo(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CTradeExecutor() { if(m_trade != NULL) { delete m_trade; m_trade = NULL; } if(m_position_info != NULL) { delete m_position_info; m_position_info = NULL; } } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( int slippage_points, int magic_number, string symbol, int digits, bool take_screenshot, long chart_id, string indicator_name, bool enable_debug = false ) { // Validate parameters if(slippage_points < 0) { Print("[ERROR] Invalid slippage points: ", slippage_points, ". Using default 30"); m_slippage_points = 30; } else { m_slippage_points = slippage_points; } if(magic_number <= 0) { Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680"); m_magic_number = 24680; } else { m_magic_number = magic_number; } if(symbol == "") { Print("[ERROR] Invalid symbol: empty string"); } m_symbol = symbol; if(digits <= 0) { Print("[ERROR] Invalid digits: ", digits); } m_digits = digits; m_take_screenshot = take_screenshot; m_chart_id = chart_id; m_indicator_name = indicator_name; m_enable_debug = enable_debug; m_trade.SetExpertMagicNumber(m_magic_number); m_trade.SetDeviationInPoints(m_slippage_points); if(m_enable_debug) { Print("[TradeExecutor] Parameters set:"); Print(" Slippage: ", m_slippage_points, " points"); Print(" Magic number: ", m_magic_number); Print(" Symbol: ", m_symbol); Print(" Digits: ", m_digits); Print(" Take screenshot: ", m_take_screenshot); Print(" Chart ID: ", m_chart_id); Print(" Indicator: ", m_indicator_name); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Open new trade with multiple TPs | //+------------------------------------------------------------------+ TradeResult ExecuteTrade( bool is_buy, double lots, double open_price, double sl_price, double &tp_prices[], int tp_count ) { TradeResult result; result.success = false; result.ticket = 0; result.error_message = ""; if(m_enable_debug) { Print("[TradeExecutor] Executing trade..."); Print(" Direction: ", (is_buy ? "BUY" : "SELL")); Print(" Lots: ", lots); Print(" SL: ", sl_price); Print(" TP count: ", tp_count); } // Validate inputs if(lots <= 0) { result.error_message = "Invalid lots: " + DoubleToString(lots, 2); Print("[ERROR] ", result.error_message); return result; } if(m_symbol == "") { result.error_message = "Symbol not set"; Print("[ERROR] ", result.error_message); return result; } // Build order comment with multiple TPs string comment = BuildOrderComment(tp_prices, tp_count); if(m_enable_debug) { Print(" Comment: ", comment); } // Get execution price double execution_price = GetExecutionPrice(is_buy); if(m_enable_debug) { Print(" Execution price: ", execution_price); } // Execute order bool order_result = false; if(is_buy) { order_result = m_trade.Buy(lots, m_symbol, execution_price, sl_price, 0, comment); } else { order_result = m_trade.Sell(lots, m_symbol, execution_price, sl_price, 0, comment); } if(order_result) { result.success = true; result.ticket = m_trade.ResultOrder(); Print("[TradeExecutor] Order opened successfully. Ticket: ", result.ticket); // Take screenshot if(m_take_screenshot) { TakeScreenshot(result.ticket); } } else { int error_code = m_trade.ResultRetcode(); result.error_message = "OrderSend failed: " + m_trade.ResultRetcodeDescription() + " (Code: " + IntegerToString(error_code) + ")"; Print("[ERROR] ", result.error_message); } return result; } //+------------------------------------------------------------------+ //| Partial close | //+------------------------------------------------------------------+ TradeResult ExecutePartialClose( ulong ticket, double close_lots, int tp_index ) { TradeResult result; result.success = false; result.ticket = 0; result.error_message = ""; if(m_enable_debug) { Print("[TradeExecutor] Executing partial close..."); Print(" Ticket: ", ticket); Print(" Close lots: ", close_lots); Print(" TP index: ", tp_index); } // Validate inputs if(ticket == 0) { result.error_message = "Invalid ticket: 0"; Print("[ERROR] ", result.error_message); return result; } if(close_lots <= 0) { result.error_message = "Invalid close lots: " + DoubleToString(close_lots, 2); Print("[ERROR] ", result.error_message); return result; } if(!PositionSelectByTicket(ticket)) { result.error_message = "Failed to select position #" + IntegerToString(ticket); Print("[ERROR] ", result.error_message); return result; } double current_lots = PositionGetDouble(POSITION_VOLUME); if(close_lots > current_lots) { Print("[WARNING] Close lots (", close_lots, ") > current lots (", current_lots, ") for ticket #", ticket, ". Adjusting to current lots."); close_lots = current_lots; } ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); bool close_result = false; if(pos_type == POSITION_TYPE_BUY) { close_result = m_trade.Sell(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index)); } else { close_result = m_trade.Buy(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index)); } if(close_result) { result.success = true; result.ticket = m_trade.ResultOrder(); Print("[TradeExecutor] Partial close executed: ", close_lots, " lots at TP", tp_index, " for ticket #", ticket); } else { int error_code = m_trade.ResultRetcode(); result.error_message = "Partial close failed: " + m_trade.ResultRetcodeDescription() + " (Code: " + IntegerToString(error_code) + ")"; Print("[ERROR] ", result.error_message); } return result; } //+------------------------------------------------------------------+ //| Check if trade is open | //+------------------------------------------------------------------+ bool IsTradeOpen() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(m_position_info.SelectByIndex(i)) { if(m_position_info.Symbol() == m_symbol && m_position_info.Magic() == m_magic_number) { return true; } } } return false; } //+------------------------------------------------------------------+ //| Close opposite trade | //+------------------------------------------------------------------+ bool CloseOppositeTrade(bool is_buy) { if(m_enable_debug) { Print("[TradeExecutor] Closing opposite trades for ", (is_buy ? "BUY" : "SELL"), " signal"); } bool closed_any = false; ENUM_POSITION_TYPE opposite_type = is_buy ? POSITION_TYPE_SELL : POSITION_TYPE_BUY; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(m_position_info.SelectByIndex(i)) { if(m_position_info.Symbol() == m_symbol && m_position_info.Magic() == m_magic_number && m_position_info.PositionType() == opposite_type) { ulong ticket = m_position_info.Ticket(); double lots = m_position_info.Volume(); if(m_enable_debug) { Print("[TradeExecutor] Found opposite trade #", ticket, " (", lots, " lots)"); } if(m_trade.PositionClose(ticket)) { Print("[TradeExecutor] Closed opposite trade Ticket#", ticket, " due to new signal."); closed_any = true; } else { int error_code = m_trade.ResultRetcode(); Print("[ERROR] Failed to close opposite trade Ticket#", ticket, ". Error: ", m_trade.ResultRetcodeDescription(), " (", error_code, ")"); } } } } if(m_enable_debug && !closed_any) { Print("[TradeExecutor] No opposite trades found to close"); } return closed_any; } private: //+------------------------------------------------------------------+ //| Build order comment with multiple TPs | //+------------------------------------------------------------------+ string BuildOrderComment(double &tp_prices[], int tp_count) { string comment = "UnivBufEA_" + IntegerToString(m_magic_number); // Add TPs to comment for(int i = 0; i < tp_count; i++) { comment += ";TP" + IntegerToString(i + 1) + "=" + DoubleToString(tp_prices[i], m_digits); } // Add breakeven and trailing flags comment += ";BE=0;TS=0"; // Truncate to max length (31 chars for some brokers) if(StringLen(comment) > 31) { comment = StringSubstr(comment, 0, 31); } return comment; } //+------------------------------------------------------------------+ //| Take screenshot | //+------------------------------------------------------------------+ bool TakeScreenshot(ulong ticket) { if(!m_take_screenshot) { if(m_enable_debug) { Print("[TradeExecutor] Screenshot disabled"); } return false; } if(m_chart_id == 0) { Print("[ERROR] Chart ID is 0, cannot take screenshot"); return false; } string time_str = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); StringReplace(time_str, ":", "_"); StringReplace(time_str, ".", "_"); string filename = m_symbol + "_T" + IntegerToString(ticket) + "_" + time_str + ".gif"; int width = (int)ChartGetInteger(m_chart_id, CHART_WIDTH_IN_PIXELS); int height = (int)ChartGetInteger(m_chart_id, CHART_HEIGHT_IN_PIXELS); if(width <= 0 || height <= 0) { Print("[ERROR] Invalid chart dimensions: ", width, "x", height); return false; } if(m_enable_debug) { Print("[TradeExecutor] Taking screenshot: ", filename, " (", width, "x", height, ")"); } if(ChartScreenShot(m_chart_id, filename, width, height, ALIGN_RIGHT)) { Print("[TradeExecutor] Screenshot saved: ", filename); return true; } int error = GetLastError(); Print("[ERROR] Failed to save screenshot. Error code: ", error); return false; } //+------------------------------------------------------------------+ //| Get execution price | //+------------------------------------------------------------------+ double GetExecutionPrice(bool is_buy) { if(is_buy) { return SymbolInfoDouble(m_symbol, SYMBOL_ASK); } else { return SymbolInfoDouble(m_symbol, SYMBOL_BID); } } }; //+------------------------------------------------------------------+