//+------------------------------------------------------------------+ //| 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 #include //+------------------------------------------------------------------+ //| 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