mql5/Experts/Advisors/Modules/RiskManager.mqh
2025-09-01 16:55:52 +01:00

750 lines
No EOL
56 KiB
MQL5

//+------------------------------------------------------------------+
//| RiskManager.mqh |
//| Fixed Risk Management Implementation |
//+------------------------------------------------------------------+
#ifndef RISK_MANAGER_MQH
#define RISK_MANAGER_MQH
#include "DataTypes.mqh"
#include "Utilities.mqh"
//+------------------------------------------------------------------+
//| Risk Manager Class |
//+------------------------------------------------------------------+
class CRiskManager
{
private:
//--- Member Variables - Complete Declaration
CUtilities* m_pUtils; // Utilities pointer
RiskParameters m_params; // Risk parameters
bool m_isInitialized; // Initialization flag
//--- Risk Limits
double m_maxRiskPerTrade; // Max risk per trade (%)
double m_maxDailyLoss; // Max daily loss (%)
double m_maxDrawdown; // Max drawdown (%)
double m_maxLeverage; // Max leverage
int m_maxPositions; // Max concurrent positions
double m_maxCorrelation; // Max correlation threshold
//--- Current State
double m_currentDailyLoss; // Today's loss
double m_currentDrawdown; // Current drawdown
double m_peakBalance; // Peak balance for DD calc
datetime m_lastResetTime; // Last daily reset
//--- Position Tracking
PositionRisk m_positions[]; // Array of position risks
int m_positionCount; // Current position count
//--- Risk Metrics Cache
RiskMetrics m_currentMetrics; // Current risk metrics
datetime m_lastUpdateTime; // Last metrics update
//--- Private Methods
void UpdateDailyMetrics();
void UpdateDrawdown();
void CalculatePositionRisks();
double CalculateCorrelation(const double &prices1[], const double &prices2[]);
bool CheckRiskLimits(double proposedRisk);
void LogRiskEvent(ENUM_RISK_EVENT event, string details);
double InternalCalculateLotSize(double riskAmount, double stopLossPoints, string symbol);
public:
//--- Constructor/Destructor
CRiskManager();
~CRiskManager();
//--- Initialization
bool Initialize(CUtilities* utils, const RiskParameters &params);
void Deinitialize();
//--- Risk Calculation Methods
double CalculatePositionSize(double stopLossPoints, string symbol = NULL);
double CalculatePositionSizeByMethod(ENUM_POSITION_SIZING method,
double balance,
double stopLossPoints,
string symbol = NULL);
double CalculateRiskAmount(double balance, double riskPercent);
//--- Risk Assessment
bool CanOpenNewPosition(string symbol, double proposedVolume);
bool IsRiskLimitExceeded();
RiskMetrics GetCurrentRiskMetrics();
double GetAvailableRisk();
//--- Stop Loss Calculation
double CalculateStopLoss(double entryPrice, bool isBuy,
ENUM_SL_MODE mode, string symbol = NULL);
double CalculateTakeProfit(double entryPrice, double stopLoss,
bool isBuy, double riskRewardRatio);
//--- Risk Monitoring
void UpdateRiskMetrics();
ENUM_RISK_ACTION EvaluateRiskAction();
void ExecuteRiskAction(ENUM_RISK_ACTION action);
//--- Utility Methods
double GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period);
double GetVolatility(string symbol);
bool IsHighVolatility(string symbol);
//--- Getters
bool IsInitialized() { return m_isInitialized; }
double GetMaxRiskPerTrade() { return m_maxRiskPerTrade; }
double GetCurrentDrawdown() { return m_currentDrawdown; }
int GetOpenPositionCount() { return m_positionCount; }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRiskManager::CRiskManager()
{
m_pUtils = NULL;
m_isInitialized = false;
m_maxRiskPerTrade = 1.0;
m_maxDailyLoss = 5.0;
m_maxDrawdown = 20.0;
m_maxLeverage = 10.0;
m_maxPositions = 5;
m_maxCorrelation = 0.7;
m_currentDailyLoss = 0;
m_currentDrawdown = 0;
m_peakBalance = 0;
m_lastResetTime = 0;
m_positionCount = 0;
m_lastUpdateTime = 0;
ArrayResize(m_positions, 0);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CRiskManager::~CRiskManager()
{
Deinitialize();
}
//+------------------------------------------------------------------+
//| Initialize Risk Manager |
//+------------------------------------------------------------------+
bool CRiskManager::Initialize(CUtilities* utils, const RiskParameters &params)
{
if(utils == NULL)
{
Print("RiskManager: Invalid utilities pointer");
return false;
}
m_pUtils = utils;
m_params = params;
// Store parameters
m_maxRiskPerTrade = params.maxRiskPerTrade;
m_maxDailyLoss = params.maxDailyRisk;
m_maxDrawdown = params.maxDrawdown;
m_maxLeverage = params.maxLeverage;
m_maxPositions = params.maxPositions;
m_maxCorrelation = params.maxCorrelation;
// Initialize tracking
m_peakBalance = AccountInfoDouble(ACCOUNT_BALANCE);
m_lastResetTime = TimeCurrent();
m_currentDailyLoss = 0;
m_currentDrawdown = 0;
// Log initialization
CLogger::Log(LOG_INFO, "Risk Manager initialized");
CLogger::Log(LOG_INFO, StringFormat("Max Risk Per Trade: %.2f%%", m_maxRiskPerTrade));
CLogger::Log(LOG_INFO, StringFormat("Max Daily Loss: %.2f%%", m_maxDailyLoss));
CLogger::Log(LOG_INFO, StringFormat("Max Drawdown: %.2f%%", m_maxDrawdown));
CLogger::Log(LOG_INFO, StringFormat("Max Positions: %d", m_maxPositions));
m_isInitialized = true;
return true;
}
//+------------------------------------------------------------------+
//| Deinitialize |
//+------------------------------------------------------------------+
void CRiskManager::Deinitialize()
{
ArrayFree(m_positions);
m_isInitialized = false;
CLogger::Log(LOG_INFO, "Risk Manager deinitialized");
}
//+------------------------------------------------------------------+
//| Calculate Position Size |
//+------------------------------------------------------------------+
double CRiskManager::CalculatePositionSize(double stopLossPoints, string symbol)
{
if(!m_isInitialized) return 0;
if(symbol == NULL) symbol = _Symbol;
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = CalculateRiskAmount(balance, m_maxRiskPerTrade);
return InternalCalculateLotSize(riskAmount, stopLossPoints, symbol);
}
//+------------------------------------------------------------------+
//| Internal Calculate Lot Size |
//+------------------------------------------------------------------+
double CRiskManager::InternalCalculateLotSize(double riskAmount, double stopLossPoints, string symbol)
{
if(stopLossPoints <= 0) return 0;
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(tickSize <= 0 || tickValue <= 0) return minLot;
double lotSize = (riskAmount * tickSize) / (stopLossPoints * tickValue);
// Round to lot step
lotSize = MathFloor(lotSize / lotStep) * lotStep;
// Apply limits
if(lotSize < minLot) lotSize = minLot;
if(lotSize > maxLot) lotSize = maxLot;
return NormalizeDouble(lotSize, 2);
}
//+------------------------------------------------------------------+
//| Calculate Position Size by Method |
//+------------------------------------------------------------------+
double CRiskManager::CalculatePositionSizeByMethod(ENUM_POSITION_SIZING method,
double balance,
double stopLossPoints,
string symbol)
{
if(!m_isInitialized) return 0;
if(symbol == NULL) symbol = _Symbol;
if(stopLossPoints <= 0) return 0;
double lotSize = 0;
double riskAmount = CalculateRiskAmount(balance, m_maxRiskPerTrade);
switch(method)
{
case POSITION_SIZE_FIXED:
lotSize = 0.01;
break;
case POSITION_SIZE_RISK:
lotSize = InternalCalculateLotSize(riskAmount, stopLossPoints, symbol);
break;
case POSITION_SIZE_VOLATILITY:
{
double atr = GetATR(symbol, PERIOD_CURRENT, 14);
if(atr > 0)
{
double adjustedStop = atr * 2.0;
double adjustedRisk = riskAmount * 0.75;
lotSize = InternalCalculateLotSize(adjustedRisk, adjustedStop, symbol);
}
else
{
lotSize = 0.01;
}
}
break;
case POSITION_SIZE_KELLY:
{
double winRate = 0.55;
double avgWinLoss = 1.5;
double kellyPercent = (winRate * avgWinLoss - (1 - winRate)) / avgWinLoss;
kellyPercent = MathMax(0, MathMin(kellyPercent, 0.25));
double kellyRisk = balance * kellyPercent;
lotSize = InternalCalculateLotSize(kellyRisk, stopLossPoints, symbol);
}
break;
default:
lotSize = 0.01;
break;
}
// Apply limits
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
lotSize = MathFloor(lotSize / lotStep) * lotStep;
lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
return NormalizeDouble(lotSize, 2);
}
//+------------------------------------------------------------------+
//| Calculate Risk Amount |
//+------------------------------------------------------------------+
double CRiskManager::CalculateRiskAmount(double balance, double riskPercent)
{
return balance * (riskPercent / 100.0);
}
//+------------------------------------------------------------------+
//| Check if Can Open New Position |
//+------------------------------------------------------------------+
bool CRiskManager::CanOpenNewPosition(string symbol, double proposedVolume)
{
if(!m_isInitialized) return false;
// Check position count
if(m_positionCount >= m_maxPositions)
{
LogRiskEvent(RISK_OVERLEVERAGED, "Max positions reached");
return false;
}
// Check daily loss
if(m_currentDailyLoss >= m_maxDailyLoss)
{
LogRiskEvent(RISK_DAILY_LOSS_LIMIT, "Daily loss limit reached");
return false;
}
// Check drawdown
if(m_currentDrawdown >= m_maxDrawdown)
{
LogRiskEvent(RISK_DRAWDOWN_LIMIT, "Drawdown limit reached");
return false;
}
// Check margin
double requiredMargin = 0;
if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, proposedVolume,
SymbolInfoDouble(symbol, SYMBOL_ASK), requiredMargin))
{
return false;
}
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
if(requiredMargin > freeMargin * 0.9) // Keep 10% buffer
{
LogRiskEvent(RISK_MARGIN_CALL, "Insufficient margin");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check if Risk Limit Exceeded |
//+------------------------------------------------------------------+
bool CRiskManager::IsRiskLimitExceeded()
{
UpdateRiskMetrics();
if(m_currentMetrics.totalRisk > m_maxRiskPerTrade) return true;
if(m_currentMetrics.currentDrawdown > m_maxDrawdown) return true;
if(m_currentMetrics.dailyLossPercent > m_maxDailyLoss) return true;
if(m_currentMetrics.leverage > m_maxLeverage) return true;
if(m_currentMetrics.marginLevel < 100 && m_currentMetrics.marginLevel > 0) return true;
return false;
}
//+------------------------------------------------------------------+
//| Get Current Risk Metrics |
//+------------------------------------------------------------------+
RiskMetrics CRiskManager::GetCurrentRiskMetrics()
{
UpdateRiskMetrics();
return m_currentMetrics;
}
//+------------------------------------------------------------------+
//| Get Available Risk |
//+------------------------------------------------------------------+
double CRiskManager::GetAvailableRisk()
{
double usedRisk = m_currentMetrics.totalRisk;
double availableRisk = m_maxRiskPerTrade - usedRisk;
return MathMax(0, availableRisk);
}
//+------------------------------------------------------------------+
//| Calculate Stop Loss |
//+------------------------------------------------------------------+
double CRiskManager::CalculateStopLoss(double entryPrice, bool isBuy,
ENUM_SL_MODE mode, string symbol)
{
if(symbol == NULL) symbol = _Symbol;
double sl = 0;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
switch(mode)
{
case SL_FIXED:
sl = isBuy ? entryPrice - (100 * point) : entryPrice + (100 * point);
break;
case SL_ATR:
{
double atr = GetATR(symbol, PERIOD_CURRENT, 14);
if(atr > 0)
{
sl = isBuy ? entryPrice - (atr * 2.0) : entryPrice + (atr * 2.0);
}
else
{
sl = isBuy ? entryPrice - (100 * point) : entryPrice + (100 * point);
}
}
break;
case SL_PERCENTAGE:
sl = isBuy ? entryPrice * 0.98 : entryPrice * 1.02;
break;
default:
sl = isBuy ? entryPrice - (100 * point) : entryPrice + (100 * point);
break;
}
return NormalizeDouble(sl, digits);
}
//+------------------------------------------------------------------+
//| Calculate Take Profit |
//+------------------------------------------------------------------+
double CRiskManager::CalculateTakeProfit(double entryPrice, double stopLoss,
bool isBuy, double riskRewardRatio)
{
double risk = MathAbs(entryPrice - stopLoss);
double reward = risk * riskRewardRatio;
double tp = isBuy ? entryPrice + reward : entryPrice - reward;
string symbol = _Symbol;
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
return NormalizeDouble(tp, digits);
}
//+------------------------------------------------------------------+
//| Update Risk Metrics |
//+------------------------------------------------------------------+
void CRiskManager::UpdateRiskMetrics()
{
// Update daily metrics if new day
UpdateDailyMetrics();
// Update drawdown
UpdateDrawdown();
// Calculate position risks
CalculatePositionRisks();
// Calculate total metrics
double totalRisk = 0;
double totalExposure = 0;
for(int i = 0; i < m_positionCount; i++)
{
totalRisk += m_positions[i].riskPercent;
totalExposure += m_positions[i].volume * m_positions[i].marginUsed;
}
// Update metrics structure
m_currentMetrics.totalRisk = totalRisk;
m_currentMetrics.maxRisk = m_maxRiskPerTrade;
m_currentMetrics.currentDrawdown = m_currentDrawdown;
m_currentMetrics.maxDrawdown = m_maxDrawdown;
m_currentMetrics.dailyLoss = m_currentDailyLoss;
m_currentMetrics.dailyLossPercent = (AccountInfoDouble(ACCOUNT_BALANCE) > 0) ?
(m_currentDailyLoss / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0;
m_currentMetrics.marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
m_currentMetrics.freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
m_currentMetrics.leverage = (AccountInfoDouble(ACCOUNT_EQUITY) > 0) ?
totalExposure / AccountInfoDouble(ACCOUNT_EQUITY) : 0;
m_currentMetrics.openPositions = m_positionCount;
m_lastUpdateTime = TimeCurrent();
}
//+------------------------------------------------------------------+
//| Update Daily Metrics |
//+------------------------------------------------------------------+
void CRiskManager::UpdateDailyMetrics()
{
MqlDateTime current, last;
TimeToStruct(TimeCurrent(), current);
TimeToStruct(m_lastResetTime, last);
// Reset daily metrics if new day
if(current.day != last.day || current.mon != last.mon || current.year != last.year)
{
m_currentDailyLoss = 0;
m_lastResetTime = TimeCurrent();
CLogger::Log(LOG_INFO, "Daily risk metrics reset");
}
}
//+------------------------------------------------------------------+
//| Update Drawdown |
//+------------------------------------------------------------------+
void CRiskManager::UpdateDrawdown()
{
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
if(currentBalance > m_peakBalance)
{
m_peakBalance = currentBalance;
}
if(m_peakBalance > 0)
{
m_currentDrawdown = ((m_peakBalance - currentBalance) / m_peakBalance) * 100;
}
else
{
m_currentDrawdown = 0;
}
}
//+------------------------------------------------------------------+
//| Calculate Position Risks |
//+------------------------------------------------------------------+
void CRiskManager::CalculatePositionRisks()
{
ArrayResize(m_positions, 0);
m_positionCount = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionSelectByTicket(ticket))
{
PositionRisk risk;
risk.ticket = ticket;
risk.symbol = PositionGetString(POSITION_SYMBOL);
risk.volume = PositionGetDouble(POSITION_VOLUME);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
double stopLoss = PositionGetDouble(POSITION_SL);
// Calculate risk amount
if(stopLoss > 0)
{
double riskPoints = MathAbs(openPrice - stopLoss);
double tickValue = SymbolInfoDouble(risk.symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(risk.symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize > 0)
{
risk.riskAmount = (riskPoints / tickSize) * tickValue * risk.volume;
}
}
risk.riskPercent = (AccountInfoDouble(ACCOUNT_BALANCE) > 0) ?
(risk.riskAmount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0;
risk.currentLoss = PositionGetDouble(POSITION_PROFIT);
// Calculate margin used (MQL5 doesn't have POSITION_MARGIN directly)
double marginInit = 0;
double marginMaint = 0;
if(OrderCalcMargin(ORDER_TYPE_BUY, risk.symbol, risk.volume,
openPrice, marginInit))
{
risk.marginUsed = marginInit;
}
else
{
risk.marginUsed = 0;
}
ArrayResize(m_positions, m_positionCount + 1);
m_positions[m_positionCount] = risk;
m_positionCount++;
}
}
}
//+------------------------------------------------------------------+
//| Evaluate Risk Action |
//+------------------------------------------------------------------+
ENUM_RISK_ACTION CRiskManager::EvaluateRiskAction()
{
if(!m_isInitialized) return ACTION_NONE;
UpdateRiskMetrics();
// Critical - close all
if(m_currentMetrics.marginLevel < 50 && m_currentMetrics.marginLevel > 0)
return ACTION_CLOSE_ALL;
if(m_currentMetrics.currentDrawdown > m_maxDrawdown)
return ACTION_CLOSE_ALL;
// High risk - close partial
if(m_currentMetrics.dailyLossPercent > m_maxDailyLoss * 0.8)
return ACTION_CLOSE_PARTIAL;
if(m_currentMetrics.totalRisk > m_maxRiskPerTrade * 0.9)
return ACTION_REDUCE_SIZE;
// Warning only
if(m_currentMetrics.leverage > m_maxLeverage * 0.8)
return ACTION_WARN;
return ACTION_NONE;
}
//+------------------------------------------------------------------+
//| Execute Risk Action |
//+------------------------------------------------------------------+
void CRiskManager::ExecuteRiskAction(ENUM_RISK_ACTION action)
{
switch(action)
{
case ACTION_WARN:
Alert("Risk Warning: Approaching risk limits");
break;
case ACTION_REDUCE_SIZE:
CLogger::Log(LOG_WARNING, "Reducing position sizes due to high risk");
break;
case ACTION_CLOSE_PARTIAL:
CLogger::Log(LOG_WARNING, "Closing partial positions due to risk limits");
break;
case ACTION_CLOSE_ALL:
CLogger::Log(LOG_ERROR, "EMERGENCY: Closing all positions");
break;
case ACTION_STOP_TRADING:
CLogger::Log(LOG_ERROR, "Trading suspended due to risk limits");
break;
default:
break;
}
}
//+------------------------------------------------------------------+
//| Get ATR Value |
//+------------------------------------------------------------------+
double CRiskManager::GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period)
{
int atrHandle = iATR(symbol, timeframe, period);
if(atrHandle == INVALID_HANDLE) return 0;
double atr[];
ArraySetAsSeries(atr, true);
if(CopyBuffer(atrHandle, 0, 0, 1, atr) <= 0)
{
IndicatorRelease(atrHandle);
return 0;
}
IndicatorRelease(atrHandle);
return atr[0];
}
//+------------------------------------------------------------------+
//| Get Volatility |
//+------------------------------------------------------------------+
double CRiskManager::GetVolatility(string symbol)
{
return GetATR(symbol, PERIOD_H1, 14);
}
//+------------------------------------------------------------------+
//| Check if High Volatility |
//+------------------------------------------------------------------+
bool CRiskManager::IsHighVolatility(string symbol)
{
double currentVol = GetVolatility(symbol);
double avgVol = GetATR(symbol, PERIOD_D1, 20);
return (avgVol > 0 && currentVol > avgVol * 1.5);
}
//+------------------------------------------------------------------+
//| Calculate Correlation |
//+------------------------------------------------------------------+
double CRiskManager::CalculateCorrelation(const double &prices1[], const double &prices2[])
{
int n = MathMin(ArraySize(prices1), ArraySize(prices2));
if(n < 2) return 0;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
for(int i = 0; i < n; i++)
{
sumX += prices1[i];
sumY += prices2[i];
sumXY += prices1[i] * prices2[i];
sumX2 += prices1[i] * prices1[i];
sumY2 += prices2[i] * prices2[i];
}
double numerator = n * sumXY - sumX * sumY;
double denominator = MathSqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
if(denominator == 0) return 0;
return numerator / denominator;
}
//+------------------------------------------------------------------+
//| Check Risk Limits |
//+------------------------------------------------------------------+
bool CRiskManager::CheckRiskLimits(double proposedRisk)
{
double totalRisk = m_currentMetrics.totalRisk + proposedRisk;
return (totalRisk <= m_maxRiskPerTrade);
}
//+------------------------------------------------------------------+
//| Log Risk Event |
//+------------------------------------------------------------------+
void CRiskManager::LogRiskEvent(ENUM_RISK_EVENT event, string details)
{
// Log the risk event directly using CUtilities static method
string eventName = "";
switch(event)
{
case RISK_OVERLEVERAGED: eventName = "OVERLEVERAGED"; break;
case RISK_CORRELATION_HIGH: eventName = "HIGH_CORRELATION"; break;
case RISK_DRAWDOWN_LIMIT: eventName = "DRAWDOWN_LIMIT"; break;
case RISK_DAILY_LOSS_LIMIT: eventName = "DAILY_LOSS_LIMIT"; break;
case RISK_MARGIN_CALL: eventName = "MARGIN_CALL"; break;
case RISK_VOLATILITY_SPIKE: eventName = "VOLATILITY_SPIKE"; break;
case RISK_LIQUIDITY_ISSUE: eventName = "LIQUIDITY_ISSUE"; break;
default: eventName = "UNKNOWN"; break;
}
// Log the event
CLogger::Log(LOG_WARNING, "Risk Event: " + eventName);
// Log details if provided
if(details != "")
{
CLogger::Log(LOG_WARNING, "Risk Event Details: " + details);
}
// Also call the static CUtilities method if you want to use it
// Note: CUtilities::LogRiskEvent is a static method that takes only the event parameter
CUtilities::LogRiskEvent(event);
}
#endif // RISK_MANAGER_MQH