135 linhas
4,8 KiB
MQL5
135 linhas
4,8 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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)
|
|
{
|
|
// 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)
|
|
}
|
|
|
|
double atr_pct;
|
|
final_multiplier = CalculateSizeMultiplier(atr_pct, reason);
|
|
|
|
if(!m_enabled)
|
|
return base_lot_size;
|
|
|
|
// Risk-based sizing if stop loss is provided (using validated SL points)
|
|
if(sl_points > 0.0 && m_target_risk_pct > 0.0)
|
|
{
|
|
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);
|
|
double risk_per_lot = sl_points * point_value;
|
|
|
|
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);
|
|
}
|
|
};
|