mirror of
https://github.com/dinethlive/dbasket-EA.git
synced 2026-08-16 20:28:13 +00:00
547 lines
19 KiB
Plaintext
547 lines
19 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| DBasketEA.mq5 |
|
|
//| D-Basket Correlation Hedging EA |
|
|
//| Three-Pair Correlation Strategy for AUDCAD/NZDCAD/AUDNZD |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "D-Basket EA"
|
|
#property version "1.00"
|
|
#property description "Three-pair correlation hedging EA exploiting temporary divergences between AUDCAD, NZDCAD, and AUDNZD"
|
|
#property strict
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Include Files |
|
|
//+------------------------------------------------------------------+
|
|
#include <Trade\Trade.mqh>
|
|
#include "..\Include\DBasket\DBasket_Defines.mqh"
|
|
#include "..\Include\DBasket\DBasket_Structures.mqh"
|
|
#include "..\Include\DBasket\DBasket_Logger.mqh"
|
|
#include "..\Include\DBasket\DBasket_CorrelationEngine.mqh"
|
|
#include "..\Include\DBasket\DBasket_SignalEngine.mqh"
|
|
#include "..\Include\DBasket\DBasket_TradeWrapper.mqh"
|
|
#include "..\Include\DBasket\DBasket_PositionManager.mqh"
|
|
#include "..\Include\DBasket\DBasket_RiskManager.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Input Parameters |
|
|
//+------------------------------------------------------------------+
|
|
// --- Symbol Configuration ---
|
|
input group "Symbol Settings"
|
|
input string InpSymbolSuffix = ""; // Symbol suffix (e.g., ".m", "_sb")
|
|
|
|
// --- Correlation Engine ---
|
|
input group "Correlation Engine"
|
|
input int InpLookbackPeriod = 250; // Lookback period (bars)
|
|
input int InpCacheUpdateSec = 30; // Cache update interval (seconds)
|
|
|
|
// --- Signal Generation ---
|
|
input group "Signal Generation"
|
|
input double InpZScoreEntry = 2.5; // Entry Z-Score threshold
|
|
input double InpZScoreExit = 0.5; // Exit Z-Score threshold
|
|
input double InpMinCorrelation = 0.75; // Minimum correlation threshold
|
|
input double InpMaxSpreadPips = 3.0; // Maximum spread (pips per symbol)
|
|
|
|
// --- Risk Management ---
|
|
input group "Risk Management"
|
|
input double InpFixedLotSize = 0.01; // Fixed lot size per leg
|
|
input double InpRiskPercent = 1.0; // Risk % per basket (if dynamic sizing)
|
|
input bool InpUseFixedLots = true; // Use fixed lot size
|
|
input double InpMaxDrawdownPct = 15.0; // Max drawdown % before halt
|
|
input double InpDailyLossLimit = 100.0; // Daily loss limit ($)
|
|
input double InpDailyLossPct = 5.0; // Daily loss limit (%)
|
|
input int InpMaxHoldingHours = 24; // Maximum basket holding hours
|
|
input double InpTakeProfitAmount = 10.0; // Take profit per basket ($)
|
|
input double InpStopLossAmount = 15.0; // Stop loss per basket ($)
|
|
|
|
// --- Trading Hours ---
|
|
input group "Trading Hours"
|
|
input int InpTradingStartHour = 0; // Trading start hour (broker time)
|
|
input int InpTradingStartMin = 0; // Trading start minute
|
|
input int InpTradingEndHour = 23; // Trading end hour (broker time)
|
|
input int InpTradingEndMin = 59; // Trading end minute
|
|
input bool InpAvoidRollover = true; // Avoid rollover period
|
|
|
|
// --- Technical Settings ---
|
|
input group "Technical Settings"
|
|
input int InpMagicNumber = 100000; // Magic number
|
|
input int InpSlippagePoints = 10; // Maximum slippage (points)
|
|
input ENUM_LOG_LEVEL InpLogLevel = LOG_LEVEL_INFO; // Log level
|
|
input bool InpLogToFile = false; // Log to file
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
// Configuration
|
|
EAConfig g_config;
|
|
|
|
// Module instances
|
|
CCorrelationEngine g_correlationEngine;
|
|
CSignalEngine g_signalEngine;
|
|
CTradeWrapper g_tradeWrapper;
|
|
CPositionManager g_positionManager;
|
|
CRiskManager g_riskManager;
|
|
|
|
// State
|
|
bool g_isInitialized = false;
|
|
bool g_tradingEnabled = true;
|
|
datetime g_lastTickProcessed = 0;
|
|
int g_tickCount = 0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Build configuration from inputs |
|
|
//+------------------------------------------------------------------+
|
|
void BuildConfiguration()
|
|
{
|
|
g_config.SetDefaults();
|
|
|
|
// Symbol configuration
|
|
g_config.symbols[SYMBOL_AUDCAD] = DEFAULT_SYMBOL_AUDCAD + InpSymbolSuffix;
|
|
g_config.symbols[SYMBOL_NZDCAD] = DEFAULT_SYMBOL_NZDCAD + InpSymbolSuffix;
|
|
g_config.symbols[SYMBOL_AUDNZD] = DEFAULT_SYMBOL_AUDNZD + InpSymbolSuffix;
|
|
g_config.timeframe = Period();
|
|
|
|
// Correlation engine
|
|
g_config.lookbackPeriod = InpLookbackPeriod;
|
|
g_config.updateIntervalSeconds = InpCacheUpdateSec;
|
|
|
|
// Signal generation
|
|
g_config.zScoreEntryThreshold = InpZScoreEntry;
|
|
g_config.zScoreExitThreshold = InpZScoreExit;
|
|
g_config.minCorrelation = InpMinCorrelation;
|
|
g_config.maxSpreadPips = InpMaxSpreadPips;
|
|
|
|
// Risk management
|
|
g_config.baseLotSize = InpFixedLotSize;
|
|
g_config.riskPercentPerBasket = InpRiskPercent;
|
|
g_config.sizingMode = InpUseFixedLots ? SIZING_FIXED : SIZING_RISK_BASED;
|
|
g_config.maxDrawdownPercent = InpMaxDrawdownPct;
|
|
g_config.maxDailyLossPercent = InpDailyLossPct;
|
|
g_config.maxDailyLossAmount = InpDailyLossLimit;
|
|
g_config.maxHoldingHours = InpMaxHoldingHours;
|
|
|
|
// Trading hours
|
|
g_config.tradingStartHour = InpTradingStartHour;
|
|
g_config.tradingStartMinute = InpTradingStartMin;
|
|
g_config.tradingEndHour = InpTradingEndHour;
|
|
g_config.tradingEndMinute = InpTradingEndMin;
|
|
g_config.avoidRollover = InpAvoidRollover;
|
|
|
|
// Technical
|
|
g_config.magicNumber = InpMagicNumber;
|
|
g_config.slippagePoints = InpSlippagePoints;
|
|
g_config.logLevel = InpLogLevel;
|
|
g_config.logToFile = InpLogToFile;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate input parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool ValidateInputs()
|
|
{
|
|
// Lookback period
|
|
if(InpLookbackPeriod < MIN_LOOKBACK_PERIOD || InpLookbackPeriod > MAX_LOOKBACK_PERIOD)
|
|
{
|
|
Logger.Error("Invalid lookback period. Must be " + IntegerToString(MIN_LOOKBACK_PERIOD) +
|
|
"-" + IntegerToString(MAX_LOOKBACK_PERIOD));
|
|
return false;
|
|
}
|
|
|
|
// Z-score thresholds
|
|
if(InpZScoreEntry <= 0 || InpZScoreEntry > 5.0)
|
|
{
|
|
Logger.Error("Invalid entry Z-score. Must be 0-5.0");
|
|
return false;
|
|
}
|
|
|
|
if(InpZScoreExit < 0 || InpZScoreExit >= InpZScoreEntry)
|
|
{
|
|
Logger.Error("Invalid exit Z-score. Must be 0 to less than entry threshold");
|
|
return false;
|
|
}
|
|
|
|
// Correlation
|
|
if(InpMinCorrelation < 0.5 || InpMinCorrelation > 0.95)
|
|
{
|
|
Logger.Error("Invalid minimum correlation. Must be 0.5-0.95");
|
|
return false;
|
|
}
|
|
|
|
// Risk parameters
|
|
if(InpRiskPercent < 0.1 || InpRiskPercent > 10.0)
|
|
{
|
|
Logger.Error("Invalid risk percent. Must be 0.1-10.0");
|
|
return false;
|
|
}
|
|
|
|
if(InpMaxDrawdownPct < 5.0 || InpMaxDrawdownPct > 50.0)
|
|
{
|
|
Logger.Error("Invalid max drawdown. Must be 5-50%");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate trading environment |
|
|
//+------------------------------------------------------------------+
|
|
bool ValidateEnvironment()
|
|
{
|
|
// Check account type (must be hedging)
|
|
ENUM_ACCOUNT_MARGIN_MODE marginMode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
|
|
if(marginMode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
|
|
{
|
|
Logger.Error("FATAL: Hedging account required. Current mode: " + EnumToString(marginMode));
|
|
Logger.Error("This EA requires a hedging account to open opposite positions.");
|
|
return false;
|
|
}
|
|
|
|
// Check if trading allowed
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
|
{
|
|
Logger.Error("Trading is not allowed in terminal settings");
|
|
return false;
|
|
}
|
|
|
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
|
|
{
|
|
Logger.Error("Automated trading is not allowed for this EA");
|
|
return false;
|
|
}
|
|
|
|
// Check connection
|
|
if(!TerminalInfoInteger(TERMINAL_CONNECTED))
|
|
{
|
|
Logger.Warning("Terminal is not connected to server");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate symbols |
|
|
//+------------------------------------------------------------------+
|
|
bool ValidateSymbols()
|
|
{
|
|
for(int i = 0; i < NUM_SYMBOLS; i++)
|
|
{
|
|
string symbol = g_config.symbols[i];
|
|
|
|
// Try to select symbol
|
|
if(!SymbolSelect(symbol, true))
|
|
{
|
|
Logger.Error("Symbol not available: " + symbol);
|
|
return false;
|
|
}
|
|
|
|
// Check trade mode
|
|
ENUM_SYMBOL_TRADE_MODE tradeMode = (ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);
|
|
if(tradeMode != SYMBOL_TRADE_MODE_FULL)
|
|
{
|
|
Logger.Error("Trading not fully allowed on " + symbol + ": " + EnumToString(tradeMode));
|
|
return false;
|
|
}
|
|
|
|
// Log symbol info
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
Logger.Info("Symbol " + symbol + " - MinLot: " + DoubleToString(minLot, 2) +
|
|
", Step: " + DoubleToString(lotStep, 2) +
|
|
", Spread: " + DoubleToString(spread * 10000, 1) + " pips");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Initialize logger first
|
|
Logger.Initialize(InpLogLevel, InpLogToFile);
|
|
|
|
// Log startup
|
|
Logger.LogInitSummary(EA_NAME, EA_VERSION,
|
|
AccountInfoDouble(ACCOUNT_BALANCE),
|
|
(int)AccountInfoInteger(ACCOUNT_LEVERAGE),
|
|
AccountInfoString(ACCOUNT_SERVER));
|
|
|
|
// Build configuration from inputs
|
|
BuildConfiguration();
|
|
|
|
// Validate inputs
|
|
if(!ValidateInputs())
|
|
{
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
}
|
|
|
|
// Validate environment
|
|
if(!ValidateEnvironment())
|
|
{
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Validate symbols
|
|
if(!ValidateSymbols())
|
|
{
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Initialize modules
|
|
Logger.Info("Initializing EA modules...");
|
|
|
|
// Trade wrapper
|
|
if(!g_tradeWrapper.Initialize(g_config.magicNumber, g_config.slippagePoints))
|
|
{
|
|
Logger.Error("Failed to initialize Trade Wrapper");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Correlation engine
|
|
if(!g_correlationEngine.Initialize(g_config.symbols, g_config.lookbackPeriod,
|
|
g_config.timeframe, g_config.updateIntervalSeconds))
|
|
{
|
|
Logger.Error("Failed to initialize Correlation Engine");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Signal engine
|
|
if(!g_signalEngine.Initialize(g_config))
|
|
{
|
|
Logger.Error("Failed to initialize Signal Engine");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Position manager
|
|
if(!g_positionManager.Initialize(g_config, &g_tradeWrapper))
|
|
{
|
|
Logger.Error("Failed to initialize Position Manager");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Set TP/SL
|
|
g_positionManager.SetTPSL(InpTakeProfitAmount, InpStopLossAmount);
|
|
|
|
// Risk manager
|
|
if(!g_riskManager.Initialize(g_config))
|
|
{
|
|
Logger.Error("Failed to initialize Risk Manager");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Recover any existing positions from previous session
|
|
g_positionManager.RecoverFromOpenPositions();
|
|
|
|
g_isInitialized = true;
|
|
g_tradingEnabled = true;
|
|
|
|
Logger.Info("EA initialization complete. Ready for trading.");
|
|
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
string reasonStr;
|
|
switch(reason)
|
|
{
|
|
case REASON_PROGRAM: reasonStr = "Program"; break;
|
|
case REASON_REMOVE: reasonStr = "Remove"; break;
|
|
case REASON_RECOMPILE: reasonStr = "Recompile"; break;
|
|
case REASON_CHARTCHANGE: reasonStr = "Chart changed"; break;
|
|
case REASON_CHARTCLOSE: reasonStr = "Chart closed"; break;
|
|
case REASON_PARAMETERS: reasonStr = "Parameters changed"; break;
|
|
case REASON_ACCOUNT: reasonStr = "Account changed"; break;
|
|
case REASON_TEMPLATE: reasonStr = "Template applied"; break;
|
|
case REASON_INITFAILED: reasonStr = "Init failed"; break;
|
|
case REASON_CLOSE: reasonStr = "Terminal closed"; break;
|
|
default: reasonStr = "Unknown (" + IntegerToString(reason) + ")";
|
|
}
|
|
|
|
Logger.Info("EA shutdown - Reason: " + reasonStr);
|
|
|
|
// Log final statistics
|
|
PerformanceMetrics metrics;
|
|
g_riskManager.GetMetrics(metrics);
|
|
|
|
Logger.Info("Final Statistics:");
|
|
Logger.Info(" Total Baskets: " + IntegerToString(metrics.totalBaskets));
|
|
Logger.Info(" Closed: " + IntegerToString(metrics.closedBaskets) +
|
|
" (Win: " + IntegerToString(metrics.winningBaskets) +
|
|
", Loss: " + IntegerToString(metrics.losingBaskets) + ")");
|
|
Logger.Info(" Win Rate: " + DoubleToString(metrics.winRate * 100, 1) + "%");
|
|
Logger.Info(" Realized P/L: $" + DoubleToString(metrics.realizedPL, 2));
|
|
Logger.Info(" Max Drawdown: " + DoubleToString(metrics.maxDrawdownPercent, 2) + "%");
|
|
|
|
// Clear chart
|
|
Comment("");
|
|
|
|
// Deinitialize logger
|
|
Logger.Deinitialize();
|
|
|
|
g_isInitialized = false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
if(!g_isInitialized)
|
|
return;
|
|
|
|
g_tickCount++;
|
|
|
|
// === Phase 1: Risk Management Check ===
|
|
string riskReason;
|
|
if(!g_riskManager.CheckRiskLimits(riskReason))
|
|
{
|
|
// Check for emergency exit
|
|
string emergencyReason;
|
|
if(g_riskManager.CheckEmergencyExit(emergencyReason))
|
|
{
|
|
Logger.Error("EMERGENCY EXIT: " + emergencyReason);
|
|
if(g_positionManager.HasOpenBasket())
|
|
{
|
|
g_positionManager.CloseBasket(EXIT_EMERGENCY);
|
|
}
|
|
}
|
|
|
|
g_tradingEnabled = false;
|
|
|
|
// Update display
|
|
if(g_tickCount % 100 == 0)
|
|
g_riskManager.DisplayMetricsOnChart();
|
|
|
|
return;
|
|
}
|
|
|
|
g_tradingEnabled = true;
|
|
|
|
// === Phase 2: Update Price Buffers ===
|
|
g_correlationEngine.UpdatePriceBuffers();
|
|
|
|
// === Phase 3: Update Correlation Cache ===
|
|
if(!g_correlationEngine.UpdateCorrelationCache())
|
|
{
|
|
// Not ready or data invalid - skip this tick
|
|
return;
|
|
}
|
|
|
|
// Get correlation data
|
|
CorrelationData corrData;
|
|
g_correlationEngine.GetCorrelationData(corrData);
|
|
|
|
// === Phase 4: Position Management ===
|
|
if(g_positionManager.HasOpenBasket())
|
|
{
|
|
// Update basket state
|
|
g_positionManager.UpdateBasketState();
|
|
|
|
// Check exit signals
|
|
BasketState basket;
|
|
g_positionManager.GetBasketState(basket);
|
|
|
|
ENUM_EXIT_REASON exitReason;
|
|
if(g_signalEngine.CheckExitSignal(corrData, basket,
|
|
g_positionManager.GetTakeProfitAmount(),
|
|
g_positionManager.GetStopLossAmount(),
|
|
g_positionManager.GetMaxHoldingHours(),
|
|
exitReason))
|
|
{
|
|
// Close basket
|
|
double pl = g_positionManager.GetBasketPL();
|
|
g_positionManager.CloseBasket(exitReason);
|
|
|
|
// Record result
|
|
g_riskManager.RecordBasketClose(pl, pl >= 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// === Phase 5: Signal Generation ===
|
|
string signalFailReason;
|
|
ENUM_BASKET_SIGNAL signal = g_signalEngine.CheckEntrySignal(corrData, false, signalFailReason);
|
|
|
|
if(signal != SIGNAL_NONE)
|
|
{
|
|
g_riskManager.RecordSignal(true);
|
|
|
|
// Attempt to open basket
|
|
if(g_positionManager.OpenBasket(signal, corrData.spreadZScore, corrData.corrAUDCAD_NZDCAD))
|
|
{
|
|
g_riskManager.RecordBasketOpen();
|
|
}
|
|
}
|
|
else if(signalFailReason != "")
|
|
{
|
|
// Signal was blocked by filter
|
|
g_riskManager.RecordSignal(false);
|
|
|
|
// Log filter reason periodically (every 1000 ticks)
|
|
if(g_tickCount % 1000 == 0)
|
|
{
|
|
Logger.Debug("Signal blocked: " + signalFailReason);
|
|
}
|
|
}
|
|
}
|
|
|
|
// === Phase 6: Display Update ===
|
|
if(g_tickCount % 50 == 0)
|
|
{
|
|
g_riskManager.DisplayMetricsOnChart();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tester function for custom optimization criterion |
|
|
//+------------------------------------------------------------------+
|
|
double OnTester()
|
|
{
|
|
// Get statistics
|
|
double profit = TesterStatistics(STAT_PROFIT);
|
|
double maxDD = TesterStatistics(STAT_EQUITY_DD);
|
|
double profitFactor = TesterStatistics(STAT_PROFIT_FACTOR);
|
|
int totalTrades = (int)TesterStatistics(STAT_TRADES);
|
|
int winTrades = (int)TesterStatistics(STAT_PROFIT_TRADES);
|
|
|
|
// Calculate win rate
|
|
double winRate = totalTrades > 0 ? (double)winTrades / totalTrades : 0;
|
|
|
|
// Custom optimization criterion
|
|
// Prioritize: profit/drawdown ratio, win rate > 65%, profit factor > 1.3
|
|
if(winRate < 0.65 || profitFactor < 1.3 || totalTrades < 20)
|
|
return 0; // Reject parameters that don't meet minimum criteria
|
|
|
|
// Risk-adjusted return
|
|
double riskAdjustedReturn = maxDD > 0 ? profit / maxDD : 0;
|
|
|
|
// Combine metrics
|
|
double score = riskAdjustedReturn * profitFactor * winRate;
|
|
|
|
return score;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Trade event handler |
|
|
//+------------------------------------------------------------------+
|
|
void OnTrade()
|
|
{
|
|
// Handle trade events if needed
|
|
// (Position state is already updated in OnTick via UpdateBasketState)
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Timer function (if using timer) |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
// Can be used for periodic tasks independent of ticks
|
|
}
|
|
//+------------------------------------------------------------------+
|