//+------------------------------------------------------------------+ //| recursions.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include"base.mqh" //--- #define SQRT2_OV_PI 0.79788456080286541 // Constant used for Gaussian EGARCH scaling const double LNSIGMA_MAX = log(DBL_MAX) - 0.1; // Overflow protection for log-variance //+------------------------------------------------------------------+ //| Helper: Adjusts sigma2 to ensure it stays within defined bounds | //| Prevents variance from collapsing to zero or exploding to inf. | //+------------------------------------------------------------------+ double bounds_check(double _sigma2, vector &varbounds) { // Enforce floor boundary if(_sigma2 < varbounds[0]) _sigma2 = varbounds[0]; else // Enforce ceiling boundary with soft-clip for numerical stability if(_sigma2 > varbounds[1]) { if(MathClassify(_sigma2) == FP_NORMAL) _sigma2 = varbounds[1] + log(_sigma2/varbounds[1]); else _sigma2 = varbounds[1] + 1000; } return _sigma2; } //+------------------------------------------------------------------+ //| ARCH recursion: Sigma^2 = omega + alpha_1 * res^2_{t-1} | //+------------------------------------------------------------------+ vector arch_recursion(vector& parameters, vector& resids, vector& sigma2, ulong p, ulong nobs, double backcast, matrix &var_bounds) { for(ulong t = 0; t < nobs; ++t) { sigma2[t] = parameters[0]; // Constant term (omega) for(ulong i = 0; i < p; ++i) { // Use backcast for pre-sample values if((t - i - 1) < 0) sigma2[t] += parameters[i + 1] * backcast; else sigma2[t] += parameters[i + 1] * pow(resids[t - i - 1], 2); } sigma2[t] = bounds_check(sigma2[t], var_bounds.Row(t)); } return sigma2; } //+------------------------------------------------------------------+ //| EGARCH recursion: Models log(sigma^2) to ensure positivity | //| Handles asymmetries using std_resids and abs_std_resids. | //+------------------------------------------------------------------+ vector egarch_recursion(vector& parameters, vector& resids, vector& sigma2, ulong p, ulong o, ulong q, ulong nobs, double backcast, matrix& var_bounds, vector& lnsigma2, vector& std_resids, vector& abs_std_resids) { for(ulong t = 0; t < nobs; ++t) { ulong loc = 0; lnsigma2[t] = parameters[loc]; loc += 1; // Compute symmetric innovation component for(ulong j = 0; j < p; ++j) { if((long(t) - 1 - long(j)) >= 0) lnsigma2[t] += parameters[loc] * (abs_std_resids[t - 1 - j] - SQRT2_OV_PI); loc += 1; } // Compute asymmetric (leverage) innovation component for(ulong j = 0; j < o; ++j) { if((long(t) - 1 - long(j)) >= 0) lnsigma2[t] += parameters[loc] * std_resids[t - 1 - j]; loc += 1; } // Compute persistence (AR) component for(ulong j = 0; j < q; ++j) { if((long(t) - 1 - long(j)) < 0) lnsigma2[t] += parameters[loc] * backcast; else lnsigma2[t] += parameters[loc] * lnsigma2[t - 1 - j]; loc += 1; } // Numerical clamping for log-variance if(lnsigma2[t] > LNSIGMA_MAX) lnsigma2[t] = LNSIGMA_MAX; sigma2[t] = exp(lnsigma2[t]); // Boundary enforcement in variance space if(sigma2[t] < var_bounds[t, 0]) { sigma2[t] = var_bounds[t, 0]; lnsigma2[t] = log(sigma2[t]); } else if(sigma2[t] > var_bounds[t, 1]) { sigma2[t] = var_bounds[t, 1] + log(sigma2[t]) - log(var_bounds[t, 1]); lnsigma2[t] = log(sigma2[t]); } // Update standardized residuals for next step std_resids[t] = resids[t] / sqrt(sigma2[t]); abs_std_resids[t] = MathAbs(std_resids[t]); } return sigma2; } //+------------------------------------------------------------------+ //| GARCH recursion: Standard variance update with GJR terms | //+------------------------------------------------------------------+ vector garch_recursion(vector ¶meters, vector& fresids, vector& sresids, vector& sigma2, ulong p, ulong o, ulong q, ulong nobs, double backcast, matrix &var_bounds) { for(long t = 0; t < long(nobs); ++t) { long loc = 0; sigma2[t] = parameters[loc]; loc += 1; // ARCH terms (p) for(long j = 0; j < long(p); ++j) { if((t - 1 - j) < 0) sigma2[t] += parameters[loc] * backcast; else sigma2[t] += parameters[loc] * fresids[t - 1 - j]; loc += 1; } // GJR terms (o) - Asymmetric leverage effect for(long j = 0; j < long(o); ++j) { if((t - 1 - j) < 0) sigma2[t] += parameters[loc] * 0.5 * backcast; else sigma2[t] += (parameters[loc] * fresids[t - 1 - j] * double((sresids[t - 1 - j] < 0))); loc += 1; } // GARCH terms (q) - Persistence for(long j = 0; j < long(q); ++j) { if((t - 1 - j) < 0) sigma2[t] += parameters[loc] * backcast; else sigma2[t] += parameters[loc] * sigma2[t - 1 - j]; loc += 1; } sigma2[t] = bounds_check(sigma2[t], var_bounds.Row(t)); } return sigma2; } //+------------------------------------------------------------------+ //| Compute variance recursion for HARCH (Heterogeneous ARCH) | //+------------------------------------------------------------------+ vector harch_recursion(vector& parameters, vector& resids, vector& sigma2, vector& lags, int nobs, double backcast, matrix& var_bounds) { for(int t = 0; t < nobs; ++t) { sigma2[t] = parameters[0]; double param; for(int i = 0; i < int(lags.Size()); ++i) { param = parameters[i + 1] / lags[i]; // Normalize params by lag length for(int j = 0; j < int(lags[i]); ++j) { if((t - j - 1) >= 0) sigma2[t] += param * resids[t - j - 1] * resids[t - j - 1]; else sigma2[t] += param * backcast; } } sigma2[t] = bounds_check(sigma2[t], var_bounds.Row(t)); } return sigma2; } //+------------------------------------------------------------------+ //| FIGARCH weight helper: Calculates fractional integration weights | //+------------------------------------------------------------------+ vector figarch_weights(vector& _params, ulong p, ulong q, ulong trunc_lag) { double phi = p ? _params[0] : 0.0; double d = p ? _params[1] : _params[0]; double beta = q ? _params[p + q] : 0.0; vector lam = vector::Zeros(trunc_lag); vector delta = vector::Zeros(trunc_lag); lam[0] = phi - beta + d; delta[0] = d; // Iterative calculation of long-memory weights for(ulong i = 1; i < trunc_lag; ++i) { delta[i] = double(i - d) / double(i + 1) * delta[i - 1]; lam[i] = beta * lam[i - 1] + (delta[i] - phi * delta[i - 1]); } return lam; } //+------------------------------------------------------------------+ //| Compute variance recursion for FIGARCH Variance | //+------------------------------------------------------------------+ vector figarch_recursion(vector& parameters, vector& fresids, vector& sigma2, ulong p, ulong q, ulong nobs, ulong trunc_lag, double backcast, matrix& var_bounds) { double omega = parameters[0]; double beta = q ? parameters[1 + p + q] : 0.0; double omega_tilde = omega / (1. - beta); vector sparams = vector::Zeros(parameters.Size() - 1); for(ulong i = 0; i < sparams.Size(); sparams[i] = parameters[i + 1], ++i); vector lam = figarch_weights(sparams, p, q, trunc_lag); ulong stop = 0; for(ulong t = 0; t < nobs; ++t) { // Calculate contribution of initial backcast double bc_weight = 0.0; for(ulong i = t; i < trunc_lag; ++i) bc_weight += lam[i]; sigma2[t] = omega_tilde + bc_weight * backcast; // Apply weighted residuals (memory) stop = fmin(t, trunc_lag); for(ulong i = 0; i < stop; ++i) sigma2[t] += lam[i] * fresids[t - i - 1]; sigma2[t] = bounds_check(sigma2[t], var_bounds.Row(t)); } return sigma2; } //+------------------------------------------------------------------+ //| EWMA recursion (RiskMetrics approach) | //+------------------------------------------------------------------+ vector ewma_recursion(double lam, vector& resids, vector& sigma2, ulong nobs, double _backcast) { matrix varbs = matrix::Ones(nobs, 2); vector temp = {-1., 1.7e308}; varbs = np::multiply(varbs, temp); // Map EWMA parameters to GARCH(1,0,1) structure temp.Resize(3, 1); temp[0] = 0.0; temp[1] = 1.0 - lam; temp[2] = lam; vector resids2 = pow(resids, 2.); sigma2 = garch_recursion(temp, resids2, resids, sigma2, 1, 0, 1, nobs, _backcast, varbs); return sigma2; } //+------------------------------------------------------------------+ //| Base class for volatility updaters (polymorphism support) | //+------------------------------------------------------------------+ class VolatilityUpdater { protected: bool m_initialized; // Internal test wrapper virtual void update_tester(ulong t, vector& parameters, vector& resids, vector& _sigma2, matrix& varbounds) { update(t, parameters, resids, _sigma2, varbounds); } public: VolatilityUpdater(void):m_initialized(false) {} VolatilityUpdater(VolatilityUpdater& other) { m_initialized = other.is_initialized(); } void operator=(VolatilityUpdater& other) { m_initialized = other.is_initialized(); } virtual ~VolatilityUpdater(void) {} virtual bool is_initialized(void) { return m_initialized; } virtual void initialize_update(vector ¶meters, vector& backcast, ulong _nobs); virtual void update(ulong t, vector ¶meters, vector& resids, vector& _sigma2, matrix& varbounds); }; //+------------------------------------------------------------------+ //| GarchUpdater class: Manages specific GARCH recursion settings | //+------------------------------------------------------------------+ class GarchUpdater: public VolatilityUpdater { private: ulong m_p, m_o, m_q; double m_power; double m_backcast; public: GarchUpdater(void) {} GarchUpdater(ulong p, ulong o, ulong q, double power) { initialize(p, o, q, power); } GarchUpdater(GarchUpdater& other) { m_p = other.get_p(); m_o = other.get_o(); m_q = other.get_q(); m_power = other.get_power(); m_backcast = other.get_backcast(); m_initialized = other.is_initialized(); } void operator=(GarchUpdater& other) { m_p = other.get_p(); m_o = other.get_o(); m_q = other.get_q(); m_power = other.get_power(); m_backcast = other.get_backcast(); m_initialized = other.is_initialized(); } virtual ~GarchUpdater(void) {} ulong get_p(void) { return m_p; } ulong get_o(void) { return m_o; } ulong get_q(void) { return m_q; } double get_power(void) { return m_power; } double get_backcast(void) { return m_backcast; } bool initialize(ulong p, ulong o, ulong q, double power) { m_p = p; m_o = o; m_backcast = -1.; m_q = q; m_power = power; m_initialized = (m_p || m_o); return m_initialized; } virtual void initialize_update(vector& parameters, vector& backcast, ulong nobs) override { m_backcast = backcast[0]; } // Core update loop for GARCH volatility virtual void update(ulong t, vector ¶meters, vector& resids, vector& sigma2, matrix& var_bounds) override { ulong loc = 0; sigma2[t] = parameters[loc]; loc += 1; // ARCH terms with power scaling for(ulong j = 0; j < m_p; ++j) { if((t - 1 - j) < 0) sigma2[t] += parameters[loc] * m_backcast; else sigma2[t] += parameters[loc] * pow(MathAbs(resids[t - 1 - j]), m_power); loc += 1; } // GJR terms with power scaling for(ulong j = 0; j < m_o; ++j) { if((t - 1 - j) < 0) sigma2[t] += parameters[loc] * 0.5 * m_backcast; else sigma2[t] += (parameters[loc] * pow(MathAbs(resids[t - 1 - j]), m_power) * (resids[t - 1 - j] < 0)); loc += 1; } // GARCH persistence terms for(ulong j = 0; j < m_q; ++j) { if((t - 1 - j) < 0) sigma2[t] += parameters[loc] * m_backcast; else sigma2[t] += parameters[loc] * sigma2[t - 1 - j]; loc += 1; } sigma2[t] = bounds_check(sigma2[t], var_bounds.Row(t)); } }; /*+------------------------------------------------------------------+ //| EWMAUpdater | //+------------------------------------------------------------------+ class EWMAUpdater: public VolatilityUpdater { private: bool m_estimate_lam; double m_backcast; vector m_params; EWMAUpdater(void) { } ~EWMAUpdater(void) { } virtual bool initialize(ulong p,ulong o, ulong q,double power) override { Print(__FUNCTION__, " wrong function call "); return false; } virtual bool initialize(double lam) override { m_estimate_lam = (lam == EMPTY_VALUE); m_params = vector::Zeros(3); if(!m_estimate_lam) { m_params[1] = 1.-lam; m_params[2] = lam; } m_initialized = true; return m_initialized; } virtual void initialize_update(vector ¶meters, vector& backcast, ulong _nobs) override { if(m_estimate_lam) { m_params[1] = 1.0 - parameters[0]; m_params[2] = parameters[0]; } m_backcast = backcast[0]; } virtual void update(ulong t, vector ¶meters, vector& resids, vector& sigma2, matrix& var_bounds) override { sigma2[t] = m_params[0]; if(t == 0) sigma2[t] += m_backcast; else sigma2[t] += ( m_params[1] * resids[t - 1] * resids[t - 1] + m_params[2] * sigma2[t - 1] ); sigma2[t] = bounds_check(sigma2[t], var_bounds.Row(t)); } }; //+------------------------------------------------------------------+ //|EGARCHUpdater | //+------------------------------------------------------------------+ class EGARCHUpdater: public VolatilityUpdater { private: ulong m_p, m_o, m_q; double m_backcast; vector m_insigma2, m_std_resids, m_abs_std_resids; void _resize(ulong nobs) { if(m_insigma2.Size()= 0) m_insigma2[t] += parameters[loc] * (m_abs_std_resids[t - 1 - j] - SQRT2_OV_PI); loc += 1; } for(ulong j = 0; j= 0) m_insigma2[t] += parameters[loc] * m_std_resids[t - 1 - j]; loc += 1; } for(ulong j = 0; j LNSIGMA_MAX) m_insigma2[t] = LNSIGMA_MAX; sigma2[t] = exp(m_insigma2[t]); if(sigma2[t] < var_bounds[t, 0]) { sigma2[t] = var_bounds[t, 0]; m_insigma2[t] = log(sigma2[t]); } else if(sigma2[t] > var_bounds[t, 1]) { sigma2[t] = var_bounds[t, 1] + log(sigma2[t]) - log(var_bounds[t, 1]); m_insigma2[t] = log(sigma2[t]); } } };*/ //+------------------------------------------------------------------+