3508 lines
131 KiB
MQL5
3508 lines
131 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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();
| |||
}
| |||
}
| |||
//+------------------------------------------------------------------+
|