Warrior_EA/Expert/Training/PooledGate.mqh

248 lines
13 KiB
MQL5
Raw Permalink Normal View History

feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
//+------------------------------------------------------------------+
//| PooledGate.mqh |
//| Cross-instrument certification for the deploy decision |
//| Measured 2026-08-17 on SP500 H4: the OOS window is 4,738 bars |
//| and the mean triple-barrier label lifespan is 75.6 bars, so the |
//| holdout carries ~63 INDEPENDENT observations (see |
//| EffectiveSampleSize). Certifying a 3pp edge at 2 sigma needs |
//| ~1,036. The deploy gate was therefore unreachable by arithmetic |
//| - not because the models were short, but because the window |
//| cannot resolve the question either way. Sixteen times short. |
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
//+------------------------------------------------------------------+
#property strict
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
#ifndef WARRIOR_TRAINING_POOLEDGATE_MQH
#define WARRIOR_TRAINING_POOLEDGATE_MQH
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
//--- Records older than this are ignored. A chart that was stopped days ago is not evidence about the
//--- model running now, and a stale pool silently certifying a dead configuration is the failure mode
//--- this guards. Long enough to survive an overnight run, short enough that yesterday cannot vote.
#define POOL_MAX_AGE_HOURS 12
//--- Minimum distinct instruments before the pooled statistic is allowed to gate anything. Below this
//--- the pool is not a pool - it is one symbol with extra steps, and SE_CORR would equal SE_INDEP.
#define POOL_MIN_INSTRUMENTS 3
//--- Ceiling on peers read, sizing the fixed arrays. Well above the 24-instrument catalog.
#define POOL_MAX_RECORDS 64
//--- Bumped whenever the record layout changes. A reader that finds a different version SKIPS the
//--- record rather than misreading its columns - the stale-enum trap in a different costume.
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
//--- v2: the targetRR column went with the barrier geometry - the swing label has no structural
//--- break-even, so poolability is timeframe + version alone.
#define POOL_RECORD_VERSION 2
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
#define POOL_DIR "Warrior_EA\\Pool"
//+------------------------------------------------------------------+
//| One instrument's contribution to the pooled certificate. |
//+------------------------------------------------------------------+
struct SPoolRecord
{
string symbol;
int timeframe;
double chancePct; // this symbol's own zero-skill rate
double winPct; // what the model actually collected
double effN; // INDEPENDENT calls, already deflated by label lifespan
double lifespanBars;
long eraCount;
datetime stamp;
};
//+------------------------------------------------------------------+
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
//| CROSS-INSTRUMENT CERTIFICATION. |
//| |
//| One symbol's holdout cannot resolve the deploy question - see the |
//| file header: sixteen times short. The answer is more instruments, |
//| not more eras, so each chart publishes its own record and every |
//| chart reads the pool. |
//| |
//| It owns a DIRECTORY OF CSV FILES and nothing else. It does not |
//| know what a model is, and the two things it needs - the symbol's |
//| own numbers and the ratio they were measured at - arrive as |
//| arguments. That is why it takes no data view: a gate over files |
//| is not a reader of training data. |
//+------------------------------------------------------------------+
class CPooledGate
{
private:
//--- ONE-SHOT warning latch. A pool that cannot be written is a silent loss of the whole
//--- mechanism, and a mechanism that declines to act must announce it - but once, not per era.
bool m_writeWarned;
int ReadPooledEvidence(double &pooledExcessPp, double &seIndep, double &seCorr,
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
string &detail);
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
public:
CPooledGate(void) : m_writeWarned(false) { }
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
//--- `id` is passed rather than bound so there is no init-order question about when the identity
//--- became available.
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
void Publish(const string id, const SPoolRecord &rec);
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
//--- The pooled verdict across every peer on this timeframe.
bool Passes(string &report);
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
};
//+------------------------------------------------------------------+
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
//| Publish this instance's evidence. Called once per era, after the |
//| local gate has computed its own numbers. |
//| |
//| Writes ONLY this instrument's file. The filename carries symbol |
//| and timeframe so two charts can never collide, and a chart that |
//| restarts overwrites its own record rather than accumulating a |
//| history that would let one instrument vote many times. |
//+------------------------------------------------------------------+
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
void CPooledGate::Publish(const string id, const SPoolRecord &rec)
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
{
//--- Nothing measurable to contribute. Writing a placeholder would let a symbol with no evidence
//--- dilute the pool's weighting, which is the opposite of what inverse-variance weighting is for.
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
if(rec.chancePct <= 0.0 || rec.chancePct >= 100.0 || rec.winPct < 0.0 || rec.effN < 2.0)
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
return;
//--- _Period, not Period(): inside a CExpertBase subclass the bare call resolves to the inherited
//--- SETTER bool CExpertBase::Period(ENUM_TIMEFRAMES) rather than the builtin. Same predefined
//--- variable AutoTune.mqh uses to build its own per-symbol/timeframe filename.
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
string fn = StringFormat("%s\\%s_%d.csv", POOL_DIR, rec.symbol, rec.timeframe);
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
int h = FileOpen(fn, FILE_COMMON | FILE_WRITE | FILE_CSV | FILE_ANSI |
FILE_SHARE_READ | FILE_SHARE_WRITE, ',');
if(h == INVALID_HANDLE)
{
//--- One-shot: a pool that cannot be written is a silent loss of the whole mechanism, and a
//--- mechanism that declines to act must announce it (the third quiet no-op this codebase has
//--- been bitten by).
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
if(!m_writeWarned)
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
{
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
m_writeWarned = true;
Print(id + StringFormat(": WARNING - cannot write the pooled-gate record %s (error %d). This "
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
"instrument will not contribute to cross-instrument certification and "
"the pool will be short one member.", fn, GetLastError()));
}
return;
}
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
FileWrite(h, POOL_RECORD_VERSION, rec.symbol, rec.timeframe,
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
rec.chancePct, rec.winPct, rec.effN, rec.lifespanBars, rec.eraCount, (long)TimeCurrent());
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
FileClose(h);
}
//+------------------------------------------------------------------+
//| Read every peer's record and combine. Returns the number of |
//| instruments that qualified; the pooled figures come back through |
//| the out-params. |
//| |
//| INCLUDES THIS INSTRUMENT: its own file was just written, so the |
//| directory scan picks it up like any other and there is no special |
//| case to get wrong. |
//+------------------------------------------------------------------+
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
int CPooledGate::ReadPooledEvidence(double &pooledExcessPp, double &seIndep, double &seCorr,
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
string &detail)
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
{
pooledExcessPp = 0.0;
seIndep = 0.0;
seCorr = 0.0;
detail = "";
SPoolRecord rec[POOL_MAX_RECORDS];
int count = 0;
string found;
long searchHandle = FileFindFirst(POOL_DIR + "\\*.csv", found, FILE_COMMON);
if(searchHandle == INVALID_HANDLE)
return 0;
do
{
if(count >= POOL_MAX_RECORDS)
break;
int h = FileOpen(POOL_DIR + "\\" + found, FILE_COMMON | FILE_READ | FILE_CSV | FILE_ANSI |
FILE_SHARE_READ | FILE_SHARE_WRITE, ',');
if(h == INVALID_HANDLE)
continue;
int ver = (int)FileReadNumber(h);
//--- Version mismatch: SKIP, never reinterpret. Reading v2 columns as v1 would produce a
//--- plausible-looking record built from the wrong fields, which is worse than one fewer member.
if(ver != POOL_RECORD_VERSION)
{
FileClose(h);
continue;
}
SPoolRecord r;
r.symbol = FileReadString(h);
r.timeframe = (int)FileReadNumber(h);
r.chancePct = FileReadNumber(h);
r.winPct = FileReadNumber(h);
r.effN = FileReadNumber(h);
r.lifespanBars = FileReadNumber(h);
r.eraCount = (long)FileReadNumber(h);
r.stamp = (datetime)(long)FileReadNumber(h);
FileClose(h);
//--- POOLABILITY. Symbol deliberately does NOT have to match - that is the entire point - but
//--- everything that changes what the numbers MEAN does.
if(r.timeframe != (int)_Period)
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
continue; // different bar semantics
if(r.effN < 2.0 || r.chancePct <= 0.0 || r.chancePct >= 100.0)
continue; // no usable estimate
if((TimeCurrent() - r.stamp) > POOL_MAX_AGE_HOURS * 3600)
continue; // stale: a stopped chart is not evidence about now
rec[count++] = r;
}
while(FileFindNext(searchHandle, found));
FileFindClose(searchHandle);
if(count <= 0)
return 0;
//--- FIXED-EFFECTS COMBINATION. Each symbol is scored against ITS OWN chance rate, so symbols with
//--- different geometries and different drifts are directly comparable in this one currency.
double sumInvVar = 0.0;
for(int i = 0; i < count; i++)
{
refactor(dry): one binomial arithmetic for every "is this edge real" test The formula p(1-p)/n was transcribed nine times across six files - the two deploy gates, the two edge floors, the collapse recall floor, the barrier rung ladder, the inference bin SE, the pooled inverse-variance weights and both detectability reports. System\BinomialStats.mqh now holds it once, as free functions with no class dependency, so the god-class declaration does not grow to host pure math. BinomialVar(p, n) p(1-p)/n BinomialSEPct(p, n) 100*sqrt(p(1-p)/n) BinomialCallsForEdge(p, edge, sigmas) the same, solved for n NormalUpperTailQ(z) Q(z), via Math\Stat\Normal.mqh SidakFamilyP(z, N) 1-(1-Q(z))^N Value-preserving by construction: rates go in as probabilities so no call site gained a *100/100 round-trip, and BinomialSEPct is written through BinomialVar so the multiply order is the one it replaced. Every degenerate guard each site carried (p<=0, p>=1, n<=0) now lives in one place and returns the 0 those sites already treated as "no bar to clear". CExpertSignalAIBase::NormalUpperTail is gone; NormalUpperTailQ replaces it. What consolidating SURFACED, and is deliberately NOT changed here: the two Sidak selection gates compute their SE on the RAW call count, while every other SE in the project deflates by EffectiveSampleSize() for triple- barrier label overlap. That makes them the most permissive test in the codebase, by ~sqrt(mean label lifespan). Correcting it tightens a live deploy bar, which is a policy decision, not a refactor - flagged in the code at both sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:50:24 -04:00
double var = BinomialVar(rec[i].chancePct / 100.0, rec[i].effN); // in fraction^2
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
if(var <= 0.0)
continue;
sumInvVar += 1.0 / var;
}
if(sumInvVar <= 0.0)
return 0;
double weighted = 0.0, sumWSd = 0.0;
for(int i = 0; i < count; i++)
{
refactor(dry): one binomial arithmetic for every "is this edge real" test The formula p(1-p)/n was transcribed nine times across six files - the two deploy gates, the two edge floors, the collapse recall floor, the barrier rung ladder, the inference bin SE, the pooled inverse-variance weights and both detectability reports. System\BinomialStats.mqh now holds it once, as free functions with no class dependency, so the god-class declaration does not grow to host pure math. BinomialVar(p, n) p(1-p)/n BinomialSEPct(p, n) 100*sqrt(p(1-p)/n) BinomialCallsForEdge(p, edge, sigmas) the same, solved for n NormalUpperTailQ(z) Q(z), via Math\Stat\Normal.mqh SidakFamilyP(z, N) 1-(1-Q(z))^N Value-preserving by construction: rates go in as probabilities so no call site gained a *100/100 round-trip, and BinomialSEPct is written through BinomialVar so the multiply order is the one it replaced. Every degenerate guard each site carried (p<=0, p>=1, n<=0) now lives in one place and returns the 0 those sites already treated as "no bar to clear". CExpertSignalAIBase::NormalUpperTail is gone; NormalUpperTailQ replaces it. What consolidating SURFACED, and is deliberately NOT changed here: the two Sidak selection gates compute their SE on the RAW call count, while every other SE in the project deflates by EffectiveSampleSize() for triple- barrier label overlap. That makes them the most permissive test in the codebase, by ~sqrt(mean label lifespan). Correcting it tightens a live deploy bar, which is a policy decision, not a refactor - flagged in the code at both sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:50:24 -04:00
double var = BinomialVar(rec[i].chancePct / 100.0, rec[i].effN);
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
if(var <= 0.0)
continue;
double w = (1.0 / var) / sumInvVar;
weighted += w * (rec[i].winPct - rec[i].chancePct); // percentage points
//--- Perfectly-correlated bound: the weighted sum of the individual standard deviations, which
//--- is what the pooled SE degenerates to when every member moves together.
sumWSd += w * MathSqrt(var) * 100.0;
detail += StringFormat("%s%s %.0f%%/%.0f%% n%.0f w%.2f", (detail == "" ? "" : " "),
rec[i].symbol, rec[i].winPct, rec[i].chancePct, rec[i].effN, w);
}
pooledExcessPp = weighted;
seIndep = 100.0 * MathSqrt(1.0 / sumInvVar);
seCorr = sumWSd;
return count;
}
//+------------------------------------------------------------------+
//| The cross-instrument verdict, as one log line and one boolean. |
//| |
//| Gates on the PESSIMISTIC bound. A pooled edge that clears SE_CORR |
//| cannot be explained by the members being correlated, because that |
//| bound already assumes they are perfectly correlated. |
//+------------------------------------------------------------------+
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
bool CPooledGate::Passes(string &report)
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
{
report = "";
double excess = 0.0, seIndep = 0.0, seCorr = 0.0;
string detail = "";
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
int members = ReadPooledEvidence(excess, seIndep, seCorr, detail);
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
if(members < POOL_MIN_INSTRUMENTS)
{
report = StringFormat(" | POOL %d/%d instruments - not enough to certify across symbols yet"
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
" (run more charts on this timeframe; each one is worth far more"
" independent evidence than more bars of the same symbol)",
members, POOL_MIN_INSTRUMENTS);
feat(gate): cross-instrument pooled certification The deploy bottleneck is CERTIFICATION, not training. A 4,738-bar OOS window at L=75.6 holds ~63 independent observations; certifying a 3pp edge at 2 sigma needs ~1,036. More bars of the same symbol barely help - they overlap. Other symbols do not. WHAT POOLS. Not win rates: symbols have different derived geometries, different break-evens and different drifts, so averaging raw rates across them is meaningless. What pools is each symbol's EXCESS OVER ITS OWN CHANCE RATE, combined by inverse-variance weighting (fixed-effects meta-analysis). Each symbol keeps its own model, geometry and chance rate; only the evidence is combined. THE CORRELATION PROBLEM, bracketed rather than assumed away. SP500 and NAS100 are ~0.9 correlated and pooling them as independent inflates the evidence. Nothing here can measure that without sharing return series, so instead of guessing a correction the gate reports both ends: SE_INDEP = sqrt(1/SUM(1/var_i)) all members independent SE_CORR = SUM(w_i * sqrt(var_i)) all members perfectly correlated The truth is always between. THE GATE USES SE_CORR, so a pass cannot be an artifact of correlated instruments - that bound already assumes the worst. The ratio is logged as the diversification credit the gate declines to claim, so the cost of that conservatism is visible instead of hidden. SCOPE, deliberately limited: the pooled result is REPORTED, never folded into tradeableOK. The local gate certifies the model that actually trades this symbol; the pool answers the different question of whether the strategy has an edge at all. Letting a cross-symbol result license a local deploy would ship a model that never cleared its own bar - so it cannot. Mechanics: one file per instrument (no concurrent-write path to get wrong), every FileOpen carrying FILE_SHARE_READ|FILE_SHARE_WRITE, records skipped rather than reinterpreted on a version mismatch, 12h staleness cutoff so a stopped chart cannot vote, and pooling refused below 3 instruments. Poolability requires matching timeframe and ratio; differing SYMBOL is the entire point. Publishing is unconditional - a pool that only hears from winners is a selection effect, not a meta-analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:45:37 -04:00
return false;
}
double bar = EDGE_MIN_SIGMAS * seCorr;
bool pass = (excess > bar);
//--- The credit the pool WOULD earn if its members were independent. Printed because the gap between
//--- the two bounds is the whole cost of refusing to assume independence, and it is the number that
//--- says whether adding a genuinely uncorrelated instrument is worth more than another correlated one.
double credit = (seIndep > 0.0) ? seCorr / seIndep : 1.0;
report = StringFormat(" | POOLED CERTIFICATE across %d instruments: excess %+.2fpp vs a %.2fpp bar"
" (%.0f x SE %.2fpp, the ALL-CORRELATED bound; SE would be %.2fpp if the"
" instruments were independent, a %.1fx diversification credit this gate"
" deliberately declines to claim) -> %s [%s]",
members, excess, bar, EDGE_MIN_SIGMAS, seCorr, seIndep, credit,
(pass ? "PASSES" : "fails"), detail);
return pass;
}
//+------------------------------------------------------------------+
refactor(pool): the cross-instrument gate owns a directory, not a model PooledGate was three CExpertSignalAIBase method bodies in an #included partial. It is now CPooledGate, a class the signal owns. It needed NO data view. Diagnosing that first is the point: the module reads a directory of CSV files and knows nothing about a model. The only things it needs from its owner - the symbol's own numbers and the ratio they were measured at - are arguments. Handing it a CTrainingDataView would have been machinery for a dependency that does not exist. The owner fills SPoolRecord (the on-disk shape, which already existed) because only it knows its symbol, its actual TargetRR and its label lifespan. `id` is passed per call rather than bound, so there is no init-order question about when the identity became available - m_symbol is set by CExpertSignal::Init and ID by SetIdentity, at different times. m_poolWriteWarned was a one-shot latch living on the signal for a warning only this module emits. It is m_writeWarned, private. targetRR is now threaded into ReadPooledEvidence rather than read from the owner. That is not plumbing for its own sake: a peer measured at a different ratio has a different structural break-even, and only the caller knows which ratio it is asking about. Caught before compiling: I declared ReadPooledEvidence from memory as (..., double &pooledEffN, const double targetRR). The real signature ends in `string &detail`. Read the definition, aligned both ends. Call sites in Training.mqh are untouched - PublishPoolRecord and PooledGatePasses remain on the signal as the thin fillers that know its geometry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:32:48 -04:00
#endif // WARRIOR_TRAINING_POOLEDGATE_MQH