forked from animatedread/Warrior_EA
73 lines
3.1 KiB
MQL5
73 lines
3.1 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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
|