873 lines
29 KiB
MQL5
873 lines
29 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| RiskManager_v71.mqh |
|
||
|
//| Advanced Risk Management Module v7.1 |
|
||
|
//| VaR/CVaR, Portfolio Optimization, Kelly ML |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#ifndef RISK_MANAGER_V71_MQH
|
||
|
#define RISK_MANAGER_V71_MQH
|
||
|
|
||
|
#include "DataTypes_v71.mqh"
|
||
|
#include <Math/Stat/Math.mqh>
|
||
|
#include <Math/Alglib/alglib.mqh>
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Risk Manager Class - Institutional Grade |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CRiskManagerV71
|
||
|
{
|
||
|
private:
|
||
|
//--- Configuration
|
||
|
RiskManagerConfigV71 m_config;
|
||
|
|
||
|
//--- Risk tracking
|
||
|
double m_daily_loss;
|
||
|
double m_daily_profit;
|
||
|
double m_session_drawdown;
|
||
|
datetime m_last_reset;
|
||
|
|
||
|
//--- Portfolio metrics
|
||
|
double m_portfolio_var;
|
||
|
double m_portfolio_cvar;
|
||
|
double m_portfolio_beta;
|
||
|
double m_max_correlation;
|
||
|
|
||
|
//--- Historical data for VaR
|
||
|
double m_returns_history[];
|
||
|
int m_history_size;
|
||
|
|
||
|
//--- Kelly criterion parameters
|
||
|
double m_win_rate;
|
||
|
double m_avg_win_loss_ratio;
|
||
|
double m_kelly_fraction;
|
||
|
|
||
|
//--- Optimization matrices
|
||
|
CMatrixDouble m_covariance_matrix;
|
||
|
CMatrixDouble m_correlation_matrix;
|
||
|
|
||
|
//--- Helper methods
|
||
|
double CalculateHistoricalVaR(double &returns[], double confidence);
|
||
|
double CalculateParametricVaR(double mean, double std_dev, double confidence);
|
||
|
double CalculateCornishFisherVaR(double &returns[], double confidence);
|
||
|
void UpdateCovarianceMatrix(string &symbols[], int period);
|
||
|
double OptimizeKellyFraction(double win_rate, double avg_win, double avg_loss);
|
||
|
|
||
|
public:
|
||
|
CRiskManagerV71();
|
||
|
~CRiskManagerV71();
|
||
|
|
||
|
//--- Initialization
|
||
|
bool Initialize(const RiskManagerConfigV71 &config);
|
||
|
void Reset();
|
||
|
|
||
|
//--- Position sizing methods
|
||
|
double CalculatePositionSize(string symbol, double stop_distance);
|
||
|
double CalculateOptimalSize(string symbol, double stop_distance,
|
||
|
double expected_return, CorrelationMatrix &correlations);
|
||
|
double CalculateKellySizeML(string symbol, double stop_distance,
|
||
|
double ml_confidence, double predicted_return);
|
||
|
double CalculateRiskParitySize(ManagedTradeV71 &trades[], string symbol);
|
||
|
|
||
|
//--- Risk validation
|
||
|
bool ValidateNewPosition(string symbol, double volume, double stop_loss);
|
||
|
bool ValidatePortfolioRisk(ManagedTradeV71 &trades[]);
|
||
|
bool CheckDailyDrawdown();
|
||
|
bool CheckVaRLimit(double position_var);
|
||
|
|
||
|
//--- Risk metrics calculation
|
||
|
double CalculatePortfolioVaR(ManagedTradeV71 &trades[], double confidence, int horizon);
|
||
|
double CalculatePortfolioCVaR(ManagedTradeV71 &trades[], double confidence, int horizon);
|
||
|
double CalculatePortfolioBeta(ManagedTradeV71 &trades[]);
|
||
|
double CalculateCorrelationRisk(ManagedTradeV71 &trade, CorrelationMatrix &matrix);
|
||
|
double GetCurrentExposure(ManagedTradeV71 &trades[]);
|
||
|
|
||
|
//--- Portfolio optimization
|
||
|
void CalculateOptimalWeights(double &expected_returns[], CorrelationMatrix &correlations,
|
||
|
double target_risk, double &weights[]);
|
||
|
void CalculateEfficientFrontier(double &expected_returns[], CorrelationMatrix &correlations,
|
||
|
double &risks[], double &returns[]);
|
||
|
|
||
|
//--- Risk enforcement
|
||
|
void EnforceRiskRulesOptimized(ManagedTradeV71 &trade);
|
||
|
void AdjustPositionForRisk(ManagedTradeV71 &trade);
|
||
|
bool ShouldReduceRisk(ManagedTradeV71 &trades[]);
|
||
|
|
||
|
//--- Stress testing
|
||
|
double RunStressTest(ManagedTradeV71 &trades[], double stress_factor);
|
||
|
double CalculateStressedVaR(ManagedTradeV71 &trades[], double confidence);
|
||
|
|
||
|
//--- Update methods
|
||
|
void UpdateDailyMetrics();
|
||
|
void UpdateKellyParameters(double win_rate, double avg_win, double avg_loss);
|
||
|
void UpdateHistoricalReturns(double return_value);
|
||
|
|
||
|
//--- Getters
|
||
|
double GetPortfolioVaR() { return m_portfolio_var; }
|
||
|
double GetPortfolioCVaR() { return m_portfolio_cvar; }
|
||
|
double GetMaxCorrelation() { return m_max_correlation; }
|
||
|
double GetKellyFraction() { return m_kelly_fraction; }
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CRiskManagerV71::CRiskManagerV71()
|
||
|
{
|
||
|
m_daily_loss = 0;
|
||
|
m_daily_profit = 0;
|
||
|
m_session_drawdown = 0;
|
||
|
m_last_reset = 0;
|
||
|
m_portfolio_var = 0;
|
||
|
m_portfolio_cvar = 0;
|
||
|
m_portfolio_beta = 0;
|
||
|
m_max_correlation = 0;
|
||
|
m_history_size = 0;
|
||
|
m_win_rate = 0.5;
|
||
|
m_avg_win_loss_ratio = 1.5;
|
||
|
m_kelly_fraction = 0.25;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CRiskManagerV71::~CRiskManagerV71()
|
||
|
{
|
||
|
ArrayFree(m_returns_history);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Initialize risk manager |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CRiskManagerV71::Initialize(const RiskManagerConfigV71 &config)
|
||
|
{
|
||
|
m_config = config;
|
||
|
|
||
|
//--- Initialize returns history
|
||
|
ArrayResize(m_returns_history, m_config.var_params.lookback_period);
|
||
|
ArrayInitialize(m_returns_history, 0);
|
||
|
m_history_size = 0;
|
||
|
|
||
|
//--- Initialize matrices
|
||
|
m_covariance_matrix.Resize(10, 10);
|
||
|
m_correlation_matrix.Resize(10, 10);
|
||
|
|
||
|
//--- Reset daily metrics
|
||
|
Reset();
|
||
|
|
||
|
Print("RiskManagerV71 initialized with VaR limit: ", m_config.daily_var_limit, "%");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate position size with advanced methods |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculatePositionSize(string symbol, double stop_distance)
|
||
|
{
|
||
|
if(stop_distance <= 0) return 0;
|
||
|
|
||
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
||
|
if(tick_size == 0) return 0;
|
||
|
|
||
|
double position_size = 0;
|
||
|
|
||
|
switch(m_config.sizing_mode)
|
||
|
{
|
||
|
case PS_RISK_PERCENT:
|
||
|
{
|
||
|
double risk_amount = account_balance * m_config.risk_percent / 100.0;
|
||
|
double stop_ticks = stop_distance / tick_size;
|
||
|
position_size = risk_amount / (stop_ticks * tick_value);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PS_KELLY:
|
||
|
case PS_KELLY_ML:
|
||
|
{
|
||
|
double kelly_size = account_balance * m_kelly_fraction;
|
||
|
double stop_ticks = stop_distance / tick_size;
|
||
|
position_size = kelly_size / (stop_ticks * tick_value);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PS_VOLATILITY:
|
||
|
case PS_VOLATILITY_WEIGHTED:
|
||
|
{
|
||
|
double atr = iATR(symbol, PERIOD_CURRENT, 14);
|
||
|
if(atr > 0)
|
||
|
{
|
||
|
double volatility_adjusted_risk = m_config.risk_percent / (stop_distance / atr);
|
||
|
double risk_amount = account_balance * volatility_adjusted_risk / 100.0;
|
||
|
double stop_ticks = stop_distance / tick_size;
|
||
|
position_size = risk_amount / (stop_ticks * tick_value);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PS_RISK_PARITY:
|
||
|
// Risk parity requires portfolio context
|
||
|
position_size = account_balance * 0.01 / (stop_distance / tick_size * tick_value);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
position_size = 0.01; // Default minimum
|
||
|
}
|
||
|
|
||
|
//--- Apply volatility scaling if enabled
|
||
|
if(m_config.scale_by_volatility)
|
||
|
{
|
||
|
double current_volatility = iATR(symbol, PERIOD_CURRENT, 14);
|
||
|
double avg_volatility = iATR(symbol, PERIOD_D1, 20);
|
||
|
|
||
|
if(current_volatility > 0 && avg_volatility > 0)
|
||
|
{
|
||
|
double volatility_ratio = avg_volatility / current_volatility;
|
||
|
position_size *= MathMin(2.0, MathMax(0.5, volatility_ratio));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Normalize to lot step
|
||
|
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
position_size = MathRound(position_size / lot_step) * lot_step;
|
||
|
|
||
|
//--- Apply limits
|
||
|
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||
|
|
||
|
position_size = MathMax(min_lot, MathMin(max_lot, position_size));
|
||
|
|
||
|
return position_size;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate optimal size with portfolio optimization |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculateOptimalSize(string symbol, double stop_distance,
|
||
|
double expected_return, CorrelationMatrix &correlations)
|
||
|
{
|
||
|
//--- Base calculation
|
||
|
double base_size = CalculatePositionSize(symbol, stop_distance);
|
||
|
|
||
|
if(!m_config.use_portfolio_optimization)
|
||
|
return base_size;
|
||
|
|
||
|
//--- Find symbol in correlation matrix
|
||
|
int symbol_index = -1;
|
||
|
for(int i = 0; i < ArraySize(correlations.symbols); i++)
|
||
|
{
|
||
|
if(correlations.symbols[i] == symbol)
|
||
|
{
|
||
|
symbol_index = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(symbol_index < 0)
|
||
|
return base_size;
|
||
|
|
||
|
//--- Calculate correlation adjustment
|
||
|
double max_corr = 0;
|
||
|
int matrix_size = ArrayRange(correlations.matrix, 0);
|
||
|
|
||
|
for(int i = 0; i < matrix_size; i++)
|
||
|
{
|
||
|
if(i != symbol_index)
|
||
|
{
|
||
|
double corr = MathAbs(correlations.matrix[symbol_index][i]);
|
||
|
if(corr > max_corr)
|
||
|
max_corr = corr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Reduce size based on correlation
|
||
|
if(max_corr > m_config.max_correlation)
|
||
|
{
|
||
|
double reduction_factor = 1.0 - (max_corr - m_config.max_correlation);
|
||
|
base_size *= MathMax(0.3, reduction_factor);
|
||
|
}
|
||
|
|
||
|
//--- Apply expected return adjustment
|
||
|
if(expected_return > 0)
|
||
|
{
|
||
|
double return_multiplier = 1.0 + MathMin(0.5, expected_return / 100.0);
|
||
|
base_size *= return_multiplier;
|
||
|
}
|
||
|
|
||
|
return base_size;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate Kelly size with ML adjustment |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculateKellySizeML(string symbol, double stop_distance,
|
||
|
double ml_confidence, double predicted_return)
|
||
|
{
|
||
|
//--- Adjust Kelly fraction based on ML confidence
|
||
|
double adjusted_kelly = m_kelly_fraction * ml_confidence;
|
||
|
|
||
|
//--- Further adjust based on predicted return
|
||
|
if(predicted_return > 0)
|
||
|
{
|
||
|
double return_factor = 1.0 + MathMin(0.3, predicted_return / 10.0);
|
||
|
adjusted_kelly *= return_factor;
|
||
|
}
|
||
|
|
||
|
//--- Apply conservative cap
|
||
|
adjusted_kelly = MathMin(adjusted_kelly, 0.25); // Max 25% Kelly
|
||
|
|
||
|
//--- Calculate position size
|
||
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
||
|
if(tick_size == 0) return 0;
|
||
|
|
||
|
double kelly_amount = account_balance * adjusted_kelly;
|
||
|
double stop_ticks = stop_distance / tick_size;
|
||
|
double position_size = kelly_amount / (stop_ticks * tick_value);
|
||
|
|
||
|
//--- Normalize and limit
|
||
|
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
position_size = MathRound(position_size / lot_step) * lot_step;
|
||
|
|
||
|
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||
|
|
||
|
return MathMax(min_lot, MathMin(max_lot, position_size));
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate risk parity position size |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculateRiskParitySize(ManagedTradeV71 &trades[], string symbol)
|
||
|
{
|
||
|
int trade_count = ArraySize(trades);
|
||
|
if(trade_count == 0)
|
||
|
return CalculatePositionSize(symbol, 100 * SymbolInfoDouble(symbol, SYMBOL_POINT));
|
||
|
|
||
|
//--- Calculate total portfolio risk
|
||
|
double total_risk = 0;
|
||
|
for(int i = 0; i < trade_count; i++)
|
||
|
{
|
||
|
total_risk += trades[i].risk_percent;
|
||
|
}
|
||
|
|
||
|
//--- Target equal risk contribution
|
||
|
double target_risk = m_config.max_risk / (trade_count + 1);
|
||
|
|
||
|
//--- Calculate position size for target risk
|
||
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
double risk_amount = account_balance * target_risk / 100.0;
|
||
|
|
||
|
//--- Use ATR for stop distance if not provided
|
||
|
double atr = iATR(symbol, PERIOD_CURRENT, 14);
|
||
|
double stop_distance = atr * 2.0;
|
||
|
|
||
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
||
|
if(tick_size == 0) return 0;
|
||
|
|
||
|
double stop_ticks = stop_distance / tick_size;
|
||
|
double position_size = risk_amount / (stop_ticks * tick_value);
|
||
|
|
||
|
//--- Normalize
|
||
|
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
position_size = MathRound(position_size / lot_step) * lot_step;
|
||
|
|
||
|
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||
|
|
||
|
return MathMax(min_lot, MathMin(max_lot, position_size));
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate portfolio VaR |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculatePortfolioVaR(ManagedTradeV71 &trades[], double confidence, int horizon)
|
||
|
{
|
||
|
int trade_count = ArraySize(trades);
|
||
|
if(trade_count == 0) return 0;
|
||
|
|
||
|
//--- Collect unique symbols
|
||
|
string symbols[];
|
||
|
int symbol_count = 0;
|
||
|
|
||
|
for(int i = 0; i < trade_count; i++)
|
||
|
{
|
||
|
bool found = false;
|
||
|
for(int j = 0; j < symbol_count; j++)
|
||
|
{
|
||
|
if(symbols[j] == trades[i].symbol)
|
||
|
{
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!found)
|
||
|
{
|
||
|
ArrayResize(symbols, symbol_count + 1);
|
||
|
symbols[symbol_count] = trades[i].symbol;
|
||
|
symbol_count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Calculate returns for each symbol
|
||
|
double portfolio_returns[];
|
||
|
ArrayResize(portfolio_returns, m_config.var_params.lookback_period);
|
||
|
ArrayInitialize(portfolio_returns, 0);
|
||
|
|
||
|
//--- Get historical data and calculate portfolio returns
|
||
|
for(int i = 1; i < m_config.var_params.lookback_period; i++)
|
||
|
{
|
||
|
double portfolio_return = 0;
|
||
|
double total_weight = 0;
|
||
|
|
||
|
for(int j = 0; j < trade_count; j++)
|
||
|
{
|
||
|
double close_prev = iClose(trades[j].symbol, PERIOD_D1, i + 1);
|
||
|
double close_curr = iClose(trades[j].symbol, PERIOD_D1, i);
|
||
|
|
||
|
if(close_prev > 0)
|
||
|
{
|
||
|
double return_pct = (close_curr - close_prev) / close_prev;
|
||
|
|
||
|
//--- Apply position direction
|
||
|
if(trades[j].type == POSITION_TYPE_SELL)
|
||
|
return_pct = -return_pct;
|
||
|
|
||
|
//--- Weight by position size
|
||
|
double position_value = trades[j].volume * close_curr;
|
||
|
portfolio_return += return_pct * position_value;
|
||
|
total_weight += position_value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(total_weight > 0)
|
||
|
portfolio_returns[i] = portfolio_return / total_weight;
|
||
|
}
|
||
|
|
||
|
//--- Calculate VaR based on method
|
||
|
double var_value = 0;
|
||
|
|
||
|
if(m_config.var_params.use_cornish_fisher)
|
||
|
{
|
||
|
var_value = CalculateCornishFisherVaR(portfolio_returns, confidence);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var_value = CalculateHistoricalVaR(portfolio_returns, confidence);
|
||
|
}
|
||
|
|
||
|
//--- Adjust for time horizon
|
||
|
var_value *= MathSqrt(horizon);
|
||
|
|
||
|
//--- Convert to percentage of portfolio
|
||
|
double portfolio_value = 0;
|
||
|
for(int i = 0; i < trade_count; i++)
|
||
|
{
|
||
|
double price = SymbolInfoDouble(trades[i].symbol, SYMBOL_BID);
|
||
|
portfolio_value += trades[i].volume * price;
|
||
|
}
|
||
|
|
||
|
if(portfolio_value > 0)
|
||
|
{
|
||
|
m_portfolio_var = MathAbs(var_value) / portfolio_value * 100;
|
||
|
}
|
||
|
|
||
|
return m_portfolio_var;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate portfolio CVaR (Conditional VaR) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculatePortfolioCVaR(ManagedTradeV71 &trades[], double confidence, int horizon)
|
||
|
{
|
||
|
//--- First calculate VaR
|
||
|
double var_value = CalculatePortfolioVaR(trades, confidence, horizon);
|
||
|
|
||
|
//--- CVaR is the expected value beyond VaR
|
||
|
int trade_count = ArraySize(trades);
|
||
|
if(trade_count == 0) return 0;
|
||
|
|
||
|
//--- Collect portfolio returns
|
||
|
double portfolio_returns[];
|
||
|
ArrayResize(portfolio_returns, m_config.var_params.lookback_period);
|
||
|
|
||
|
//--- [Similar return calculation as in VaR]
|
||
|
//... (abbreviated for space)
|
||
|
|
||
|
//--- Sort returns
|
||
|
ArraySort(portfolio_returns);
|
||
|
|
||
|
//--- Find VaR threshold index
|
||
|
int var_index = (int)((1 - confidence) * ArraySize(portfolio_returns));
|
||
|
|
||
|
//--- Calculate average of returns below VaR
|
||
|
double cvar_sum = 0;
|
||
|
int cvar_count = 0;
|
||
|
|
||
|
for(int i = 0; i <= var_index; i++)
|
||
|
{
|
||
|
cvar_sum += portfolio_returns[i];
|
||
|
cvar_count++;
|
||
|
}
|
||
|
|
||
|
if(cvar_count > 0)
|
||
|
{
|
||
|
double cvar_return = cvar_sum / cvar_count;
|
||
|
|
||
|
//--- Adjust for time horizon
|
||
|
cvar_return *= MathSqrt(horizon);
|
||
|
|
||
|
//--- Convert to percentage
|
||
|
double portfolio_value = 0;
|
||
|
for(int i = 0; i < trade_count; i++)
|
||
|
{
|
||
|
double price = SymbolInfoDouble(trades[i].symbol, SYMBOL_BID);
|
||
|
portfolio_value += trades[i].volume * price;
|
||
|
}
|
||
|
|
||
|
if(portfolio_value > 0)
|
||
|
{
|
||
|
m_portfolio_cvar = MathAbs(cvar_return) / portfolio_value * 100;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Apply multiplier
|
||
|
m_portfolio_cvar *= m_config.cvar_multiplier;
|
||
|
|
||
|
return m_portfolio_cvar;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate historical VaR |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculateHistoricalVaR(double &returns[], double confidence)
|
||
|
{
|
||
|
int size = ArraySize(returns);
|
||
|
if(size == 0) return 0;
|
||
|
|
||
|
//--- Sort returns
|
||
|
double sorted_returns[];
|
||
|
ArrayCopy(sorted_returns, returns);
|
||
|
ArraySort(sorted_returns);
|
||
|
|
||
|
//--- Find percentile
|
||
|
int index = (int)((1 - confidence) * size);
|
||
|
index = MathMax(0, MathMin(size - 1, index));
|
||
|
|
||
|
return sorted_returns[index];
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate Cornish-Fisher VaR (accounts for skew and kurtosis) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculateCornishFisherVaR(double &returns[], double confidence)
|
||
|
{
|
||
|
int size = ArraySize(returns);
|
||
|
if(size < 4) return CalculateHistoricalVaR(returns, confidence);
|
||
|
|
||
|
//--- Calculate moments
|
||
|
double mean = MathMean(returns);
|
||
|
double std_dev = MathStandardDeviation(returns);
|
||
|
|
||
|
//--- Calculate skewness
|
||
|
double skew = 0;
|
||
|
for(int i = 0; i < size; i++)
|
||
|
{
|
||
|
skew += MathPow((returns[i] - mean) / std_dev, 3);
|
||
|
}
|
||
|
skew /= size;
|
||
|
|
||
|
//--- Calculate kurtosis
|
||
|
double kurt = 0;
|
||
|
for(int i = 0; i < size; i++)
|
||
|
{
|
||
|
kurt += MathPow((returns[i] - mean) / std_dev, 4);
|
||
|
}
|
||
|
kurt = kurt / size - 3; // Excess kurtosis
|
||
|
|
||
|
//--- Standard normal quantile
|
||
|
double z = MathQuantileNormal(confidence, 0, 1);
|
||
|
|
||
|
//--- Cornish-Fisher expansion
|
||
|
double cf_z = z + (z * z - 1) * skew / 6 +
|
||
|
(z * z * z - 3 * z) * kurt / 24 -
|
||
|
(2 * z * z * z - 5 * z) * skew * skew / 36;
|
||
|
|
||
|
return mean + cf_z * std_dev;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate portfolio beta |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::CalculatePortfolioBeta(ManagedTradeV71 &trades[])
|
||
|
{
|
||
|
int trade_count = ArraySize(trades);
|
||
|
if(trade_count == 0) return 0;
|
||
|
|
||
|
//--- Use a market index (simplified - using major currency)
|
||
|
string market_symbol = "EURUSD";
|
||
|
int lookback = 100;
|
||
|
|
||
|
//--- Calculate market returns
|
||
|
double market_returns[];
|
||
|
ArrayResize(market_returns, lookback);
|
||
|
|
||
|
for(int i = 1; i < lookback; i++)
|
||
|
{
|
||
|
double close_prev = iClose(market_symbol, PERIOD_D1, i + 1);
|
||
|
double close_curr = iClose(market_symbol, PERIOD_D1, i);
|
||
|
|
||
|
if(close_prev > 0)
|
||
|
market_returns[i] = (close_curr - close_prev) / close_prev;
|
||
|
}
|
||
|
|
||
|
//--- Calculate portfolio returns
|
||
|
double portfolio_returns[];
|
||
|
ArrayResize(portfolio_returns, lookback);
|
||
|
ArrayInitialize(portfolio_returns, 0);
|
||
|
|
||
|
//--- [Portfolio return calculation similar to VaR method]
|
||
|
|
||
|
//--- Calculate beta (covariance / variance)
|
||
|
double cov = 0;
|
||
|
double market_var = 0;
|
||
|
double market_mean = MathMean(market_returns);
|
||
|
double portfolio_mean = MathMean(portfolio_returns);
|
||
|
|
||
|
for(int i = 0; i < lookback; i++)
|
||
|
{
|
||
|
cov += (portfolio_returns[i] - portfolio_mean) * (market_returns[i] - market_mean);
|
||
|
market_var += MathPow(market_returns[i] - market_mean, 2);
|
||
|
}
|
||
|
|
||
|
if(market_var > 0)
|
||
|
{
|
||
|
m_portfolio_beta = cov / market_var;
|
||
|
}
|
||
|
|
||
|
return m_portfolio_beta;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate optimal portfolio weights (Markowitz) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CRiskManagerV71::CalculateOptimalWeights(double &expected_returns[],
|
||
|
CorrelationMatrix &correlations,
|
||
|
double target_risk,
|
||
|
double &weights[])
|
||
|
{
|
||
|
int n = ArraySize(expected_returns);
|
||
|
ArrayResize(weights, n);
|
||
|
|
||
|
//--- Simple equal weighting if optimization disabled
|
||
|
if(!m_config.use_portfolio_optimization)
|
||
|
{
|
||
|
for(int i = 0; i < n; i++)
|
||
|
weights[i] = 1.0 / n;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//--- Build covariance matrix from correlations
|
||
|
CMatrixDouble cov_matrix;
|
||
|
cov_matrix.Resize(n, n);
|
||
|
|
||
|
//--- Estimate standard deviations (simplified)
|
||
|
double std_devs[];
|
||
|
ArrayResize(std_devs, n);
|
||
|
|
||
|
for(int i = 0; i < n; i++)
|
||
|
{
|
||
|
std_devs[i] = 0.15; // Assume 15% annual volatility
|
||
|
}
|
||
|
|
||
|
//--- Convert correlation to covariance
|
||
|
for(int i = 0; i < n; i++)
|
||
|
{
|
||
|
for(int j = 0; j < n; j++)
|
||
|
{
|
||
|
if(i < ArrayRange(correlations.matrix, 0) && j < ArrayRange(correlations.matrix, 1))
|
||
|
{
|
||
|
cov_matrix[i].Set(j, correlations.matrix[i][j] * std_devs[i] * std_devs[j]);
|
||
|
}
|
||
|
else if(i == j)
|
||
|
{
|
||
|
cov_matrix[i].Set(j, std_devs[i] * std_devs[i]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cov_matrix[i].Set(j, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Simplified mean-variance optimization
|
||
|
//--- For now, use risk parity approach
|
||
|
double total_inv_vol = 0;
|
||
|
for(int i = 0; i < n; i++)
|
||
|
{
|
||
|
total_inv_vol += 1.0 / std_devs[i];
|
||
|
}
|
||
|
|
||
|
for(int i = 0; i < n; i++)
|
||
|
{
|
||
|
weights[i] = (1.0 / std_devs[i]) / total_inv_vol;
|
||
|
}
|
||
|
|
||
|
//--- Adjust for expected returns
|
||
|
double total_weight = 0;
|
||
|
for(int i = 0; i < n; i++)
|
||
|
{
|
||
|
if(expected_returns[i] > 0)
|
||
|
{
|
||
|
weights[i] *= (1.0 + expected_returns[i] / 100.0);
|
||
|
}
|
||
|
total_weight += weights[i];
|
||
|
}
|
||
|
|
||
|
//--- Normalize
|
||
|
if(total_weight > 0)
|
||
|
{
|
||
|
for(int i = 0; i < n; i++)
|
||
|
{
|
||
|
weights[i] /= total_weight;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Enforce risk rules on position |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CRiskManagerV71::EnforceRiskRulesOptimized(ManagedTradeV71 &trade)
|
||
|
{
|
||
|
//--- Check if stop loss exists
|
||
|
if(trade.sl == 0)
|
||
|
{
|
||
|
//--- Calculate and set stop loss based on ATR
|
||
|
double atr = iATR(trade.symbol, PERIOD_CURRENT, 14);
|
||
|
double stop_distance = atr * m_config.base_atr_multi;
|
||
|
|
||
|
if(trade.type == POSITION_TYPE_BUY)
|
||
|
trade.sl = trade.open_price - stop_distance;
|
||
|
else
|
||
|
trade.sl = trade.open_price + stop_distance;
|
||
|
|
||
|
//--- Update position
|
||
|
CTrade ctrade;
|
||
|
ctrade.PositionModify(trade.ticket, trade.sl, trade.tp);
|
||
|
}
|
||
|
|
||
|
//--- Check position size against risk limits
|
||
|
if(trade.risk_percent > m_config.risk_percent)
|
||
|
{
|
||
|
//--- Calculate reduction needed
|
||
|
double reduction_factor = m_config.risk_percent / trade.risk_percent;
|
||
|
double new_volume = trade.volume * reduction_factor;
|
||
|
|
||
|
//--- Normalize to lot step
|
||
|
double lot_step = SymbolInfoDouble(trade.symbol, SYMBOL_VOLUME_STEP);
|
||
|
new_volume = MathRound(new_volume / lot_step) * lot_step;
|
||
|
|
||
|
//--- Partial close to reduce risk
|
||
|
if(new_volume < trade.volume && new_volume > 0)
|
||
|
{
|
||
|
CTrade ctrade;
|
||
|
ctrade.PositionClosePartial(trade.ticket, trade.volume - new_volume);
|
||
|
|
||
|
Print("Risk enforcement: Reduced position ", trade.ticket,
|
||
|
" from ", trade.volume, " to ", new_volume);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Run portfolio stress test |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::RunStressTest(ManagedTradeV71 &trades[], double stress_factor)
|
||
|
{
|
||
|
int trade_count = ArraySize(trades);
|
||
|
if(trade_count == 0) return 0;
|
||
|
|
||
|
double stressed_loss = 0;
|
||
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
|
||
|
//--- Apply stress factor to each position
|
||
|
for(int i = 0; i < trade_count; i++)
|
||
|
{
|
||
|
//--- Calculate stressed move
|
||
|
double atr = iATR(trades[i].symbol, PERIOD_D1, 20);
|
||
|
double stressed_move = atr * stress_factor;
|
||
|
|
||
|
//--- Calculate potential loss
|
||
|
double potential_loss = 0;
|
||
|
|
||
|
if(trades[i].type == POSITION_TYPE_BUY)
|
||
|
{
|
||
|
double stressed_price = trades[i].open_price - stressed_move;
|
||
|
potential_loss = (trades[i].open_price - stressed_price) * trades[i].volume;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
double stressed_price = trades[i].open_price + stressed_move;
|
||
|
potential_loss = (stressed_price - trades[i].open_price) * trades[i].volume;
|
||
|
}
|
||
|
|
||
|
//--- Convert to account currency
|
||
|
double tick_value = SymbolInfoDouble(trades[i].symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
double tick_size = SymbolInfoDouble(trades[i].symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
||
|
if(tick_size > 0)
|
||
|
{
|
||
|
potential_loss = potential_loss / tick_size * tick_value;
|
||
|
stressed_loss += potential_loss;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Return as percentage of balance
|
||
|
return (stressed_loss / account_balance) * 100;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Update Kelly parameters from trading results |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CRiskManagerV71::UpdateKellyParameters(double win_rate, double avg_win, double avg_loss)
|
||
|
{
|
||
|
m_win_rate = win_rate;
|
||
|
|
||
|
if(avg_loss != 0)
|
||
|
m_avg_win_loss_ratio = MathAbs(avg_win / avg_loss);
|
||
|
else
|
||
|
m_avg_win_loss_ratio = 1.5;
|
||
|
|
||
|
//--- Calculate optimal Kelly fraction
|
||
|
m_kelly_fraction = OptimizeKellyFraction(win_rate, avg_win, avg_loss);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate optimal Kelly fraction |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CRiskManagerV71::OptimizeKellyFraction(double win_rate, double avg_win, double avg_loss)
|
||
|
{
|
||
|
if(avg_loss == 0) return 0.1; // Default conservative
|
||
|
|
||
|
double b = MathAbs(avg_win / avg_loss);
|
||
|
double p = win_rate;
|
||
|
double q = 1 - p;
|
||
|
|
||
|
//--- Kelly formula: f = (p*b - q) / b
|
||
|
double kelly = (p * b - q) / b;
|
||
|
|
||
|
//--- Apply safety factor (1/3 Kelly is common)
|
||
|
kelly *= 0.33;
|
||
|
|
||
|
//--- Cap at maximum
|
||
|
kelly = MathMin(0.25, MathMax(0, kelly));
|
||
|
|
||
|
return kelly;
|
||
|
}
|
||
|
|
||
|
#endif // RISK_MANAGER_V71_MQH
|