Files

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
//+------------------------------------------------------------------+