refactor(oos): twenty-one counters with one lifetime become one object
SOosTally holds this era's OOS confusion counts and the rates they imply.
The signal keeps one member where it kept twenty-one, and the era-reset
block loses twenty of its twenty-one clearing lines.
THE SHAPE THIS ENDS is the one that produced 7452bd1: a group of tallies
read together but cleared one-per-line, so a second reset path could clear
a subset and leave stale numerators over restarted denominators. Reset()
is now the only way to clear them and it clears all of them.
The pair had already started to drift. m_oosBuyFired/m_oosBuyFiredHits sat
at line 1085 and their Sell twins at line 1140 - 55 lines and an unrelated
member apart, with the Buy comment still claiming to describe both.
DERIVED RATES MOVE WITH THE DATA. `(bars > 0) ? (int)MathRound(100.0 * x /
bars) : -1` was written out twelve times, and the "-1 means not measurable,
never 0" convention re-spelled at each - a convention the deploy gate
depends on, since every caller tests `< 0` to mean "this does not block".
One rounding rule and one sentinel now.
GROUPED BY LIFETIME, NOT BY NAME. m_oosSamples looks like it belongs here
and does not: it is RUN-level, reset only with the weights, and the status
panel prints it beside dOosError which is also a run-level EMA. That pairing
is correct and stays. But the confidence-calibration block divided per-era
numerators by it, naming the results `empiricalAccuracy` and
`avgClaimedConfidence` when neither is that - the run-level denominator
cancels in their ratio, so eraScale was right and the two named
intermediates were not. Now written as the ratio it actually is, with the
cancellation stated, so nobody logs or gates on a half that decays with era
count.
BEHAVIOUR UNCHANGED: every moved expression preserves its formula, its
denominator and its sentinel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 14:15:22 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Warrior_EA |
|
|
|
|
|
//| AnimateDread |
|
|
|
|
|
//| |
|
|
|
|
|
//| One era's out-of-sample confusion counts, and the rates they |
|
|
|
|
|
//| imply. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#ifndef WARRIOR_TRAINING_OOSTALLY_MQH
|
|
|
|
|
#define WARRIOR_TRAINING_OOSTALLY_MQH
|
|
|
|
|
//+------------------------------------------------------------------+
|
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
|
|
|
//| ONE ERA'S CONFUSION COUNTERS, WITH ONE LIFETIME. |
|
refactor(oos): twenty-one counters with one lifetime become one object
SOosTally holds this era's OOS confusion counts and the rates they imply.
The signal keeps one member where it kept twenty-one, and the era-reset
block loses twenty of its twenty-one clearing lines.
THE SHAPE THIS ENDS is the one that produced 7452bd1: a group of tallies
read together but cleared one-per-line, so a second reset path could clear
a subset and leave stale numerators over restarted denominators. Reset()
is now the only way to clear them and it clears all of them.
The pair had already started to drift. m_oosBuyFired/m_oosBuyFiredHits sat
at line 1085 and their Sell twins at line 1140 - 55 lines and an unrelated
member apart, with the Buy comment still claiming to describe both.
DERIVED RATES MOVE WITH THE DATA. `(bars > 0) ? (int)MathRound(100.0 * x /
bars) : -1` was written out twelve times, and the "-1 means not measurable,
never 0" convention re-spelled at each - a convention the deploy gate
depends on, since every caller tests `< 0` to mean "this does not block".
One rounding rule and one sentinel now.
GROUPED BY LIFETIME, NOT BY NAME. m_oosSamples looks like it belongs here
and does not: it is RUN-level, reset only with the weights, and the status
panel prints it beside dOosError which is also a run-level EMA. That pairing
is correct and stays. But the confidence-calibration block divided per-era
numerators by it, naming the results `empiricalAccuracy` and
`avgClaimedConfidence` when neither is that - the run-level denominator
cancels in their ratio, so eraScale was right and the two named
intermediates were not. Now written as the ratio it actually is, with the
cancellation stated, so nobody logs or gates on a half that decays with era
count.
BEHAVIOUR UNCHANGED: every moved expression preserves its formula, its
denominator and its sentinel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 14:15:22 -04:00
|
|
|
//| |
|
|
|
|
|
//| Every field here is cleared at the start of an era and read at |
|
|
|
|
|
//| the end of it. They were twenty-one separate members cleared |
|
|
|
|
|
//| one-per-line at a single site, which is the arrangement that |
|
|
|
|
|
//| produced 7452bd1: a second reset path cleared ONE of ten tallies |
|
|
|
|
|
//| and nine stale numerators went on being divided by restarted |
|
|
|
|
|
//| denominators. A partial reset is not expressible here. |
|
|
|
|
|
//| |
|
|
|
|
|
//| GROUPED BY LIFETIME, NOT BY NAME. m_oosSamples and dOosError look |
|
|
|
|
|
//| like they belong in here and do not: both are RUN-level (a |
|
|
|
|
|
//| rolling EMA and its sample count, reset only when the weights |
|
|
|
|
|
//| are), and the status panel prints them together on purpose. Two |
|
|
|
|
|
//| quantities that are cleared at different times must not sit in |
|
|
|
|
|
//| one object, however similar their names - that similarity is |
|
|
|
|
|
//| precisely what makes the mistake easy to make later. |
|
|
|
|
|
//| |
|
|
|
|
|
//| The derived rates live here too, because every one of them was |
|
|
|
|
|
//| written out at the call site and the "-1 means not measurable" |
|
|
|
|
|
//| convention was re-spelled at each: `(bars > 0) ? (int)MathRound( |
|
|
|
|
|
//| 100.0 * x / bars) : -1` appeared a dozen times. One rounding rule |
|
|
|
|
|
//| and one absent-value sentinel, so a reader checking whether the |
|
|
|
|
|
//| gate and the log agree does not have to compare a dozen |
|
|
|
|
|
//| expressions character by character. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SOosTally
|
|
|
|
|
{
|
|
|
|
|
//--- BY TRUE CLASS: how many OOS bars actually were each class, and how many the net caught.
|
|
|
|
|
//--- hits/total is per-class RECALL - the convergence gate that stops a model "winning" by
|
|
|
|
|
//--- calling everything Neutral.
|
|
|
|
|
int buyTotal, sellTotal, neutralTotal;
|
|
|
|
|
int buyHits, sellHits, neutralHits;
|
|
|
|
|
//--- BY PREDICTED CLASS: the same population keyed the other way, so hits/predicted is per-class
|
|
|
|
|
//--- PRECISION - of the bars it called Sell, how many were Sell - rather than recall.
|
|
|
|
|
int buyPredicted, sellPredicted, neutralPredicted;
|
|
|
|
|
int buyPredictedHits, sellPredictedHits, neutralPredictedHits;
|
|
|
|
|
//--- FIRED: the same era under the LIVE decision rule - a directional call counted whenever the
|
|
|
|
|
//--- prior-corrected posterior is non-Neutral, i.e. exactly the bars on which the deployed EA
|
|
|
|
|
//--- would cast a directional vote. This is the population the deploy gate judges.
|
|
|
|
|
int buyFired, sellFired;
|
|
|
|
|
int buyFiredHits, sellFiredHits;
|
|
|
|
|
//--- Summed claimed confidence over this era's scored bars, for the calibration blend.
|
|
|
|
|
double confidenceSum;
|
|
|
|
|
|
|
|
|
|
SOosTally(void) { Reset(); }
|
|
|
|
|
//--- ALL of them, together. The only way to clear this object.
|
|
|
|
|
void Reset(void)
|
|
|
|
|
{
|
|
|
|
|
buyTotal = sellTotal = neutralTotal = 0;
|
|
|
|
|
buyHits = sellHits = neutralHits = 0;
|
|
|
|
|
buyPredicted = sellPredicted = neutralPredicted = 0;
|
|
|
|
|
buyPredictedHits = sellPredictedHits = neutralPredictedHits = 0;
|
|
|
|
|
buyFired = sellFired = 0;
|
|
|
|
|
buyFiredHits = sellFiredHits = 0;
|
|
|
|
|
confidenceSum = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- THE DENOMINATORS, derived once ---------------------------------------------------------
|
|
|
|
|
//--- Every OOS bar this era gets exactly one true class, so these three sum to the era's bars.
|
|
|
|
|
int Bars(void) const { return buyTotal + sellTotal + neutralTotal; }
|
|
|
|
|
//--- The bars the deployed EA would have TRADED, and how many of those paid.
|
|
|
|
|
int DirCalls(void) const { return buyFired + sellFired; }
|
|
|
|
|
int DirHits(void) const { return buyFiredHits + sellFiredHits; }
|
|
|
|
|
//--- The bars that actually WERE directional - the base rate coverage is judged against.
|
|
|
|
|
int DirTrue(void) const { return buyTotal + sellTotal; }
|
|
|
|
|
//--- Correct calls across all three classes, for the calibration blend's accuracy term.
|
|
|
|
|
int Hits(void) const { return buyHits + sellHits + neutralHits; }
|
|
|
|
|
|
|
|
|
|
//--- THE RATES ------------------------------------------------------------------------------
|
|
|
|
|
//--- -1 IS "NOT MEASURABLE", never 0. A gate reading 0 would treat an unmeasured quantity as a
|
|
|
|
|
//--- failure; every caller here tests `< 0` to mean "this does not block".
|
|
|
|
|
static int Pct(const int num, const int den)
|
|
|
|
|
{
|
|
|
|
|
return (den > 0) ? (int)MathRound(100.0 * num / den) : -1;
|
|
|
|
|
}
|
|
|
|
|
static double PctD(const double num, const double den)
|
|
|
|
|
{
|
|
|
|
|
return (den > 0.0) ? 100.0 * num / den : -1.0;
|
|
|
|
|
}
|
|
|
|
|
//--- Share of this era's OOS bars. The one denominator every predicted/true/fired rate uses, so
|
|
|
|
|
//--- the log's columns are directly comparable rather than nearly so.
|
|
|
|
|
int PctOfBars(const int count) const { return Pct(count, Bars()); }
|
|
|
|
|
|
|
|
|
|
//--- Per-class recall, blocked below a minimum true-sample count: a class with almost no true
|
|
|
|
|
//--- bars this era must not gate convergence, because there is nothing to measure it against.
|
|
|
|
|
//--- (Requiring a real minimum is what stopped a run converging at era 44 on an OOS window
|
|
|
|
|
//--- holding ZERO true Buy/Sell bars.)
|
|
|
|
|
int BuyRecallPct(const int minSamples) const
|
|
|
|
|
{ return (buyTotal >= minSamples) ? Pct(buyHits, buyTotal) : -1; }
|
|
|
|
|
int SellRecallPct(const int minSamples) const
|
|
|
|
|
{ return (sellTotal >= minSamples) ? Pct(sellHits, sellTotal) : -1; }
|
|
|
|
|
int NeutralRecallPct(const int minSamples) const
|
|
|
|
|
{ return (neutralTotal >= minSamples) ? Pct(neutralHits, neutralTotal) : -1; }
|
|
|
|
|
|
|
|
|
|
//--- Neutral's predicted and fired shares are the RESIDUAL, not their own counters: every OOS bar
|
|
|
|
|
//--- gets exactly one call, so what is not Buy and not Sell is Neutral by construction.
|
|
|
|
|
int NeutralPredictedShare(void) const
|
|
|
|
|
{ return PctOfBars(Bars() - buyPredicted - sellPredicted); }
|
|
|
|
|
int NeutralFiredShare(void) const
|
|
|
|
|
{ return PctOfBars(Bars() - buyFired - sellFired); }
|
|
|
|
|
|
|
|
|
|
//--- THE DEPLOY-GATE INPUTS -----------------------------------------------------------------
|
|
|
|
|
//--- Coverage is measurable only when there were bars AND some of them were directional; without
|
|
|
|
|
//--- both, "the model calls too rarely" and "there was nothing to call" are indistinguishable.
|
|
|
|
|
bool CoverageMeasurable(void) const { return (Bars() > 0 && DirTrue() > 0); }
|
|
|
|
|
double CoveragePct(void) const
|
|
|
|
|
{ return CoverageMeasurable() ? PctD(DirCalls(), Bars()) : -1.0; }
|
|
|
|
|
double BaseRatePct(void) const
|
|
|
|
|
{ return CoverageMeasurable() ? PctD(DirTrue(), Bars()) : -1.0; }
|
|
|
|
|
double DirPrecPct(void) const { return PctD(DirHits(), DirCalls()); }
|
|
|
|
|
//--- CHANCE PRECISION: what an always-call-one-direction model would have scored on these same
|
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
|
|
|
//--- bars - the base rate of the LARGER directional class, because beating the smaller one proves
|
|
|
|
|
//--- only that the model found the drift.
|
refactor(oos): twenty-one counters with one lifetime become one object
SOosTally holds this era's OOS confusion counts and the rates they imply.
The signal keeps one member where it kept twenty-one, and the era-reset
block loses twenty of its twenty-one clearing lines.
THE SHAPE THIS ENDS is the one that produced 7452bd1: a group of tallies
read together but cleared one-per-line, so a second reset path could clear
a subset and leave stale numerators over restarted denominators. Reset()
is now the only way to clear them and it clears all of them.
The pair had already started to drift. m_oosBuyFired/m_oosBuyFiredHits sat
at line 1085 and their Sell twins at line 1140 - 55 lines and an unrelated
member apart, with the Buy comment still claiming to describe both.
DERIVED RATES MOVE WITH THE DATA. `(bars > 0) ? (int)MathRound(100.0 * x /
bars) : -1` was written out twelve times, and the "-1 means not measurable,
never 0" convention re-spelled at each - a convention the deploy gate
depends on, since every caller tests `< 0` to mean "this does not block".
One rounding rule and one sentinel now.
GROUPED BY LIFETIME, NOT BY NAME. m_oosSamples looks like it belongs here
and does not: it is RUN-level, reset only with the weights, and the status
panel prints it beside dOosError which is also a run-level EMA. That pairing
is correct and stays. But the confidence-calibration block divided per-era
numerators by it, naming the results `empiricalAccuracy` and
`avgClaimedConfidence` when neither is that - the run-level denominator
cancels in their ratio, so eraScale was right and the two named
intermediates were not. Now written as the ratio it actually is, with the
cancellation stated, so nobody logs or gates on a half that decays with era
count.
BEHAVIOUR UNCHANGED: every moved expression preserves its formula, its
denominator and its sentinel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 14:15:22 -04:00
|
|
|
double ChancePrecPct(void) const
|
|
|
|
|
{
|
|
|
|
|
return CoverageMeasurable()
|
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
|
|
|
? PctD((double)MathMax(buyTotal, sellTotal), (double)Bars()) : -1.0;
|
refactor(oos): twenty-one counters with one lifetime become one object
SOosTally holds this era's OOS confusion counts and the rates they imply.
The signal keeps one member where it kept twenty-one, and the era-reset
block loses twenty of its twenty-one clearing lines.
THE SHAPE THIS ENDS is the one that produced 7452bd1: a group of tallies
read together but cleared one-per-line, so a second reset path could clear
a subset and leave stale numerators over restarted denominators. Reset()
is now the only way to clear them and it clears all of them.
The pair had already started to drift. m_oosBuyFired/m_oosBuyFiredHits sat
at line 1085 and their Sell twins at line 1140 - 55 lines and an unrelated
member apart, with the Buy comment still claiming to describe both.
DERIVED RATES MOVE WITH THE DATA. `(bars > 0) ? (int)MathRound(100.0 * x /
bars) : -1` was written out twelve times, and the "-1 means not measurable,
never 0" convention re-spelled at each - a convention the deploy gate
depends on, since every caller tests `< 0` to mean "this does not block".
One rounding rule and one sentinel now.
GROUPED BY LIFETIME, NOT BY NAME. m_oosSamples looks like it belongs here
and does not: it is RUN-level, reset only with the weights, and the status
panel prints it beside dOosError which is also a run-level EMA. That pairing
is correct and stays. But the confidence-calibration block divided per-era
numerators by it, naming the results `empiricalAccuracy` and
`avgClaimedConfidence` when neither is that - the run-level denominator
cancels in their ratio, so eraScale was right and the two named
intermediates were not. Now written as the ratio it actually is, with the
cancellation stated, so nobody logs or gates on a half that decays with era
count.
BEHAVIOUR UNCHANGED: every moved expression preserves its formula, its
denominator and its sentinel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 14:15:22 -04:00
|
|
|
}
|
|
|
|
|
//--- An era in which the model fired nothing directional at all. Distinguished from a bad era on
|
|
|
|
|
//--- purpose: there is no precision to rank it by, so it must not win "best era".
|
|
|
|
|
bool FullyCollapsed(void) const { return (DirCalls() <= 0); }
|
|
|
|
|
};
|
|
|
|
|
#endif // WARRIOR_TRAINING_OOSTALLY_MQH
|
|
|
|
|
//+------------------------------------------------------------------+
|