530 lines
18 KiB
MQL5
530 lines
18 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| RiskManager.mqh - Advanced Risk Management System |
|
||
|
//| Professional Risk Control and Position Sizing |
|
||
|
//+------------------------------------------------------------------+
|
||
|
|
||
|
#ifndef RISK_MANAGER_MQH
|
||
|
#define RISK_MANAGER_MQH
|
||
|
|
||
|
enum ENUM_RISK_LEVEL
|
||
|
{
|
||
|
RISK_LOW = 1,
|
||
|
RISK_MEDIUM = 2,
|
||
|
RISK_HIGH = 3,
|
||
|
RISK_EXTREME = 4
|
||
|
};
|
||
|
|
||
|
struct RiskParameters
|
||
|
{
|
||
|
double maxRiskPerTrade; // Maximum risk per trade (%)
|
||
|
double maxDailyRisk; // Maximum daily risk (%)
|
||
|
double maxDrawdown; // Maximum allowed drawdown (%)
|
||
|
double maxCorrelation; // Maximum correlation between positions
|
||
|
double maxPositionSize; // Maximum position size (%)
|
||
|
int maxOpenPositions; // Maximum number of open positions
|
||
|
double emergencyStopLevel; // Emergency stop level (%)
|
||
|
};
|
||
|
|
||
|
struct PositionRisk
|
||
|
{
|
||
|
ulong ticket;
|
||
|
string symbol;
|
||
|
double volume;
|
||
|
double riskAmount;
|
||
|
double riskPercent;
|
||
|
double unrealizedPL;
|
||
|
double var95; // Value at Risk 95%
|
||
|
double correlationScore;
|
||
|
};
|
||
|
|
||
|
struct PortfolioRisk
|
||
|
{
|
||
|
double totalRisk;
|
||
|
double dailyRisk;
|
||
|
double portfolioVar;
|
||
|
double currentDrawdown;
|
||
|
double correlationRisk;
|
||
|
ENUM_RISK_LEVEL riskLevel;
|
||
|
string riskStatus;
|
||
|
};
|
||
|
|
||
|
class CAdvancedRiskManager
|
||
|
{
|
||
|
private:
|
||
|
RiskParameters m_riskParams;
|
||
|
double m_accountBalance;
|
||
|
double m_accountEquity;
|
||
|
double m_startingBalance;
|
||
|
double m_peakBalance;
|
||
|
double m_dailyStartBalance;
|
||
|
|
||
|
// Risk tracking
|
||
|
double m_currentRisk;
|
||
|
double m_dailyRisk;
|
||
|
double m_maxHistoricalDrawdown;
|
||
|
datetime m_lastResetTime;
|
||
|
|
||
|
// Position tracking
|
||
|
PositionRisk m_positions[50]; // Max 50 positions
|
||
|
int m_positionCount;
|
||
|
|
||
|
public:
|
||
|
CAdvancedRiskManager(void);
|
||
|
~CAdvancedRiskManager(void);
|
||
|
|
||
|
// Initialization
|
||
|
bool Initialize(void);
|
||
|
void SetRiskParameters(RiskParameters ¶ms);
|
||
|
|
||
|
// Risk Assessment
|
||
|
bool IsTradeAllowed(string symbol, ENUM_ORDER_TYPE orderType, double volume);
|
||
|
double CalculateOptimalLotSize(string symbol, double riskPercent, double stopLossPips);
|
||
|
double CalculatePositionRisk(string symbol, double volume, double stopLoss);
|
||
|
|
||
|
// Portfolio Risk Management
|
||
|
PortfolioRisk GetPortfolioRisk(void);
|
||
|
bool CheckRiskLimits(void);
|
||
|
double GetMaxAllowedVolume(string symbol);
|
||
|
|
||
|
// Position Management
|
||
|
void UpdatePositionRisk(void);
|
||
|
void AddPosition(ulong ticket, string symbol, double volume, double stopLoss);
|
||
|
void RemovePosition(ulong ticket);
|
||
|
|
||
|
// Risk Monitoring
|
||
|
ENUM_RISK_LEVEL AssessRiskLevel(void);
|
||
|
bool IsEmergencyStop(void);
|
||
|
void UpdateDailyRisk(void);
|
||
|
void ResetDailyCounters(void);
|
||
|
|
||
|
// Value at Risk (VaR)
|
||
|
double CalculateVaR(string symbol, double volume, int confidence = 95);
|
||
|
double CalculatePortfolioVaR(void);
|
||
|
|
||
|
// Drawdown Management
|
||
|
double GetCurrentDrawdown(void);
|
||
|
double GetMaxDrawdown(void);
|
||
|
bool IsDrawdownExceeded(void);
|
||
|
|
||
|
// Utility Methods
|
||
|
void PrintRiskReport(void);
|
||
|
string GetRiskStatusString(void);
|
||
|
double GetRiskReward(double stopLoss, double takeProfit);
|
||
|
|
||
|
// Getters
|
||
|
double GetCurrentRisk(void) { return m_currentRisk; }
|
||
|
double GetDailyRisk(void) { return m_dailyRisk; }
|
||
|
int GetPositionCount(void) { return m_positionCount; }
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CAdvancedRiskManager::CAdvancedRiskManager(void)
|
||
|
{
|
||
|
// Initialize default risk parameters
|
||
|
m_riskParams.maxRiskPerTrade = 2.0;
|
||
|
m_riskParams.maxDailyRisk = 6.0;
|
||
|
m_riskParams.maxDrawdown = 15.0;
|
||
|
m_riskParams.maxCorrelation = 0.7;
|
||
|
m_riskParams.maxPositionSize = 10.0;
|
||
|
m_riskParams.maxOpenPositions = 5;
|
||
|
m_riskParams.emergencyStopLevel = 20.0;
|
||
|
|
||
|
m_currentRisk = 0;
|
||
|
m_dailyRisk = 0;
|
||
|
m_positionCount = 0;
|
||
|
m_maxHistoricalDrawdown = 0;
|
||
|
m_lastResetTime = TimeCurrent();
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CAdvancedRiskManager::~CAdvancedRiskManager(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Initialize risk manager |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CAdvancedRiskManager::Initialize(void)
|
||
|
{
|
||
|
m_accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
m_accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
m_startingBalance = m_accountBalance;
|
||
|
m_peakBalance = m_accountBalance;
|
||
|
m_dailyStartBalance = m_accountBalance;
|
||
|
|
||
|
UpdatePositionRisk();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Set risk parameters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::SetRiskParameters(RiskParameters ¶ms)
|
||
|
{
|
||
|
m_riskParams = params;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Check if trade is allowed |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CAdvancedRiskManager::IsTradeAllowed(string symbol, ENUM_ORDER_TYPE orderType, double volume)
|
||
|
{
|
||
|
// Update current risk state
|
||
|
UpdatePositionRisk();
|
||
|
UpdateDailyRisk();
|
||
|
|
||
|
// Check emergency stop
|
||
|
if(IsEmergencyStop())
|
||
|
{
|
||
|
Print("🛑 Emergency stop activated - no new trades allowed");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check daily risk limit
|
||
|
if(m_dailyRisk >= m_riskParams.maxDailyRisk)
|
||
|
{
|
||
|
Print("⚠️ Daily risk limit exceeded: ", DoubleToString(m_dailyRisk, 2), "%");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check maximum positions
|
||
|
if(m_positionCount >= m_riskParams.maxOpenPositions)
|
||
|
{
|
||
|
Print("⚠️ Maximum position count reached: ", m_positionCount);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check drawdown
|
||
|
if(IsDrawdownExceeded())
|
||
|
{
|
||
|
Print("⚠️ Maximum drawdown exceeded: ", DoubleToString(GetCurrentDrawdown(), 2), "%");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check position size limit
|
||
|
double positionRisk = CalculatePositionRisk(symbol, volume, 0);
|
||
|
if(positionRisk > m_riskParams.maxRiskPerTrade)
|
||
|
{
|
||
|
Print("⚠️ Position risk too high: ", DoubleToString(positionRisk, 2), "%");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate optimal lot size |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CAdvancedRiskManager::CalculateOptimalLotSize(string symbol, double riskPercent, double stopLossPips)
|
||
|
{
|
||
|
if(stopLossPips <= 0) return 0;
|
||
|
|
||
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
double riskAmount = accountBalance * riskPercent / 100.0;
|
||
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
double pointValue = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||
|
double pipValue = tickValue * stopLossPips;
|
||
|
|
||
|
if(pipValue <= 0) return 0;
|
||
|
|
||
|
double lotSize = riskAmount / pipValue;
|
||
|
|
||
|
// Normalize to symbol specifications
|
||
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||
|
double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
|
||
|
lotSize = MathMax(minLot, lotSize);
|
||
|
lotSize = MathMin(maxLot, lotSize);
|
||
|
lotSize = MathRound(lotSize / stepLot) * stepLot;
|
||
|
|
||
|
return lotSize;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate position risk |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CAdvancedRiskManager::CalculatePositionRisk(string symbol, double volume, double stopLoss)
|
||
|
{
|
||
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
double currentPrice = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||
|
|
||
|
double riskAmount = 0;
|
||
|
if(stopLoss > 0)
|
||
|
{
|
||
|
double stopLossPips = MathAbs(currentPrice - stopLoss) / SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||
|
riskAmount = volume * stopLossPips * tickValue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Use default 2% risk if no stop loss specified
|
||
|
riskAmount = accountBalance * 0.02;
|
||
|
}
|
||
|
|
||
|
return (riskAmount / accountBalance) * 100.0;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get portfolio risk |
|
||
|
//+------------------------------------------------------------------+
|
||
|
PortfolioRisk CAdvancedRiskManager::GetPortfolioRisk(void)
|
||
|
{
|
||
|
PortfolioRisk risk;
|
||
|
|
||
|
UpdatePositionRisk();
|
||
|
|
||
|
risk.totalRisk = m_currentRisk;
|
||
|
risk.dailyRisk = m_dailyRisk;
|
||
|
risk.portfolioVar = CalculatePortfolioVaR();
|
||
|
risk.currentDrawdown = GetCurrentDrawdown();
|
||
|
risk.correlationRisk = 0; // Simplified for now
|
||
|
risk.riskLevel = AssessRiskLevel();
|
||
|
risk.riskStatus = GetRiskStatusString();
|
||
|
|
||
|
return risk;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Assess risk level |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ENUM_RISK_LEVEL CAdvancedRiskManager::AssessRiskLevel(void)
|
||
|
{
|
||
|
double riskScore = 0;
|
||
|
|
||
|
// Risk from current positions
|
||
|
riskScore += (m_currentRisk / m_riskParams.maxRiskPerTrade) * 25;
|
||
|
|
||
|
// Risk from daily exposure
|
||
|
riskScore += (m_dailyRisk / m_riskParams.maxDailyRisk) * 25;
|
||
|
|
||
|
// Risk from drawdown
|
||
|
double drawdown = GetCurrentDrawdown();
|
||
|
riskScore += (drawdown / m_riskParams.maxDrawdown) * 25;
|
||
|
|
||
|
// Position count risk
|
||
|
riskScore += ((double)m_positionCount / m_riskParams.maxOpenPositions) * 25;
|
||
|
|
||
|
if(riskScore < 25) return RISK_LOW;
|
||
|
if(riskScore < 50) return RISK_MEDIUM;
|
||
|
if(riskScore < 75) return RISK_HIGH;
|
||
|
return RISK_EXTREME;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Check if emergency stop is needed |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CAdvancedRiskManager::IsEmergencyStop(void)
|
||
|
{
|
||
|
double currentDrawdown = GetCurrentDrawdown();
|
||
|
return (currentDrawdown >= m_riskParams.emergencyStopLevel);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get current drawdown |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CAdvancedRiskManager::GetCurrentDrawdown(void)
|
||
|
{
|
||
|
m_accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
m_peakBalance = MathMax(m_peakBalance, m_accountEquity);
|
||
|
|
||
|
if(m_peakBalance <= 0) return 0;
|
||
|
|
||
|
double drawdown = ((m_peakBalance - m_accountEquity) / m_peakBalance) * 100.0;
|
||
|
m_maxHistoricalDrawdown = MathMax(m_maxHistoricalDrawdown, drawdown);
|
||
|
|
||
|
return drawdown;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Check if drawdown is exceeded |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CAdvancedRiskManager::IsDrawdownExceeded(void)
|
||
|
{
|
||
|
return (GetCurrentDrawdown() >= m_riskParams.maxDrawdown);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Update position risk |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::UpdatePositionRisk(void)
|
||
|
{
|
||
|
m_positionCount = 0;
|
||
|
m_currentRisk = 0;
|
||
|
|
||
|
for(int i = 0; i < PositionsTotal(); i++)
|
||
|
{
|
||
|
if(PositionSelectByTicket(PositionGetTicket(i)))
|
||
|
{
|
||
|
double unrealizedPL = PositionGetDouble(POSITION_PROFIT);
|
||
|
double volume = PositionGetDouble(POSITION_VOLUME);
|
||
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
||
|
|
||
|
if(m_positionCount < 50) // Max 50 positions
|
||
|
{
|
||
|
m_positions[m_positionCount].ticket = PositionGetInteger(POSITION_TICKET);
|
||
|
m_positions[m_positionCount].symbol = symbol;
|
||
|
m_positions[m_positionCount].volume = volume;
|
||
|
m_positions[m_positionCount].unrealizedPL = unrealizedPL;
|
||
|
m_positions[m_positionCount].riskPercent = CalculatePositionRisk(symbol, volume, PositionGetDouble(POSITION_SL));
|
||
|
|
||
|
m_currentRisk += m_positions[m_positionCount].riskPercent;
|
||
|
m_positionCount++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate portfolio Value at Risk |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CAdvancedRiskManager::CalculatePortfolioVaR(void)
|
||
|
{
|
||
|
// Simplified VaR calculation
|
||
|
// In a real implementation, this would use historical data and Monte Carlo simulation
|
||
|
|
||
|
double totalExposure = 0;
|
||
|
for(int i = 0; i < m_positionCount; i++)
|
||
|
{
|
||
|
totalExposure += m_positions[i].riskAmount;
|
||
|
}
|
||
|
|
||
|
// Assume 95% confidence level with 1.65 standard deviations
|
||
|
double portfolioVolatility = 0.02; // 2% daily volatility assumption
|
||
|
double var95 = totalExposure * 1.65 * portfolioVolatility;
|
||
|
|
||
|
return var95;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Print risk report |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::PrintRiskReport(void)
|
||
|
{
|
||
|
PortfolioRisk risk = GetPortfolioRisk();
|
||
|
|
||
|
Print("=== RISK MANAGEMENT REPORT ===");
|
||
|
Print("Current Risk: ", DoubleToString(risk.totalRisk, 2), "%");
|
||
|
Print("Daily Risk: ", DoubleToString(risk.dailyRisk, 2), "%");
|
||
|
Print("Portfolio VaR: $", DoubleToString(risk.portfolioVar, 2));
|
||
|
Print("Current Drawdown: ", DoubleToString(risk.currentDrawdown, 2), "%");
|
||
|
Print("Risk Level: ", EnumToString(risk.riskLevel));
|
||
|
Print("Open Positions: ", m_positionCount);
|
||
|
Print("=============================");
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get risk status string |
|
||
|
//+------------------------------------------------------------------+
|
||
|
string CAdvancedRiskManager::GetRiskStatusString(void)
|
||
|
{
|
||
|
ENUM_RISK_LEVEL level = AssessRiskLevel();
|
||
|
|
||
|
switch(level)
|
||
|
{
|
||
|
case RISK_LOW: return "🟢 LOW RISK";
|
||
|
case RISK_MEDIUM: return "🟡 MEDIUM RISK";
|
||
|
case RISK_HIGH: return "🟠 HIGH RISK";
|
||
|
case RISK_EXTREME: return "🔴 EXTREME RISK";
|
||
|
default: return "UNKNOWN";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Update daily risk |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::UpdateDailyRisk(void)
|
||
|
{
|
||
|
// Check if new day
|
||
|
datetime currentTime = TimeCurrent();
|
||
|
MqlDateTime timeStruct;
|
||
|
TimeToStruct(currentTime, timeStruct);
|
||
|
|
||
|
MqlDateTime lastResetStruct;
|
||
|
TimeToStruct(m_lastResetTime, lastResetStruct);
|
||
|
|
||
|
if(timeStruct.day != lastResetStruct.day)
|
||
|
{
|
||
|
ResetDailyCounters();
|
||
|
}
|
||
|
|
||
|
// Calculate daily P&L
|
||
|
double dailyPL = m_accountEquity - m_dailyStartBalance;
|
||
|
m_dailyRisk = MathAbs(dailyPL / m_dailyStartBalance) * 100.0;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Reset daily counters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::ResetDailyCounters(void)
|
||
|
{
|
||
|
m_dailyStartBalance = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
m_dailyRisk = 0;
|
||
|
m_lastResetTime = TimeCurrent();
|
||
|
Print("📅 Daily risk counters reset");
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get max drawdown |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CAdvancedRiskManager::GetMaxDrawdown(void)
|
||
|
{
|
||
|
return m_maxHistoricalDrawdown;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Add position to tracking |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::AddPosition(ulong ticket, string symbol, double volume, double stopLoss)
|
||
|
{
|
||
|
if(m_positionCount < 50)
|
||
|
{
|
||
|
m_positions[m_positionCount].ticket = ticket;
|
||
|
m_positions[m_positionCount].symbol = symbol;
|
||
|
m_positions[m_positionCount].volume = volume;
|
||
|
m_positions[m_positionCount].riskPercent = CalculatePositionRisk(symbol, volume, stopLoss);
|
||
|
m_positionCount++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Remove position from tracking |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CAdvancedRiskManager::RemovePosition(ulong ticket)
|
||
|
{
|
||
|
for(int i = 0; i < m_positionCount; i++)
|
||
|
{
|
||
|
if(m_positions[i].ticket == ticket)
|
||
|
{
|
||
|
// Shift remaining positions
|
||
|
for(int j = i; j < m_positionCount - 1; j++)
|
||
|
{
|
||
|
m_positions[j] = m_positions[j + 1];
|
||
|
}
|
||
|
m_positionCount--;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate VaR for specific position |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CAdvancedRiskManager::CalculateVaR(string symbol, double volume, int confidence = 95)
|
||
|
{
|
||
|
// Simplified VaR calculation
|
||
|
double volatility = 0.015; // 1.5% daily volatility assumption
|
||
|
double confidenceLevel = (confidence == 95) ? 1.65 : 2.33; // 95% or 99%
|
||
|
|
||
|
double positionValue = volume * SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||
|
double var = positionValue * volatility * confidenceLevel;
|
||
|
|
||
|
return var;
|
||
|
}
|
||
|
|
||
|
#endif // RISK_MANAGER_MQH
|