Overview To fully integrate the enhanced external trade management system, updates are required to 5 out of 7 existing modules. The updates maintain backward compatibility while adding new functionality for external trade handling. Module Update Requirements 🟢 No Updates Required (2 modules) TechnicalAnalysis.mqh - Already provides necessary calculations EntrySystem.mqh - Only handles EA's own entry signals 🟡 Minor Updates (2 modules) DataTypes.mqh - Add external trade structures and fields Utilities.mqh - Enhanced logging for external trades 🟠 Moderate Updates (3 modules) RiskManager.mqh - Enhanced risk enforcement methods TradeManager.mqh - Improved stop management for externals Dashboard.mqh - Display external trade information Integration Steps Phase 1: Data Structures (DataTypes.mqh) Add ENUM_EXTERNAL_STATUS enumeration Extend ManagedTrade structure with external-specific fields Add ExternalTradeStats structure for metrics Update DashboardConfig with show_external flag Key additions: external_status - Track state of external trade source_name - Identify where trade came from stops_modified - Track if we modified the trade original_sl/tp - Store original values for comparison Phase 2: Risk Management (RiskManager.mqh) Add EnforceRiskRulesEnhanced() method Implement GetExternalExposure() for risk aggregation Add UpdateExternalStats() for tracking Enhance ValidateAndAdjustRiskExternal() method Key features: Separate risk calculation for external trades Cache mechanism for performance Statistical tracking of external positions Smart risk adjustment without closing trades Phase 3: Trade Management (TradeManager.mqh) Add ApplyDefaultStopsEnhanced() with better logic Implement OverrideExternalStops() with smart override Create ManageExternalTrade() with different rules Add ApplyBreakevenExternal() with wider triggers Key features: Smart stop override (only improve, never worsen) Different management rules for external trades Respect minimum broker distances Track modification success/failure rates Phase 4: User Interface (Dashboard.mqh) Add CreateExternalSection() for display area Implement UpdateExternalSection() for real-time updates Add SetCustomText() for flexible display Create ShowExternalTrades() toggle method Key features: Real-time external trade count and risk Color-coded risk warnings List of active external positions Modification statistics display Phase 5: Logging (Utilities.mqh) Add LogExternalTrade() for detailed event logging Create separate CSV log for external trades Enhance GenerateReportEnhanced() with external section Add IdentifyTradeSource() for magic number interpretation Key features: Separate CSV log for external trade events Detailed tracking of all modifications Source identification from magic numbers Enhanced reporting with external statistics
873 lines
No EOL
29 KiB
MQL5
873 lines
No EOL
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 |