mql5/Include/Experts/TradeExecutor.mqh

3509 lines
131 KiB
MQL5
Raw Permalink Normal View History

2025-08-16 12:30:04 -04:00
//+------------------------------------------------------------------+
//| TradeExecutor.mqh |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>
#include <Trade\DealInfo.mqh>
#include <Object.mqh> // For CObject
#include <Arrays\ArrayObj.mqh> // For CArrayObj
#include <Trade\OrderInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include "RiskManager.mqh"
//+------------------------------------------------------------------+
//| Error Codes |
//+------------------------------------------------------------------+
enum ENUM_TRADE_ERROR
{
ERR_TRADE_SUCCESS = 0, // Operation successful
ERR_TRADE_UNKNOWN_ERROR = -1, // Unknown error occurred
ERR_TRADE_INVALID_PARAMETER = -2, // Invalid parameter passed
ERR_TRADE_NOT_INITIALIZED = -3, // Trade executor not initialized
ERR_TRADE_DISABLED = -4, // Trading is disabled
ERR_TRADE_INVALID_SYMBOL = -5, // Invalid symbol
ERR_TRADE_INVALID_VOLUME = -6, // Invalid trade volume
ERR_TRADE_INVALID_PRICE = -7, // Invalid price
ERR_TRADE_INVALID_STOPS = -8, // Invalid stop loss/take profit
ERR_TRADE_INSUFFICIENT_FUNDS = -9, // Not enough money
ERR_TRADE_MARKET_CLOSED = -10, // Market is closed
ERR_TRADE_TOO_MANY_REQUESTS = -11, // Too many trade requests
ERR_TRADE_TIMEOUT = -12, // Trade operation timed out
ERR_TRADE_SPREAD_TOO_WIDE = -13, // Spread is too wide
ERR_TRADE_POSITION_NOT_FOUND = -14, // Position not found
ERR_TRADE_ORDER_NOT_FOUND = -15, // Order not found
ERR_TRADE_CONNECTION_LOST = -16, // Connection to trade server lost
ERR_TRADE_SERVER_BUSY = -17, // Trade server is busy
ERR_TRADE_CONTEXT_BUSY = -18, // Trade context is busy
ERR_TRADE_TOO_FREQUENT = -19, // Too frequent trading
ERR_TRADE_LIMIT_REACHED = -20 // Trade limit reached
};
//+------------------------------------------------------------------+
//| Trade Execution Constants |
//+------------------------------------------------------------------+
#define MAX_ORDER_RETRIES 3 // Maximum number of order retry attempts
#define RETRY_DELAY_MS 1000 // Delay between retries in milliseconds
#define MAX_SLIPPAGE_PIPS 10.0 // Maximum allowed slippage in pips
#define MIN_VOLUME 0.01 // Minimum trade volume
#define MAX_VOLUME 100.0 // Maximum trade volume
#define DEFAULT_TRADE_TIMEOUT_MS 5000 // Default trade operation timeout
#define MAX_TRADE_ATTEMPTS 3 // Maximum trade attempts
#define MIN_SECONDS_BETWEEN_TRADES 1 // Minimum seconds between trades
#define MAX_DAILY_TRADES 100 // Maximum trades per day
#define MAX_OPEN_TRADES 10 // Maximum open trades
#define MAX_SPREAD_PIPS 30.0 // Maximum allowed spread in pips
#define MAX_CONSECUTIVE_FAILURES 5 // Maximum consecutive failures before pausing
#define TRADE_RETRY_JITTER_MS 200 // Random jitter for retry delays
// Trade execution states
enum ENUM_TRADE_STATE
{
TRADE_STATE_IDLE,
TRADE_STATE_PENDING,
TRADE_STATE_EXECUTING,
TRADE_STATE_COMPLETED,
TRADE_STATE_FAILED
};
//+------------------------------------------------------------------+
//| Trade Executor Class |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Trade Execution Statistics |
//+------------------------------------------------------------------+
struct STradeStats
{
int totalTrades; // Total number of trades executed
int successfulTrades; // Number of successful trades
int failedTrades; // Number of failed trades
double totalProfit; // Total profit in account currency
double totalVolume; // Total volume traded
datetime lastTradeTime; // Time of last trade
// Initialize all members
void Init()
{
totalTrades = 0;
successfulTrades = 0;
failedTrades = 0;
totalProfit = 0.0;
totalVolume = 0.0;
lastTradeTime = 0;
}
};
//+------------------------------------------------------------------+
//| Trade Order Information |
//+------------------------------------------------------------------+
struct STradeOrder
{
ulong ticket; // Order ticket
string symbol; // Order symbol
double volume; // Order volume
double price; // Open price
double sl; // Stop loss
double tp; // Take profit
string comment; // Order comment
datetime time; // Order time
ENUM_ORDER_TYPE type; // Order type
ENUM_TRADE_STATE state; // Current order state
// Initialize order
void Init()
{
ticket = 0;
symbol = "";
volume = 0.0;
price = 0.0;
sl = 0.0;
tp = 0.0;
comment = "";
time = 0;
type = WRONG_VALUE;
state = TRADE_STATE_IDLE;
}
};
//+------------------------------------------------------------------+
//| Trade Executor Class |
//+------------------------------------------------------------------+
class CTradeExecutor
{
private:
// Default configuration values
static const int DEFAULT_MAGIC_NUMBER = 0; // Default magic number (0 = no filtering)
static const int DEFAULT_SLIPPAGE = 30; // Default slippage in points
static const int DEFAULT_MAX_RETRIES = 3; // Default max retry attempts
static const int DEFAULT_RETRY_DELAY_MS = 100; // Default delay between retries in ms
static const double DEFAULT_MAX_SPREAD = 20.0; // Default max spread in points
static const int DEFAULT_MAX_OPEN_TRADES = 10; // Default max open trades
static const int DEFAULT_MAX_DAILY_TRADES = 50; // Default max trades per day
// Error codes
static const int ERR_TRADE_DISABLED = 10001; // Trading is currently disabled
static const int ERR_TRADE_NOT_INITIALIZED = 10002; // Trade executor not initialized
static const int ERR_INVALID_TICKET = 10003; // Invalid order/position ticket
static const int ERR_ORDER_NOT_FOUND = 10004; // Order not found
static const int ERR_POSITION_NOT_FOUND = 10005; // Position not found
static const int ERR_TRADE_WRONG_SYMBOL = 10006; // Wrong symbol for operation
static const int ERR_TRADE_INVALID_VOLUME = 10007; // Invalid trade volume
static const int ERR_TRADE_INVALID_PRICE = 10008; // Invalid trade price
static const int ERR_TRADE_SPREAD_TOO_WIDE = 10009; // Spread is too wide
static const int ERR_TRADE_TOO_MANY_ORDERS = 10010; // Too many open orders
static const int ERR_TRADE_LIMIT_REACHED = 10011; // Trade limit reached
static const int ERR_INVALID_STOPS = 10012; // Invalid stop levels
// Core trading objects
CTrade m_trade; // Trade object for order execution
CRiskManager *m_riskManager; // Pointer to risk manager
CSymbolInfo *m_symbol; // Pointer to symbol info
CAccountInfo *m_account; // Pointer to account info
CPositionInfo m_position; // Position info object
COrderInfo m_order; // Order info object
// Configuration
int m_magicNumber; // Magic number for order identification
int m_slippage; // Maximum allowed slippage in points
int m_maxRetries; // Maximum number of retry attempts for failed operations
int m_retryDelay; // Delay between retry attempts in milliseconds
double m_maxSpread; // Maximum allowed spread in points
int m_maxOpenTrades; // Maximum number of open trades
int m_maxDailyTrades; // Maximum number of trades per day
// State tracking
bool m_isInitialized; // Flag indicating if the executor is initialized
bool m_isTradeAllowed; // Flag indicating if trading is allowed
int m_todayTrades; // Number of trades executed today
datetime m_lastTradeTime; // Time of the last trade
int m_consecutiveFailures; // Number of consecutive failures
// Thread synchronization
int m_mutexHandle; // Handle for mutex synchronization
// Error handling
struct ErrorInfo
{
int lastError; // Last error code
string lastErrorMsg; // Last error message
datetime lastErrorTime; // Time of the last error
int consecutiveErrors; // Number of consecutive errors
int totalErrors; // Total number of errors
} m_error;
// Error recovery actions
enum ENUM_ERROR_ACTION
{
ERROR_ACTION_NONE = 0, // No specific action needed
ERROR_ACTION_RETRY, // Operation can be retried
ERROR_ACTION_WAIT, // Wait before retrying
ERROR_ACTION_ABORT, // Abort the current operation
ERROR_ACTION_SHUTDOWN // Critical error, shutdown trading
};
// Thread synchronization methods
bool CreateMutex();
void DestroyMutex();
bool LockMutex(const int timeout_ms = 1000);
void UnlockMutex();
// Trade execution helper methods
bool ExecuteTrade(ENUM_ORDER_TYPE orderType, double lots, double price, double sl, double tp,
datetime expiration = 0, string comment = "", double stopLimit = 0.0);
bool ExecuteOrderModify(ulong ticket, double price, double sl, double tp,
datetime expiration = 0, string comment = "");
bool ExecuteOrderDelete(ulong ticket, string comment = "");
bool ExecutePositionModify(ulong ticket, double sl, double tp, string comment = "");
// Error handling methods
void SetError(const int errorCode, const string message, const bool isRecoverable = true);
bool CanRetryAfterError(const int retryCount);
ENUM_ERROR_ACTION GetErrorAction(const int errorCode) const;
bool HandleError(const int errorCode, const string context = "");
void ResetErrorState();
string GetErrorDescription(const int errorCode) const;
int retryDelay; // Delay between retries in milliseconds
double maxSlippagePips; // Maximum allowed slippage in pips
double maxSpread; // Maximum allowed spread in points
int maxOpenTrades; // Maximum number of open trades
int maxDailyTrades; // Maximum trades per day
} m_config;
// Symbol information
struct {
double minLot; // Minimum lot size
double maxLot; // Maximum lot size
double lotStep; // Lot step size
double tickSize; // Symbol tick size
double tickValue; // Symbol tick value
} m_symbolInfo;
// Trade tracking
struct {
int todayTrades; // Trades executed today
datetime lastTradeDay; // Last trade day
datetime lastTradeTime; // Time of last trade execution
} m_tracking;
// Error handling
struct {
int lastError; // Last error code
string lastErrorMsg; // Last error message
string lastErrorFunc; // Function where last error occurred
datetime lastErrorTime; // Time of last error
int consecutiveFailures; // Consecutive error count
int totalErrors; // Total error count
} m_error;
// Private methods
bool IsTradeTimeWindow(); // Check if current time is within allowed trading window
void UpdateTradeStats(bool success); // Update trade statistics
bool CheckLimits(); // Check trade limits
bool CheckExecutionConditions(); // Check if execution conditions are met
void UpdateErrorInfo(); // Update error information
// Error handling methods
void SetError(const int errorCode, const string message, const string funcName = __FUNCTION__);
bool IsRecoverableError(const int errorCode) const;
bool ShouldRetryAfterError(const int errorCode) const;
void HandleRecoverableError(const int errorCode, const string &context);
void ResetErrorState();
string GetErrorDescription(const int errorCode) const;
public:
// Constructor/Destructor (Rule of Five)
CTradeExecutor(void);
~CTradeExecutor(void);
// Disable copy constructor and copy assignment
CTradeExecutor(const CTradeExecutor&) = delete;
CTradeExecutor& operator=(const CTradeExecutor&) = delete;
// Enable move constructor and move assignment
CTradeExecutor(CTradeExecutor&& other) noexcept;
CTradeExecutor& operator=(CTradeExecutor&& other) noexcept;
// Initialization and configuration
bool Initialize(CSymbolInfo *symbol, CAccountInfo *account,
CRiskManager *riskManager, ulong magicNumber);
void Deinitialize();
bool IsInitialized() const { return m_isInitialized; }
// Configuration methods
void SetMaxSlippage(double slippage) { m_slippage = slippage; }
void SetMaxRetries(int retries) { m_maxRetries = max(1, retries); }
void SetRetryDelay(int delayMs) { m_retryDelay = max(100, delayMs); }
void SetMaxSpread(double spreadPips) { m_maxSpread = spreadPips; }
void SetMaxOpenTrades(int maxTrades) { m_maxOpenTrades = max(0, maxTrades); }
void SetMaxDailyTrades(int maxTrades) { m_maxDailyTrades = max(0, maxTrades); }
// Status methods
bool IsTradeAllowed() const { return m_isTradeAllowed; }
void SetTradeAllowed(bool allowed) { m_isTradeAllowed = allowed; }
int GetLastError() const { return m_error.lastError; }
string GetLastErrorMsg() const { return m_error.lastErrorMsg; }
string GetLastErrorContext() const { return m_error.lastErrorFunc; }
datetime GetLastErrorTime() const { return m_error.lastErrorTime; }
int GetErrorCount() const { return m_error.totalErrors; }
int GetConsecutiveErrorCount() const { return m_error.consecutiveFailures; }
// Trade statistics
STradeStats GetTradeStats() const { return m_stats; }
int GetOpenPositionsCount() const;
int GetTodayTradesCount() const;
double GetCurrentSpread() const { return m_currentSpread; }
double GetAverageSpread() const { return m_avgSpread; }
// Trade execution methods
bool Buy(double lots, double price, double sl = 0.0, double tp = 0.0, string comment = "");
bool Sell(double lots, double price, double sl = 0.0, double tp = 0.0, string comment = "");
bool BuyLimit(double lots, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "");
bool SellLimit(double lots, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "");
bool BuyStop(double lots, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "");
bool SellStop(double lots, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "");
bool ModifyOrder(ulong ticket, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "");
bool DeleteOrder(ulong ticket, string comment = "");
bool DeleteAllOrders(string comment = "");
bool ModifyPosition(ulong ticket, double sl = 0.0, double tp = 0.0, string comment = "");
//+------------------------------------------------------------------+
//| Execute buy stop order with enhanced error handling |
//+------------------------------------------------------------------+
bool BuyStop(double lots, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "")
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
m_lastError = ERR_INVALID_TRADE_VOLUME;
m_lastErrorMsg = StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep);
return false;
}
if(!IsPriceValid(price)) {
m_lastError = ERR_INVALID_PRICE;
m_lastErrorMsg = StringFormat("Invalid price: %.5f", price);
return false;
}
// Check if we've reached the maximum number of pending orders
if(GetOpenOrdersCount() >= m_maxOpenTrades) {
m_lastError = ERR_TRADE_TOO_MANY_ORDERS;
m_lastErrorMsg = StringFormat("Maximum number of pending orders reached: %d", m_maxOpenTrades);
return false;
}
// Execute the order with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the trade
bool success = m_trade.BuyStop(lots, price, m_symbol.Name(), sl, tp,
ORDER_TIME_SPECIFIED, expiration, comment);
// If successful, return true
if(success) {
m_todayTrades++;
m_lastTradeTime = TimeCurrent();
m_consecutiveFailures = 0;
PrintFormat("Buy stop order placed: %s %.2f lots at %.5f, SL: %.5f, TP: %.5f, Ticket: %I64d",
m_symbol.Name(), lots, price, sl, tp, m_trade.ResultOrder());
return true;
}
// If we get here, the order failed
int errorCode = GetLastError();
m_lastError = errorCode;
m_lastErrorMsg = "Buy stop order failed: " + ErrorDescription(errorCode);
m_consecutiveFailures++;
// Log the failed attempt
PrintFormat("Buy stop order attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, m_lastErrorMsg);
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
PrintFormat("Failed to place buy stop order after %d attempts. Last error: %d - %s",
m_maxRetries, m_lastError, m_lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Execute sell stop order with enhanced error handling |
//+------------------------------------------------------------------+
bool SellStop(double lots, double price, double sl = 0.0, double tp = 0.0, datetime expiration = 0, string comment = "")
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
m_lastError = ERR_INVALID_TRADE_VOLUME;
m_lastErrorMsg = StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep);
return false;
}
if(!IsPriceValid(price)) {
m_lastError = ERR_INVALID_PRICE;
m_lastErrorMsg = StringFormat("Invalid price: %.5f", price);
return false;
}
// Check if we've reached the maximum number of pending orders
if(GetOpenOrdersCount() >= m_maxOpenTrades) {
m_lastError = ERR_TRADE_TOO_MANY_ORDERS;
m_lastErrorMsg = StringFormat("Maximum number of pending orders reached: %d", m_maxOpenTrades);
return false;
}
// Execute the order with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the trade
bool success = m_trade.SellStop(lots, price, m_symbol.Name(), sl, tp,
ORDER_TIME_SPECIFIED, expiration, comment);
// If successful, return true
if(success) {
m_todayTrades++;
m_lastTradeTime = TimeCurrent();
m_consecutiveFailures = 0;
PrintFormat("Sell stop order placed: %s %.2f lots at %.5f, SL: %.5f, TP: %.5f, Ticket: %I64d",
m_symbol.Name(), lots, price, sl, tp, m_trade.ResultOrder());
return true;
}
// If we get here, the order failed
int errorCode = GetLastError();
m_lastError = errorCode;
m_lastErrorMsg = "Sell stop order failed: " + ErrorDescription(errorCode);
m_consecutiveFailures++;
// Log the failed attempt
PrintFormat("Sell stop order attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, m_lastErrorMsg);
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
PrintFormat("Failed to place sell stop order after %d attempts. Last error: %d - %s",
m_maxRetries, m_lastError, m_lastErrorMsg);
return false;
}
// Position management
//+------------------------------------------------------------------+
//| Close specified position with enhanced error handling |
//+------------------------------------------------------------------+
bool ClosePosition(ulong ticket, double lots = 0, string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Validate ticket
if(ticket <= 0) {
m_lastError = ERR_INVALID_TICKET;
m_lastErrorMsg = "Invalid position ticket: " + IntegerToString(ticket);
return false;
}
// Select the position by ticket
if(!m_position.SelectByTicket(ticket)) {
m_lastError = ERR_TRADE_POSITION_NOT_FOUND;
m_lastErrorMsg = "Position not found with ticket: " + IntegerToString(ticket);
return false;
}
// Validate symbol matches
if(m_position.Symbol() != m_symbol.Name()) {
m_lastError = ERR_TRADE_WRONG_SYMBOL;
m_lastErrorMsg = "Position symbol " + m_position.Symbol() + " does not match " + m_symbol.Name();
return false;
}
// If lots is 0, close the entire position, otherwise validate partial close
double closeLots = (lots <= 0) ? m_position.Volume() : lots;
if(closeLots <= 0 || closeLots > m_position.Volume()) {
m_lastError = ERR_INVALID_TRADE_VOLUME;
m_lastErrorMsg = StringFormat("Invalid close volume: %.2f (position volume: %.2f)",
closeLots, m_position.Volume());
return false;
}
// Execute the close with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the close
bool success = m_trade.PositionClose(ticket, (ulong)m_slippage, comment);
// If successful, return true
if(success) {
PrintFormat("Position %I64u closed successfully: %s %.2f lots",
ticket, m_symbol.Name(), closeLots);
return true;
}
// If we get here, the close failed
int errorCode = GetLastError();
m_lastError = errorCode;
m_lastErrorMsg = "Position close failed: " + ErrorDescription(errorCode);
// Log the failed attempt
PrintFormat("Close position attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, m_lastErrorMsg);
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
PrintFormat("Failed to close position %I64u after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_lastError, m_lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Close all open positions |
//+------------------------------------------------------------------+
bool CloseAllPositions(string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
bool result = true;
int totalPositions = PositionsTotal();
// Create array to store position tickets
ulong tickets[];
ArrayResize(tickets, totalPositions);
int count = 0;
// First, collect all position tickets
for(int i = totalPositions - 1; i >= 0; i--) {
if(m_position.SelectByIndex(i)) {
if(m_position.Symbol() == m_symbol.Name() &&
(m_magicNumber == 0 || m_position.Magic() == m_magicNumber)) {
tickets[count++] = m_position.Ticket();
}
}
}
// Now close all collected positions
for(int i = 0; i < count; i++) {
if(!ClosePosition(tickets[i], 0, comment)) {
PrintFormat("Failed to close position %I64u: %s",
tickets[i], m_lastErrorMsg);
result = false;
}
}
return result;
}
//+------------------------------------------------------------------+
//| Close positions by type |
//+------------------------------------------------------------------+
bool ClosePositionsByType(ENUM_POSITION_TYPE type, string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
bool result = true;
int totalPositions = PositionsTotal();
// Create array to store position tickets
ulong tickets[];
ArrayResize(tickets, totalPositions);
int count = 0;
// First, collect all matching position tickets
for(int i = totalPositions - 1; i >= 0; i--) {
if(m_position.SelectByIndex(i)) {
if(m_position.Symbol() == m_symbol.Name() &&
m_position.PositionType() == type &&
(m_magicNumber == 0 || m_position.Magic() == m_magicNumber)) {
tickets[count++] = m_position.Ticket();
}
}
}
// Now close all collected positions
for(int i = 0; i < count; i++) {
if(!ClosePosition(tickets[i], 0, comment)) {
PrintFormat("Failed to close position %I64u: %s",
tickets[i], m_lastErrorMsg);
result = false;
}
}
return result;
}
//+------------------------------------------------------------------+
//| Modify position stop loss and take profit |
//+------------------------------------------------------------------+
bool ModifyPosition(ulong ticket, double sl, double tp, string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Validate ticket
if(ticket <= 0) {
m_lastError = ERR_INVALID_TICKET;
m_lastErrorMsg = "Invalid position ticket: " + IntegerToString(ticket);
return false;
}
// Select the position by ticket
if(!m_position.SelectByTicket(ticket)) {
m_lastError = ERR_TRADE_POSITION_NOT_FOUND;
m_lastErrorMsg = "Position not found with ticket: " + IntegerToString(ticket);
return false;
}
// Validate symbol matches
if(m_position.Symbol() != m_symbol.Name()) {
m_lastError = ERR_TRADE_WRONG_SYMBOL;
m_lastErrorMsg = "Position symbol " + m_position.Symbol() + " does not match " + m_symbol.Name();
return false;
}
// Get current position details
double openPrice = m_position.PriceOpen();
double currentSl = m_position.StopLoss();
double currentTp = m_position.TakeProfit();
// If SL/TP values haven't changed, no need to modify
if(MathAbs(sl - currentSl) < Point() && MathAbs(tp - currentTp) < Point()) {
PrintFormat("No changes needed for position %I64u - SL/TP already at requested levels", ticket);
return true;
}
// Execute the modification with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the modification
bool success = m_trade.PositionModify(ticket, sl, tp);
// If successful, return true
if(success) {
PrintFormat("Position %I64u modified successfully: SL=%.5f, TP=%.5f",
ticket, sl, tp);
return true;
}
// If we get here, the modification failed
int errorCode = GetLastError();
m_lastError = errorCode;
m_lastErrorMsg = "Position modify failed: " + ErrorDescription(errorCode);
// Log the failed attempt
PrintFormat("Modify position attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, m_lastErrorMsg);
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
PrintFormat("Failed to modify position %I64u after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_lastError, m_lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Modify all open positions' stop loss and take profit |
//+------------------------------------------------------------------+
bool ModifyAllPositions(double sl, double tp, string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
bool result = true;
int totalPositions = PositionsTotal();
// Create array to store position tickets
ulong tickets[];
ArrayResize(tickets, totalPositions);
int count = 0;
// First, collect all position tickets
for(int i = totalPositions - 1; i >= 0; i--) {
if(m_position.SelectByIndex(i)) {
if(m_position.Symbol() == m_symbol.Name() &&
(m_magicNumber == 0 || m_position.Magic() == m_magicNumber)) {
tickets[count++] = m_position.Ticket();
}
}
}
// Now modify all collected positions
for(int i = 0; i < count; i++) {
if(!ModifyPosition(tickets[i], sl, tp, comment)) {
PrintFormat("Failed to modify position %I64u: %s",
tickets[i], m_lastErrorMsg);
result = false;
}
}
return result;
}
// Order management
//+------------------------------------------------------------------+
//| Modify pending order with enhanced error handling |
//+------------------------------------------------------------------+
bool ModifyOrder(ulong ticket, double price, double sl = 0.0, double tp = 0.0,
datetime expiration = 0, string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Validate ticket
if(ticket <= 0) {
m_lastError = ERR_INVALID_TICKET;
m_lastErrorMsg = "Invalid order ticket: " + IntegerToString(ticket);
return false;
}
// Select the order by ticket
COrderInfo order;
if(!order.Select(ticket)) {
m_lastError = ERR_ORDER_NOT_FOUND;
m_lastErrorMsg = "Order not found with ticket: " + IntegerToString(ticket);
return false;
}
// Validate symbol matches
if(order.Symbol() != m_symbol.Name()) {
m_lastError = ERR_TRADE_WRONG_SYMBOL;
m_lastErrorMsg = "Order symbol " + order.Symbol() + " does not match " + m_symbol.Name();
return false;
}
// Get current order details
double currentPrice = order.PriceOpen();
double currentSl = order.StopLoss();
double currentTp = order.TakeProfit();
datetime currentExpiration = order.TimeExpiration();
// If no changes are needed, return success
if(MathAbs(price - currentPrice) < Point() &&
MathAbs(sl - currentSl) < Point() &&
MathAbs(tp - currentTp) < Point() &&
expiration == currentExpiration) {
PrintFormat("No changes needed for order %I64u - already at requested levels", ticket);
return true;
}
// Execute the modification with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the modification
bool success = m_trade.OrderModify(ticket, price, sl, tp, expiration, 0, 0);
// If successful, return true
if(success) {
PrintFormat("Order %I64u modified successfully: Price=%.5f, SL=%.5f, TP=%.5f, Expiration=%s",
ticket, price, sl, tp, TimeToString(expiration));
return true;
}
// If we get here, the modification failed
int errorCode = GetLastError();
m_lastError = errorCode;
m_lastErrorMsg = "Order modify failed: " + ErrorDescription(errorCode);
// Log the failed attempt
PrintFormat("Modify order attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, m_lastErrorMsg);
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
PrintFormat("Failed to modify order %I64u after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_lastError, m_lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Delete pending order with enhanced error handling |
//+------------------------------------------------------------------+
bool DeleteOrder(ulong ticket, string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Validate ticket
if(ticket <= 0) {
m_lastError = ERR_INVALID_TICKET;
m_lastErrorMsg = "Invalid order ticket: " + IntegerToString(ticket);
return false;
}
// Select the order by ticket
COrderInfo order;
if(!order.Select(ticket)) {
// If order is already deleted, consider it a success
if(GetLastError() == ERR_INVALID_TICKET) {
PrintFormat("Order %I64u already deleted or does not exist", ticket);
return true;
}
m_lastError = ERR_ORDER_NOT_FOUND;
m_lastErrorMsg = "Order not found with ticket: " + IntegerToString(ticket);
return false;
}
// Validate symbol matches
if(order.Symbol() != m_symbol.Name()) {
m_lastError = ERR_TRADE_WRONG_SYMBOL;
m_lastErrorMsg = "Order symbol " + order.Symbol() + " does not match " + m_symbol.Name();
return false;
}
// Execute the deletion with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the deletion
bool success = m_trade.OrderDelete(ticket);
// If successful, return true
if(success) {
PrintFormat("Order %I64u deleted successfully", ticket);
return true;
}
// If order is already deleted, consider it a success
if(GetLastError() == ERR_INVALID_TICKET) {
PrintFormat("Order %I64u already deleted", ticket);
return true;
}
// If we get here, the deletion failed
int errorCode = GetLastError();
m_lastError = errorCode;
m_lastErrorMsg = "Order delete failed: " + ErrorDescription(errorCode);
// Log the failed attempt
PrintFormat("Delete order attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, m_lastErrorMsg);
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
PrintFormat("Failed to delete order %I64u after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_lastError, m_lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Delete all pending orders |
//+------------------------------------------------------------------+
bool DeleteAllOrders(string comment = "")
{
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
bool result = true;
int totalOrders = OrdersTotal();
// Create array to store order tickets
ulong tickets[];
ArrayResize(tickets, totalOrders);
int count = 0;
// First, collect all order tickets
for(int i = totalOrders - 1; i >= 0; i--) {
if(m_order.SelectByIndex(i)) {
if(m_order.Symbol() == m_symbol.Name() &&
(m_magicNumber == 0 || m_order.Magic() == m_magicNumber)) {
tickets[count++] = m_order.Ticket();
}
}
}
// Now delete all collected orders
for(int i = 0; i < count; i++) {
if(!DeleteOrder(tickets[i], comment)) {
PrintFormat("Failed to delete order %I64u: %s",
tickets[i], m_lastErrorMsg);
result = false;
}
}
return result;
}
// Rule of Five: Copy constructor, move constructor, copy assignment, move assignment, destructor
CTradeExecutor(const CTradeExecutor&) = delete; // Disable copy constructor
CTradeExecutor(CTradeExecutor&&) = delete; // Disable move constructor
CTradeExecutor& operator=(const CTradeExecutor&) = delete; // Disable copy assignment
CTradeExecutor& operator=(CTradeExecutor&&) = delete; // Disable move assignment
~CTradeExecutor(); // Destructor already declared
};
//+------------------------------------------------------------------+
//| Trading Constants |
//+------------------------------------------------------------------+
// Default trading parameters
#define DEFAULT_SLIPPAGE_POINTS 10.0 // Default slippage in points
#define DEFAULT_MAGIC_NUMBER 0 // Default magic number (0 = not set)
#define DEFAULT_MAX_RETRIES 3 // Default number of retry attempts
#define DEFAULT_RETRY_DELAY_MS 1000 // 1 second delay between retries
#define DEFAULT_MAX_SLIPPAGE_PIPS 5.0 // 5 pips max slippage
#define DEFAULT_MAX_SPREAD_POINTS 30.0 // 30 points max spread
#define DEFAULT_MAX_OPEN_TRADES 10 // Max 10 open trades
#define DEFAULT_MAX_DAILY_TRADES 20 // Max 20 trades per day
#define DEFAULT_MIN_LOT 0.01 // Minimum lot size
#define DEFAULT_MAX_LOT 100.0 // Maximum lot size
#define DEFAULT_LOT_STEP 0.01 // Lot size step
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTradeExecutor::CTradeExecutor(void) :
m_riskManager(NULL),
m_symbol(NULL),
m_account(NULL),
m_mutexHandle(INVALID_HANDLE),
m_magicNumber(DEFAULT_MAGIC_NUMBER),
m_slippage(DEFAULT_SLIPPAGE_POINTS),
m_maxRetries(DEFAULT_MAX_RETRIES),
m_retryDelay(DEFAULT_RETRY_DELAY_MS),
m_maxSpread(DEFAULT_MAX_SPREAD_POINTS),
m_maxOpenTrades(DEFAULT_MAX_OPEN_TRADES),
m_maxDailyTrades(DEFAULT_MAX_DAILY_TRADES),
m_isInitialized(false),
m_isTradeAllowed(false),
m_todayTrades(0),
m_lastTradeTime(0),
m_consecutiveFailures(0)
{
// Initialize performance metrics with zeros
ZeroMemory(m_performance);
// Initialize trade configuration with enhanced defaults
m_config.slippage = DEFAULT_SLIPPAGE_POINTS;
m_config.magicNumber = DEFAULT_MAGIC_NUMBER;
m_config.maxRetries = DEFAULT_MAX_RETRIES;
m_config.retryDelay = DEFAULT_RETRY_DELAY_MS;
m_config.maxSlippagePips = DEFAULT_MAX_SLIPPAGE_PIPS;
m_config.maxSpread = DEFAULT_MAX_SPREAD_POINTS;
m_config.maxOpenTrades = DEFAULT_MAX_OPEN_TRADES;
m_config.maxDailyTrades = DEFAULT_MAX_DAILY_TRADES;
// Initialize symbol information (will be set in Initialize())
m_symbolInfo.minLot = DEFAULT_MIN_LOT;
m_symbolInfo.maxLot = DEFAULT_MAX_LOT;
m_symbolInfo.lotStep = DEFAULT_LOT_STEP;
m_symbolInfo.tickSize = 0.0;
m_symbolInfo.tickValue = 0.0;
// Initialize trade tracking
m_tracking.todayTrades = 0;
m_tracking.lastTradeDay = 0;
m_tracking.lastTradeTime = TimeCurrent();
// Initialize error handling
m_error.lastError = 0;
m_error.lastErrorMsg = "";
// Set up the trade object
m_trade.SetDeviationInPoints((ulong)m_config.slippage);
m_trade.SetTypeFilling(ORDER_FILLING_RETURN);
m_trade.SetExpertMagicNumber(m_config.magicNumber);
m_trade.SetAsyncMode(true); // Enable asynchronous execution
m_trade.LogLevel(LOG_LEVEL_ALL); // Log all trade operations
// Initialize statistics
m_stats.Init();
m_currentOrder.Init();
}
//+------------------------------------------------------------------+
//| Create a mutex for thread synchronization |
//+------------------------------------------------------------------+
bool CTradeExecutor::CreateMutex()
{
// Generate a unique name for the mutex based on the EA name and account number
string mutexName = StringFormat("MTX_%s_%d_%d", MQLInfoString(MQL_PROGRAM_NAME), AccountInfoInteger(ACCOUNT_LOGIN), TimeCurrent());
// Create a mutex with a unique name
m_mutexHandle = CreateMutex(mutexName, false);
if(m_mutexHandle == INVALID_HANDLE)
{
Print("Failed to create mutex: ", GetLastError());
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Destroy the mutex |
//+------------------------------------------------------------------+
void CTradeExecutor::DestroyMutex()
{
if(m_mutexHandle != INVALID_HANDLE)
{
ReleaseMutex(m_mutexHandle);
CloseHandle(m_mutexHandle);
m_mutexHandle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
//| Lock the mutex with a timeout |
//+------------------------------------------------------------------+
bool CTradeExecutor::LockMutex(const int timeout_ms)
{
if(m_mutexHandle == INVALID_HANDLE)
{
if(!CreateMutex())
return false;
}
// Try to acquire the mutex with the specified timeout
const int startTime = GetTickCount();
while(GetTickCount() - startTime < timeout_ms)
{
if(WaitForSingleObject(m_mutexHandle, 0) == WAIT_OBJECT_0)
return true;
Sleep(10); // Small delay to prevent CPU overuse
}
Print("Failed to acquire mutex: timeout");
return false;
}
//+------------------------------------------------------------------+
//| Unlock the mutex |
//+------------------------------------------------------------------+
void CTradeExecutor::UnlockMutex()
{
if(m_mutexHandle != INVALID_HANDLE)
{
ReleaseMutex(m_mutexHandle);
}
}
//+------------------------------------------------------------------+
//| Execute a trade with the specified parameters |
//+------------------------------------------------------------------+
bool CTradeExecutor::ExecuteTrade(ENUM_ORDER_TYPE orderType, double lots, double price,
double sl, double tp, datetime expiration,
string comment, double stopLimit)
{
// Validate input parameters
if(lots <= 0) {
SetError(ERR_INVALID_PARAMETER, "Invalid lot size: " + DoubleToString(lots), false);
return false;
}
if(price <= 0) {
SetError(ERR_INVALID_PRICE, "Invalid price: " + DoubleToString(price), false);
return false;
}
// Ensure we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled", false);
return false;
}
// Lock the mutex for thread safety
if(!LockMutex()) {
SetError(ERR_TRADE_CONTEXT_BUSY, "Failed to acquire mutex for trading", true);
return false;
}
// Execute the trade with retry logic
bool result = false;
int attempt = 0;
while(attempt < m_maxRetries) {
attempt++;
// Reset trade result
m_trade.ResultReset();
// Execute the appropriate trade type
bool success = false;
switch(orderType) {
case ORDER_TYPE_BUY:
success = m_trade.Buy(lots, m_symbol.Name(), price, sl, tp, comment);
break;
case ORDER_TYPE_SELL:
success = m_trade.Sell(lots, m_symbol.Name(), price, sl, tp, comment);
break;
case ORDER_TYPE_BUY_LIMIT:
success = m_trade.BuyLimit(lots, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
break;
case ORDER_TYPE_SELL_LIMIT:
success = m_trade.SellLimit(lots, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
break;
case ORDER_TYPE_BUY_STOP:
success = m_trade.BuyStop(lots, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
break;
case ORDER_TYPE_SELL_STOP:
success = m_trade.SellStop(lots, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
break;
case ORDER_TYPE_BUY_STOP_LIMIT:
success = m_trade.BuyStopLimit(lots, stopLimit, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
break;
case ORDER_TYPE_SELL_STOP_LIMIT:
success = m_trade.SellStopLimit(lots, stopLimit, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
break;
default:
SetError(ERR_INVALID_PARAMETER, "Unsupported order type: " + IntegerToString(orderType), false);
UnlockMutex();
return false;
}
// Check if the trade was executed successfully
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update trade statistics
m_todayTrades++;
m_lastTradeTime = TimeCurrent();
m_stats.successfulTrades++;
m_stats.totalTrades++;
ResetErrorState();
// Log successful execution
string orderTypeStr = OrderTypeToString(orderType);
PrintFormat("%s order executed: %s %.2f lots at %.5f, SL: %.5f, TP: %.5f, Ticket: %I64d",
orderTypeStr, m_symbol.Name(), lots, price, sl, tp, m_trade.ResultOrder());
result = true;
break;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt
SetError(errorCode,
StringFormat("%s order attempt %d/%d failed: %s",
OrderTypeToString(orderType),
attempt, m_maxRetries, errorMsg));
// Check if we should retry
if(!CanRetryAfterError(attempt) || !IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// Unlock the mutex
UnlockMutex();
return result;
}
//+------------------------------------------------------------------+
//| Modify an existing order with the specified parameters |
//+------------------------------------------------------------------+
bool CTradeExecutor::ExecuteOrderModify(ulong ticket, double price, double sl,
double tp, datetime expiration, string comment)
{
// Validate input parameters
if(ticket <= 0) {
SetError(ERR_INVALID_TICKET, "Invalid order ticket: " + IntegerToString(ticket), false);
return false;
}
// Ensure we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Lock the mutex for thread safety
if(!LockMutex()) {
SetError(ERR_TRADE_CONTEXT_BUSY, "Failed to acquire mutex for order modification", true);
return false;
}
// Execute the order modification with retry logic
bool result = false;
int attempt = 0;
while(attempt < m_maxRetries) {
attempt++;
// Reset trade result
m_trade.ResultReset();
// Execute the order modification
bool success = m_trade.OrderModify(ticket, price, sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
// Check if the modification was successful
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Log successful modification
PrintFormat("Order #%I64d modified: New price=%.5f, SL=%.5f, TP=%.5f, Expiration=%s",
ticket, price, sl, tp, TimeToString(expiration));
m_stats.successfulModifications++;
ResetErrorState();
result = true;
break;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt
SetError(errorCode,
StringFormat("OrderModify #%I64d attempt %d/%d failed: %s",
ticket, attempt, m_maxRetries, errorMsg));
// Check if we should retry
if(!CanRetryAfterError(attempt) || !IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// Unlock the mutex
UnlockMutex();
return result;
}
//+------------------------------------------------------------------+
//| Delete an existing order |
//+------------------------------------------------------------------+
bool CTradeExecutor::ExecuteOrderDelete(ulong ticket, string comment)
{
// Validate input parameters
if(ticket <= 0) {
SetError(ERR_INVALID_TICKET, "Invalid order ticket: " + IntegerToString(ticket), false);
return false;
}
// Ensure we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Lock the mutex for thread safety
if(!LockMutex()) {
SetError(ERR_TRADE_CONTEXT_BUSY, "Failed to acquire mutex for order deletion", true);
return false;
}
// Execute the order deletion with retry logic
bool result = false;
int attempt = 0;
while(attempt < m_maxRetries) {
attempt++;
// Reset trade result
m_trade.ResultReset();
// Execute the order deletion
bool success = m_trade.OrderDelete(ticket, comment);
// Check if the deletion was successful
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Log successful deletion
PrintFormat("Order #%I64d deleted: %s", ticket, comment);
m_stats.successfulDeletions++;
ResetErrorState();
result = true;
break;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt
SetError(errorCode,
StringFormat("OrderDelete #%I64d attempt %d/%d failed: %s",
ticket, attempt, m_maxRetries, errorMsg));
// Check if we should retry
if(!CanRetryAfterError(attempt) || !IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// Unlock the mutex
UnlockMutex();
return result;
}
//+------------------------------------------------------------------+
//| Modify an existing position's stop loss and take profit |
//+------------------------------------------------------------------+
bool CTradeExecutor::ExecutePositionModify(ulong ticket, double sl, double tp, string comment)
{
// Validate input parameters
if(ticket <= 0) {
SetError(ERR_INVALID_TICKET, "Invalid position ticket: " + IntegerToString(ticket), false);
return false;
}
// Ensure we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Lock the mutex for thread safety
if(!LockMutex()) {
SetError(ERR_TRADE_CONTEXT_BUSY, "Failed to acquire mutex for position modification", true);
return false;
}
// Execute the position modification with retry logic
bool result = false;
int attempt = 0;
while(attempt < m_maxRetries) {
attempt++;
// Reset trade result
m_trade.ResultReset();
// Execute the position modification
bool success = m_trade.PositionModify(ticket, sl, tp, comment);
// Check if the modification was successful
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Log successful modification
PrintFormat("Position #%I64d modified: New SL=%.5f, TP=%.5f", ticket, sl, tp);
m_stats.successfulModifications++;
ResetErrorState();
result = true;
break;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt
SetError(errorCode,
StringFormat("PositionModify #%I64d attempt %d/%d failed: %s",
ticket, attempt, m_maxRetries, errorMsg));
// Check if we should retry
if(!CanRetryAfterError(attempt) || !IsRecoverableError(errorCode)) {
break;
}
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// Unlock the mutex
UnlockMutex();
return result;
}
//+------------------------------------------------------------------+
//| Convert order type to string for logging |
//+------------------------------------------------------------------+
string CTradeExecutor::OrderTypeToString(ENUM_ORDER_TYPE orderType) const
{
switch(orderType) {
case ORDER_TYPE_BUY: return "Buy";
case ORDER_TYPE_SELL: return "Sell";
case ORDER_TYPE_BUY_LIMIT: return "Buy Limit";
case ORDER_TYPE_SELL_LIMIT: return "Sell Limit";
case ORDER_TYPE_BUY_STOP: return "Buy Stop";
case ORDER_TYPE_SELL_STOP: return "Sell Stop";
case ORDER_TYPE_BUY_STOP_LIMIT: return "Buy Stop Limit";
case ORDER_TYPE_SELL_STOP_LIMIT:return "Sell Stop Limit";
default: return "Unknown(" + IntegerToString(orderType) + ")";
}
}
//+------------------------------------------------------------------+
//| Set error information and handle error state |
//+------------------------------------------------------------------+
void CTradeExecutor::SetError(const int errorCode, const string message, const bool isRecoverable)
{
m_error.lastError = errorCode;
m_error.lastErrorMsg = message;
m_error.lastErrorTime = TimeCurrent();
m_error.consecutiveErrors++;
m_error.totalErrors++;
string logMsg = StringFormat("Error %d: %s (Recoverable: %s)",
errorCode, message, isRecoverable ? "Yes" : "No");
Print(logMsg);
if(!isRecoverable)
{
m_isTradeAllowed = false;
Print("Trading disabled due to critical error");
}
}
//+------------------------------------------------------------------+
//| Check if we can retry after an error |
//+------------------------------------------------------------------+
bool CTradeExecutor::CanRetryAfterError(const int retryCount)
{
return (retryCount < m_maxRetries && m_error.consecutiveErrors <= m_maxRetries * 2);
}
//+------------------------------------------------------------------+
//| Reset the error state |
//+------------------------------------------------------------------+
void CTradeExecutor::ResetErrorState()
{
m_error.lastError = 0;
m_error.lastErrorMsg = "";
m_error.consecutiveErrors = 0;
}
//+------------------------------------------------------------------+
//| Initialize trade executor |
//+------------------------------------------------------------------+
bool CTradeExecutor::Initialize(CSymbolInfo *symbol, CAccountInfo *account,
CRiskManager *riskManager, ulong magicNumber)
{
// Check for NULL pointers
if(symbol == NULL || account == NULL || riskManager == NULL)
{
m_error.lastError = ERR_INVALID_PARAMETER;
m_error.lastErrorMsg = "Invalid parameters in TradeExecutor initialization: " +
(symbol == NULL ? "symbol is NULL, " : "") +
(account == NULL ? "account is NULL, " : "") +
(riskManager == NULL ? "riskManager is NULL" : "");
Print(m_error.lastErrorMsg);
return false;
}
// Initialize pointers
m_symbol = symbol;
m_account = account;
m_riskManager = riskManager;
m_config.magicNumber = magicNumber;
// Initialize trade statistics and current order
m_stats.Init();
m_currentOrder.Init();
// Set up trade parameters
m_trade.SetExpertMagicNumber(m_config.magicNumber);
m_trade.SetDeviationInPoints((ulong)m_config.slippage);
m_trade.SetTypeFilling(ORDER_FILLING_RETURN);
// Initialize symbol info
if(!m_symbol.RefreshRates()) {
m_error.lastError = GetLastError();
m_error.lastErrorMsg = "Failed to refresh symbol rates: " + ErrorDescription(m_error.lastError);
Print(m_error.lastErrorMsg);
return false;
}
// Get and validate symbol parameters
m_symbolInfo.minLot = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MIN);
m_symbolInfo.maxLot = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MAX);
m_symbolInfo.lotStep = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_STEP);
m_symbolInfo.tickSize = m_symbol.TickSize();
m_symbolInfo.tickValue = m_symbol.TickValue();
// Check if symbol parameters are valid
if(m_symbolInfo.minLot <= 0 || m_symbolInfo.maxLot <= 0 ||
m_symbolInfo.lotStep <= 0 || m_symbolInfo.tickSize <= 0) {
m_error.lastError = ERR_INVALID_PARAMETER;
m_error.lastErrorMsg = StringFormat("Invalid symbol parameters: minLot=%.2f, maxLot=%.2f, lotStep=%.2f, tickSize=%.8f",
m_symbolInfo.minLot, m_symbolInfo.maxLot,
m_symbolInfo.lotStep, m_symbolInfo.tickSize);
Print(m_error.lastErrorMsg);
return false;
}
// Initialize trade tracking
m_tracking.lastTradeDay = TimeCurrent() - (TimeCurrent() % 86400); // Start of current day
m_tracking.todayTrades = 0; // Will be updated in UpdateTradeStats()
// Mark as initialized and ready for trading
m_isInitialized = true;
m_isTradeAllowed = true;
Print("TradeExecutor initialized successfully for ", m_symbol.Name(),
" (Magic: ", m_config.magicNumber, ")");
return true;
}
//+------------------------------------------------------------------+
//| Deinitialize trade executor |
//+------------------------------------------------------------------+
void CTradeExecutor::Deinitialize()
{
if(!m_isInitialized)
return;
// Close any open positions (optional)
// CloseAllPositions();
// Reset pointers
m_symbol = NULL;
m_account = NULL;
m_riskManager = NULL;
// Reset state
m_isInitialized = false;
m_isTradeAllowed = false;
m_lastError = 0;
m_lastErrorMsg = "";
Print("TradeExecutor deinitialized");
//+------------------------------------------------------------------+
//| Place buy limit order with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::BuyLimit(double lots, double price, double sl, double tp, datetime expiration, string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
SetError(ERR_TRADE_INVALID_VOLUME,
StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep));
return false;
}
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()));
return false;
}
// Check if limit price is valid (must be below current price for buy limit)
if(price >= m_symbol.Ask()) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Buy limit price (%.5f) must be below current ask (%.5f)",
price, m_symbol.Ask()));
return false;
}
// Check if spread is acceptable
if(!IsSpreadAcceptable()) {
SetError(ERR_TRADE_SPREAD_TOO_WIDE,
StringFormat("Spread too high: %.1f pips (max: %.1f)",
m_currentSpread / m_symbol.Point(),
m_maxSpread / m_symbol.Point()));
return false;
}
// Check if we've reached the maximum number of pending orders
if(GetOpenOrdersCount() >= m_maxOpenTrades) {
SetError(ERR_TRADE_TOO_MANY_ORDERS,
StringFormat("Maximum number of pending orders reached: %d", m_maxOpenTrades));
return false;
}
// Check daily trade limit
if(m_todayTrades >= m_maxDailyTrades) {
SetError(ERR_TRADE_LIMIT_REACHED,
StringFormat("Daily trade limit reached: %d", m_maxDailyTrades));
return false;
}
// Update current market data
m_symbol.RefreshRates();
m_currentSpread = m_symbol.Ask() - m_symbol.Bid();
// Log the trade attempt with detailed information
PrintFormat("Attempting to place buy limit order: %s %.2f lots at %.5f (Spread: %.1f pips), SL: %.5f, TP: %.5f, Expiration: %s",
m_symbol.Name(), lots, price, m_currentSpread / m_symbol.Point(), sl, tp,
(expiration > 0) ? TimeToString(expiration) : "No expiration");
// Execute the order with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset trade result
m_trade.ResultReset();
// Execute the limit order
bool success = m_trade.BuyLimit(lots, price, m_symbol.Name(), sl, tp,
(expiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
expiration, comment);
// Check if the order was placed successfully
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update trade statistics
m_todayTrades++;
m_lastTradeTime = TimeCurrent();
m_stats.successfulTrades++;
m_stats.totalTrades++;
ResetErrorState();
// Log successful execution with ticket information
PrintFormat("Buy limit order placed: %s %.2f lots at %.5f, SL: %.5f, TP: %.5f, Ticket: %I64d",
m_symbol.Name(), lots, price, sl, tp, m_trade.ResultOrder());
return true;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt with detailed error information
SetError(errorCode,
StringFormat("Buy limit order attempt %d/%d failed: %s",
attempt, m_maxRetries, errorMsg));
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
PrintFormat("Non-recoverable error detected. Aborting buy limit order.");
break;
}
// If we should retry, handle the recoverable error
if(attempt < m_maxRetries && ShouldRetryAfterError(errorCode)) {
HandleRecoverableError(errorCode, "Buy limit order");
continue;
}
// If we get here, either we shouldn't retry or we've exceeded max retries
break;
}
// If we get here, all retry attempts failed
m_stats.failedTrades++;
m_stats.totalTrades++;
// Log the final failure with all available context
PrintFormat("Failed to place buy limit order after %d attempts. Last error: %d - %s",
m_maxRetries, m_error.lastError, m_error.lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Place buy stop order with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::BuyStop(double lots, double price, double sl, double tp, datetime expiration, string comment)
{
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
SetError(ERR_TRADE_INVALID_VOLUME,
StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep),
false);
return false;
}
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()),
false);
return false;
}
// Check if stop price is valid (must be above current price for buy stop)
if(price <= m_symbol.Ask()) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Buy stop price (%.5f) must be above current ask (%.5f)",
price, m_symbol.Ask()),
false);
return false;
}
// Check if spread is acceptable
if(!IsSpreadAcceptable()) {
SetError(ERR_TRADE_SPREAD_TOO_WIDE,
StringFormat("Spread too high: %.1f pips (max: %.1f)",
m_currentSpread / m_symbol.Point(),
m_maxSpread / m_symbol.Point()),
true);
return false;
}
// Check if we've reached the maximum number of pending orders
if(GetOpenOrdersCount() >= m_maxOpenTrades) {
SetError(ERR_TRADE_TOO_MANY_ORDERS,
StringFormat("Maximum number of pending orders reached: %d", m_maxOpenTrades),
true);
return false;
}
// Check daily trade limit
if(m_todayTrades >= m_maxDailyTrades) {
SetError(ERR_TRADE_LIMIT_REACHED,
StringFormat("Daily trade limit reached: %d", m_maxDailyTrades),
false);
return false;
}
// Update current market data
m_symbol.RefreshRates();
m_currentSpread = m_symbol.Ask() - m_symbol.Bid();
// Calculate stop level in points
double stopLevel = m_symbol.StopsLevel() * m_symbol.Point();
// Validate stop loss and take profit levels if provided
if(sl > 0 && price - sl <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Stop loss too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
(price - sl) / m_symbol.Point()),
false);
return false;
}
if(tp > 0 && tp - price <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Take profit too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
(tp - price) / m_symbol.Point()),
false);
return false;
}
// Log the trade attempt with detailed information
PrintFormat("Attempting to place buy stop order: %s %.2f lots at %.5f (Spread: %.1f pips), SL: %.5f, TP: %.5f, Expiration: %s",
m_symbol.Name(), lots, price, m_currentSpread / m_symbol.Point(), sl, tp,
(expiration > 0) ? TimeToString(expiration) : "No expiration");
// Execute the trade using the helper method
return ExecuteTrade(ORDER_TYPE_BUY_STOP, lots, price, sl, tp, expiration, comment);
}
//+------------------------------------------------------------------+
//| Modify an existing order with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::ModifyOrder(ulong ticket, double price, double sl, double tp, datetime expiration, string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Validate ticket
if(ticket <= 0) {
SetError(ERR_INVALID_TICKET, "Invalid order ticket");
return false;
}
// Try to select the order by ticket
if(!m_order.Select(ticket)) {
SetError(ERR_ORDER_NOT_FOUND, "Order not found with ticket: " + IntegerToString(ticket));
return false;
}
// Validate the order belongs to this symbol
if(m_order.Symbol() != m_symbol.Name()) {
SetError(ERR_TRADE_WRONG_SYMBOL,
"Order symbol " + m_order.Symbol() + " does not match " + m_symbol.Name());
return false;
}
// Get current order type
ENUM_ORDER_TYPE orderType = (ENUM_ORDER_TYPE)m_order.OrderType();
// For limit/stop orders, validate the new price
if(orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_SELL_LIMIT ||
orderType == ORDER_TYPE_BUY_STOP || orderType == ORDER_TYPE_SELL_STOP) {
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()));
return false;
}
// For limit orders, validate price is on the right side of the market
if((orderType == ORDER_TYPE_BUY_LIMIT && price >= m_symbol.Ask()) ||
(orderType == ORDER_TYPE_SELL_LIMIT && price <= m_symbol.Bid())) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid %s price: %.5f (must be %s current price)",
EnumToString(orderType), price,
orderType == ORDER_TYPE_BUY_LIMIT ? "below" : "above"));
return false;
}
// For stop orders, validate price is on the right side of the market
if((orderType == ORDER_TYPE_BUY_STOP && price <= m_symbol.Ask()) ||
(orderType == ORDER_TYPE_SELL_STOP && price >= m_symbol.Bid())) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid %s price: %.5f (must be %s current price)",
EnumToString(orderType), price,
orderType == ORDER_TYPE_BUY_STOP ? "above" : "below"));
return false;
}
}
// Calculate stop level in points
double stopLevel = m_symbol.StopsLevel() * m_symbol.Point();
// Validate stop loss and take profit levels if provided
if(sl > 0) {
double slDistance = MathAbs(price - sl);
if(slDistance <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Stop loss too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
slDistance / m_symbol.Point()));
return false;
}
}
if(tp > 0) {
double tpDistance = MathAbs(tp - price);
if(tpDistance <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Take profit too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
tpDistance / m_symbol.Point()));
return false;
}
}
// Get current order values if not provided
double newPrice = (price > 0) ? price : m_order.PriceOpen();
double newSL = (sl > 0) ? sl : m_order.StopLoss();
double newTP = (tp > 0) ? tp : m_order.TakeProfit();
datetime newExpiration = (expiration > 0) ? expiration : m_order.Expiration();
// Log the modification attempt with detailed information
PrintFormat("Attempting to modify order #%I64d: %s %s at %.5f, New price: %.5f, New SL: %.5f, New TP: %.5f, New Expiration: %s",
ticket, m_symbol.Name(), EnumToString(orderType), m_order.PriceOpen(),
newPrice, newSL, newTP,
(newExpiration > 0) ? TimeToString(newExpiration) : "No expiration");
// Execute the modification with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset trade result
m_trade.ResultReset();
// Execute the order modification
bool success = m_trade.OrderModify(ticket, newPrice, newSL, newTP,
(newExpiration > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC,
newExpiration, comment);
// Check if the modification was successful
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update statistics
m_lastTradeTime = TimeCurrent();
ResetErrorState();
// Log successful modification
PrintFormat("Order #%I64d modified successfully: New price: %.5f, New SL: %.5f, New TP: %.5f",
ticket, newPrice, newSL, newTP);
return true;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt with detailed error information
SetError(errorCode,
StringFormat("Order modification attempt %d/%d failed: %s",
attempt, m_maxRetries, errorMsg));
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
PrintFormat("Non-recoverable error detected. Aborting order modification.");
break;
}
// If we should retry, handle the recoverable error
if(attempt < m_maxRetries && ShouldRetryAfterError(errorCode)) {
HandleRecoverableError(errorCode, "Order modification");
continue;
}
// If we get here, either we shouldn't retry or we've exceeded max retries
break;
}
// If we get here, all retry attempts failed
PrintFormat("Failed to modify order #%I64d after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_error.lastError, m_error.lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Delete an order with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::DeleteOrder(ulong ticket, string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Validate ticket
if(ticket <= 0) {
SetError(ERR_INVALID_TICKET, "Invalid order ticket");
return false;
}
// Try to select the order by ticket
if(!m_order.Select(ticket)) {
SetError(ERR_ORDER_NOT_FOUND, "Order not found with ticket: " + IntegerToString(ticket));
return false;
}
// Validate the order belongs to this symbol
if(m_order.Symbol() != m_symbol.Name()) {
SetError(ERR_TRADE_WRONG_SYMBOL,
"Order symbol " + m_order.Symbol() + " does not match " + m_symbol.Name());
return false;
}
// Log the deletion attempt
PrintFormat("Attempting to delete order #%I64d: %s %s at %.5f, Lots: %.2f, SL: %.5f, TP: %.5f",
ticket, m_symbol.Name(), EnumToString((ENUM_ORDER_TYPE)m_order.OrderType()),
m_order.PriceOpen(), m_order.VolumeInitial(),
m_order.StopLoss(), m_order.TakeProfit());
// Execute the deletion with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset trade result
m_trade.ResultReset();
// Execute the order deletion
bool success = m_trade.OrderDelete(ticket, comment);
// Check if the deletion was successful
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update statistics
m_lastTradeTime = TimeCurrent();
ResetErrorState();
// Log successful deletion
PrintFormat("Order #%I64d deleted successfully", ticket);
return true;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt with detailed error information
SetError(errorCode,
StringFormat("Order deletion attempt %d/%d failed: %s",
attempt, m_maxRetries, errorMsg));
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
PrintFormat("Non-recoverable error detected. Aborting order deletion.");
break;
}
// If we should retry, handle the recoverable error
if(attempt < m_maxRetries && ShouldRetryAfterError(errorCode)) {
HandleRecoverableError(errorCode, "Order deletion");
continue;
}
// If we get here, either we shouldn't retry or we've exceeded max retries
break;
}
// If we get here, all retry attempts failed
PrintFormat("Failed to delete order #%I64d after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_error.lastError, m_error.lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Delete all pending orders with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::DeleteAllOrders(string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Get total number of orders
int totalOrders = OrdersTotal();
if(totalOrders <= 0) {
// No orders to delete is not an error
return true;
}
Print("Attempting to delete all pending orders (", totalOrders, " orders found)");
bool allDeleted = true;
// We need to process orders from last to first because the order list changes as we delete
for(int i = totalOrders - 1; i >= 0; i--) {
// Select the order by position
if(!m_order.SelectByIndex(i)) {
PrintFormat("Failed to select order at index %d. Error: %d", i, GetLastError());
allDeleted = false;
continue;
}
// Check if the order belongs to our symbol
if(m_order.Symbol() != m_symbol.Name()) {
continue; // Skip orders for other symbols
}
// Check if the order has our magic number if magic number filtering is enabled
if(m_magicNumber != 0 && m_order.Magic() != m_magicNumber) {
continue; // Skip orders with different magic number
}
// Delete the order
if(!DeleteOrder(m_order.Ticket(), comment)) {
PrintFormat("Failed to delete order #%I64d: %s",
m_order.Ticket(), m_error.lastErrorMsg);
allDeleted = false;
}
// Small delay to avoid flooding the server
Sleep(100);
}
return allDeleted;
}
//+------------------------------------------------------------------+
//| Modify an open position with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::ModifyPosition(ulong ticket, double sl, double tp, string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Validate ticket
if(ticket <= 0) {
SetError(ERR_INVALID_TICKET, "Invalid position ticket");
return false;
}
// Try to select the position by ticket
if(!m_position.SelectByTicket(ticket)) {
SetError(ERR_POSITION_NOT_FOUND, "Position not found with ticket: " + IntegerToString(ticket));
return false;
}
// Validate the position belongs to this symbol
if(m_position.Symbol() != m_symbol.Name()) {
SetError(ERR_TRADE_WRONG_SYMBOL,
"Position symbol " + m_position.Symbol() + " does not match " + m_symbol.Name());
return false;
}
// Get current position details
double positionPrice = m_position.PriceOpen();
double positionLots = m_position.Volume();
ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)m_position.PositionType();
// If SL/TP not provided, use current values
double newSL = (sl > 0) ? sl : m_position.StopLoss();
double newTP = (tp > 0) ? tp : m_position.TakeProfit();
// Calculate stop level in points
double stopLevel = m_symbol.StopsLevel() * m_symbol.Point();
// Validate stop loss and take profit levels
if(newSL > 0) {
double slDistance = (positionType == POSITION_TYPE_BUY) ?
(positionPrice - newSL) : (newSL - positionPrice);
if(slDistance <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Stop loss too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
slDistance / m_symbol.Point()));
return false;
}
}
if(newTP > 0) {
double tpDistance = (positionType == POSITION_TYPE_BUY) ?
(newTP - positionPrice) : (positionPrice - newTP);
if(tpDistance <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Take profit too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
tpDistance / m_symbol.Point()));
return false;
}
}
// Log the modification attempt with detailed information
PrintFormat("Attempting to modify position #%I64d: %s %s %.2f lots at %.5f, New SL: %.5f, New TP: %.5f",
ticket, m_symbol.Name(), EnumToString(positionType),
positionLots, positionPrice, newSL, newTP);
// Execute the modification with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset trade result
m_trade.ResultReset();
// Execute the position modification
bool success = m_trade.PositionModify(ticket, newSL, newTP);
// Check if the modification was successful
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update statistics
m_lastTradeTime = TimeCurrent();
ResetErrorState();
// Log successful modification
PrintFormat("Position #%I64d modified successfully: New SL: %.5f, New TP: %.5f",
ticket, newSL, newTP);
return true;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt with detailed error information
SetError(errorCode,
StringFormat("Position modification attempt %d/%d failed: %s",
attempt, m_maxRetries, errorMsg));
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
PrintFormat("Non-recoverable error detected. Aborting position modification.");
break;
}
// If we should retry, handle the recoverable error
if(attempt < m_maxRetries && ShouldRetryAfterError(errorCode)) {
HandleRecoverableError(errorCode, "Position modification");
continue;
}
// If we get here, either we shouldn't retry or we've exceeded max retries
break;
}
// If we get here, all retry attempts failed
PrintFormat("Failed to modify position #%I64d after %d attempts. Last error: %d - %s",
ticket, m_maxRetries, m_error.lastError, m_error.lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Place sell stop order with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::SellStop(double lots, double price, double sl, double tp, datetime expiration, string comment)
{
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
SetError(ERR_TRADE_INVALID_VOLUME,
StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep),
false);
return false;
}
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()),
false);
return false;
}
// Check if stop price is valid (must be below current price for sell stop)
if(price >= m_symbol.Bid()) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Sell stop price (%.5f) must be below current bid (%.5f)",
price, m_symbol.Bid()),
false);
return false;
}
// Check if spread is acceptable
if(!IsSpreadAcceptable()) {
SetError(ERR_TRADE_SPREAD_TOO_WIDE,
StringFormat("Spread too high: %.1f pips (max: %.1f)",
m_currentSpread / m_symbol.Point(),
m_maxSpread / m_symbol.Point()),
true);
return false;
}
// Check if we've reached the maximum number of pending orders
if(GetOpenOrdersCount() >= m_maxOpenTrades) {
SetError(ERR_TRADE_TOO_MANY_ORDERS,
StringFormat("Maximum number of pending orders reached: %d", m_maxOpenTrades),
true);
return false;
}
// Check daily trade limit
if(m_todayTrades >= m_maxDailyTrades) {
SetError(ERR_TRADE_LIMIT_REACHED,
StringFormat("Daily trade limit reached: %d", m_maxDailyTrades),
false);
return false;
}
// Update current market data
m_symbol.RefreshRates();
m_currentSpread = m_symbol.Ask() - m_symbol.Bid();
// Calculate stop level in points
double stopLevel = m_symbol.StopsLevel() * m_symbol.Point();
// Validate stop loss and take profit levels if provided
if(sl > 0 && sl - price <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Stop loss too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
(sl - price) / m_symbol.Point()),
false);
return false;
}
if(tp > 0 && price - tp <= stopLevel) {
SetError(ERR_INVALID_STOPS,
StringFormat("Take profit too close to entry. Required distance: %.1f pips, Actual: %.1f pips",
stopLevel / m_symbol.Point(),
(price - tp) / m_symbol.Point()),
false);
return false;
}
// Log the trade attempt with detailed information
PrintFormat("Attempting to place sell stop order: %s %.2f lots at %.5f (Spread: %.1f pips), SL: %.5f, TP: %.5f, Expiration: %s",
m_symbol.Name(), lots, price, m_currentSpread / m_symbol.Point(), sl, tp,
(expiration > 0) ? TimeToString(expiration) : "No expiration");
// Execute the trade using the helper method
return ExecuteTrade(ORDER_TYPE_SELL_STOP, lots, price, sl, tp, expiration, comment);
}
//+------------------------------------------------------------------+
//| Place sell limit order with enhanced error handling |
//+------------------------------------------------------------------+
bool CTradeExecutor::SellLimit(double lots, double price, double sl, double tp, datetime expiration, string comment)
{
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized", false);
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
SetError(ERR_TRADE_INVALID_VOLUME,
StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep),
false);
return false;
}
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()),
false);
return false;
}
// Check if limit price is valid (must be above current price for sell limit)
if(price <= m_symbol.Bid()) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Sell limit price (%.5f) must be above current bid (%.5f)",
price, m_symbol.Bid()),
false);
return false;
}
// Check if spread is acceptable
if(!IsSpreadAcceptable()) {
SetError(ERR_TRADE_SPREAD_TOO_WIDE,
StringFormat("Spread too high: %.1f pips (max: %.1f)",
m_currentSpread / m_symbol.Point(),
m_maxSpread / m_symbol.Point()),
true);
return false;
}
// Check if we've reached the maximum number of pending orders
if(GetOpenOrdersCount() >= m_maxOpenTrades) {
SetError(ERR_TRADE_TOO_MANY_ORDERS,
StringFormat("Maximum number of pending orders reached: %d", m_maxOpenTrades),
true);
return false;
}
// Check daily trade limit
if(m_todayTrades >= m_maxDailyTrades) {
SetError(ERR_TRADE_LIMIT_REACHED,
StringFormat("Daily trade limit reached: %d", m_maxDailyTrades),
false);
return false;
}
// Update current market data
m_symbol.RefreshRates();
m_currentSpread = m_symbol.Ask() - m_symbol.Bid();
// Log the trade attempt with detailed information
PrintFormat("Attempting to place sell limit order: %s %.2f lots at %.5f (Spread: %.1f pips), SL: %.5f, TP: %.5f, Expiration: %s",
m_symbol.Name(), lots, price, m_currentSpread / m_symbol.Point(), sl, tp,
(expiration > 0) ? TimeToString(expiration) : "No expiration");
// Execute the trade using the helper method
return ExecuteTrade(ORDER_TYPE_SELL_LIMIT, lots, price, sl, tp, expiration, comment);
}
//+------------------------------------------------------------------+
//| Execute buy order with enhanced error handling and retry logic |
//+------------------------------------------------------------------+
bool CTradeExecutor::Buy(double lots, double price, double sl, double tp, string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
SetError(ERR_TRADE_INVALID_VOLUME,
StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep));
return false;
}
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()));
return false;
}
// Check if spread is acceptable
if(!IsSpreadAcceptable()) {
SetError(ERR_TRADE_SPREAD_TOO_WIDE,
StringFormat("Spread too high: %.1f pips (max: %.1f)",
m_currentSpread / m_symbol.Point(),
m_maxSpread / m_symbol.Point()));
return false;
}
// Check if we've reached the maximum number of open positions
if(GetOpenPositionsCount() >= m_maxOpenTrades) {
SetError(ERR_TRADE_TOO_MANY_ORDERS,
StringFormat("Maximum number of open positions reached: %d", m_maxOpenTrades));
return false;
}
// Check daily trade limit
if(m_todayTrades >= m_maxDailyTrades) {
SetError(ERR_TRADE_LIMIT_REACHED,
StringFormat("Daily trade limit reached: %d", m_maxDailyTrades));
return false;
}
// Update current market data
m_symbol.RefreshRates();
m_currentSpread = m_symbol.Ask() - m_symbol.Bid();
// Log the trade attempt with detailed information
PrintFormat("Attempting to buy %s %.2f lots at %.5f (Spread: %.1f pips), SL: %.5f, TP: %.5f",
m_symbol.Name(), lots, price, m_currentSpread / m_symbol.Point(), sl, tp);
// Execute the trade with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset trade result
m_trade.ResultReset();
// Execute the trade with current market price
bool success = m_trade.Buy(lots, m_symbol.Name(), price, sl, tp, comment);
// Check if the trade was executed successfully
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update trade statistics
m_todayTrades++;
m_lastTradeTime = TimeCurrent();
m_stats.successfulTrades++;
m_stats.totalTrades++;
ResetErrorState();
// Log successful execution with ticket information
PrintFormat("Buy order executed: %s %.2f lots at %.5f, SL: %.5f, TP: %.5f, Ticket: %I64d",
m_symbol.Name(), lots, price, sl, tp, m_trade.ResultOrder());
return true;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt with detailed error information
SetError(errorCode,
StringFormat("Buy order attempt %d/%d failed: %s",
attempt, m_maxRetries, errorMsg));
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
PrintFormat("Non-recoverable error detected. Aborting buy order.");
break;
}
// If we should retry, handle the recoverable error
if(attempt < m_maxRetries && ShouldRetryAfterError(errorCode)) {
HandleRecoverableError(errorCode, "Buy order");
continue;
}
// If we get here, either we shouldn't retry or we've exceeded max retries
break;
}
// If we get here, all retry attempts failed
m_stats.failedTrades++;
m_stats.totalTrades++;
// Log the final failure with all available context
PrintFormat("Failed to execute buy order after %d attempts. Last error: %d - %s",
m_maxRetries, m_error.lastError, m_error.lastErrorMsg);
return false;
}
//+------------------------------------------------------------------+
//| Execute sell order with enhanced error handling and retry logic |
//+------------------------------------------------------------------+
bool CTradeExecutor::Sell(double lots, double price, double sl, double tp, string comment)
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
SetError(ERR_TRADE_DISABLED, "Trading is currently disabled");
return false;
}
// Check if we're initialized
if(!m_isInitialized) {
SetError(ERR_TRADE_NOT_INITIALIZED, "Trade executor not initialized");
return false;
}
// Validate input parameters
if(!IsVolumeValid(lots)) {
SetError(ERR_TRADE_INVALID_VOLUME,
StringFormat("Invalid lot size: %.2f (min: %.2f, max: %.2f, step: %.2f)",
lots, m_minLot, m_maxLot, m_lotStep));
return false;
}
if(!IsPriceValid(price)) {
SetError(ERR_TRADE_INVALID_PRICE,
StringFormat("Invalid price: %.5f (Bid: %.5f, Ask: %.5f)",
price, m_symbol.Bid(), m_symbol.Ask()));
return false;
}
// Check if spread is acceptable
if(!IsSpreadAcceptable()) {
SetError(ERR_TRADE_SPREAD_TOO_WIDE,
StringFormat("Spread too high: %.1f pips (max: %.1f)",
m_currentSpread / m_symbol.Point(),
m_maxSpread / m_symbol.Point()));
return false;
}
// Check if we've reached the maximum number of open positions
if(GetOpenPositionsCount() >= m_maxOpenTrades) {
SetError(ERR_TRADE_TOO_MANY_ORDERS,
StringFormat("Maximum number of open positions reached: %d", m_maxOpenTrades));
return false;
}
// Check daily trade limit
if(m_todayTrades >= m_maxDailyTrades) {
SetError(ERR_TRADE_LIMIT_REACHED,
StringFormat("Daily trade limit reached: %d", m_maxDailyTrades));
return false;
}
// Update current market data
m_symbol.RefreshRates();
m_currentSpread = m_symbol.Ask() - m_symbol.Bid();
// Log the trade attempt with detailed information
PrintFormat("Attempting to sell %s %.2f lots at %.5f (Spread: %.1f pips), SL: %.5f, TP: %.5f",
m_symbol.Name(), lots, price, m_currentSpread / m_symbol.Point(), sl, tp);
// Execute the trade with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset trade result
m_trade.ResultReset();
// Execute the trade with current market price
bool success = m_trade.Sell(lots, m_symbol.Name(), price, sl, tp, comment);
// Check if the trade was executed successfully
if(success && m_trade.ResultRetcode() == TRADE_RETCODE_DONE) {
// Update trade statistics
m_todayTrades++;
m_lastTradeTime = TimeCurrent();
m_stats.successfulTrades++;
m_stats.totalTrades++;
ResetErrorState();
// Log successful execution with ticket information
PrintFormat("Sell order executed: %s %.2f lots at %.5f, SL: %.5f, TP: %.5f, Ticket: %I64d",
m_symbol.Name(), lots, price, sl, tp, m_trade.ResultOrder());
return true;
}
// Get the error code and message
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0) {
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Log the failed attempt with detailed error information
SetError(errorCode,
StringFormat("Sell order attempt %d/%d failed: %s",
attempt, m_maxRetries, errorMsg));
// If error is not recoverable, stop retrying
if(!IsRecoverableError(errorCode)) {
PrintFormat("Non-recoverable error detected. Aborting sell order.");
break;
}
// If we should retry, handle the recoverable error
if(attempt < m_maxRetries && ShouldRetryAfterError(errorCode)) {
HandleRecoverableError(errorCode, "Sell order");
continue;
}
// If we get here, either we shouldn't retry or we've exceeded max retries
break;
}
// If we get here, all retry attempts failed
m_stats.failedTrades++;
m_stats.totalTrades++;
// Log the final failure with all available context
PrintFormat("Failed to execute sell order after %d attempts. Last error: %d - %s",
m_maxRetries, m_error.lastError, m_error.lastErrorMsg);
return false;
m_lastError = ERR_TRADE_CONTEXT_BUSY;
m_lastErrorMsg = "Trade not allowed in current time window";
Print(m_lastErrorMsg);
return false;
}
// Check if we've reached the maximum number of consecutive failures
if(m_consecutiveFailures >= m_maxRetries) {
m_lastError = ERR_TOO_MANY_REQUESTS;
m_lastErrorMsg = "Maximum number of consecutive failures reached";
Print(m_lastErrorMsg, ". Please check your connection and account status.");
return false;
}
// Add rate limiting
static datetime lastTradeTime = 0;
datetime currentTime = TimeCurrent();
if(currentTime - lastTradeTime < 1) { // At least 1 second between trades
m_lastError = ERR_TRADE_TOO_MANY_REQUESTS;
m_lastErrorMsg = "Trade rate limit exceeded";
Print(m_lastErrorMsg, ". Please wait before placing another trade.");
return false;
}
// Log entry
Print(__FUNCTION__, " called with lots=", lots, ", price=", price,
", sl=", sl, ", tp=", tp, ", comment='", comment, "'");
// Validate parameters with detailed error messages
if(lots <= 0)
{
m_lastError = ERR_INVALID_PARAMETER;
m_lastErrorMsg = StringFormat("Invalid lot size in Sell(): %.2f (must be > 0)", lots);
Print(m_lastErrorMsg);
return false;
}
if(price <= 0)
{
m_lastError = ERR_INVALID_PRICE;
m_lastErrorMsg = StringFormat("Invalid price in Sell(): %.5f (must be > 0)", price);
Print(m_lastErrorMsg);
return false;
}
}
//+------------------------------------------------------------------+
//| Close specified position |
//+------------------------------------------------------------------+
bool CTradeExecutor::ClosePosition(ulong ticket, double lots = 0, string comment = "")
{
// Check if trading is allowed
if(!m_isTradeAllowed) {
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is currently disabled";
return false;
}
// Try to select the position by ticket
if(!m_position.SelectByTicket(ticket)) {
m_lastError = GetLastError();
m_lastErrorMsg = StringFormat("Failed to select position with ticket %I64u: %s",
ticket, ErrorDescription(m_lastError));
return false;
}
// Verify this position belongs to our symbol and magic number
if(m_position.Symbol() != m_symbol.Name() || m_position.Magic() != m_magicNumber) {
m_lastError = ERR_TRADE_WRONG_PROPERTY;
m_lastErrorMsg = StringFormat("Position %I64u does not belong to this EA", ticket);
return false;
}
// If lots is 0, close the entire position
if(lots <= 0) {
lots = m_position.Volume();
}
// Validate lot size
if(!IsVolumeValid(lots) || lots > m_position.Volume()) {
m_lastError = ERR_INVALID_TRADE_VOLUME;
m_lastErrorMsg = StringFormat("Invalid lot size: %.2f (position size: %.2f)",
lots, m_position.Volume());
return false;
}
// Log the close attempt
PrintFormat("Attempting to close position %I64u: %s %.2f/%s lots at %.5f (SL: %.5f, TP: %.5f)",
ticket,
m_position.PositionType() == POSITION_TYPE_BUY ? "BUY" : "SELL",
lots, DoubleToString(m_position.Volume(), 2),
m_position.PriceOpen(),
m_position.StopLoss(),
m_position.TakeProfit());
// Execute the close with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++) {
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Execute the close
bool success = m_trade.PositionClose(ticket, lots,
StringFormat("EAClose:%s", comment));
// If successful, wait for order completion and return
if(success && WaitForOrderCompletion(m_trade.ResultOrder(), 5000)) {
m_consecutiveFailures = 0; // Reset failure counter on success
m_lastTradeTime = TimeCurrent();
// Log successful execution
PrintFormat("Position %I64u closed: %s %.2f lots at %.5f",
ticket,
m_position.PositionType() == POSITION_TYPE_BUY ? "BUY" : "SELL",
lots, m_position.PriceCurrent());
return true;
}
// If we get here, the order failed
int errorCode = GetLastError();
// If position is already closed, return success
if(errorCode == ERR_INVALID_TICKET || errorCode == ERR_POSITION_NOT_FOUND)
{
PrintFormat("Position %I64u not found (already closed?)", ticket);
return true;
}
// If error is not recoverable, give up
if(!IsRecoverableError(errorCode))
{
HandleExecutionError("ClosePosition", errorCode);
return false;
}
// Log the failed attempt
PrintFormat("Close position attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, m_symbol.Name(),
errorCode, ErrorDescription(errorCode));
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
{
if(tickets[i] > 0) // Only process valid tickets
{
for(int attempt = 1; attempt <= m_maxRetries; attempt++)
{
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Close the position
bool success = ClosePosition(tickets[i], 0, comment);
// If successful, break out of retry loop
if(success)
{
break;
". Error: ", m_lastError, " - ", m_lastErrorMsg);
result = false;
}
}
}
}
return result;
}
//+------------------------------------------------------------------+
//| Modify position's stop loss and take profit with retry logic |
//+------------------------------------------------------------------+
bool CTradeExecutor::ModifyPosition(ulong ticket, double sl, double tp, string comment = "")
{
// Validate ticket
if(ticket <= 0)
{
m_lastError = ERR_INVALID_TICKET;
m_lastErrorMsg = "Invalid ticket number: " + IntegerToString(ticket);
return false;
}
// Check execution conditions
if(!CheckExecutionConditions())
{
Print("Modify position pre-check failed: ", m_lastErrorMsg);
return false;
}
// Select the position by ticket
if(!m_position.SelectByTicket(ticket))
{
m_lastError = GetLastError();
m_lastErrorMsg = "Failed to select position with ticket " + IntegerToString(ticket);
// If position is already closed, this isn't an error
if(m_lastError == ERR_TRADE_POSITION_NOT_FOUND || m_lastError == ERR_INVALID_TICKET)
{
PrintFormat("Position %d not found (already closed?)", ticket);
return true;
}
return false;
}
// Check if modification is needed
if(m_position.StopLoss() == sl && m_position.TakeProfit() == tp)
{
PrintFormat("No modification needed for position %d - SL/TP already at desired levels", ticket);
return true;
}
// Get position details
string symbol = m_position.Symbol();
// Execute with retry logic
for(int attempt = 1; attempt <= m_maxRetries; attempt++)
{
// Reset error state
m_lastError = 0;
m_lastErrorMsg = "";
// Modify the position
bool success = m_trade.PositionModify(ticket, sl, tp);
// If successful, wait for order completion and return
if(success && WaitForOrderCompletion(m_trade.ResultOrder(), 5000))
{
m_consecutiveFailures = 0; // Reset failure counter on success
m_lastTradeTime = TimeCurrent();
// Log successful execution
PrintFormat("Position %d modified: %s SL=%.5f, TP=%.5f",
ticket, symbol, sl, tp);
return true;
}
// If we get here, the order failed
int errorCode = GetLastError();
// If position is already closed, this isn't an error
if(errorCode == ERR_INVALID_TICKET || errorCode == ERR_POSITION_NOT_FOUND)
{
PrintFormat("Position %d not found (already closed?)", ticket);
return true;
}
// If error is not recoverable, give up
if(!IsRecoverableError(errorCode))
{
HandleExecutionError("ModifyPosition", errorCode);
return false;
}
// Log the failed attempt
PrintFormat("Modify position attempt %d/%d failed: %s (Error %d: %s)",
attempt, m_maxRetries, symbol,
errorCode, ErrorDescription(errorCode));
// Wait before retry (exponential backoff)
int delay = m_retryDelay * (1 << (attempt - 1));
Sleep(delay);
}
// If we get here, all retry attempts failed
HandleExecutionError("ModifyPosition", m_lastError);
return false;
}
//+------------------------------------------------------------------+
//| Check if trading limits are not exceeded |
//+------------------------------------------------------------------+
bool CTradeExecutor::CheckLimits()
{
if(m_symbol == NULL || m_account == NULL || m_riskManager == NULL)
{
m_lastError = ERR_NOT_INITIALIZED;
m_lastErrorMsg = "Trade executor not properly initialized";
return false;
}
// Check if we can trade
if(!m_account.TradeAllowed())
{
m_lastError = ERR_TRADE_DISABLED;
m_lastErrorMsg = "Trading is not allowed for the current account";
return false;
}
// Check if the symbol is selected and market is open
if(!m_symbol.IsTradeAllowed())
{
m_lastError = ERR_MARKET_CLOSED;
m_lastErrorMsg = "Trading is not allowed for " + m_symbol.Name();
return false;
}
// Check if we're not too close to market close (for daily charts)
if(m_symbol.Time() >= (TimeCurrent() - 60)) // Last tick was within the last minute
{
m_lastError = ERR_MARKET_CLOSED;
m_lastErrorMsg = "Market is closed or no new prices";
return false;
}
// Check for consecutive failures (circuit breaker)
if(m_consecutiveFailures >= m_maxRetries)
{
m_lastError = ERR_LIMIT_ORDERS;
m_lastErrorMsg = "Maximum consecutive failures reached. Trading temporarily suspended.";
return false;
}
// Check minimum time between trades
if(TimeCurrent() - m_lastTradeTime < 1) // At least 1 second between trades
{
m_lastError = ERR_TOO_FREQUENT_REQUESTS;
m_lastErrorMsg = "Too frequent trade requests. Please wait.";
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check if execution conditions are met |
//+------------------------------------------------------------------+
bool CTradeExecutor::CheckExecutionConditions()
{
// Check basic trading conditions
if(!CheckLimits())
return false;
// Check if symbol is selected and market data is fresh
if(!m_symbol.RefreshRates())
{
m_lastError = ERR_MARKET_CLOSED;
m_lastErrorMsg = "Failed to refresh market data for " + m_symbol.Name();
return false;
}
// Check if spread is acceptable
double spread = m_symbol.Ask() - m_symbol.Bid();
double spreadPips = spread / m_symbol.Point();
if(spreadPips > m_maxSlippagePips * 2) // Allow spread up to 2x max slippage
{
m_lastError = ERR_INVALID_SPREAD;
m_lastErrorMsg = StringFormat("Spread too wide: %.1f pips (max: %.1f)",
spreadPips, m_maxSlippagePips * 2);
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Handle execution error and update error info |
//+------------------------------------------------------------------+
void CTradeExecutor::HandleExecutionError(const string &func, const int errorCode)
{
m_lastError = errorCode;
m_lastErrorMsg = StringFormat("%s failed with error %d: %s",
func, errorCode, ErrorDescription(errorCode));
m_consecutiveFailures++;
// Log the error
Print(m_lastErrorMsg);
// If we have too many consecutive failures, log a warning
if(m_consecutiveFailures >= m_maxRetries)
{
string warning = StringFormat("WARNING: %d consecutive failures. Trading suspended.",
m_consecutiveFailures);
Print(warning);
// Could also send an alert or notification here
}
}
//+------------------------------------------------------------------+
//| Wait for order completion with timeout |
//+------------------------------------------------------------------+
bool CTradeExecutor::WaitForOrderCompletion(ulong ticket, const uint timeout_ms)
{
if(ticket <= 0) return false;
uint start = GetTickCount();
while(GetTickCount() - start < timeout_ms)
{
if(OrderSelect(ticket, SELECT_BY_TICKET) &&
(OrderCloseTime() > 0 || OrderType() == OP_BUY || OrderType() == OP_SELL))
{
return true; // Order is closed or is a position
}
// Small delay to prevent CPU overload
Sleep(10);
}
// If we get here, the order didn't complete in time
m_lastError = ERR_TRADE_TIMEOUT;
m_lastErrorMsg = StringFormat("Order %d did not complete within %d ms",
ticket, timeout_ms);
return false;
}
//+------------------------------------------------------------------+
//| Check if volume is valid |
//+------------------------------------------------------------------+
bool CTradeExecutor::IsVolumeValid(double volume) const
{
if(volume < m_minLot || volume > m_maxLot)
return false;
// Check if volume is a multiple of lot step
double steps = volume / m_lotStep;
return steps == MathFloor(steps);
}
//+------------------------------------------------------------------+
//| Check if price is valid |
//+------------------------------------------------------------------+
bool CTradeExecutor::IsPriceValid(double price) const
{
if(price <= 0)
return false;
// Check if price is a multiple of tick size
double ticks = price / m_tickSize;
return ticks == MathFloor(ticks);
}
//+------------------------------------------------------------------+
//| Check if current spread is acceptable |
//+------------------------------------------------------------------+
bool CTradeExecutor::IsSpreadAcceptable() const
{
if(!m_symbol.RefreshRates()) {
Print("Failed to refresh symbol rates");
return false;
}
double spread = m_symbol.Ask() - m_symbol.Bid();
double spreadPips = spread / m_symbol.Point();
return (spreadPips <= m_maxSpread);
}
//+------------------------------------------------------------------+
//| Get count of open positions |
//+------------------------------------------------------------------+
int CTradeExecutor::GetOpenPositionsCount() const
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(m_position.SelectByIndex(i)) {
if(m_position.Symbol() == m_symbol.Name() &&
m_position.Magic() == m_magicNumber) {
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Set error information |
//+------------------------------------------------------------------+
void CTradeExecutor::SetError(const int errorCode, const string message, const string funcName)
{
m_error.lastError = errorCode;
m_error.lastErrorMsg = message;
m_error.lastErrorFunc = funcName;
m_error.lastErrorTime = TimeCurrent();
m_error.totalErrors++;
// Update consecutive failures
if(errorCode != ERR_TRADE_SUCCESS)
{
m_error.consecutiveFailures++;
// If we've had too many consecutive failures, disable trading
if(m_error.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES)
{
m_isTradeAllowed = false;
string warning = StringFormat("WARNING: Too many consecutive failures (%d). Trading disabled.",
m_error.consecutiveFailures);
Print(warning);
}
}
else
{
// Reset consecutive failures on success
m_error.consecutiveFailures = 0;
}
// Log the error
PrintFormat("[%s] Error %d: %s", funcName, errorCode, message);
}
//+------------------------------------------------------------------+
//| Check if an error is recoverable |
//+------------------------------------------------------------------+
bool CTradeExecutor::IsRecoverableError(const int errorCode) const
{
// No error or success
if(errorCode == ERR_NO_ERROR || errorCode == ERR_TRADE_SUCCESS)
return true;
// Non-critical errors that might be recoverable
switch(errorCode)
{
case ERR_TRADE_CONTEXT_BUSY:
case ERR_SERVER_BUSY:
case ERR_OFF_QUOTES:
case ERR_BROKER_TOO_BUSY:
case ERR_TRADE_TIMEOUT:
case ERR_TOO_FREQUENT_REQUESTS:
case ERR_TRADE_TOO_MANY_ORDERS:
case ERR_TRADE_CONNECTION_LOST:
case ERR_TRADE_SERVER_BUSY:
case ERR_TRADE_CONTEXT_BUSY:
case ERR_TRADE_TOO_FREQUENT:
return true;
// Critical errors that are not recoverable
case ERR_TRADE_DISABLED:
case ERR_MARKET_CLOSED:
case ERR_NOT_ENOUGH_RIGHTS:
case ERR_ACCOUNT_DISABLED:
case ERR_ACCOUNT_MARGIN_CALL:
case ERR_ACCOUNT_WAS_SUSPENDED:
case ERR_ACCOUNT_INVALID:
case ERR_TRADE_HEDGE_PROHIBITED:
case ERR_TRADE_PROHIBITED_BY_FIFO:
case ERR_ACCOUNT_READONLY:
case ERR_TRADE_INVALID_ACCOUNT:
case ERR_TRADE_NOT_ALLOWED:
return false;
default:
// For unknown errors, assume they're not recoverable to be safe
return false;
}
}
//+------------------------------------------------------------------+
//| Check if we should retry after an error |
//+------------------------------------------------------------------+
bool CTradeExecutor::ShouldRetryAfterError(const int errorCode) const
{
// Only retry recoverable errors
if(!IsRecoverableError(errorCode))
return false;
// Don't retry if we've had too many consecutive failures
if(m_error.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES)
return false;
// Don't retry if trading is disabled
if(!m_isTradeAllowed)
return false;
return true;
}
//+------------------------------------------------------------------+
//| Handle a recoverable error |
//+------------------------------------------------------------------+
void CTradeExecutor::HandleRecoverableError(const int errorCode, const string &context)
{
// Log the error
string errorMsg = StringFormat("Recoverable error in %s: %s (%d)",
context, ErrorDescription(errorCode), errorCode);
SetError(errorCode, errorMsg);
// Add a small delay before retrying
int jitter = MathRand() % TRADE_RETRY_JITTER_MS; // Add some randomness to avoid thundering herd
Sleep(RETRY_DELAY_MS + jitter);
// Refresh market data before retrying
if(m_symbol != NULL)
m_symbol.RefreshRates();
}
//+------------------------------------------------------------------+
//| Reset error state |
//+------------------------------------------------------------------+
void CTradeExecutor::ResetErrorState()
{
m_error.lastError = ERR_TRADE_SUCCESS;
m_error.lastErrorMsg = "";
m_error.lastErrorFunc = "";
m_error.consecutiveFailures = 0;
// Don't reset totalErrors or lastErrorTime as they're for historical tracking
}
//+------------------------------------------------------------------+
//| Get error description for a given error code |
//+------------------------------------------------------------------+
string CTradeExecutor::GetErrorDescription(const int errorCode) const
{
switch(errorCode)
{
case ERR_TRADE_SUCCESS: return "Operation completed successfully";
case ERR_TRADE_INVALID_PARAMETER: return "Invalid parameter";
case ERR_TRADE_NOT_INITIALIZED: return "Trade executor not initialized";
case ERR_TRADE_DISABLED: return "Trading is disabled";
case ERR_TRADE_INVALID_SYMBOL: return "Invalid symbol";
case ERR_TRADE_INVALID_VOLUME: return "Invalid volume";
case ERR_TRADE_INVALID_PRICE: return "Invalid price";
case ERR_TRADE_INVALID_STOPS: return "Invalid stop loss/take profit";
case ERR_TRADE_INSUFFICIENT_FUNDS:return "Insufficient funds";
case ERR_TRADE_MARKET_CLOSED: return "Market is closed";
case ERR_TRADE_TOO_MANY_REQUESTS: return "Too many trade requests";
case ERR_TRADE_TIMEOUT: return "Trade operation timed out";
case ERR_TRADE_SPREAD_TOO_WIDE: return "Spread is too wide";
case ERR_TRADE_POSITION_NOT_FOUND:return "Position not found";
case ERR_TRADE_ORDER_NOT_FOUND: return "Order not found";
case ERR_TRADE_CONNECTION_LOST: return "Connection to trade server lost";
case ERR_TRADE_SERVER_BUSY: return "Trade server is busy";
case ERR_TRADE_CONTEXT_BUSY: return "Trade context is busy";
case ERR_TRADE_TOO_FREQUENT: return "Trading too frequently";
case ERR_TRADE_LIMIT_REACHED: return "Trade limit reached";
default: return "Unknown error";
}
}
//+------------------------------------------------------------------+
//| Update error information from the last trade operation |
//+------------------------------------------------------------------+
void CTradeExecutor::UpdateErrorInfo()
{
int errorCode = m_trade.ResultRetcode();
string errorMsg = m_trade.ResultRetcodeDescription();
// If no error from trade object, check for system error
if(errorCode == 0)
{
errorCode = GetLastError();
errorMsg = ErrorDescription(errorCode);
}
// Only update if there's an actual error
if(errorCode != 0 && errorCode != ERR_NO_ERROR)
{
SetError(errorCode, errorMsg, __FUNCTION__);
}
else
{
// Reset error state on success
ResetErrorState();
}
}
//+------------------------------------------------------------------+