mql5/Include/Experts/RiskManager.mqh

882 lines
31 KiB
MQL5
Raw Permalink Normal View History

2025-08-16 12:30:04 -04:00
//+------------------------------------------------------------------+
//| RiskManager.mqh |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Indicators\Trend.mqh>
#include <Indicators\Oscilators.mqh>
// Risk calculation modes
enum ENUM_RISK_CALC_MODE
{
RISK_FIXED_PERCENT, // Fixed percentage of balance
RISK_VOLATILITY_BASED, // Based on market volatility (ATR)
RISK_FIXED_FRACTIONAL, // Fixed fractional position sizing
RISK_KELLY_CRITERION // Kelly Criterion for position sizing
};
// Enhanced risk state with more granular control
enum ENUM_RISK_STATE
{
RISK_STATE_OPTIMAL, // Optimal conditions (increased position size)
RISK_STATE_NORMAL, // Normal trading conditions
RISK_STATE_CAUTION, // Increased risk (reduced position size)
RISK_STATE_ALERT, // High risk (no new positions)
RISK_STATE_STOP, // Stop trading (emergency)
RISK_STATE_RECOVERY // Recovery mode after stop
};
// Market regime detection
enum ENUM_MARKET_REGIME
{
REGIME_TRENDING_UP, // Strong uptrend
REGIME_TRENDING_DOWN, // Strong downtrend
REGIME_RANGING, // Sideways market
REGIME_VOLATILE, // High volatility
REGIME_LOW_VOLATILITY // Low volatility
};
//+------------------------------------------------------------------+
//| Risk Manager Class |
//+------------------------------------------------------------------+
class CRiskManager
{
private:
// Basic risk parameters
double m_riskPerTrade; // Risk per trade as percentage of balance
double m_maxLeverage; // Maximum allowed leverage
int m_maxOpenTrades; // Maximum number of open trades
double m_dailyLossLimit; // Daily loss limit as percentage of balance
double m_maxPositionSize; // Maximum position size in lots
double m_maxPortfolioRisk; // Maximum portfolio risk as percentage
double m_maxDrawdown; // Maximum allowed drawdown percentage
// Advanced risk parameters
ENUM_RISK_CALC_MODE m_riskMode; // Risk calculation mode
ENUM_RISK_STATE m_riskState; // Current risk state
double m_volatilityFactor; // Volatility adjustment factor
double m_correlationFactor; // Correlation adjustment factor
// Enhanced risk metrics
double m_riskOfRuin; // Current risk of ruin
double m_sharpeRatio; // Current Sharpe ratio
double m_sortinoRatio; // Sortino ratio (downside risk)
double m_valueAtRisk; // Value at Risk (VaR)
double m_expectedShortfall; // Expected Shortfall (ES)
double m_maxAdverseExcursion; // Maximum adverse excursion
double m_winLossRatio; // Current win/loss ratio
double m_profitFactor; // Profit factor
double m_recoveryFactor; // Recovery factor
int m_consecutiveLosses; // Number of consecutive losses
int m_consecutiveWins; // Number of consecutive wins
double m_maxDrawdown; // Maximum drawdown (peak to trough)
double m_avgWin; // Average win amount
double m_avgLoss; // Average loss amount
int m_totalTrades; // Total number of trades
double m_winRate; // Win rate percentage
// Market regime detection
ENUM_MARKET_REGIME m_marketRegime; // Current market regime
double m_volatilityIndex; // Volatility index (0-100)
double m_trendStrength; // Trend strength (0-100)
double m_correlationIndex; // Correlation index (-100 to 100)
// Time-based controls
datetime m_lastTradeTime; // Time of last trade
int m_minMinutesBetweenTrades; // Minimum minutes between trades
// Objects
CSymbolInfo *m_symbol; // Pointer to symbol info
CAccountInfo *m_account; // Pointer to account info
CiATR *m_atr; // ATR indicator for volatility
// Internal methods
double NormalizeLots(double lots);
double CalculateVolatilityFactor(int period = 14);
double CalculateCorrelationFactor();
void UpdateRiskMetrics();
void UpdateRiskState();
bool CheckTimeRestrictions();
bool CheckNewsRisk();
double CalculateKellyFraction(double winRate, double winLossRatio);
// Enhanced risk calculations
double CalculateValueAtRisk(int period = 20, double confidence = 0.95);
double CalculateExpectedShortfall(int period = 20, double confidence = 0.95);
void UpdateMarketRegime();
double CalculateVolatilityIndex(int period = 14);
double CalculateTrendStrength(int period = 20);
double CalculateCorrelationIndex(string symbol1, string symbol2, ENUM_TIMEFRAMES timeframe, int period);
void UpdateWinLossMetrics();
void UpdateDrawdownMetrics();
void SaveStateToFile();
bool LoadStateFromFile();
public:
CRiskManager();
~CRiskManager();
// Initialization
bool Initialize(CSymbolInfo *symbol, CAccountInfo *account,
double riskPerTrade = 1.0, double maxLeverage = 30.0,
int maxOpenTrades = 5, double dailyLossLimit = 5.0,
double maxPortfolioRisk = 30.0, double maxDrawdown = 20.0);
// Enhanced position sizing
double CalculatePositionSize(double stopLossPoints, ENUM_ORDER_TYPE orderType = ORDER_TYPE_BUY);
double CalculateCorrelationAdjustedSize(double baseLots, string symbol1, string symbol2, ENUM_TIMEFRAMES timeframe, int period);
double CalculateRegimeAdjustedSize(double baseLots, ENUM_MARKET_REGIME regime);
double CalculateMLAdjustedSize(double baseLots, double mlConfidence);
// Enhanced risk validation
bool CheckRisk(double lots, double stopLoss, double takeProfit, ENUM_ORDER_TYPE orderType = ORDER_TYPE_BUY);
bool CheckPortfolioRisk();
bool CheckLeverageRisk();
bool CheckConcentrationRisk();
bool CheckLiquidityRisk();
bool CheckEventRisk();
// Enhanced risk management
void Update(); // Call this on each tick or bar close
void ResetDailyMetrics();
void UpdateMarketRegime();
void UpdateRiskMetrics();
void UpdateRiskState();
bool CheckDrawdownLimits();
bool CheckVolatilityLimits();
bool CheckCorrelationLimits();
void SaveState();
bool LoadState();
void TriggerRecovery();
// Getters
double GetRiskPerTrade() const { return m_riskPerTrade; }
double GetMaxLeverage() const { return m_maxLeverage; }
int GetMaxOpenTrades() const { return m_maxOpenTrades; }
double GetDailyLossLimit() const { return m_dailyLossLimit; }
double GetMaxPortfolioRisk() const { return m_maxPortfolioRisk; }
double GetMaxDrawdown() const { return m_maxDrawdown; }
double GetRiskOfRuin() const { return m_riskOfRuin; }
double GetSharpeRatio() const { return m_sharpeRatio; }
ENUM_RISK_STATE GetRiskState() const { return m_riskState; }
// Setters
void SetRiskPerTrade(double risk) { m_riskPerTrade = MathMax(0.1, MathMin(risk, 10.0)); }
void SetMaxLeverage(double leverage) { m_maxLeverage = MathMax(1.0, MathMin(leverage, 100.0)); }
void SetMaxOpenTrades(int maxTrades) { m_maxOpenTrades = MathMax(1, maxTrades); }
void SetDailyLossLimit(double limit) { m_dailyLossLimit = MathMax(0.1, MathMin(limit, 50.0)); }
void SetMaxPortfolioRisk(double risk) { m_maxPortfolioRisk = MathMax(5.0, MathMin(risk, 100.0)); }
void SetMaxDrawdown(double drawdown) { m_maxDrawdown = MathMax(1.0, MathMin(drawdown, 50.0)); }
void SetRiskMode(ENUM_RISK_CALC_MODE mode) { m_riskMode = mode; }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRiskManager::CRiskManager() : m_riskPerTrade(1.0),
m_maxLeverage(30.0),
m_maxOpenTrades(5),
m_dailyLossLimit(5.0),
m_maxPositionSize(100.0),
m_maxPortfolioRisk(30.0),
m_maxDrawdown(20.0),
m_riskMode(RISK_FIXED_PERCENT),
m_riskState(RISK_STATE_NORMAL),
m_volatilityFactor(1.0),
m_correlationFactor(1.0),
m_riskOfRuin(0.0),
m_sharpeRatio(0.0),
m_maxAdverseExcursion(0.0),
m_winLossRatio(0.0),
m_consecutiveLosses(0),
m_lastTradeTime(0),
m_minMinutesBetweenTrades(1),
m_symbol(NULL),
m_account(NULL),
m_atr(NULL)
{
// Create ATR indicator
m_atr = new CiATR();
// Set default ATR period (can be changed later)
if(m_atr != NULL)
{
m_atr.Create(m_symbol.Name(), PERIOD_CURRENT, 14);
}
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CRiskManager::~CRiskManager()
{
// Clean up ATR indicator
if(m_atr != NULL)
{
delete m_atr;
m_atr = NULL;
}
// Reset pointers
m_symbol = NULL;
m_account = NULL;
}
//+------------------------------------------------------------------+
//| Initialize risk manager with enhanced parameters |
//+------------------------------------------------------------------+
bool CRiskManager::Initialize(CSymbolInfo *symbol, CAccountInfo *account,
double riskPerTrade, double maxLeverage,
int maxOpenTrades, double dailyLossLimit,
double maxPortfolioRisk, double maxDrawdown)
{
// Validate inputs
if(symbol == NULL || account == NULL)
{
Print("Error: Invalid symbol or account pointer in RiskManager initialization");
return false;
}
// Store pointers
m_symbol = symbol;
m_account = account;
// Set basic risk parameters with validation
SetRiskPerTrade(riskPerTrade);
SetMaxLeverage(maxLeverage);
SetMaxOpenTrades(maxOpenTrades);
SetDailyLossLimit(dailyLossLimit);
SetMaxPortfolioRisk(maxPortfolioRisk);
SetMaxDrawdown(maxDrawdown);
// Get maximum position size from symbol
m_maxPositionSize = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MAX);
// Initialize ATR indicator if not already done
if(m_atr == NULL)
{
m_atr = new CiATR();
if(m_atr != NULL)
{
if(!m_atr.Create(m_symbol.Name(), PERIOD_CURRENT, 14))
{
Print("Warning: Failed to create ATR indicator");
delete m_atr;
m_atr = NULL;
}
}
}
// Initialize risk metrics
UpdateRiskMetrics();
UpdateRiskState();
Print("RiskManager initialized successfully");
PrintFormat("Risk per trade: %.2f%%, Max leverage: 1:%.0f, Max open trades: %d",
m_riskPerTrade, m_maxLeverage, m_maxOpenTrades);
PrintFormat("Daily loss limit: %.2f%%, Max portfolio risk: %.2f%%, Max drawdown: %.2f%%",
m_dailyLossLimit, m_maxPortfolioRisk, m_maxDrawdown);
return true;
}
//+------------------------------------------------------------------+
//| Calculate position size based on risk and current market conditions |
//+------------------------------------------------------------------+
double CRiskManager::CalculatePositionSize(double stopLossPoints, ENUM_ORDER_TYPE orderType)
{
if(stopLossPoints <= 0 || m_symbol == NULL || m_account == NULL)
return 0.0;
// Update risk metrics before calculating position size
UpdateRiskMetrics();
UpdateRiskState();
// Check if we're in a high-risk state that prevents new positions
if(m_riskState == RISK_STATE_STOP)
{
Print("Position size calculation: Trading stopped due to risk state");
return 0.0;
}
// Get account balance and symbol information
double balance = m_account.Balance();
double equity = m_account.Equity();
double freeMargin = m_account.FreeMargin();
double tickValue = m_symbol.TickValue();
double tickSize = m_symbol.TickSize();
double point = m_symbol.Point();
if(balance <= 0 || tickValue <= 0 || tickSize <= 0 || point <= 0)
{
Print("Position size calculation: Invalid account or symbol data");
return 0.0;
}
// Calculate base risk amount based on risk per trade
double riskAmount = balance * (m_riskPerTrade / 100.0);
// Adjust risk based on current risk state
switch(m_riskState)
{
case RISK_STATE_CAUTION:
riskAmount *= 0.5; // Reduce position size by 50%
break;
case RISK_STATE_ALERT:
riskAmount *= 0.25; // Reduce position size by 75%
break;
default:
break; // Normal risk, use full amount
}
// Get symbol lot information
double lotStep = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MAX);
if(lotStep <= 0 || minLot <= 0 || maxLot <= 0)
{
Print("Position size calculation: Invalid lot size parameters");
return 0.0;
}
// Calculate position size based on the selected risk calculation mode
double lots = 0.0;
switch(m_riskMode)
{
case RISK_VOLATILITY_BASED:
// Use ATR-based position sizing
if(m_atr != NULL && m_atr.Main(0) > 0)
{
double atrValue = m_atr.Main(0);
double atrMultiplier = 2.0; // Standard multiplier for ATR-based sizing
double atrStop = atrValue * atrMultiplier;
// Calculate position size based on ATR stop
double riskPerUnit = atrStop * tickValue / point;
if(riskPerUnit > 0)
{
lots = riskAmount / riskPerUnit;
}
}
break;
case RISK_FIXED_FRACTIONAL:
// Fixed fractional position sizing
lots = (balance * (m_riskPerTrade / 100.0)) / stopLossPoints;
break;
case RISK_KELLY_CRITERION:
// Kelly Criterion position sizing
if(m_winLossRatio > 0)
{
double kellyFraction = CalculateKellyFraction(0.5, m_winLossRatio);
lots = (balance * kellyFraction) / stopLossPoints;
}
break;
case RISK_FIXED_PERCENT:
default:
// Standard fixed percentage risk model
double moneyPerLot = (stopLossPoints * point / tickSize) * tickValue;
if(moneyPerLot > 0)
{
lots = riskAmount / moneyPerLot;
}
break;
}
// Apply volatility adjustment if ATR is available
if(m_atr != NULL && m_volatilityFactor > 0)
{
lots *= m_volatilityFactor;
}
// Apply correlation adjustment
lots *= m_correlationFactor;
// Normalize lots to allowed values
lots = MathFloor(lots / lotStep) * lotStep;
lots = MathMax(minLot, MathMin(lots, MathMin(maxLot, m_maxPositionSize)));
// Check margin requirements
double marginRequired = 0.0;
double price = (orderType == ORDER_TYPE_BUY) ? m_symbol.Ask() : m_symbol.Bid();
// Calculate margin required for this position
marginRequired = lots * price / m_account.Leverage();
// If we don't have enough margin, reduce position size
if(marginRequired > freeMargin * 0.9) // Leave 10% margin buffer
{
double maxLotsByMargin = (freeMargin * 0.9 * m_account.Leverage()) / price;
lots = MathMin(lots, maxLotsByMargin);
// Re-normalize after margin adjustment
lots = MathFloor(lots / lotStep) * lotStep;
lots = MathMax(minLot, MathMin(lots, Math.min(maxLot, m_maxPositionSize)));
PrintFormat("Position size reduced to %.2f lots due to margin requirements", lots);
}
// Final validation
if(lots < minLot || lots > m_maxPositionSize)
{
PrintFormat("Invalid position size calculated: %.2f (min: %.2f, max: %.2f)",
lots, minLot, m_maxPositionSize);
return 0.0;
}
PrintFormat("Calculated position size: %.2f lots (Risk: %.2f%%, Stop: %.1f pips)",
lots, m_riskPerTrade, stopLossPoints / 10.0);
return lots;
}
//+------------------------------------------------------------------+
//| Check if trade meets all risk parameters and is allowed |
//+------------------------------------------------------------------+
bool CRiskManager::CheckRisk(double lots, double stopLoss, double takeProfit, ENUM_ORDER_TYPE orderType)
{
if(m_symbol == NULL || m_account == NULL)
{
Print("Risk check failed: Symbol or account not initialized");
return false;
}
// Update risk metrics before checking
UpdateRiskMetrics();
UpdateRiskState();
// Check if we're in a stop state
if(m_riskState == RISK_STATE_STOP)
{
Print("Risk check failed: Trading stopped due to risk state");
return false;
}
// Check time restrictions (e.g., news events, market close, etc.)
if(!CheckTimeRestrictions())
{
Print("Risk check failed: Time restrictions in effect");
return false;
}
// Check for news events that might increase risk
if(!CheckNewsRisk())
{
Print("Risk check failed: High-impact news event detected");
return false;
}
// Check if we've reached max open trades for this symbol
CPositionInfo position;
int totalPositions = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(position.SelectByIndex(i))
{
if(position.Symbol() == m_symbol.Name() &&
(m_magicNumber == 0 || position.Magic() == m_magicNumber))
{
totalPositions++;
}
}
}
if(totalPositions >= m_maxOpenTrades)
{
Print("Risk check failed: Maximum open trades (", m_maxOpenTrades, ") reached");
return false;
}
// Check daily loss limit
double dailyProfit = m_account.Profit() - m_account.Balance() + m_account.Equity();
double dailyLimit = m_account.Balance() * (m_dailyLossLimit / 100.0);
if(dailyProfit <= -dailyLimit)
{
Print("Risk check failed: Daily loss limit (", m_dailyLossLimit, "%) reached");
return false;
}
// Check maximum portfolio risk
double portfolioRisk = (1.0 - (m_account.Equity() / m_account.Balance())) * 100.0;
if(portfolioRisk >= m_maxPortfolioRisk)
{
Print("Risk check failed: Portfolio risk (", DoubleToString(portfolioRisk, 2),
"%) exceeds maximum (", m_maxPortfolioRisk, "%)");
return false;
}
// Check maximum drawdown
double drawdown = (1.0 - (m_account.Equity() / m_account.Balance())) * 100.0;
if(drawdown >= m_maxDrawdown)
{
Print("Risk check failed: Maximum drawdown (", m_maxDrawdown, "%) reached");
return false;
}
// Calculate position value and check margin
double price = (orderType == ORDER_TYPE_BUY) ? m_symbol.Ask() : m_symbol.Bid();
double marginRequired = lots * price / m_account.Leverage();
// Add a 10% buffer to margin requirement for price fluctuations
if(marginRequired > m_account.FreeMargin() * 0.9)
{
Print("Risk check failed: Not enough margin. Required: ", marginRequired,
", Free: ", m_account.FreeMargin());
return false;
}
// Check if stop loss and take profit are valid
if(stopLoss > 0 && takeProfit > 0)
{
double slDistance = MathAbs(price - stopLoss) / m_symbol.Point();
double tpDistance = MathAbs(takeProfit - price) / m_symbol.Point();
// Get minimum distance from broker and add a small buffer
double minDistance = (double)SymbolInfoInteger(m_symbol.Name(), SYMBOL_TRADE_STOPS_LEVEL) * 1.5;
if(slDistance < minDistance || tpDistance < minDistance)
{
Print("Risk check failed: Stop loss or take profit too close to current price");
return false;
}
// Check risk/reward ratio (minimum 1:1.5)
double riskRewardRatio = tpDistance / slDistance;
if(riskRewardRatio < 1.5)
{
Print("Risk check failed: Risk/reward ratio (", DoubleToString(riskRewardRatio, 2),
":1) is below minimum (1.5:1)");
return false;
}
}
// Check if we have too many consecutive losses
if(m_consecutiveLosses >= 3)
{
Print("Risk check failed: ", m_consecutiveLosses, " consecutive losses. Reducing risk.");
return false;
}
// Check minimum time between trades
if(TimeCurrent() - m_lastTradeTime < m_minMinutesBetweenTrades * 60)
{
Print("Risk check failed: Minimum time between trades not met");
return false;
}
// All checks passed
Print("Risk check passed: Trade allowed");
return true;
}
//+------------------------------------------------------------------+
//| Update all risk metrics based on current market conditions |
//+------------------------------------------------------------------+
void CRiskManager::UpdateRiskMetrics()
{
if(m_symbol == NULL || m_account == NULL)
return;
// Update market regime first as it affects other metrics
UpdateMarketRegime();
// Update volatility metrics
if(m_atr != NULL)
{
m_atr.Refresh(OBJ_ALL_PERIODS);
m_volatilityFactor = CalculateVolatilityFactor();
m_volatilityIndex = CalculateVolatilityIndex();
}
// Update correlation metrics
m_correlationFactor = CalculateCorrelationFactor();
m_correlationIndex = CalculateCorrelationIndex(_Symbol, "USDX", PERIOD_H1, 50);
// Update trend metrics
m_trendStrength = CalculateTrendStrength();
// Update trade performance metrics
UpdateWinLossMetrics();
UpdateDrawdownMetrics();
// Calculate advanced risk metrics
m_valueAtRisk = CalculateValueAtRisk();
m_expectedShortfall = CalculateExpectedShortfall();
m_riskOfRuin = CalculateRiskOfRuin();
m_sharpeRatio = CalculateSharpeRatio();
m_sortinoRatio = CalculateSortinoRatio();
m_profitFactor = CalculateProfitFactor();
m_recoveryFactor = CalculateRecoveryFactor();
m_maxAdverseExcursion = CalculateMaxAdverseExcursion();
// Update risk state based on all metrics
UpdateRiskState();
// Save state periodically
static datetime lastSave = 0;
if(TimeCurrent() - lastSave > 3600) // Save hourly
{
SaveState();
lastSave = TimeCurrent();
}
}
//+------------------------------------------------------------------+
//| Update risk state based on comprehensive market analysis |
//+------------------------------------------------------------------+
void CRiskManager::UpdateRiskState()
{
// Check for emergency stop conditions first
if(CheckDrawdownLimits() || CheckVolatilityLimits() || CheckCorrelationLimits())
{
if(m_riskState != RISK_STATE_STOP)
{
m_riskState = RISK_STATE_STOP;
Print("Emergency stop: Risk limits breached!");
// Save state before stopping
SaveState();
}
return;
}
// If in recovery state, check if we can exit recovery
if(m_riskState == RISK_STATE_RECOVERY)
{
if(m_account.Profit() > 0 && m_consecutiveWins >= 2)
{
m_riskState = RISK_STATE_CAUTION;
Print("Exiting recovery mode, moving to CAUTION state");
}
return;
}
// If in stop state, check if we can start recovery
if(m_riskState == RISK_STATE_STOP)
{
if(m_account.Profit() > 0 && m_consecutiveWins >= 1)
{
m_riskState = RISK_STATE_RECOVERY;
Print("Starting recovery process");
}
return;
}
// Calculate risk score (0-100, higher is riskier)
double riskScore = 0;
// Account metrics
riskScore += NormalizeDouble(m_maxDrawdown, 2) * 0.5;
riskScore += (1.0 - (m_account.Equity() / m_account.Balance())) * 100 * 0.3;
// Trade metrics
riskScore += (m_consecutiveLosses * 5);
riskScore += (m_winRate < 50) ? (50 - m_winRate) * 0.5 : 0;
// Market metrics
riskScore += (m_volatilityIndex > 70) ? 15 : (m_volatilityIndex > 50) ? 5 : 0;
riskScore += (m_trendStrength < 30) ? 10 : 0; // Low trend strength = more risk
// Update state based on risk score
if(riskScore >= 80 || m_consecutiveLosses >= 3)
{
m_riskState = RISK_STATE_ALERT;
}
else if(riskScore >= 60 || m_consecutiveLosses >= 1)
{
m_riskState = RISK_STATE_CAUTION;
}
else if(riskScore <= 20 && m_consecutiveWins >= 3)
{
m_riskState = RISK_STATE_OPTIMAL;
}
else
{
m_riskState = RISK_STATE_NORMAL;
}
// Log state changes
static ENUM_RISK_STATE lastState = (ENUM_RISK_STATE)-1;
if(m_riskState != lastState)
{
PrintFormat("Risk state changed from %d to %d (Score: %.1f)", lastState, m_riskState, riskScore);
lastState = m_riskState;
}
}
//+------------------------------------------------------------------+
//| Check if trading is allowed based on comprehensive risk analysis |
//+------------------------------------------------------------------+
bool CRiskManager::IsTradingAllowed()
{
// Update all metrics before checking
UpdateRiskMetrics();
// Check risk state first (fastest check)
switch(m_riskState)
{
case RISK_STATE_STOP:
Print("Trading stopped: Emergency stop condition active");
return false;
case RISK_STATE_ALERT:
Print("Trading paused: High risk condition detected");
return false;
case RISK_STATE_RECOVERY:
// Only allow reduced size trades in recovery
if(AccountInfoDouble(ACCOUNT_EQUITY) < AccountInfoDouble(ACCOUNT_BALANCE) * 0.95)
{
Print("Trading in recovery mode: Only reduced size trades allowed");
return true;
}
break;
case RISK_STATE_OPTIMAL:
// Full trading allowed
break;
case RISK_STATE_CAUTION:
case RISK_STATE_NORMAL:
default:
// Continue with other checks
break;
}
// Check portfolio-level risk
if(!CheckPortfolioRisk())
{
Print("Trading not allowed: Portfolio risk limit reached");
return false;
}
// Check leverage risk
if(!CheckLeverageRisk())
{
Print("Trading not allowed: Leverage risk too high");
return false;
}
// Check concentration risk
if(!CheckConcentrationRisk())
{
Print("Trading not allowed: Position concentration too high");
return false;
}
// Check liquidity risk
if(!CheckLiquidityRisk())
{
Print("Trading not allowed: Insufficient liquidity");
return false;
}
// Check event risk (news, etc.)
if(!CheckEventRisk())
{
Print("Trading not allowed: High event risk detected");
return false;
}
// Check time restrictions
if(!CheckTimeRestrictions())
{
Print("Trading not allowed: Time restriction active");
return false;
}
// All checks passed
return true;
}
//+------------------------------------------------------------------+
//| Check if trading is allowed based on time restrictions |
//+------------------------------------------------------------------+
bool CRiskManager::CheckTimeRestrictions()
{
// Check if we're in a time-restricted period (e.g., news events, market close)
// This is a placeholder - implement actual time restrictions based on your strategy
// Example: Don't trade during the first and last hour of the trading day
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
// Skip weekend trading
if(dt.day_of_week == 0 || dt.day_of_week == 6) // Sunday or Saturday
return false;
// Skip first and last hour of the trading day
int hour = dt.hour;
if((hour >= 0 && hour < 1) || (hour >= 23 && hour <= 23))
return false;
return true;
}
//+------------------------------------------------------------------+
//| Check for high-impact news events |
//+------------------------------------------------------------------+
bool CRiskManager::CheckNewsRisk()
{
// This is a placeholder - implement actual news checking
// In a real implementation, you would check an economic calendar
// Example: Skip trading during major news events
// You would typically use an economic calendar API here
return true; // No news risk detected
}
//+------------------------------------------------------------------+
//| Calculate position size using the Kelly Criterion |
//+------------------------------------------------------------------+
double CRiskManager::CalculateKellyFraction(double winRate, double winLossRatio)
{
// Kelly % = W - [(1 - W) / R]
// Where:
// W = Win probability
// R = Win/loss ratio
if(winRate <= 0 || winRate >= 1.0 || winLossRatio <= 0)
return 0.0;
double kelly = winRate - ((1.0 - winRate) / winLossRatio);
// Conservative approach: use half-Kelly to reduce risk
return kelly * 0.5;
}
//+------------------------------------------------------------------+
//| Normalize lots to allowed values |
//+------------------------------------------------------------------+
double CRiskManager::NormalizeLots(double lots)
{
if(m_symbol == NULL)
return 0.0;
double lotStep = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(m_symbol.Name(), SYMBOL_VOLUME_MAX);
if(lotStep <= 0 || minLot <= 0 || maxLot <= 0)
return 0.0;
lots = MathFloor(lots / lotStep) * lotStep;
lots = MathMax(minLot, MathMin(lots, maxLot));
return lots;
}