mirror of
https://github.com/dinethlive/dbasket-EA.git
synced 2026-08-25 16:48:17 +00:00
Initial commit: D-Basket EA v2.0 Pro
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DBasket_TradeWrapper.mqh |
|
||||
//| D-Basket Correlation Hedging EA |
|
||||
//| Trade Execution Abstraction |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "D-Basket EA"
|
||||
#property version "1.00"
|
||||
#property strict
|
||||
|
||||
#ifndef DBASKET_TRADEWRAPPER_MQH
|
||||
#define DBASKET_TRADEWRAPPER_MQH
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "DBasket_Defines.mqh"
|
||||
#include "DBasket_Structures.mqh"
|
||||
#include "DBasket_Logger.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trade Wrapper Class |
|
||||
//| Centralized trade execution with error handling and retry logic |
|
||||
//+------------------------------------------------------------------+
|
||||
class CTradeWrapper
|
||||
{
|
||||
private:
|
||||
CTrade m_trade; // MQL5 trade object
|
||||
int m_magicNumber; // EA magic number
|
||||
int m_slippagePoints; // Maximum slippage
|
||||
int m_maxRetries; // Maximum retry attempts
|
||||
bool m_isInitialized;
|
||||
|
||||
// Statistics
|
||||
int m_totalOrders;
|
||||
int m_successfulOrders;
|
||||
int m_failedOrders;
|
||||
int m_retriedOrders;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if error is retriable |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsRetriableError(uint retcode)
|
||||
{
|
||||
switch(retcode)
|
||||
{
|
||||
case TRADE_RETCODE_REQUOTE:
|
||||
case TRADE_RETCODE_PRICE_OFF:
|
||||
case TRADE_RETCODE_PRICE_CHANGED:
|
||||
case TRADE_RETCODE_TIMEOUT:
|
||||
case TRADE_RETCODE_CONNECTION:
|
||||
case TRADE_RETCODE_SERVER_DISABLES_AT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Wait between retries |
|
||||
//+------------------------------------------------------------------+
|
||||
void WaitForRetry(int attempt)
|
||||
{
|
||||
int waitMs = RETRY_DELAY_MS * (attempt + 1); // Exponential backoff
|
||||
Sleep(waitMs);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check pre-trade conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PreTradeCheck(string symbol, double lots, string &failReason)
|
||||
{
|
||||
// Check symbol tradability
|
||||
ENUM_SYMBOL_TRADE_MODE tradeMode = (ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode != SYMBOL_TRADE_MODE_FULL)
|
||||
{
|
||||
failReason = "Symbol " + symbol + " is not fully tradeable. Mode: " + EnumToString(tradeMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check volume constraints
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
if(lots < minLot)
|
||||
{
|
||||
failReason = "Lot size " + DoubleToString(lots, 2) + " below minimum " + DoubleToString(minLot, 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(lots > maxLot)
|
||||
{
|
||||
failReason = "Lot size " + DoubleToString(lots, 2) + " exceeds maximum " + DoubleToString(maxLot, 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check margin
|
||||
double marginRequired;
|
||||
double price = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
|
||||
if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, lots, price, marginRequired))
|
||||
{
|
||||
failReason = "Failed to calculate margin requirement";
|
||||
return false;
|
||||
}
|
||||
|
||||
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
||||
|
||||
if(freeMargin < marginRequired * 1.5) // 50% buffer
|
||||
{
|
||||
failReason = "Insufficient margin. Required: " + DoubleToString(marginRequired, 2) +
|
||||
", Available: " + DoubleToString(freeMargin, 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
//+------------------------------------------------------------------+
|
||||
//| Constructor |
|
||||
//+------------------------------------------------------------------+
|
||||
CTradeWrapper()
|
||||
{
|
||||
m_magicNumber = 100000;
|
||||
m_slippagePoints = DEFAULT_SLIPPAGE_POINTS;
|
||||
m_maxRetries = MAX_RETRY_ATTEMPTS;
|
||||
m_isInitialized = false;
|
||||
m_totalOrders = 0;
|
||||
m_successfulOrders = 0;
|
||||
m_failedOrders = 0;
|
||||
m_retriedOrders = 0;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Initialize trade wrapper |
|
||||
//+------------------------------------------------------------------+
|
||||
bool Initialize(int magicNumber, int slippagePoints = DEFAULT_SLIPPAGE_POINTS, int maxRetries = MAX_RETRY_ATTEMPTS)
|
||||
{
|
||||
m_magicNumber = magicNumber;
|
||||
m_slippagePoints = slippagePoints;
|
||||
m_maxRetries = maxRetries;
|
||||
|
||||
// Configure CTrade
|
||||
m_trade.SetExpertMagicNumber(m_magicNumber);
|
||||
m_trade.SetDeviationInPoints(m_slippagePoints);
|
||||
m_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
m_trade.SetAsyncMode(false); // Synchronous mode for reliable basket execution
|
||||
|
||||
m_isInitialized = true;
|
||||
|
||||
Logger.Info("Trade Wrapper initialized - Magic: " + IntegerToString(m_magicNumber) +
|
||||
", Slippage: " + IntegerToString(m_slippagePoints) + " points");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open a position with retry logic |
|
||||
//+------------------------------------------------------------------+
|
||||
bool OpenPosition(string symbol, ENUM_ORDER_TYPE orderType, double lots,
|
||||
string comment, ulong &ticket, string &errorMsg)
|
||||
{
|
||||
ticket = 0;
|
||||
errorMsg = "";
|
||||
m_totalOrders++;
|
||||
|
||||
// Pre-trade validation
|
||||
if(!PreTradeCheck(symbol, lots, errorMsg))
|
||||
{
|
||||
Logger.Error("Pre-trade check failed: " + errorMsg);
|
||||
m_failedOrders++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current price
|
||||
double price = (orderType == ORDER_TYPE_BUY) ?
|
||||
SymbolInfoDouble(symbol, SYMBOL_ASK) :
|
||||
SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
|
||||
if(price == 0)
|
||||
{
|
||||
errorMsg = "Invalid price for " + symbol;
|
||||
m_failedOrders++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute with retry logic
|
||||
for(int attempt = 0; attempt < m_maxRetries; attempt++)
|
||||
{
|
||||
// Refresh price on retry
|
||||
if(attempt > 0)
|
||||
{
|
||||
WaitForRetry(attempt);
|
||||
price = (orderType == ORDER_TYPE_BUY) ?
|
||||
SymbolInfoDouble(symbol, SYMBOL_ASK) :
|
||||
SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
m_retriedOrders++;
|
||||
Logger.Debug("Retrying order - Attempt " + IntegerToString(attempt + 1));
|
||||
}
|
||||
|
||||
// Attempt to open position
|
||||
bool result = m_trade.PositionOpen(symbol, orderType, lots, price, 0, 0, comment);
|
||||
|
||||
uint retcode = m_trade.ResultRetcode();
|
||||
|
||||
if(result && retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
ticket = m_trade.ResultOrder();
|
||||
m_successfulOrders++;
|
||||
|
||||
Logger.Info("Position opened - Symbol: " + symbol +
|
||||
", Type: " + (orderType == ORDER_TYPE_BUY ? "BUY" : "SELL") +
|
||||
", Lots: " + DoubleToString(lots, 2) +
|
||||
", Price: " + DoubleToString(m_trade.ResultPrice(), 5) +
|
||||
", Ticket: " + IntegerToString(ticket));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if error is retriable
|
||||
if(!IsRetriableError(retcode))
|
||||
{
|
||||
errorMsg = Logger.ErrorDescription((int)retcode);
|
||||
Logger.TradeError("OpenPosition", symbol, (int)retcode);
|
||||
break;
|
||||
}
|
||||
|
||||
Logger.Debug("Retriable error: " + Logger.ErrorDescription((int)retcode));
|
||||
}
|
||||
|
||||
m_failedOrders++;
|
||||
|
||||
if(errorMsg == "")
|
||||
errorMsg = "Max retries exceeded";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close a position by ticket |
|
||||
//+------------------------------------------------------------------+
|
||||
bool ClosePosition(ulong ticket, string &errorMsg)
|
||||
{
|
||||
errorMsg = "";
|
||||
|
||||
// Select position
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
{
|
||||
errorMsg = "Position not found: " + IntegerToString(ticket);
|
||||
return false;
|
||||
}
|
||||
|
||||
string symbol = PositionGetString(POSITION_SYMBOL);
|
||||
double lots = PositionGetDouble(POSITION_VOLUME);
|
||||
|
||||
// Execute with retry logic
|
||||
for(int attempt = 0; attempt < m_maxRetries; attempt++)
|
||||
{
|
||||
if(attempt > 0)
|
||||
{
|
||||
WaitForRetry(attempt);
|
||||
m_retriedOrders++;
|
||||
}
|
||||
|
||||
bool result = m_trade.PositionClose(ticket);
|
||||
uint retcode = m_trade.ResultRetcode();
|
||||
|
||||
if(result && retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
Logger.Info("Position closed - Ticket: " + IntegerToString(ticket) +
|
||||
", Symbol: " + symbol +
|
||||
", Lots: " + DoubleToString(lots, 2));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!IsRetriableError(retcode))
|
||||
{
|
||||
errorMsg = Logger.ErrorDescription((int)retcode);
|
||||
Logger.TradeError("ClosePosition", symbol, (int)retcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(errorMsg == "")
|
||||
errorMsg = "Max retries exceeded";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close all positions by magic number |
|
||||
//+------------------------------------------------------------------+
|
||||
int CloseAllPositions(string &errorMsg)
|
||||
{
|
||||
int closed = 0;
|
||||
int total = PositionsTotal();
|
||||
|
||||
// Close from end to avoid index shifting
|
||||
for(int i = total - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
|
||||
if(PositionGetInteger(POSITION_MAGIC) != m_magicNumber)
|
||||
continue;
|
||||
|
||||
string closeError;
|
||||
if(ClosePosition(ticket, closeError))
|
||||
closed++;
|
||||
else
|
||||
Logger.Error("Failed to close position " + IntegerToString(ticket) + ": " + closeError);
|
||||
}
|
||||
|
||||
if(closed < total)
|
||||
errorMsg = "Closed " + IntegerToString(closed) + " of " + IntegerToString(total) + " positions";
|
||||
|
||||
return closed;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get position P&L by ticket |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetPositionProfit(ulong ticket)
|
||||
{
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
return 0;
|
||||
|
||||
return PositionGetDouble(POSITION_PROFIT) +
|
||||
PositionGetDouble(POSITION_SWAP);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if position exists |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PositionExists(ulong ticket)
|
||||
{
|
||||
return PositionSelectByTicket(ticket);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Normalize lot size to broker requirements |
|
||||
//+------------------------------------------------------------------+
|
||||
double NormalizeLots(string symbol, double lots)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
lots = MathMax(minLot, lots);
|
||||
lots = MathMin(maxLot, lots);
|
||||
lots = MathFloor(lots / lotStep) * lotStep;
|
||||
|
||||
return NormalizeDouble(lots, 2);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get execution statistics |
|
||||
//+------------------------------------------------------------------+
|
||||
void GetStatistics(int &total, int &successful, int &failed, int &retried)
|
||||
{
|
||||
total = m_totalOrders;
|
||||
successful = m_successfulOrders;
|
||||
failed = m_failedOrders;
|
||||
retried = m_retriedOrders;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get magic number |
|
||||
//+------------------------------------------------------------------+
|
||||
int GetMagicNumber() const
|
||||
{
|
||||
return m_magicNumber;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Count positions by magic number |
|
||||
//+------------------------------------------------------------------+
|
||||
int CountPositions()
|
||||
{
|
||||
int count = 0;
|
||||
int total = PositionsTotal();
|
||||
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
|
||||
if(PositionGetInteger(POSITION_MAGIC) == m_magicNumber)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DBASKET_TRADEWRAPPER_MQH
|
||||
//+------------------------------------------------------------------+
|
||||
Reference in New Issue
Block a user