//+------------------------------------------------------------------+ //| Warrior_EA | //| AnimateDread | //| | //| Label-overlap correction for the swing label's effective sample. | //+------------------------------------------------------------------+ #ifndef WARRIOR_LABELING_LABELOVERLAP_MQH #define WARRIOR_LABELING_LABELOVERLAP_MQH //+------------------------------------------------------------------+ //| Lopez de Prado ch. 4: overlapping labels are not independent | //| observations. This is the running mean lifespan + the correction | //| it feeds, owned as ONE object instead of two members reset from | //| three separate sites - the "N loose members cleared in more than | //| one place" shape the candidate-geometry incident (7452bd1) found | //| a bug in. | //+------------------------------------------------------------------+ class CLabelOverlap { private: double m_sum; // bars-to-resolution, summed over every resolved label long m_count; public: CLabelOverlap(void) { Reset(); } //--- Rebuilt with the label cache, not once per process: a window change makes every previous //--- measurement answer a different question, and carrying it forward would deflate the new //--- standard errors by the old overlap. void Reset(void) { m_sum = 0.0; m_count = 0; } //--- lifespanBars <= 0 means "not measured for this bar" (an early return in the label walk) and is //--- silently skipped - the same guard the two hand-written call sites both applied. void Accumulate(const int lifespanBars) { if(lifespanBars > 0) { m_sum += (double)lifespanBars; m_count++; } } long Count(void) const { return m_count; } //--- Mean bars-to-resolution, capped at the label's structural bound and floored at one bar. //--- 1.0 until something has been measured - deliberate: an UNMEASURED overlap must not silently //--- shrink anyone's sample, so the correction switches itself on only once it has evidence. double MeanLifespan(const int lifespanCap) const { if(m_count <= 0 || m_sum <= 0.0) return 1.0; double mean = m_sum / (double)m_count; double cap = (double)MathMax(lifespanCap, 1); return MathMax(1.0, MathMin(mean, cap)); } //--- Independent observations behind `rawN` overlapping labels. double EffectiveSampleSize(const double rawN, const int lifespanCap) const { if(rawN <= 0.0) return 0.0; double eff = rawN / MeanLifespan(lifespanCap); //--- Floor, so a caller dividing by it cannot hit zero... if(eff < 2.0) eff = 2.0; //--- ...then the cap, LAST, so the floor can never exceed the observations that actually exist. return MathMin(eff, rawN); } }; #endif // WARRIOR_LABELING_LABELOVERLAP_MQH