881 lines
31 KiB
MQL5
881 lines
31 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
}
|