The error "Invalid stop distance: 0.0" was occurring because the entry signals were being generated with invalid (zero) stop distances. This happened when the market volatility (ATR) value was 0 or not properly calculated. Key Changes Made (Version 6.5.1): Added GetValidATR() Helper Method in EntrySystem.mqh: This method ensures we always have a valid ATR value Includes multiple fallback mechanisms: Direct ATR calculation if market data is invalid 0.1% of current price as a fallback Minimum 20 pips for forex pairs Updated All Entry Strategy Methods to use GetValidATR(): CheckMACrossover() CheckMAPullback() CheckMomentum() CheckContrarian() CheckBreakout() CheckMeanReversion() CheckMultiStrategy() Enhanced Signal Validation in CheckSignal(): Added explicit check for stop_loss_distance > 0 Signal is rejected if stop distance is invalid Better error logging to identify which strategy produced invalid signals Improv
755 lines
No EOL
54 KiB
MQL5
755 lines
No EOL
54 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| RiskManager.mqh |
|
|
//| Risk Management Module 2.1 |
|
|
//| Fixed: Lot size calculation accuracy |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef RISK_MANAGER_MQH
|
|
#define RISK_MANAGER_MQH
|
|
|
|
#include "DataTypes.mqh"
|
|
#include <Trade/Trade.mqh>
|
|
#include <Trade/SymbolInfo.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Risk Manager Class |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManager
|
|
{
|
|
private:
|
|
//--- Configuration
|
|
RiskManagerConfig m_config;
|
|
|
|
//--- Market info
|
|
CSymbolInfo m_symbol;
|
|
MarketConditions m_market_conditions;
|
|
|
|
//--- Risk tracking
|
|
double m_daily_loss;
|
|
double m_daily_profit;
|
|
datetime m_last_reset;
|
|
double m_current_exposure;
|
|
double m_max_daily_loss;
|
|
|
|
//--- Kelly Criterion tracking
|
|
double m_kelly_wins[100];
|
|
double m_kelly_losses[100];
|
|
int m_kelly_win_count;
|
|
int m_kelly_loss_count;
|
|
double m_kelly_optimal;
|
|
|
|
//--- Helper methods
|
|
double CalculateKellyFraction();
|
|
double GetAccountRiskAmount();
|
|
double CalculateVolatilityAdjustment();
|
|
bool ValidateRiskLimits(double risk_amount);
|
|
double GetMinStopDistance();
|
|
double GetSymbolMinLot();
|
|
double GetSymbolMaxLot();
|
|
double GetSymbolLotStep();
|
|
|
|
public:
|
|
CRiskManager();
|
|
~CRiskManager();
|
|
|
|
//--- Initialization
|
|
bool Initialize(const RiskManagerConfig &config);
|
|
|
|
//--- Position sizing
|
|
double CalculatePositionSize(double stop_distance,
|
|
double risk_override = 0);
|
|
double CalculateLotSize(double risk_amount, double stop_distance_price);
|
|
|
|
//--- Risk validation
|
|
bool ValidateNewPosition(double lot_size, double stop_distance);
|
|
bool ValidateAndAdjustRisk(ManagedTrade &trade);
|
|
bool CheckDailyDrawdown();
|
|
bool CheckMaxExposure();
|
|
|
|
//--- External trade management
|
|
void EnforceRiskRules(ManagedTrade &trade,
|
|
double default_risk_percent,
|
|
bool close_excessive);
|
|
double CalculateTradeRisk(const ManagedTrade &trade);
|
|
|
|
//--- Risk metrics
|
|
double GetCurrentExposure();
|
|
double GetDailyPnL();
|
|
ENUM_RISK_LEVEL GetCurrentRiskLevel();
|
|
double GetRiskCapacity();
|
|
|
|
//--- Updates
|
|
void UpdateMarketConditions(const MarketConditions &conditions);
|
|
void UpdateKellyData(double profit);
|
|
void ResetDailyMetrics();
|
|
|
|
//--- Getters
|
|
double GetOptimalKelly() { return m_kelly_optimal; }
|
|
double GetMaxDailyLoss() { return m_max_daily_loss; }
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager::CRiskManager()
|
|
{
|
|
m_daily_loss = 0;
|
|
m_daily_profit = 0;
|
|
m_last_reset = 0;
|
|
m_current_exposure = 0;
|
|
m_max_daily_loss = 0;
|
|
m_kelly_win_count = 0;
|
|
m_kelly_loss_count = 0;
|
|
m_kelly_optimal = 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager::~CRiskManager()
|
|
{
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize risk manager |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::Initialize(const RiskManagerConfig &config)
|
|
{
|
|
m_config = config;
|
|
|
|
//--- Initialize symbol info
|
|
if(!m_symbol.Name(_Symbol))
|
|
{
|
|
Print("RiskManager: Failed to initialize symbol info");
|
|
return false;
|
|
}
|
|
|
|
//--- Calculate max daily loss in account currency
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
m_max_daily_loss = balance * (m_config.max_daily_dd / 100.0);
|
|
|
|
//--- Reset daily metrics
|
|
ResetDailyMetrics();
|
|
|
|
Print("RiskManager initialized: Risk=", m_config.risk_percent,
|
|
"%, MaxDD=", m_config.max_daily_dd, "%");
|
|
|
|
return true;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------------+
|
|
//| Calculate position size based on stop distance |
|
|
//Fix 2: Position Sizing Issues (RiskManager.mqh) |
|
|
//Fix 3: Improve Lot Size Calculation (RiskManager.mqh) |
|
|
//Let's add better error handling and logging to the position sizing calculation |
|
|
//Let's also improve the lot size calculation to handle edge cases better | |
|
|
//+---------------------------------------------------------------------------------+
|
|
double CRiskManager::CalculatePositionSize(double stop_distance, double risk_override)
|
|
{
|
|
//--- Check daily drawdown limit
|
|
if(!CheckDailyDrawdown())
|
|
{
|
|
Print("RiskManager: Daily drawdown limit reached");
|
|
return 0;
|
|
}
|
|
|
|
//--- Validate stop distance
|
|
if(stop_distance <= 0)
|
|
{
|
|
Print("RiskManager: Invalid stop distance: ", stop_distance);
|
|
Print("RiskManager: Stop distance must be greater than 0");
|
|
return 0;
|
|
}
|
|
|
|
//--- Get risk amount
|
|
double risk_percent = (risk_override > 0) ? risk_override : m_config.risk_percent;
|
|
double account_balance = GetAccountRiskAmount();
|
|
double risk_amount = account_balance * (risk_percent / 100.0);
|
|
|
|
Print("RiskManager: Balance=", account_balance, ", Risk%=", risk_percent,
|
|
", RiskAmount=", risk_amount, ", StopDistance=", stop_distance/_Point, " points");
|
|
|
|
//--- Apply Kelly Criterion if enabled
|
|
if(m_config.use_kelly)
|
|
{
|
|
double kelly_fraction = CalculateKellyFraction();
|
|
risk_amount *= kelly_fraction;
|
|
Print("RiskManager: Kelly fraction = ", kelly_fraction);
|
|
}
|
|
|
|
//--- Apply volatility adjustment
|
|
if(m_config.sizing_mode == PS_ATR_BASED)
|
|
{
|
|
double vol_adjustment = CalculateVolatilityAdjustment();
|
|
risk_amount *= vol_adjustment;
|
|
Print("RiskManager: Volatility adjustment = ", vol_adjustment);
|
|
}
|
|
|
|
//--- Validate risk limits
|
|
if(!ValidateRiskLimits(risk_amount))
|
|
{
|
|
risk_amount = account_balance * (m_config.max_risk_percent / 100.0);
|
|
Print("RiskManager: Risk amount adjusted to max allowed: ", risk_amount);
|
|
}
|
|
|
|
//--- Calculate lot size
|
|
double lot_size = CalculateLotSize(risk_amount, stop_distance);
|
|
Print("RiskManager: Raw lot size calculated: ", lot_size);
|
|
|
|
//--- Safety check for unreasonable lot sizes
|
|
if(lot_size > 1000.0)
|
|
{
|
|
Print("RiskManager: ERROR - Calculated lot size unreasonably high: ", lot_size);
|
|
Print("RiskManager: Check your broker's contract specifications");
|
|
return 0;
|
|
}
|
|
|
|
//--- Get symbol limits
|
|
double min_lot = GetSymbolMinLot();
|
|
double max_lot = GetSymbolMaxLot();
|
|
double lot_step = GetSymbolLotStep();
|
|
|
|
Print("RiskManager: Symbol limits - MinLot=", min_lot, ", MaxLot=", max_lot,
|
|
", LotStep=", lot_step);
|
|
|
|
//--- Check if calculated lot is too small
|
|
if(lot_size < min_lot)
|
|
{
|
|
Print("RiskManager: Calculated lot ", lot_size, " below minimum ", min_lot);
|
|
|
|
// Calculate required balance for minimum lot
|
|
double required_balance = (min_lot * stop_distance * m_symbol.TickValue() * m_symbol.Point())
|
|
/ (risk_percent / 100.0);
|
|
Print("RiskManager: Required balance for min lot: ", required_balance);
|
|
|
|
// If account is too small, return 0
|
|
if(account_balance < required_balance * 0.5) // 50% safety margin
|
|
{
|
|
Print("RiskManager: Account too small for minimum position size");
|
|
return 0;
|
|
}
|
|
|
|
// Otherwise use minimum lot
|
|
lot_size = min_lot;
|
|
}
|
|
|
|
//--- Apply maximum limit from configuration
|
|
lot_size = MathMin(lot_size, m_config.max_lot_size);
|
|
|
|
//--- Apply symbol maximum limit
|
|
lot_size = MathMin(lot_size, max_lot);
|
|
|
|
//--- Round to lot step
|
|
lot_size = MathFloor(lot_size / lot_step) * lot_step;
|
|
lot_size = NormalizeDouble(lot_size, 2);
|
|
|
|
//--- Final check against configured limits
|
|
if(lot_size < m_config.min_lot_size)
|
|
{
|
|
Print("RiskManager: Lot size ", lot_size, " below configured minimum ", m_config.min_lot_size);
|
|
return 0;
|
|
}
|
|
|
|
//--- ✅ FIXED: Return the calculated lot size
|
|
Print("RiskManager: Final lot size: ", lot_size);
|
|
return lot_size;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate lot size from risk amount and stop distance |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::CalculateLotSize(double risk_amount, double stop_distance_price)
|
|
{
|
|
if(stop_distance_price <= 0) return 0;
|
|
|
|
// Method 1: Using tick calculations (more accurate)
|
|
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
double point_size = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
// Convert stop distance from price units to points
|
|
double stop_points = stop_distance_price / point_size;
|
|
|
|
// Convert points to ticks
|
|
double point_to_tick_ratio = point_size / tick_size;
|
|
double stop_ticks = stop_points * point_to_tick_ratio;
|
|
|
|
// Calculate the monetary value of the stop loss for 1 lot
|
|
double stop_value_per_lot = stop_ticks * tick_value;
|
|
|
|
// Calculate required lot size
|
|
double lot_size = risk_amount / stop_value_per_lot;
|
|
|
|
// Method 2: Simplified calculation (for verification)
|
|
// This uses contract specifications directly
|
|
double contract_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
|
|
string profit_currency = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);
|
|
|
|
// For forex pairs where profit currency is the quote currency
|
|
double lot_size_simple = 0;
|
|
if(profit_currency == AccountInfoString(ACCOUNT_CURRENCY))
|
|
{
|
|
// Direct calculation for pairs like EURUSD (USD account)
|
|
lot_size_simple = risk_amount / (stop_distance_price * contract_size);
|
|
}
|
|
else
|
|
{
|
|
// For cross pairs, we'd need currency conversion
|
|
// For now, use the tick-based calculation
|
|
lot_size_simple = lot_size;
|
|
}
|
|
|
|
// Debug output
|
|
Print("CalculateLotSize Debug:");
|
|
Print(" Risk Amount: ", risk_amount);
|
|
Print(" Stop Distance (price): ", stop_distance_price);
|
|
Print(" Stop Distance (points): ", stop_points);
|
|
Print(" Point Size: ", point_size);
|
|
Print(" Tick Size: ", tick_size);
|
|
Print(" Tick Value: ", tick_value);
|
|
Print(" Contract Size: ", contract_size);
|
|
Print(" Stop Ticks: ", stop_ticks);
|
|
Print(" Stop Value per Lot: ", stop_value_per_lot);
|
|
Print(" Calculated Lot Size (tick method): ", lot_size);
|
|
Print(" Calculated Lot Size (simple method): ", lot_size_simple);
|
|
|
|
// Use the tick-based calculation as it's more universal
|
|
return lot_size;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate new position against risk limits |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::ValidateNewPosition(double lot_size, double stop_distance)
|
|
{
|
|
//--- Check daily drawdown
|
|
if(!CheckDailyDrawdown())
|
|
return false;
|
|
|
|
//--- Calculate position risk using the same method as CalculateLotSize
|
|
double tick_value = m_symbol.TickValue();
|
|
double tick_size = m_symbol.TickSize();
|
|
double stop_ticks = stop_distance / tick_size;
|
|
double position_risk = lot_size * stop_ticks * tick_value;
|
|
|
|
|
|
//--- Check against max risk percent
|
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double risk_percent = (position_risk / account_balance) * 100.0;
|
|
|
|
if(risk_percent > m_config.max_risk_percent)
|
|
{
|
|
Print("RiskManager: Position risk ", risk_percent,
|
|
"% exceeds maximum ", m_config.max_risk_percent, "%");
|
|
return false;
|
|
}
|
|
|
|
//--- Check total exposure
|
|
double new_exposure = m_current_exposure + risk_percent;
|
|
if(new_exposure > m_config.max_exposure)
|
|
{
|
|
Print("RiskManager: Total exposure would be ", new_exposure,
|
|
"% exceeding limit ", m_config.max_exposure, "%");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate and adjust risk for existing trade |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::ValidateAndAdjustRisk(ManagedTrade &trade)
|
|
{
|
|
//--- Calculate current risk
|
|
trade.risk_amount = CalculateTradeRisk(trade);
|
|
trade.risk_percent = (trade.risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
|
|
|
|
//--- Calculate R-multiple
|
|
if(trade.risk_amount > 0)
|
|
{
|
|
double total_pnl = trade.profit + trade.commission + trade.swap;
|
|
trade.r_multiple = total_pnl / trade.risk_amount;
|
|
}
|
|
|
|
//--- Update MAE/MFE
|
|
double current_pnl = trade.profit + trade.commission + trade.swap;
|
|
if(current_pnl > trade.mfe)
|
|
trade.mfe = current_pnl;
|
|
if(current_pnl < trade.mae)
|
|
trade.mae = current_pnl;
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Enforce risk rules on external trades |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::EnforceRiskRules(ManagedTrade &trade,
|
|
double default_risk_percent,
|
|
bool close_excessive)
|
|
{
|
|
CTrade trade_ops;
|
|
|
|
//--- Calculate actual risk
|
|
double actual_risk = CalculateTradeRisk(trade);
|
|
double risk_percent = (actual_risk / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
|
|
|
|
//--- Check if risk exceeds maximum
|
|
if(risk_percent > m_config.max_risk_percent)
|
|
{
|
|
Print("RiskManager: External trade #", trade.ticket,
|
|
" risk ", risk_percent, "% exceeds max ", m_config.max_risk_percent, "%");
|
|
|
|
if(close_excessive)
|
|
{
|
|
//--- Close the trade
|
|
trade_ops.PositionClose(trade.ticket);
|
|
Print("RiskManager: Closed excessive risk trade #", trade.ticket);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//--- If no stop loss, calculate and set one
|
|
if(trade.sl == 0)
|
|
{
|
|
double atr = m_market_conditions.volatility;
|
|
double stop_distance = atr * 2.0; // 2 ATR default
|
|
|
|
if(trade.type == POSITION_TYPE_BUY)
|
|
trade.sl = trade.open_price - stop_distance;
|
|
else
|
|
trade.sl = trade.open_price + stop_distance;
|
|
|
|
//--- Ensure minimum stop distance
|
|
double min_stop = GetMinStopDistance();
|
|
double current_price = (trade.type == POSITION_TYPE_BUY) ?
|
|
m_symbol.Bid() : m_symbol.Ask();
|
|
double actual_distance = MathAbs(current_price - trade.sl);
|
|
|
|
if(actual_distance < min_stop)
|
|
{
|
|
if(trade.type == POSITION_TYPE_BUY)
|
|
trade.sl = current_price - min_stop;
|
|
else
|
|
trade.sl = current_price + min_stop;
|
|
}
|
|
|
|
//--- Modify position
|
|
trade_ops.PositionModify(trade.ticket, trade.sl, trade.tp);
|
|
Print("RiskManager: Set stop loss for trade #", trade.ticket, " at ", trade.sl);
|
|
}
|
|
|
|
//--- Update trade risk values
|
|
trade.risk_amount = actual_risk;
|
|
trade.risk_percent = risk_percent;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate trade risk amount |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::CalculateTradeRisk(const ManagedTrade &trade)
|
|
{
|
|
if(trade.sl == 0) return 0;
|
|
|
|
double stop_distance = MathAbs(trade.open_price - trade.sl);
|
|
double tick_value = m_symbol.TickValue();
|
|
double tick_size = m_symbol.TickSize();
|
|
|
|
// Convert stop distance to ticks
|
|
double stop_ticks = stop_distance / tick_size;
|
|
|
|
// Calculate risk amount
|
|
double risk_amount = trade.volume * stop_ticks * tick_value;
|
|
|
|
return risk_amount;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check daily drawdown limit |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckDailyDrawdown()
|
|
{
|
|
double daily_pnl = GetDailyPnL();
|
|
|
|
if(daily_pnl < -m_max_daily_loss)
|
|
{
|
|
Print("RiskManager: Daily loss limit reached: ", daily_pnl);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check maximum exposure limit |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckMaxExposure()
|
|
{
|
|
return (m_current_exposure <= m_config.max_exposure);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current total exposure |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetCurrentExposure()
|
|
{
|
|
m_current_exposure = 0;
|
|
|
|
int total = PositionsTotal();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
// Use PositionGetSymbol instead of PositionSelectByIndex
|
|
if(PositionGetSymbol(i) == _Symbol)
|
|
{
|
|
if(PositionSelect(_Symbol))
|
|
{
|
|
ManagedTrade temp_trade;
|
|
temp_trade.ticket = PositionGetInteger(POSITION_TICKET);
|
|
temp_trade.open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
temp_trade.sl = PositionGetDouble(POSITION_SL);
|
|
temp_trade.volume = PositionGetDouble(POSITION_VOLUME);
|
|
temp_trade.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double risk = CalculateTradeRisk(temp_trade);
|
|
m_current_exposure += (risk / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return m_current_exposure;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get daily P&L |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetDailyPnL()
|
|
{
|
|
//--- Check for new day
|
|
MqlDateTime current_time;
|
|
TimeToStruct(TimeCurrent(), current_time);
|
|
MqlDateTime reset_time;
|
|
TimeToStruct(m_last_reset, reset_time);
|
|
|
|
if(current_time.day != reset_time.day)
|
|
{
|
|
ResetDailyMetrics();
|
|
}
|
|
|
|
return m_daily_profit - m_daily_loss;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current risk level |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_RISK_LEVEL CRiskManager::GetCurrentRiskLevel()
|
|
{
|
|
double exposure = GetCurrentExposure();
|
|
double daily_loss_percent = (m_daily_loss / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
|
|
|
|
//--- Critical risk
|
|
if(daily_loss_percent > m_config.max_daily_dd * 0.8 ||
|
|
exposure > m_config.max_exposure * 0.9)
|
|
return RISK_CRITICAL;
|
|
|
|
//--- Extreme risk
|
|
if(daily_loss_percent > m_config.max_daily_dd * 0.6 ||
|
|
exposure > m_config.max_exposure * 0.7)
|
|
return RISK_EXTREME;
|
|
|
|
//--- High risk
|
|
if(daily_loss_percent > m_config.max_daily_dd * 0.4 ||
|
|
exposure > m_config.max_exposure * 0.5)
|
|
return RISK_HIGH;
|
|
|
|
//--- Medium risk
|
|
if(daily_loss_percent > m_config.max_daily_dd * 0.2 ||
|
|
exposure > m_config.max_exposure * 0.3)
|
|
return RISK_MEDIUM;
|
|
|
|
return RISK_LOW;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get risk capacity (how much more risk can be taken) |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetRiskCapacity()
|
|
{
|
|
double daily_remaining = m_max_daily_loss - MathAbs(MathMin(GetDailyPnL(), 0));
|
|
double exposure_remaining = m_config.max_exposure - GetCurrentExposure();
|
|
|
|
//--- Return the more restrictive limit
|
|
double daily_capacity = (daily_remaining / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
|
|
|
|
return MathMin(daily_capacity, exposure_remaining);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update market conditions |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::UpdateMarketConditions(const MarketConditions &conditions)
|
|
{
|
|
m_market_conditions = conditions;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update Kelly criterion data |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::UpdateKellyData(double profit)
|
|
{
|
|
if(profit > 0)
|
|
{
|
|
if(m_kelly_win_count < 100)
|
|
{
|
|
m_kelly_wins[m_kelly_win_count] = profit;
|
|
m_kelly_win_count++;
|
|
}
|
|
}
|
|
else if(profit < 0)
|
|
{
|
|
if(m_kelly_loss_count < 100)
|
|
{
|
|
m_kelly_losses[m_kelly_loss_count] = MathAbs(profit);
|
|
m_kelly_loss_count++;
|
|
}
|
|
}
|
|
|
|
//--- Recalculate Kelly fraction
|
|
m_kelly_optimal = CalculateKellyFraction();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate Kelly fraction |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::CalculateKellyFraction()
|
|
{
|
|
if(m_kelly_win_count < 10 || m_kelly_loss_count < 10)
|
|
return m_config.kelly_fraction; // Use default until enough data
|
|
|
|
//--- Calculate win probability
|
|
double win_prob = (double)m_kelly_win_count / (m_kelly_win_count + m_kelly_loss_count);
|
|
|
|
//--- Calculate average win/loss
|
|
double avg_win = 0, avg_loss = 0;
|
|
|
|
for(int i = 0; i < m_kelly_win_count; i++)
|
|
avg_win += m_kelly_wins[i];
|
|
avg_win /= m_kelly_win_count;
|
|
|
|
for(int i = 0; i < m_kelly_loss_count; i++)
|
|
avg_loss += m_kelly_losses[i];
|
|
avg_loss /= m_kelly_loss_count;
|
|
|
|
//--- Kelly formula: f = (p*b - q) / b
|
|
//--- where p = win prob, q = loss prob, b = win/loss ratio
|
|
double b = avg_win / avg_loss;
|
|
double q = 1 - win_prob;
|
|
double kelly = (win_prob * b - q) / b;
|
|
|
|
//--- Apply safety factor
|
|
kelly *= m_config.kelly_fraction;
|
|
|
|
//--- Ensure reasonable bounds
|
|
kelly = MathMax(kelly, 0.01); // Minimum 1%
|
|
kelly = MathMin(kelly, 0.25); // Maximum 25%
|
|
|
|
return kelly;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate volatility adjustment factor |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::CalculateVolatilityAdjustment()
|
|
{
|
|
if(m_market_conditions.volatility <= 0)
|
|
return 1.0;
|
|
|
|
//--- Get historical average volatility (simplified)
|
|
double avg_volatility = iATR(_Symbol, PERIOD_D1, 20);
|
|
|
|
if(avg_volatility <= 0)
|
|
return 1.0;
|
|
|
|
//--- Reduce size in high volatility, increase in low volatility
|
|
double vol_ratio = avg_volatility / m_market_conditions.volatility;
|
|
|
|
//--- Apply bounds
|
|
vol_ratio = MathMax(vol_ratio, 0.5); // Minimum 50% size
|
|
vol_ratio = MathMin(vol_ratio, 1.5); // Maximum 150% size
|
|
|
|
return vol_ratio;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get account risk amount (considers balance/equity) |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetAccountRiskAmount()
|
|
{
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
|
|
//--- Use the lower of balance or equity for safety
|
|
return MathMin(balance, equity);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate risk limits |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::ValidateRiskLimits(double risk_amount)
|
|
{
|
|
double account_value = GetAccountRiskAmount();
|
|
double risk_percent = (risk_amount / account_value) * 100.0;
|
|
|
|
return (risk_percent <= m_config.max_risk_percent);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get minimum stop distance |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetMinStopDistance()
|
|
{
|
|
int stops_level = (int)m_symbol.StopsLevel();
|
|
double min_stop = stops_level * m_symbol.Point();
|
|
|
|
//--- Add buffer for safety
|
|
return min_stop * 1.1;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get symbol minimum lot |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetSymbolMinLot()
|
|
{
|
|
return m_symbol.LotsMin();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get symbol maximum lot |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetSymbolMaxLot()
|
|
{
|
|
return m_symbol.LotsMax();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get symbol lot step |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetSymbolLotStep()
|
|
{
|
|
return m_symbol.LotsStep();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset daily metrics |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::ResetDailyMetrics()
|
|
{
|
|
m_daily_loss = 0;
|
|
m_daily_profit = 0;
|
|
m_last_reset = TimeCurrent();
|
|
|
|
//--- Recalculate max daily loss based on current balance
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
m_max_daily_loss = balance * (m_config.max_daily_dd / 100.0);
|
|
}
|
|
|
|
#endif // RISK_MANAGER_MQH
|
|
//+------------------------------------------------------------------+ |