Warrior_EA/Expert/Labeling/LabelOverlap.mqh
AnimateDread 8f2164698b feat(target): delete the barrier/geometry stack - the label is the verdict
Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE
target and the era verdict is precision + recall per class against the
label's own base rate - no win rate, no break-even, no expectancy, no
geometry anywhere in training.

DELETED
- Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep,
  FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives
  in Labeling/LabelOverlap.mqh), 3 test EAs.
- TripleBarrierLabel + walk, fractal label, geometry derivation/scan/
  adoption, exit-policy replay, excursion MI targets, the drift verdict
  (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry,
  the barrier defines, the .cfg geometry adopt (slots kept as zeros for
  the positional layout), the derived-geometry live-order override.
- TRAINING_TARGET input/enum: direction models are always swing; META2
  re-keys the meta head onto label agreement (descriptor loses its two
  geometry slots).

REWORKED
- Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with
  FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is
  never cached, so it can never freeze as a false Neutral; training,
  calibration, OOS scoring and online learning all skip unresolved bars.
- SDeployVerdict: significance-only; SOosTally chance = larger
  directional class share; pooled gate poolability = timeframe (record v2).
- Purge/embargo/declustering gaps: the measured mean label resolution
  lag (LabelResolutionBars), not a barrier horizon.
- Pool purge key + backfill DB rows: marked at the bar the label
  resolved on (m_labelResolveAge), not a fabricated barrier touch.
- Online learning frontier: finality, not a horizon delay.
- m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced ->
  m_erasSinceBest, ensemble vote outcome arrays -> label arrays.

STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection
are inputs again - trade management is the tester GA's search space.

Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional,
CUT token gone); META1 -> META2. Full retrain, as planned.

Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs,
0 errors, 0 warnings each.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00

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