mirror of
https://github.com/dinethlive/dbasket-EA.git
synced 2026-08-19 13:48:16 +00:00
453 lines
16 KiB
Plaintext
453 lines
16 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| DBasket_RiskManager.mqh |
|
|
//| D-Basket Correlation Hedging EA |
|
|
//| Risk Management Module |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "D-Basket EA"
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#ifndef DBASKET_RISKMANAGER_MQH
|
|
#define DBASKET_RISKMANAGER_MQH
|
|
|
|
#include "DBasket_Defines.mqh"
|
|
#include "DBasket_Structures.mqh"
|
|
#include "DBasket_Logger.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Risk Manager Class |
|
|
//| Monitors and enforces risk limits with circuit breaker |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManager
|
|
{
|
|
private:
|
|
// Configuration
|
|
double m_maxDrawdownPercent;
|
|
double m_warningDrawdownPercent;
|
|
double m_maxDailyLossAmount;
|
|
double m_maxDailyLossPercent;
|
|
double m_minMarginLevel;
|
|
double m_warningMarginLevel;
|
|
int m_maxConsecutiveLosses;
|
|
|
|
// State tracking
|
|
PerformanceMetrics m_metrics;
|
|
ENUM_CIRCUIT_BREAKER_STATE m_cbState;
|
|
string m_cbTripReason;
|
|
datetime m_cbTripTime;
|
|
|
|
// Daily tracking
|
|
datetime m_lastDailyReset;
|
|
double m_dailyStartEquity;
|
|
double m_dailyRealizedPL;
|
|
int m_consecutiveLosses;
|
|
|
|
// Historical high
|
|
double m_peakEquity;
|
|
double m_startingBalance;
|
|
|
|
bool m_isInitialized;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager()
|
|
{
|
|
m_maxDrawdownPercent = DEFAULT_MAX_DRAWDOWN_PERCENT;
|
|
m_warningDrawdownPercent = CB_WARNING_DRAWDOWN_PERCENT;
|
|
m_maxDailyLossAmount = DEFAULT_DAILY_LOSS_LIMIT;
|
|
m_maxDailyLossPercent = 5.0;
|
|
m_minMarginLevel = DEFAULT_MIN_MARGIN_LEVEL;
|
|
m_warningMarginLevel = DEFAULT_WARNING_MARGIN_LEVEL;
|
|
m_maxConsecutiveLosses = CB_MAX_CONSECUTIVE_LOSSES;
|
|
|
|
m_cbState = CB_NORMAL;
|
|
m_cbTripReason = "";
|
|
m_cbTripTime = 0;
|
|
|
|
m_lastDailyReset = 0;
|
|
m_dailyStartEquity = 0;
|
|
m_dailyRealizedPL = 0;
|
|
m_consecutiveLosses = 0;
|
|
|
|
m_peakEquity = 0;
|
|
m_startingBalance = 0;
|
|
|
|
m_isInitialized = false;
|
|
m_metrics.Reset();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize risk manager |
|
|
//+------------------------------------------------------------------+
|
|
bool Initialize(const EAConfig &config)
|
|
{
|
|
m_maxDrawdownPercent = config.maxDrawdownPercent;
|
|
m_warningDrawdownPercent = m_maxDrawdownPercent * 0.6; // 60% of max
|
|
m_maxDailyLossAmount = config.maxDailyLossAmount;
|
|
m_maxDailyLossPercent = config.maxDailyLossPercent;
|
|
|
|
// Initialize tracking
|
|
m_startingBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
m_peakEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
m_dailyStartEquity = m_peakEquity;
|
|
m_lastDailyReset = TimeCurrent();
|
|
|
|
m_metrics.Reset();
|
|
m_metrics.startingBalance = m_startingBalance;
|
|
m_metrics.peakEquity = m_peakEquity;
|
|
m_metrics.metricsStartTime = TimeCurrent();
|
|
m_metrics.dailyStartEquity = m_dailyStartEquity;
|
|
m_metrics.dailyResetTime = m_lastDailyReset;
|
|
|
|
m_cbState = CB_NORMAL;
|
|
m_isInitialized = true;
|
|
|
|
Logger.Info("Risk Manager initialized - Max DD: " + DoubleToString(m_maxDrawdownPercent, 1) +
|
|
"%, Daily Limit: $" + DoubleToString(m_maxDailyLossAmount, 2));
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update metrics (call every tick or periodically) |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateMetrics()
|
|
{
|
|
if(!m_isInitialized)
|
|
return;
|
|
|
|
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
// Update peak equity
|
|
if(currentEquity > m_peakEquity)
|
|
m_peakEquity = currentEquity;
|
|
|
|
// Calculate current drawdown
|
|
double drawdown = 0;
|
|
if(m_peakEquity > 0)
|
|
drawdown = ((m_peakEquity - currentEquity) / m_peakEquity) * 100;
|
|
|
|
// Check for daily reset
|
|
CheckDailyReset();
|
|
|
|
// Update metrics structure
|
|
m_metrics.currentEquity = currentEquity;
|
|
m_metrics.currentBalance = currentBalance;
|
|
m_metrics.peakEquity = m_peakEquity;
|
|
m_metrics.currentDrawdownPercent = drawdown;
|
|
m_metrics.dailyPnL = currentEquity - m_dailyStartEquity;
|
|
m_metrics.uptimeSeconds = (int)(TimeCurrent() - m_metrics.metricsStartTime);
|
|
|
|
if(drawdown > m_metrics.maxDrawdownPercent)
|
|
{
|
|
m_metrics.maxDrawdownPercent = drawdown;
|
|
m_metrics.maxDrawdownValue = m_peakEquity - currentEquity;
|
|
}
|
|
|
|
// Update win rate
|
|
if(m_metrics.closedBaskets > 0)
|
|
m_metrics.winRate = (double)m_metrics.winningBaskets / m_metrics.closedBaskets;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for daily reset |
|
|
//+------------------------------------------------------------------+
|
|
void CheckDailyReset()
|
|
{
|
|
MqlDateTime dtNow, dtLast;
|
|
TimeToStruct(TimeCurrent(), dtNow);
|
|
TimeToStruct(m_lastDailyReset, dtLast);
|
|
|
|
// Check if day changed
|
|
if(dtNow.day != dtLast.day || dtNow.mon != dtLast.mon || dtNow.year != dtLast.year)
|
|
{
|
|
// New trading day
|
|
m_dailyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
m_dailyRealizedPL = 0;
|
|
m_lastDailyReset = TimeCurrent();
|
|
|
|
m_metrics.dailyStartEquity = m_dailyStartEquity;
|
|
m_metrics.dailyResetTime = m_lastDailyReset;
|
|
m_metrics.dailyPnL = 0;
|
|
|
|
Logger.Info("Daily reset - New equity baseline: " + DoubleToString(m_dailyStartEquity, 2));
|
|
|
|
// Reset circuit breaker if tripped due to daily limits or consecutive losses
|
|
if(m_cbState == CB_TRIPPED)
|
|
{
|
|
if(m_cbTripReason == "Daily loss limit exceeded" ||
|
|
StringFind(m_cbTripReason, "Maximum consecutive losses") >= 0)
|
|
{
|
|
Logger.Info("Resetting circuit breaker after daily reset");
|
|
ResetCircuitBreaker();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check all risk limits |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckRiskLimits(string &failReason)
|
|
{
|
|
failReason = "";
|
|
|
|
if(!m_isInitialized)
|
|
return true;
|
|
|
|
// Update metrics first
|
|
UpdateMetrics();
|
|
|
|
// If already tripped, stay tripped
|
|
if(m_cbState == CB_TRIPPED)
|
|
{
|
|
failReason = "Circuit breaker tripped: " + m_cbTripReason;
|
|
return false;
|
|
}
|
|
|
|
ENUM_CIRCUIT_BREAKER_STATE newState = CB_NORMAL;
|
|
string reason = "";
|
|
|
|
// Check 1: Drawdown limit
|
|
if(m_metrics.currentDrawdownPercent >= m_maxDrawdownPercent)
|
|
{
|
|
reason = "Maximum drawdown exceeded: " + DoubleToString(m_metrics.currentDrawdownPercent, 2) + "%";
|
|
newState = CB_TRIPPED;
|
|
}
|
|
else if(m_metrics.currentDrawdownPercent >= m_warningDrawdownPercent)
|
|
{
|
|
reason = "Approaching drawdown limit: " + DoubleToString(m_metrics.currentDrawdownPercent, 2) + "%";
|
|
if(newState < CB_WARNING)
|
|
newState = CB_WARNING;
|
|
}
|
|
|
|
// Check 2: Daily loss limit
|
|
if(m_metrics.dailyPnL <= -m_maxDailyLossAmount)
|
|
{
|
|
reason = "Daily loss limit exceeded: $" + DoubleToString(MathAbs(m_metrics.dailyPnL), 2);
|
|
newState = CB_TRIPPED;
|
|
}
|
|
else if(m_maxDailyLossPercent > 0)
|
|
{
|
|
double dailyLossPercent = MathAbs(m_metrics.dailyPnL) / m_dailyStartEquity * 100;
|
|
if(m_metrics.dailyPnL < 0 && dailyLossPercent >= m_maxDailyLossPercent)
|
|
{
|
|
reason = "Daily loss % exceeded: " + DoubleToString(dailyLossPercent, 2) + "%";
|
|
newState = CB_TRIPPED;
|
|
}
|
|
}
|
|
|
|
// Check 3: Margin level
|
|
double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
|
|
if(marginLevel > 0) // 0 means no positions
|
|
{
|
|
if(marginLevel < m_minMarginLevel)
|
|
{
|
|
reason = "Margin level critical: " + DoubleToString(marginLevel, 0) + "%";
|
|
newState = CB_TRIPPED;
|
|
}
|
|
else if(marginLevel < m_warningMarginLevel)
|
|
{
|
|
if(reason == "")
|
|
reason = "Margin level warning: " + DoubleToString(marginLevel, 0) + "%";
|
|
if(newState < CB_WARNING)
|
|
newState = CB_WARNING;
|
|
}
|
|
}
|
|
|
|
// Check 4: Consecutive losses
|
|
if(m_consecutiveLosses >= m_maxConsecutiveLosses)
|
|
{
|
|
reason = "Maximum consecutive losses: " + IntegerToString(m_consecutiveLosses);
|
|
newState = CB_TRIPPED;
|
|
}
|
|
|
|
// Apply state change
|
|
if(newState > m_cbState)
|
|
{
|
|
m_cbState = newState;
|
|
|
|
if(newState == CB_TRIPPED)
|
|
{
|
|
m_cbTripReason = reason;
|
|
m_cbTripTime = TimeCurrent();
|
|
Logger.Error("CIRCUIT BREAKER TRIPPED: " + reason);
|
|
}
|
|
else if(newState == CB_WARNING)
|
|
{
|
|
Logger.Warning("RISK WARNING: " + reason);
|
|
}
|
|
}
|
|
|
|
failReason = reason;
|
|
return (m_cbState != CB_TRIPPED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Record a basket close result |
|
|
//+------------------------------------------------------------------+
|
|
void RecordBasketClose(double pl, bool isWin)
|
|
{
|
|
m_metrics.closedBaskets++;
|
|
m_dailyRealizedPL += pl;
|
|
m_metrics.realizedPL += pl;
|
|
|
|
if(isWin)
|
|
{
|
|
m_metrics.winningBaskets++;
|
|
m_consecutiveLosses = 0;
|
|
}
|
|
else
|
|
{
|
|
m_metrics.losingBaskets++;
|
|
m_consecutiveLosses++;
|
|
|
|
if(m_consecutiveLosses > m_metrics.maxConsecutiveLosses)
|
|
m_metrics.maxConsecutiveLosses = m_consecutiveLosses;
|
|
}
|
|
|
|
m_metrics.consecutiveLosses = m_consecutiveLosses;
|
|
|
|
// Update win rate
|
|
if(m_metrics.closedBaskets > 0)
|
|
m_metrics.winRate = (double)m_metrics.winningBaskets / m_metrics.closedBaskets;
|
|
|
|
// Calculate profit factor
|
|
double totalWins = 0, totalLosses = 0;
|
|
// (Would need to track these separately for accurate profit factor)
|
|
|
|
Logger.Debug("Basket recorded - P/L: $" + DoubleToString(pl, 2) +
|
|
", Win Rate: " + DoubleToString(m_metrics.winRate * 100, 1) + "%" +
|
|
", Consecutive Losses: " + IntegerToString(m_consecutiveLosses));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Record basket open |
|
|
//+------------------------------------------------------------------+
|
|
void RecordBasketOpen()
|
|
{
|
|
m_metrics.totalBaskets++;
|
|
m_metrics.executedSignals++;
|
|
m_metrics.lastTradeTime = TimeCurrent();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Record signal generation |
|
|
//+------------------------------------------------------------------+
|
|
void RecordSignal(bool executed)
|
|
{
|
|
m_metrics.totalSignals++;
|
|
if(!executed)
|
|
m_metrics.filteredSignals++;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if trading is allowed |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTradingAllowed()
|
|
{
|
|
return (m_cbState != CB_TRIPPED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get circuit breaker state |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_CIRCUIT_BREAKER_STATE GetCircuitBreakerState()
|
|
{
|
|
return m_cbState;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset circuit breaker (manual reset) |
|
|
//+------------------------------------------------------------------+
|
|
void ResetCircuitBreaker()
|
|
{
|
|
if(m_cbState == CB_TRIPPED)
|
|
{
|
|
Logger.Info("Circuit breaker reset - Previous reason: " + m_cbTripReason);
|
|
m_cbState = CB_NORMAL;
|
|
m_cbTripReason = "";
|
|
m_cbTripTime = 0;
|
|
m_consecutiveLosses = 0;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get performance metrics |
|
|
//+------------------------------------------------------------------+
|
|
void GetMetrics(PerformanceMetrics &metrics)
|
|
{
|
|
metrics = m_metrics;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current drawdown |
|
|
//+------------------------------------------------------------------+
|
|
double GetCurrentDrawdown()
|
|
{
|
|
return m_metrics.currentDrawdownPercent;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get daily P&L |
|
|
//+------------------------------------------------------------------+
|
|
double GetDailyPnL()
|
|
{
|
|
return m_metrics.dailyPnL;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check emergency exit conditions |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckEmergencyExit(string &reason)
|
|
{
|
|
reason = "";
|
|
|
|
// Check margin level emergency
|
|
double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
|
|
if(marginLevel > 0 && marginLevel < 150) // Very critical
|
|
{
|
|
reason = "Emergency: Margin call imminent (" + DoubleToString(marginLevel, 0) + "%)";
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Display metrics on chart |
|
|
//+------------------------------------------------------------------+
|
|
void DisplayMetricsOnChart()
|
|
{
|
|
string status = (m_cbState == CB_TRIPPED) ? "HALTED" :
|
|
(m_cbState == CB_WARNING) ? "WARNING" : "NORMAL";
|
|
|
|
string display = StringFormat(
|
|
"=== D-Basket EA Risk Monitor ===\n" +
|
|
"Status: %s\n" +
|
|
"Net P/L: $%.2f (%.1f%%)\n" +
|
|
"Daily P/L: $%.2f\n" +
|
|
"Drawdown: %.2f%% (Max: %.2f%%)\n" +
|
|
"Baskets: %d | Win Rate: %.1f%%\n" +
|
|
"Consecutive Losses: %d",
|
|
status,
|
|
m_metrics.currentEquity - m_startingBalance,
|
|
((m_metrics.currentEquity - m_startingBalance) / m_startingBalance) * 100,
|
|
m_metrics.dailyPnL,
|
|
m_metrics.currentDrawdownPercent,
|
|
m_metrics.maxDrawdownPercent,
|
|
m_metrics.closedBaskets,
|
|
m_metrics.winRate * 100,
|
|
m_consecutiveLosses
|
|
);
|
|
|
|
Comment(display);
|
|
}
|
|
};
|
|
|
|
#endif // DBASKET_RISKMANAGER_MQH
|
|
//+------------------------------------------------------------------+
|