mql5/Experts/Advisors/DualEA/Include/VolatilitySizer.mqh

135 lines
4.8 KiB
MQL5
Raw Permalink Normal View History

2025-09-20 02:27:35 -04:00
//+------------------------------------------------------------------+
//| VolatilitySizer.mqh |
//| FR-06: Volatility Position Sizing Module |
//+------------------------------------------------------------------+
#property copyright "DualEA"
#property version "1.00"
//+------------------------------------------------------------------+
//| Volatility-based position sizing calculator |
//+------------------------------------------------------------------+
class CVolatilitySizer
{
private:
string m_symbol;
ENUM_TIMEFRAMES m_timeframe;
// Configuration
bool m_enabled;
int m_atr_period;
double m_base_atr_pct;
double m_min_multiplier;
double m_max_multiplier;
double m_target_risk_pct;
public:
CVolatilitySizer(const string symbol, const ENUM_TIMEFRAMES timeframe) :
m_symbol(symbol), m_timeframe(timeframe), m_enabled(false),
m_atr_period(14), m_base_atr_pct(1.0), m_min_multiplier(0.1),
m_max_multiplier(3.0), m_target_risk_pct(1.0) {}
// Configuration methods
void SetEnabled(const bool enabled) { m_enabled = enabled; }
void SetATRPeriod(const int period) { m_atr_period = period; }
void SetBaseATRPercent(const double pct) { m_base_atr_pct = pct; }
void SetMultiplierRange(const double min_mult, const double max_mult)
{
m_min_multiplier = min_mult;
m_max_multiplier = max_mult;
}
void SetTargetRiskPercent(const double risk_pct) { m_target_risk_pct = risk_pct; }
// Main sizing calculation
double CalculateSizeMultiplier(double &atr_pct_out, string &reason)
{
reason = "ok";
atr_pct_out = 0.0;
if(!m_enabled)
{
reason = "vol_sizer_disabled";
return 1.0; // Neutral multiplier
}
// Get current ATR
int atr_handle = iATR(m_symbol, m_timeframe, m_atr_period);
if(atr_handle == INVALID_HANDLE)
{
reason = "atr_handle_failed";
return 1.0;
}
double atr_buf[1];
int copied = CopyBuffer(atr_handle, 0, 0, 1, atr_buf);
IndicatorRelease(atr_handle);
if(copied != 1)
{
reason = "atr_copy_failed";
return 1.0;
}
double current_price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
if(current_price <= 0.0 || atr_buf[0] <= 0.0)
{
reason = "invalid_price_or_atr";
return 1.0;
}
// Calculate ATR as percentage of price
atr_pct_out = 100.0 * atr_buf[0] / current_price;
// Calculate volatility multiplier
// Higher volatility = smaller position size (inverse relationship)
double vol_ratio = m_base_atr_pct / atr_pct_out;
double multiplier = MathSqrt(vol_ratio); // Square root for smoother scaling
// Apply bounds
multiplier = MathMax(m_min_multiplier, MathMin(m_max_multiplier, multiplier));
reason = StringFormat("atr_pct=%.2f vol_ratio=%.3f", atr_pct_out, vol_ratio);
return multiplier;
}
// Calculate position size based on account equity and risk
double CalculatePositionSize(const double base_lot_size, const double stop_loss_points,
double &final_multiplier, string &reason)
{
2026-02-24 12:47:37 -05:00
// CRITICAL FIX: Validate SL distance first - prevent invalid position sizing
double sl_points = stop_loss_points;
if(sl_points <= 10.0) // Less than 10 points (1 pip for most brokers)
{
// LogWarning equivalent - use Print for now
Print(StringFormat("[VolatilitySizer] Invalid SL distance (%.1f points) - using fallback 100 points", sl_points));
sl_points = 100.0; // Fallback to 100 points (10 pips)
}
2025-09-20 02:27:35 -04:00
double atr_pct;
final_multiplier = CalculateSizeMultiplier(atr_pct, reason);
if(!m_enabled)
return base_lot_size;
2026-02-24 12:47:37 -05:00
// Risk-based sizing if stop loss is provided (using validated SL points)
if(sl_points > 0.0 && m_target_risk_pct > 0.0)
2025-09-20 02:27:35 -04:00
{
double account_equity = AccountInfoDouble(ACCOUNT_EQUITY);
double risk_amount = account_equity * m_target_risk_pct / 100.0;
double point_value = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
2026-02-24 12:47:37 -05:00
double risk_per_lot = sl_points * point_value;
2025-09-20 02:27:35 -04:00
if(risk_per_lot > 0.0)
{
double risk_based_size = risk_amount / risk_per_lot;
double vol_adjusted_size = risk_based_size * final_multiplier;
reason = StringFormat("%s risk_based=%.2f", reason, risk_based_size);
return MathMax(0.01, vol_adjusted_size); // Minimum lot size
}
}
// Fallback to base size with volatility adjustment
return MathMax(0.01, base_lot_size * final_multiplier);
}
};