494 lines
16 KiB
Plaintext
494 lines
16 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| TradeExecutor.mqh |
|
|
//| Universal Buffer Reader EA v2.0 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025"
|
|
#property link ""
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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);
|
|
}
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+ |