405 lines
13 KiB
MQL5
405 lines
13 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| TradeExecutor_Impl.mqh |
| |||
//| Implementation of CTradeExecutor methods |
| |||
//+------------------------------------------------------------------+
| |||
#property strict
| |||
| |||
#include "TradeExecutor.mqh" // include header for class definition
| |||
| |||
// NOTE: This file contains implementations of CTradeExecutor methods
| |||
// Only method implementations should be in this file - no class definitions.
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Check if trading is allowed |
| |||
//+------------------------------------------------------------------+
| |||
bool CTradeExecutor::IsTradingAllowed()
| |||
{
| |||
if(!m_isInitialized)
| |||
{
| |||
m_lastError = "Trade executor not initialized";
| |||
return false;
| |||
}
| |||
if(!m_isTradingAllowed)
| |||
{
| |||
m_lastError = "Trading is disabled by configuration";
| |||
return false;
| |||
}
| |||
if(m_circuitBroken)
| |||
{
| |||
if(TimeCurrent() - m_circuitBreakTime < m_circuitBreakPeriod)
| |||
{
| |||
m_lastError = "Circuit breaker active";
| |||
return false;
| |||
}
| |||
else
| |||
{
| |||
m_circuitBroken = false;
| |||
m_consecutiveErrors = 0;
| |||
}
| |||
}
| |||
| |||
// Rate limit check
| |||
datetime currentTime = TimeCurrent();
| |||
if(TimeMinute(currentTime) != TimeMinute(m_currentMinute))
| |||
{
| |||
m_currentMinute = currentTime - (currentTime % 60);
| |||
m_ordersThisMinute = 0;
| |||
}
| |||
| |||
if(m_ordersThisMinute >= m_maxOrdersPerMinute)
| |||
{
| |||
m_lastError = "Rate limit exceeded";
| |||
return false;
| |||
}
| |||
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Constructor |
| |||
//+------------------------------------------------------------------+
| |||
CTradeExecutor::CTradeExecutor() :
| |||
m_totalTrades(0),
| |||
m_successfulTrades(0),
| |||
m_failedTrades(0),
| |||
m_totalSlippage(0.0),
| |||
m_avgExecutionTime(0.0),
| |||
m_isInitialized(false),
| |||
m_isTradingAllowed(false),
| |||
m_riskManager(NULL),
| |||
m_retryDelay(1000), // 1 second default retry delay
| |||
m_maxSlippage(10), // 10 points default slippage
| |||
m_useCircuitBreaker(true),
| |||
m_checkMarketConditions(true)
| |||
{
| |||
// Initialize trade object with default settings
| |||
m_trade.SetDeviationInPoints(m_maxSlippage);
| |||
m_trade.SetExpertMagicNumber(0);
| |||
m_trade.SetMarginMode();
| |||
m_trade.SetTypeFilling(ORDER_FILLING_FOK);
| |||
| |||
// Initialize arrays
| |||
m_tradeHistory = new CArrayObj();
| |||
m_pendingOrders = new CArrayObj();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Destructor |
| |||
//+------------------------------------------------------------------+
| |||
CTradeExecutor::~CTradeExecutor()
| |||
{
| |||
// Clean up trade history
| |||
if(CheckPointer(m_tradeHistory) == POINTER_DYNAMIC)
| |||
delete m_tradeHistory;
| |||
| |||
// Clean up pending orders
| |||
if(CheckPointer(m_pendingOrders) == POINTER_DYNAMIC)
| |||
delete m_pendingOrders;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Reset circuit breaker |
| |||
//+------------------------------------------------------------------+
| |||
void CTradeExecutor::ResetCircuitBreaker()
| |||
{
| |||
m_circuitBreakTime = 0;
| |||
m_consecutiveErrors = 0;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Clean up old trade records |
| |||
//+------------------------------------------------------------------+
| |||
void CTradeExecutor::CleanupOldRecords()
| |||
{
| |||
datetime cutoff = TimeCurrent() - 7 * 24 * 60 * 60; // 1 week
| |||
| |||
for(int i = m_tradeHistory.Total() - 1; i >= 0; i--)
| |||
{
| |||
CTradeRecord* record = m_tradeHistory.At(i);
| |||
if(record != NULL && record.time < cutoff)
| |||
{
| |||
m_tradeHistory.Delete(i);
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Update circuit breaker state |
| |||
//+------------------------------------------------------------------+
| |||
void CTradeExecutor::UpdateCircuitBreaker(bool errorOccurred)
| |||
{
| |||
if(errorOccurred)
| |||
{
| |||
m_consecutiveErrors++;
| |||
| |||
// If we've hit the maximum consecutive errors, activate circuit breaker
| |||
if(m_consecutiveErrors >= m_maxConsecutiveErrors)
| |||
{
| |||
m_circuitBreakTime = TimeCurrent() + 300; // 5 minutes cooldown
| |||
m_isTradingAllowed = false;
| |||
Print("Circuit breaker activated! Trading paused for 5 minutes.");
| |||
}
| |||
}
| |||
else
| |||
{
| |||
// Reset error counter on successful operation
| |||
m_consecutiveErrors = 0;
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Initialize |
| |||
//+------------------------------------------------------------------+
| |||
bool CTradeExecutor::Initialize(CRiskManager *riskManager)
| |||
{
| |||
if(m_isInitialized)
| |||
return true;
| |||
m_trade.SetExpertMagicNumber(0);
| |||
m_trade.SetDeviationInPoints(m_maxSlippage);
| |||
m_trade.SetAsyncMode(true);
| |||
m_riskManager = riskManager;
| |||
if(!m_security.Initialize())
| |||
{
| |||
m_lastError = "Failed to initialize security enhancements: "+m_security.GetLastError();
| |||
return false;
| |||
}
| |||
ResetCircuitBreaker();
| |||
m_totalTrades = m_successfulTrades = m_failedTrades = 0;
| |||
m_totalSlippage = m_avgExecutionTime = 0.0;
| |||
m_trade.SetDeviationInPoints(InpMaxSlippage);
| |||
CleanupOldRecords();
| |||
m_isInitialized = true;
| |||
m_isTradingAllowed = true;
| |||
Print("TradeExecutor: Initialized successfully");
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Deinitialize |
| |||
//+------------------------------------------------------------------+
| |||
void CTradeExecutor::Deinitialize(void)
| |||
{
| |||
if(!m_isInitialized)
| |||
return;
| |||
m_tradeHistory.Clear();
| |||
m_activeTrades.Clear();
| |||
m_hasPendingOrder = false;
| |||
ZeroMemory(m_pendingOrder);
| |||
m_circuitBroken = false;
| |||
m_consecutiveErrors = 0;
| |||
m_circuitBreakTime = 0;
| |||
s_ordersThisMinute = 0;
| |||
s_currentMinute = 0;
| |||
m_totalTrades = m_successfulTrades = m_failedTrades = 0;
| |||
m_totalSlippage = m_avgExecutionTime = 0.0;
| |||
m_lastError = "";
| |||
m_isInitialized = false;
| |||
m_isTradingAllowed = false;
| |||
Print("TradeExecutor: Deinitialized");
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Open a new market position with comprehensive security checks |
| |||
//+------------------------------------------------------------------+
| |||
bool CTradeExecutor::OpenPosition(string symbol, ENUM_ORDER_TYPE type, double volume,
| |||
double price, double sl, double tp, string comment,
| |||
int magic, datetime expiration, double maxSlippage)
| |||
{
| |||
// Check if trading is allowed
| |||
if(!m_isInitialized || !m_isTradingAllowed)
| |||
{
| |||
m_lastError = "Trading is not allowed. Check initialization.";
| |||
Print("TradeExecutor Error: ", m_lastError);
| |||
return false;
| |||
}
| |||
| |||
// Check circuit breaker
| |||
if(m_circuitBroken)
| |||
{
| |||
if(TimeCurrent() >= m_circuitBreakTime + m_circuitBreakTime)
| |||
{
| |||
Print("Circuit breaker cooldown period ended. Resuming normal operations.");
| |||
ResetCircuitBreaker();
| |||
}
| |||
else
| |||
{
| |||
int remaining = (int)(m_circuitBreakTime + m_circuitBreakTime - TimeCurrent());
| |||
m_lastError = "Circuit breaker active. Trading suspended for " + IntegerToString(remaining) + " seconds";
| |||
Print("TradeExecutor Error: ", m_lastError);
| |||
return false;
| |||
}
| |||
}
| |||
| |||
// Check rate limiting
| |||
if(!CheckRateLimit())
| |||
{
| |||
m_lastError = "Rate limit exceeded";
| |||
return false;
| |||
}
| |||
| |||
// Check market conditions if enabled
| |||
if(m_checkMarketConditions && !CheckMarketConditions(symbol))
| |||
{
| |||
m_lastError = "Market conditions check failed for " + symbol;
| |||
Print("TradeExecutor Error: ", m_lastError);
| |||
return false;
| |||
}
| |||
| |||
// Prepare order request with additional security parameters
| |||
SOrderRequest request = {};
| |||
request.symbol = symbol;
| |||
request.type = type;
| |||
request.volume = volume;
| |||
request.price = price;
| |||
request.sl = sl;
| |||
request.tp = tp;
| |||
request.comment = comment;
| |||
request.magic = (magic > 0) ? magic : m_trade.RequestMagic();
| |||
request.expiration = expiration;
| |||
request.attempts = 0;
| |||
request.reqId = IntegerToString(GetTickCount()) + "_" + IntegerToString(OrdersTotal());
| |||
request.maxSlippage = (maxSlippage > 0) ? maxSlippage : InpMaxSlippage;
| |||
request.requireVerification = InpVerifyOrderFills;
| |||
| |||
// Log the trade attempt
| |||
if(InpLogAllTrades)
| |||
{
| |||
string orderType = (type == ORDER_TYPE_BUY) ? "BUY" : "SELL";
| |||
PrintFormat("TradeExecutor: Attempting %s %s %.2f lots @ %s (SL: %s, TP: %s)",
| |||
orderType, symbol, volume, DoubleToString(price, _Digits),
| |||
DoubleToString(sl, _Digits), DoubleToString(tp, _Digits));
| |||
}
| |||
| |||
// Execute the order with retry logic
| |||
bool result = false;
| |||
MqlTradeRequest tradeRequest = {};
| |||
MqlTradeResult tradeResult = {};
| |||
| |||
// Prepare trade request
| |||
tradeRequest.action = TRADE_ACTION_DEAL;
| |||
tradeRequest.symbol = symbol;
| |||
tradeRequest.volume = volume;
| |||
tradeRequest.type = type;
| |||
tradeRequest.price = price;
| |||
tradeRequest.sl = sl;
| |||
tradeRequest.tp = tp;
| |||
tradeRequest.deviation = (ushort)request.maxSlippage;
| |||
tradeRequest.magic = request.magic;
| |||
tradeRequest.comment = comment;
| |||
tradeRequest.type_filling = ORDER_FILLING_FOK;
| |||
| |||
// Validate the request using the unified validator
| |||
if(!ValidateOrder(symbol, type, volume, price, sl, tp, request.magic, request.maxSlippage, comment))
| |||
{
| |||
m_lastError = "Trade request validation failed: " + m_lastError;
| |||
Print("TradeExecutor Error: ", m_lastError);
| |||
UpdateCircuitBreaker(true);
| |||
return false;
| |||
}
| |||
| |||
// Execute the trade with retry logic
| |||
ulong startTime = GetTickCount();
| |||
for(int attempt = 0; attempt <= InpMaxRetries; attempt++)
| |||
{
| |||
// Check for user break
| |||
if(IsStopped())
| |||
{
| |||
m_lastError = "Operation canceled by user";
| |||
Print("TradeExecutor: ", m_lastError);
| |||
return false;
| |||
}
| |||
| |||
// Add delay between retries (except first attempt)
| |||
if(attempt > 0)
| |||
{
| |||
int delay = MathMin(InpRetryDelay * (int)MathPow(2, attempt-1), 5000); // Exponential backoff, max 5s
| |||
PrintFormat("TradeExecutor: Retry attempt %d of %d after %d ms",
| |||
attempt, InpMaxRetries, delay);
| |||
Sleep(delay);
| |||
}
| |||
| |||
// Execute the trade
| |||
ZeroMemory(tradeResult);
| |||
bool success = OrderSend(tradeRequest, tradeResult);
| |||
| |||
// Log the execution
| |||
ulong duration = GetTickCount() - startTime;
| |||
LogTradeExecution(tradeRequest, tradeResult, duration);
| |||
| |||
// Check the result
| |||
if(success && tradeResult.retcode == TRADE_RETCODE_DONE)
| |||
{
| |||
// Verify the order fill if required
| |||
if(!request.requireVerification || VerifyOrderFill(tradeRequest, tradeResult))
| |||
{
| |||
// Create trade record
| |||
CTradeRecord *record = new CTradeRecord();
| |||
record.Set(tradeResult.order, symbol, type, volume, tradeResult.price,
| |||
sl, tp, tradeResult.deviation, comment);
| |||
AddToHistory(record);
| |||
| |||
// Update metrics
| |||
m_successfulTrades++;
| |||
m_totalSlippage += tradeResult.deviation;
| |||
m_avgExecutionTime = (m_avgExecutionTime * (m_successfulTrades - 1) + duration) / m_successfulTrades;
| |||
| |||
if(InpLogAllTrades)
| |||
{
| |||
PrintFormat("TradeExecutor: Order #%d executed successfully in %d ms. Slippage: %.1f pips",
| |||
tradeResult.order, duration, tradeResult.deviation);
| |||
}
| |||
| |||
result = true;
| |||
break;
| |||
}
| |||
else
| |||
{
| |||
// Verification failed
| |||
m_lastError = "Order fill verification failed";
| |||
Print("TradeExecutor Error: ", m_lastError);
| |||
UpdateCircuitBreaker(true);
| |||
return false;
| |||
}
| |||
}
| |||
else
| |||
{
| |||
// Log the error
| |||
string errorDesc = GetRetcodeDescription(tradeResult.retcode);
| |||
m_lastError = StringFormat("OrderSend failed: %s (code: %d)", errorDesc, tradeResult.retcode);
| |||
Print("TradeExecutor Error: ", m_lastError);
| |||
| |||
// Update error tracking
| |||
m_failedTrades++;
| |||
m_consecutiveErrors++;
| |||
| |||
// Check if we should continue retrying
| |||
if(attempt >= InpMaxRetries - 1)
| |||
{
| |||
UpdateCircuitBreaker(true);
| |||
return false;
| |||
}
| |||
}
| |||
}
| |||
| |||
// Update metrics
| |||
m_totalTrades++;
| |||
| |||
// Update last order time for rate limiting
| |||
m_lastOrderTime = TimeCurrent();
| |||
| |||
// Check if we need to trigger circuit breaker
| |||
if(!result)
| |||
{
| |||
UpdateCircuitBreaker(true);
| |||
}
| |||
| |||
return result;
| |||
}
| |||
| |||
// Static member definitions --------------------------------------------------
| |||
int CTradeExecutor::s_ordersThisMinute = 0;
| |||
datetime CTradeExecutor::s_currentMinute = 0;
| |||
| |||
//---------------------------------------------------------------------------
| |||
// END MOVED IMPLEMENTATIONS
| |||
//---------------------------------------------------------------------------
|