forked from animatedread/Warrior_EA
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>
134 lines
8.3 KiB
MQL5
134 lines
8.3 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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
|
|
//+------------------------------------------------------------------+
|
|
//| ONE ERA'S CONFUSION COUNTERS, WITH ONE LIFETIME. |
|
|
//| |
|
|
//| 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
|
|
//--- bars - the base rate of the LARGER directional class, because beating the smaller one proves
|
|
//--- only that the model found the drift.
|
|
double ChancePrecPct(void) const
|
|
{
|
|
return CoverageMeasurable()
|
|
? PctD((double)MathMax(buyTotal, sellTotal), (double)Bars()) : -1.0;
|
|
}
|
|
//--- 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
|
|
//+------------------------------------------------------------------+
|