//+------------------------------------------------------------------+ //| RegimeLabeler.mqh | //| MMQ — Muhammad Minhas Qamar | //| www.mql5.com/en/articles/23491 | //+------------------------------------------------------------------+ #property copyright "MMQ — Muhammad Minhas Qamar" #property link "https://www.mql5.com/en/articles/23491" #property version "1.00" #property strict //--- regime a window is assigned to. enum ENUM_REGIME { REGIME_TREND, // persistent directional drift REGIME_RANGE, // mean-reverting oscillation REGIME_VOLATILE, // large erratic moves, no clear direction REGIME_UNDEFINED // not enough data / degenerate window }; //+------------------------------------------------------------------+ //| CRegimeLabeler - rule-based ground truth for training. | //| | //| BOSS is a supervised classifier: it needs labelled example | //| windows to learn from. Hand-labelling price history is | //| impractical, so we derive labels mechanically from two classic | //| statistics of the window's log returns: | //| | //| - lag-1 autocorrelation of returns. Positive autocorrelation | //| means moves tend to continue (trending); near-zero or | //| negative means they tend to reverse (ranging). | //| - return volatility (standard deviation), compared against a | //| rolling reference so "high" is relative to the instrument, | //| not an absolute number. | //| | //| Decision order matches the usual regime hierarchy: unusually | //| high volatility dominates and marks the window VOLATILE; among | //| the rest, strong positive autocorrelation marks TREND, and the | //| remainder is RANGE. | //| | //| This labeller is deliberately simple and transparent: it is the | //| benchmark's source of truth, not the thing under test. BOSS | //| must learn to reproduce these labels from window SHAPE alone, | //| and then hold up under noise where this statistic-based rule | //| starts to wobble. | //+------------------------------------------------------------------+ class CRegimeLabeler { private: double m_vol_hi_mult; // volatility above mult*reference -> volatile double m_acf_trend; // autocorrelation above this -> trend double m_ref_vol; // rolling reference volatility (set by caller) double LogReturns(const double &price[],int len,double &ret[]) const; double Autocorr1(const double &ret[],int n) const; double StdDev(const double &ret[],int n) const; public: CRegimeLabeler(void); //--- thresholds. Defaults are reasonable starting points; the //--- volatility multiplier is relative to the reference vol. void SetVolHighMult(double m) { if(m>0.0) m_vol_hi_mult=m; } void SetTrendACF(double a) { if(a>-1.0 && a<1.0) m_acf_trend=a; } //--- set the reference volatility that "high" is measured against. //--- Typically the median/mean window volatility over the sample. void SetReferenceVol(double v) { m_ref_vol=(v>0.0)?v:0.0; } //--- label one price window (len closes, oldest first). Needs a //--- reference vol to have been set for the VOLATILE test to be //--- meaningful; with none set, volatility is judged against the //--- window's own returns only (absolute). ENUM_REGIME Label(const double &price[],int len) const; //--- volatility (return stdev) of one price window, exposed so the //--- caller can build the reference distribution in a first pass. double WindowVolatility(const double &price[],int len) const; //--- human-readable regime name. static string RegimeName(ENUM_REGIME r); }; //+------------------------------------------------------------------+ //| Construct with default thresholds. | //+------------------------------------------------------------------+ CRegimeLabeler::CRegimeLabeler(void) { m_vol_hi_mult=1.6; // 1.6x the reference volatility reads as "volatile" m_acf_trend =0.15; // lag-1 ACF above 0.15 reads as "trending" m_ref_vol =0.0; } //+------------------------------------------------------------------+ //| Fill ret[0..len-2] with log returns of price[0..len-1]. | //| Returns the number of returns produced. | //+------------------------------------------------------------------+ double CRegimeLabeler::LogReturns(const double &price[],int len,double &ret[]) const { int k=0; ArrayResize(ret,MathMax(0,len-1)); for(int i=1;i0.0 && price[i]>0.0) ret[k++]=MathLog(price[i]/price[i-1]); } ArrayResize(ret,k); return k; } //+------------------------------------------------------------------+ //| Lag-1 autocorrelation of a return series. | //| corr = sum((r[i]-mean)(r[i-1]-mean)) / sum((r[i]-mean)^2). | //+------------------------------------------------------------------+ double CRegimeLabeler::Autocorr1(const double &ret[],int n) const { if(n<3) return 0.0; double mean=0.0; for(int i=0;i0) num+=d*(ret[i-1]-mean); } if(den<=0.0) return 0.0; return num/den; } //+------------------------------------------------------------------+ //| Standard deviation of a return series (population). | //+------------------------------------------------------------------+ double CRegimeLabeler::StdDev(const double &ret[],int n) const { if(n<=0) return 0.0; double mean=0.0; for(int i=0;i0.0)?m_ref_vol:vol; if(vol>m_vol_hi_mult*vol_ref) return REGIME_VOLATILE; //--- among calmer windows, strong persistence marks a trend. if(acf>m_acf_trend) return REGIME_TREND; return REGIME_RANGE; } //+------------------------------------------------------------------+ //| Human-readable regime name. | //+------------------------------------------------------------------+ string CRegimeLabeler::RegimeName(ENUM_REGIME r) { switch(r) { case REGIME_TREND: return "trend"; case REGIME_RANGE: return "range"; case REGIME_VOLATILE: return "volatile"; case REGIME_UNDEFINED: return "undefined"; } return "unknown"; } //+------------------------------------------------------------------+