Warrior_EA/Signals/SignalMETA.mqh

579 lines
31 KiB
MQL5
Raw Permalink Normal View History

feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
//+------------------------------------------------------------------+
//| Warrior_EA |
//| AnimateDread |
//| |
//+------------------------------------------------------------------+
#include "..\Expert\ExpertSignalAIBase.mqh"
refactor(meta): one owner for the pattern taxonomy and its table names MetaCorpus was already a class, so the raw-include problem was not the one here. The problem was that the rule for naming a signal-DB pattern table MetaFamilyName(f) + "_Pattern_" + p + ("_Buy" | "_Sell") was written out FOUR times, each wrapped in its own identical family/pattern/side triple loop: the corpus loader, the stale-DB guard, META's row counter and META's exporter. Four chances for a rename to leave three of them querying an absent table and reporting it as "family disabled" - which is what that code says when a table is missing, so the failure would have looked like normal operation. CMetaFamilies now owns the taxonomy and that rule. Callers walk ONE flat index over all 52 tables and never spell a name: for(int ti = 0; CMetaFamilies::TableAt(ti, table, f, p, isBuy); ti++) Enumeration order is unchanged - Buy then Sell within a pattern, families in order - so the corpus is assembled in exactly the same sequence as before. META's OneHotSlot had a second hardcoded 0/4/8/14 ladder with a comment reading "matches MetaFamilyPatterns' 4/4/6/12" - a note asking a reader to keep two constants in step by hand. The ladder is now summed from the pattern counts, so they agree by construction. The bound against META_ONE_HOT_SLOTS stays in META: the head's input width is that class's business, and a taxonomy grown past it must be caught rather than silently truncated. Caught while re-reading the rewritten loop: my first counter was `t`, and the body declares `MqlDateTime t`. Renamed to `ti` at all four sites before it reached a compile. MetaCorpus.mqh moves to Expert\Training\ with the other real classes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 12:26:33 -04:00
#include "..\Expert\Training\MetaCorpus.mqh"
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
// wizard description start
//+------------------------------------------------------------------+
//| Description of the class |
//| Title=Signals of indicator 'Meta AI' |
//| Type=SignalAdvanced |
//| Name=Meta AI |
//| ShortName=META |
//| Class=CSignalMETA |
//| Page=signal_meta |
//+------------------------------------------------------------------+
// wizard description end
//+------------------------------------------------------------------+
//| Class CSignalMETA - stage S2 of Meta_Labeling_Design.md. |
//| |
//| The meta-labeling head: instead of asking "which way will the |
//| next bar go" (measured dead - direction-closed verdict), it asks |
//| "given that a SPECIFIC classic-pattern candidate just fired, |
//| will THAT trade reach its target before its stop, at the EA's |
//| own geometry, net of cost". One net for all 52 pattern-sides; |
//| pattern identity rides in as input features. |
//| |
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
//| Sample: CMetaCorpus - preferably swept off THIS chart's own |
//| history through the live classic ladders, otherwise |
//| the signal DB built by an 18-year backtest with |
//| UseDatabaseRanking on (per-side journaling means the |
//| DB IS the candidate stream, uncensored). |
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
//| Label: triple-barrier win/loss of the candidate's own side |
//| from its fire bar - the side-conditional win caches |
//| the label prebuild already computes; the DB's stop- |
//| and-reverse outcome is NEVER reused as a label. |
//| Features: the shared BuildFeatureWindow() output plus a 31-wide |
//| setup descriptor appended at the input (26-slot |
//| pattern one-hot, side, tanh-squashed netVote, SL/TP |
//| in ATR, spread/ATR at fire time). Appended at the |
//| input rather than "at the head" because CNet has no |
//| concat layer; on the MLP front end the two are |
//| equivalent up to one linear layer. |
//| Head: 2 outputs, softmax+CE (== logistic/BCE); see the |
//| total==2 branches in AI\Impl\NetForward.mqh. |
//| Front end: MLP only in S2 (AddCustomLayers no-op inherited). |
//| Conv/LSTM meta variants would need the descriptor |
//| padded to whole pseudo-bars to keep their bar-major |
//| window/step geometry - deliberately out of S2 scope. |
//| |
//| S2 trains and reports (coverage x (win rate - break-even) vs the |
//| base-rate null, in Training.mqh's era-end META line). It casts |
//| NO votes: dPrevSignal never leaves its sentinel, so the base |
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//| LongCondition/ShortCondition return 0. |
//| |
//| WHAT IT IS INSTEAD (S3, 2026-08-19): a GATE. ScoreProposal() |
//| scores each vote-cleared entry and vetoes those whose P(win) |
//| sits under the cost-adjusted break-even. It casts no votes and |
//| cannot dilute the consensus (VoteCapableWeight/ProspectiveVote |
//| are 0/false for the meta target). |
//| |
//| The trading pipeline reaches that through CMetaGate, not through |
//| this class: see Expert\Trading\MetaGate.mqh for why the veto |
//| stopped being a virtual that every signal in the tree carried. |
//| This class still EXTENDS the AI signal base, and has to - the |
//| net, the era loop, the feature windows, the label caches and the |
//| persistence all live there, and MQL5 grants one base class. What |
//| it no longer does is ACT as one on the entry path. |
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
//+------------------------------------------------------------------+
//--- Setup descriptor layout (AppendCandidateFeatures): 26 one-hot + side + netVote + SL + TP + spread/ATR.
//--- MetaDescWidth() returns this and the input layer is sized with it - the three MUST stay in step.
#define META_ONE_HOT_SLOTS 26
#define META_DESC_FEATURES (META_ONE_HOT_SLOTS + 5)
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//+------------------------------------------------------------------+
//| The meta head AS A GATE. |
//| |
//| CSignalMETA cannot implement CMetaGate itself: it already |
//| extends CExpertSignalAIBase for the training machinery, and MQL5 |
//| has single inheritance and no interfaces. So the gate role is a |
//| small bound object the head owns and hands out - the same |
//| adapter shape CTrainingDataView uses, for the same reason. |
//| |
//| It holds a raw owner pointer deliberately: the owner OWNS the |
//| adapter, so the adapter can never outlive it. |
//+------------------------------------------------------------------+
class CSignalMETA;
class CMetaGateAdapter : public CMetaGate
{
protected:
CSignalMETA *m_owner;
public:
CMetaGateAdapter(void) : m_owner(NULL) {}
void Bind(CSignalMETA *owner) { m_owner = owner; }
virtual int Evaluate(const bool isLong, const double netVote, double &pWin,
double &bePct, const int barIdx) override;
};
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
class CSignalMETA : public CExpertSignalAIBase
{
protected:
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
//--- The corpus: every journaled pattern instance, filled ONCE per attach - by the on-chart
//--- ladder sweep below, or from the largest matching signal DB on disk. Timestamps are fixed;
//--- bar INDICES are re-resolved every era, because a new bar shifts every one of them.
CMetaCorpus m_corpus;
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
bool m_prepareReported; // first-era diagnostics print loudly, later eras verbose
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
bool m_datasetExported; // one export per attach (Meta_ExportDataset input)
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
//--- ON-CHART CANDIDATE SOURCES (the classic filters attached to this same chart). When present,
//--- the corpus is generated by SWEEPING these real ladders over the chart's own history via the
//--- StartIndex/EvalShift mechanism - no tester corpus run, no DB dependency, no GMT-offset
//--- resolution ambiguity (the sweep IS on this chart's bars). The DB loader stays as fallback.
CExpertSignalCustom *m_srcFilter[4];
int m_srcFamily[4];
int m_srcCount;
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//--- This head's gate role, bound to `this` in the constructor and never rebound.
CMetaGateAdapter m_gateRole;
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
public:
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//--- The ONE door the trading pipeline uses. It gets a gate, not a signal.
CMetaGate *AsMetaGate(void) { return GetPointer(m_gateRole); }
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
void AddCandidateSource(CExpertSignalCustom *filter, const int family)
{
if(m_srcCount < 4 && CheckPointer(filter) != POINTER_INVALID)
{
m_srcFilter[m_srcCount] = filter;
m_srcFamily[m_srcCount] = family;
m_srcCount++;
}
}
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
CSignalMETA(void);
virtual int MetaDescWidth(void) const override { return META_DESC_FEATURES; }
virtual bool MetaPrepareEra(const int bars) override;
virtual void AppendCandidateFeatures(const int candId) override;
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//--- S3 (2026-08-19): the scoring behind the gate role. Reached through m_gateRole, whose
//--- contract is CMetaGate::Evaluate; the honesty notes are in the body's header below.
int ScoreProposal(const bool isLong, const double netVote, double &pWin,
double &bePct, const int barIdx);
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
protected:
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
bool BuildCorpusBySweep(void);
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
void ExportMetaDataset(void);
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
//--- Gate-time twin of AppendCandidateFeatures - same width, same slot order, same squash.
void AppendLiveDescriptor(const char side, const double netVote, const int barIdx);
refactor(meta): one owner for the pattern taxonomy and its table names MetaCorpus was already a class, so the raw-include problem was not the one here. The problem was that the rule for naming a signal-DB pattern table MetaFamilyName(f) + "_Pattern_" + p + ("_Buy" | "_Sell") was written out FOUR times, each wrapped in its own identical family/pattern/side triple loop: the corpus loader, the stale-DB guard, META's row counter and META's exporter. Four chances for a rename to leave three of them querying an absent table and reporting it as "family disabled" - which is what that code says when a table is missing, so the failure would have looked like normal operation. CMetaFamilies now owns the taxonomy and that rule. Callers walk ONE flat index over all 52 tables and never spell a name: for(int ti = 0; CMetaFamilies::TableAt(ti, table, f, p, isBuy); ti++) Enumeration order is unchanged - Buy then Sell within a pattern, families in order - so the corpus is assembled in exactly the same sequence as before. META's OneHotSlot had a second hardcoded 0/4/8/14 ladder with a comment reading "matches MetaFamilyPatterns' 4/4/6/12" - a note asking a reader to keep two constants in step by hand. The ladder is now summed from the pattern counts, so they agree by construction. The bound against META_ONE_HOT_SLOTS stays in META: the head's input width is that class's business, and a taxonomy grown past it must be caught rather than silently truncated. Caught while re-reading the rewritten loop: my first counter was `t`, and the body declares `MqlDateTime t`. Renamed to `ti` at all four sites before it reached a compile. MetaCorpus.mqh moves to Expert\Training\ with the other real classes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 12:26:33 -04:00
//--- The ladder is SUMMED from the pattern counts in CMetaFamilies, not written out a second
//--- time here. The bound against META_ONE_HOT_SLOTS stays: the head's input width is this
//--- class's business, and a taxonomy that grew past it must be caught, not silently truncated.
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
int OneHotSlot(const int family, const int pattern) const
{
refactor(meta): one owner for the pattern taxonomy and its table names MetaCorpus was already a class, so the raw-include problem was not the one here. The problem was that the rule for naming a signal-DB pattern table MetaFamilyName(f) + "_Pattern_" + p + ("_Buy" | "_Sell") was written out FOUR times, each wrapped in its own identical family/pattern/side triple loop: the corpus loader, the stale-DB guard, META's row counter and META's exporter. Four chances for a rename to leave three of them querying an absent table and reporting it as "family disabled" - which is what that code says when a table is missing, so the failure would have looked like normal operation. CMetaFamilies now owns the taxonomy and that rule. Callers walk ONE flat index over all 52 tables and never spell a name: for(int ti = 0; CMetaFamilies::TableAt(ti, table, f, p, isBuy); ti++) Enumeration order is unchanged - Buy then Sell within a pattern, families in order - so the corpus is assembled in exactly the same sequence as before. META's OneHotSlot had a second hardcoded 0/4/8/14 ladder with a comment reading "matches MetaFamilyPatterns' 4/4/6/12" - a note asking a reader to keep two constants in step by hand. The ladder is now summed from the pattern counts, so they agree by construction. The bound against META_ONE_HOT_SLOTS stays in META: the head's input width is that class's business, and a taxonomy grown past it must be caught rather than silently truncated. Caught while re-reading the rewritten loop: my first counter was `t`, and the body declares `MqlDateTime t`. Renamed to `ti` at all four sites before it reached a compile. MetaCorpus.mqh moves to Expert\Training\ with the other real classes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 12:26:33 -04:00
int slot = CMetaFamilies::OneHotSlot(family, pattern);
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
return (slot >= 0 && slot < META_ONE_HOT_SLOTS) ? slot : -1;
}
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
CSignalMETA::CSignalMETA(void) : m_prepareReported(false), m_datasetExported(false), m_srcCount(0)
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
{
SetIdentity("Meta", "META");
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//--- The gate role is a view onto this object; binding here means it is never handed out unbound.
m_gateRole.Bind(GetPointer(this));
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
//--- identity-defining, set once (feeds the |TGT:META fingerprint token and every IsMetaTarget seam)
m_trainTarget = 1;
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
for(int k = 0; k < 4; k++)
{
m_srcFilter[k] = NULL;
m_srcFamily[k] = -1;
}
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
}
//+------------------------------------------------------------------+
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
//| Generate the candidate corpus by sweeping the REAL classic |
//| ladders over this chart's own history. |
//| |
//| Every pattern condition anchors on StartIndex() (verified across |
//| all four signal files), so EvalShift(i) makes the exact live |
//| code answer "what would you have fired at bar i" - the silent- |
//| divergence trap that justified the DB corpus does not exist on |
//| this path, and neither do the tester run, the GMT-offset |
//| ambiguity, or the DB row caps. Times/prices stored are this |
//| chart's own bar opens, so MetaPrepareEra's resolution matches at |
//| offset +0 with zero price error by construction. |
//| |
//| One-time cost at first era: bars x sources Direction() calls - |
//| a few seconds. Runs on the chart thread like the label prebuild. |
//+------------------------------------------------------------------+
bool CSignalMETA::BuildCorpusBySweep(void)
{
if(m_srcCount <= 0)
return false;
ENUM_TIMEFRAMES per = (ENUM_TIMEFRAMES)m_period;
int bars = Bars(_Symbol, per);
if(bars <= 300)
return false;
for(int s = 0; s < m_srcCount; s++)
if(!m_srcFilter[s].SweepPrepare(bars))
{
Print(ID + ": candidate sweep - filter " + m_srcFilter[s].GetFilterID() +
" could not prepare deep buffers; falling back to a DB corpus.");
return false;
}
//--- skip the indicator warm-up tail at the oldest edge of history (reads there are EMPTY/garbage
//--- and would fabricate patterns); 150 bars comfortably covers every classic period in use.
int deepest = bars - 150;
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
//--- A HINT, NOT A CAP - the corpus grows itself. A fixed bars*2 ceiling used to be enforced
//--- here and failed both ways: it silently truncated the corpus when the guard tripped at a
//--- bar boundary (SP500 filled to exactly cap, dropping the newest bars entirely), and it
//--- overflowed mid-bar on denser symbols because the guard only reserved room for 2 appends
//--- (array-out-of-range right here on USDJPY/XAUUSD/XTIUSD, killing the chart at attach).
//--- The worst case really is m_srcCount*2 appends per bar: state-model patterns stay active
//--- on most bars, and both sides can fire on one.
m_corpus.Clear();
m_corpus.Reserve(bars * 2);
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
Print(ID + StringFormat(": sweeping %d classic ladder(s) over %d bars for candidates - the chart"
" is busy for a few seconds...", m_srcCount, deepest));
uint t0 = GetTickCount();
for(int i = deepest; i >= 2; i--)
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
{
datetime bt = iTime(_Symbol, per, i);
double bo = iOpen(_Symbol, per, i);
if(bt <= 0 || bo <= 0.0)
continue;
for(int s = 0; s < m_srcCount; s++)
{
CExpertSignalCustom *f = m_srcFilter[s];
f.EvalShift(i);
f.Direction();
f.EvalShift(0);
string pl = f.GetActivePatternLong();
string ps = f.GetActivePatternShort();
double nv = f.LastNetVote();
//--- "Pattern_N" -> N; same per-side, one-candidate-per-bar semantics as live journaling
if(pl != "NULL")
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
m_corpus.Add(bt, (short)1, (short)m_srcFamily[s],
(short)StringToInteger(StringSubstr(pl, 8)), nv, bo);
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
if(ps != "NULL")
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
m_corpus.Add(bt, (short) -1, (short)m_srcFamily[s],
(short)StringToInteger(StringSubstr(ps, 8)), nv, bo);
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
}
}
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
//--- the reserve above is a guess - trim whatever it over-allocated
m_corpus.ShrinkToFit();
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
Print(ID + StringFormat(": candidate sweep done - %d candidates from %d bars in %.1fs (no tester"
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
" corpus run needed; DB corpus not used).", m_corpus.Count(), deepest,
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
(GetTickCount() - t0) / 1000.0));
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
return !m_corpus.Empty();
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
}
//+------------------------------------------------------------------+
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
//| Resolve the corpus onto this era's bar grid. |
//| |
//| Rows pre-dbVersion-4.0 are GMT, BROKER time since; history is |
//| server time either way (EET-ish, DST moves |
//| it). The offset is MEASURED PER ROW, not assumed: rows are |
//| journaled at the bar's opening tick with the touchable price, so |
//| the RIGHT offset's bar open matches entryPrice to within the |
//| spread while a wrong offset lands a full hourly move away. Each |
//| row takes the offset (0..+4h) minimizing |open - entry| over |
//| offsets that land on an exact bar, then must pass a tolerance - |
//| decisive per row, and immune to DST regime changes across an |
//| 18-year corpus (the histogram printed below shows the winter/ |
//| summer split directly). |
//| |
//| The window-span filter kills the pre-2017 daily-backfill regime |
//| (measured 2026-08-13: hour-0-only rows, one per day): a |
//| candidate whose m_historyBars-deep window spans more than 4x its |
//| nominal duration is sitting on bars that are not really H1, and |
//| its geometry/labels would be silently wrong. |
//+------------------------------------------------------------------+
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
bool CSignalMETA::MetaPrepareEra(const int bars)
{
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
//--- corpus source order: the on-chart ladder sweep (self-contained, preferred), then a
//--- tester-built DB corpus as fallback for charts whose classic filters are disabled.
//--- m_period, not _Period: the loader's symbol+period filter exists so the rows it returns
//--- resolve onto the grid this function resolves them onto, and that grid is m_period. The two
//--- are the same value here (CExpert::Init is given Period()), so this is not a change of
//--- behaviour - it is the filter naming the thing it is actually filtering for.
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
if(m_corpus.Empty() && !BuildCorpusBySweep()
&& m_corpus.LoadLargestOnDisk(_Symbol, (int)m_period, ID) <= 0)
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
return false;
ENUM_TIMEFRAMES per = (ENUM_TIMEFRAMES)m_period;
//--- one call sizes the grid, reserves the rows and empties the chain - they are one operation
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
m_metaCands.Reset(bars, m_corpus.Count());
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
int offCount[5] = {0, 0, 0, 0, 0};
int dropNoBar = 0, dropPrice = 0, dropRegime = 0, dropRange = 0;
long spanCap = (long)PeriodSeconds(per) * (long)MathMax(m_historyBars, 1) * 4;
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
for(int r = 0; r < m_corpus.Count(); r++)
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
{
int bestSh = -1, bestOff = -1;
double bestDiff = DBL_MAX;
for(int off = 0; off <= 4; off++)
{
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
int sh = iBarShift(_Symbol, per, m_corpus.Stamp(r) + off * 3600, true);
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
if(sh < 0)
continue;
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
double diff = MathAbs(iOpen(_Symbol, per, sh) - m_corpus.EntryPrice(r));
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
if(diff < bestDiff)
{
bestDiff = diff;
bestSh = sh;
bestOff = off;
}
}
if(bestSh < 0)
{
dropNoBar++;
continue;
}
//--- price tolerance: right-offset |diff| <= the spread; wrong-offset ~ an hourly move. 15% of
//--- the bar's ATR separates the two with a wide margin either way; the fallback (5 basis
//--- points) covers bars where the ATR indicator has no value that deep in history.
double atr = m_ATR.Main(bestSh);
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
double tol = (MathIsValidNumber(atr) && atr > 0.0) ? 0.15 * atr : 0.0005 * m_corpus.EntryPrice(r);
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
if(bestDiff > tol)
{
dropPrice++;
continue;
}
if(bestSh >= bars)
{
dropRange++;
continue;
}
datetime tSh = iTime(_Symbol, per, bestSh);
datetime tDeep = iTime(_Symbol, per, bestSh + (int)MathMax(m_historyBars, 1));
if(tSh <= 0 || tDeep <= 0 || (long)(tSh - tDeep) > spanCap)
{
dropRegime++;
continue;
}
//--- Add() links the same-bar chain itself. It declines a row it cannot store - a bar off
//--- the grid (excluded above) or no memory for it - and a declined row is counted, not
//--- assumed impossible.
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
if(m_metaCands.Add(bestSh, (char)m_corpus.Side(r), m_corpus.NetVote(r),
m_corpus.Family(r), m_corpus.Pattern(r)) < 0)
{
dropRange++;
continue;
}
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
if(bestOff >= 0 && bestOff <= 4)
offCount[bestOff]++;
}
string line = StringFormat("%s: era candidate resolution - %d of %d corpus rows usable | GMT->server"
" offset histogram +0h:%d +1h:%d +2h:%d +3h:%d +4h:%d | dropped: %d no"
" exact bar, %d price mismatch (wrong offset/bad data), %d non-H1 regime"
" (pre-intraday backfill), %d beyond era grid",
refactor(meta): one corpus, three sources - not two corpora, two schemas CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate row. CSignalMETA kept a SECOND corpus of the same journaled candidates right next to it: six parallel arrays, a second walk of the same 52 pattern tables, a second row-filling loop, and THREE six-line ArrayResize blocks keeping the six arrays the same length by hand. Same rows, same tables, same meaning - and neither copy was reviewable without the other. Now one class with one row schema and three sources: LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm; what the S1 report reads. LoadLargestOnDisk() moved in from CSignalMETA, header and all - the symbol+period filter and the read-only open are the point of it, and so is NOT going through the config fingerprint (the trap that burned four corpus-build runs). CountDbPatternRows moved with it as the one "how big is this corpus" table walk. Add() the on-chart ladder sweep, which needs the EA's live filters and so stays in CSignalMETA - but stores here. Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are hints, and every read is bounds-checked with an out-of-range answer that cannot pass for a real candidate. That retires the sweep's hand-rolled capacity block, which had already failed both ways - silently truncating the corpus at a bar boundary on SP500, and running off the end mid-bar on USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The lesson stays in the comment; the arithmetic does not. SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header comment back above MetaPrepareEra - it had drifted two functions away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:41:47 -04:00
ID, m_metaCands.Count(), m_corpus.Count(), offCount[0], offCount[1], offCount[2],
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
offCount[3], offCount[4], dropNoBar, dropPrice, dropRegime, dropRange);
if(!m_prepareReported)
{
m_prepareReported = true;
Print(line);
}
else
PrintVerbose(line);
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
//--- one-shot dataset export for offline pooled training - runs here because this is the first
//--- moment candidates AND labels both exist on the current bar grid (the label prebuild completed
//--- before the era start that called us).
if(Meta_ExportDataset && !m_datasetExported && m_metaCands.Count() > 0)
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
ExportMetaDataset();
return m_metaCands.Count() > 0;
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
}
//+------------------------------------------------------------------+
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
//| Dump the full training set for offline (cross-sectional pooled) |
//| work: one float32 row per resolved, LABELED candidate - |
//| [barTime int64][family int32][pattern int32][side int32] |
//| [won int32][NetInputWidth() floats: window + descriptor]. |
//| The sidecar .meta.csv carries the layout + the geometry/BE the |
//| labels were computed at, so the offline side never guesses. |
//| This is EXACTLY what pass 2 trains on - same window builder, |
//| same descriptor, same caches - so an offline model on this file |
//| and the EA's own training see byte-equivalent examples. |
//+------------------------------------------------------------------+
void CSignalMETA::ExportMetaDataset(void)
{
m_datasetExported = true;
const string base = "Warrior_EA\\MetaExport\\" + _Symbol + "_" + IntegerToString((int)m_period);
refactor(stdlib): adopt Math\Stat for the deploy gate's normal tail; retire the b1/b2/lr/momentum macros The gate's NormalUpperTail was a hand-rolled Abramowitz & Stegun 26.2.17 approximation. Its own comment gave the reason - "drags a chain of headers behind it" - and that turned out to be one file: Math\Stat\Normal.mqh includes only Math.mqh, which includes nothing. Swapped for Cody's rational approximation in the library (~18 significant digits vs |error| < 7.5e-8). No past verdict changes: at the z the gate operates on, the difference is orders of magnitude below DEPLOY_FAMILY_WISE_ALPHA. Adopting it needed the four bare macros in AI\Network.mqh gone first. "#define b1 AdamBeta1" collides with an identifier in Math.mqh, so the include would have macro-expanded the library's own local and failed to compile - the same landmine that made the original author rename the approximation's coefficients to ntB1..ntB5 rather than use the reference's b1..b5. lr, b2 and momentum are the same class of hazard: single-token global macros in a 52k-line codebase. All four now resolve to the input names they always aliased, which is a pure textual identity - verified zero bare occurrences remain. Also: - SelectionSort over the buffered signals was O(n^2) with an O(n^2) count of StructToTime calls, because the comparison rebuilt both datetimes from the six int date fields every time. Now materialises the keys once and does an insertion sort; ArraySort cannot permute a struct array. IsEarlier goes with it, MakeDateTime becomes SignalTime. - Seven FileOpen sites lacked FILE_SHARE_READ|FILE_SHARE_WRITE, including AtomicWriteBegin, which stages every model save. All 43 sites now carry them - an exclusive open fails outright when another process holds the path, which here has meant a silently skipped save. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:31:36 -04:00
int fh = FileOpen(base + ".f32", FILE_BIN | FILE_WRITE | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE);
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
if(fh == INVALID_HANDLE)
{
Print(ID + ": dataset export FAILED - cannot open Common\\Files\\" + base + ".f32 (error " +
IntegerToString(GetLastError()) + ")");
return;
}
Print(ID + StringFormat(": exporting %d candidates to Common\\Files\\%s.f32 - one pass-1-sized"
" sweep, the chart is busy for it...", m_metaCands.Count(), base));
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
int width = NetInputWidth();
int rows = 0, skipLabel = 0, skipWindow = 0;
for(int cd = 0; cd < m_metaCands.Count(); cd++)
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
{
int idx = m_metaCands.Bar(cd);
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
if(idx < 0 || idx >= ArraySize(m_labelCacheHasValue) || !m_labelCacheHasValue[idx])
{
skipLabel++;
continue;
}
if(!BuildFeatureWindow(idx))
{
skipWindow++;
continue;
}
AppendCandidateFeatures(cd);
if(TempData.Total() != width)
{
skipWindow++;
continue;
}
FileWriteLong(fh, (long)iTime(_Symbol, (ENUM_TIMEFRAMES)m_period, idx));
FileWriteInteger(fh, (int)m_metaCands.Family(cd));
FileWriteInteger(fh, (int)m_metaCands.Pattern(cd));
FileWriteInteger(fh, m_metaCands.Side(cd));
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
FileWriteInteger(fh, MetaCandidateWon(cd, idx) ? 1 : 0);
for(int k = 0; k < width; k++)
FileWriteFloat(fh, (float)TempData.At(k));
rows++;
}
FileClose(fh);
double slMult = 0.0, tpMult = 0.0;
BarrierMultiples(slMult, tpMult);
double bePct = (slMult + tpMult > 0.0) ? 100.0 * slMult / (slMult + tpMult) : 50.0;
refactor(stdlib): adopt Math\Stat for the deploy gate's normal tail; retire the b1/b2/lr/momentum macros The gate's NormalUpperTail was a hand-rolled Abramowitz & Stegun 26.2.17 approximation. Its own comment gave the reason - "drags a chain of headers behind it" - and that turned out to be one file: Math\Stat\Normal.mqh includes only Math.mqh, which includes nothing. Swapped for Cody's rational approximation in the library (~18 significant digits vs |error| < 7.5e-8). No past verdict changes: at the z the gate operates on, the difference is orders of magnitude below DEPLOY_FAMILY_WISE_ALPHA. Adopting it needed the four bare macros in AI\Network.mqh gone first. "#define b1 AdamBeta1" collides with an identifier in Math.mqh, so the include would have macro-expanded the library's own local and failed to compile - the same landmine that made the original author rename the approximation's coefficients to ntB1..ntB5 rather than use the reference's b1..b5. lr, b2 and momentum are the same class of hazard: single-token global macros in a 52k-line codebase. All four now resolve to the input names they always aliased, which is a pure textual identity - verified zero bare occurrences remain. Also: - SelectionSort over the buffered signals was O(n^2) with an O(n^2) count of StructToTime calls, because the comparison rebuilt both datetimes from the six int date fields every time. Now materialises the keys once and does an insertion sort; ArraySort cannot permute a struct array. IsEarlier goes with it, MakeDateTime becomes SignalTime. - Seven FileOpen sites lacked FILE_SHARE_READ|FILE_SHARE_WRITE, including AtomicWriteBegin, which stages every model save. All 43 sites now carry them - an exclusive open fails outright when another process holds the path, which here has meant a silently skipped save. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:31:36 -04:00
int mh = FileOpen(base + ".meta.csv", FILE_CSV | FILE_WRITE | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE, ',');
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
if(mh != INVALID_HANDLE)
{
FileWrite(mh, "symbol", "period", "rows", "width", "historyBars", "featuresPerBar", "descWidth",
"slMult", "tpMult", "breakEvenPct", "horizonBars", "spreadPoints");
FileWrite(mh, _Symbol, IntegerToString((int)m_period), IntegerToString(rows),
IntegerToString(width), IntegerToString((int)m_historyBars),
IntegerToString(m_neuronsCount), IntegerToString(MetaDescWidth()),
DoubleToString(slMult, 4), DoubleToString(tpMult, 4), DoubleToString(bePct, 2),
IntegerToString(m_barrierHorizonBars), IntegerToString((int)m_symbol.Spread()));
FileClose(mh);
}
Print(ID + StringFormat(": dataset exported - %d rows (%d skipped: %d unlabeled near the era edge,"
" %d unusable windows) x %d floats | geometry %.2f/%.2f BE %.1f%% |"
" %s.f32 + .meta.csv", rows, skipLabel + skipWindow, skipLabel, skipWindow,
width, slMult, tpMult, bePct, base));
}
//+------------------------------------------------------------------+
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
//| The setup descriptor - MUST append exactly META_DESC_FEATURES |
//| values (the input layer is sized for them; a short append fails |
//| the NetInputWidth() guard and the sample is skipped, a long one |
//| would corrupt the forward pass). |
//+------------------------------------------------------------------+
void CSignalMETA::AppendCandidateFeatures(const int candId)
{
if(!m_metaCands.Contains(candId))
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
return;
int idx = m_metaCands.Bar(candId);
int slot = OneHotSlot(m_metaCands.Family(candId), m_metaCands.Pattern(candId));
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
for(int k = 0; k < META_ONE_HOT_SLOTS; k++)
TempData.Add(k == slot ? 1.0 : 0.0);
TempData.Add((double)m_metaCands.Side(candId));
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
//--- netVote is in raw pattern-weight units (+-100ish); tanh(nv/20) keeps resolution where the
//--- votes actually live while bounding the tails. MQL5 has no MathTanh - via exp.
double e2 = MathExp(2.0 * (m_metaCands.NetVote(candId) / 20.0));
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
TempData.Add((e2 - 1.0) / (e2 + 1.0));
//--- geometry in ATR units - constant across candidates TODAY (one pinned barrier pair), but the
//--- design's excursion-head integration makes it per-candidate later, so it rides as a feature.
double slMult = 0.0, tpMult = 0.0;
BarrierMultiples(slMult, tpMult);
TempData.Add(slMult);
TempData.Add(tpMult);
//--- spread/ATR at the fire bar (EnsureSpreadSeries copies unconditionally for the meta target)
double sprAtr = 0.0;
double atr = m_ATR.Main(idx);
if(idx >= 0 && idx < m_spreadSeriesBars && MathIsValidNumber(atr) && atr > 0.0)
sprAtr = (double)m_spreadSeries[idx] * m_symbol.Point() / atr;
TempData.Add(sprAtr);
}
//+------------------------------------------------------------------+
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
//| S3 - THE LIVE META GATE (2026-08-19, user design: "the META NN |
//| should be integrated into the voting decision pipeline"). |
//| |
//| Scores the PROPOSED trade the consensus vote just cleared: the |
//| shared feature window at barIdx plus a descriptor of the proposal |
//| itself (side, the net vote, the live SL/TP geometry, spread/ATR). |
//| The pattern one-hot is ZEROED - the proposal is the vote, not a |
//| classic-ladder fire. On the S2 MLP the one-hot columns act as |
//| additive per-pattern bias vectors into the first layer, so |
//| zeroing them asks the trained context trunk for its pattern-free |
//| score. That is an extrapolation by one additive term (no training |
//| row ever had an empty one-hot), so the honest reading is RANKING, |
//| not calibrated probability - which is why the threshold is the |
//| cost-adjusted break-even (the least arbitrary zero-knob rule) and |
//| the gate ships default-off. The measured next lever is journaling |
//| the vote itself as a candidate family so the head trains on the |
//| exact stream it gates - see Meta_Labeling_Design.md's S3 note. |
//| |
//| FAIL-OPEN EVERYWHERE: not trained, no net, window unbuildable, |
//| width mismatch, non-finite forward - all return codes that let |
//| the trade proceed. The only -1 is a real scored veto. The BN |
//| freeze save/restore bracket makes the forward a pure function |
//| (the snapshot rule - the same bracket DisplayInference uses); the |
//| meta front-end is MLP-only, so there is no LSTM state to guard. |
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
//| Telemetry is written only for LIVE queries (barIdx == 1): the |
//| ensemble verdict's historical replays must not inflate the HUD's |
//| live approve/veto counters. |
//| |
//| Return codes are CMetaGate's four, and only META_GATE_VETOED |
//| blocks anything. |
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
//+------------------------------------------------------------------+
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
int CSignalMETA::ScoreProposal(const bool isLong, const double netVote, double &pWin,
double &bePct, const int barIdx)
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
{
pWin = -1.0;
bePct = CostAdjustedBreakEvenPct();
bool live = (barIdx == 1);
//--- One readiness test + transition announcement for every observer - see MetaGateArmedNow.
if(!MetaGateArmedNow())
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return META_GATE_INACTIVE;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
if(!BuildFeatureWindow(barIdx))
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return META_GATE_FAIL_OPEN;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
AppendLiveDescriptor((char)(isLong ? 1 : -1), netVote, barIdx);
if(TempData.Total() != NetInputWidth())
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return META_GATE_FAIL_OPEN;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
//--- Save/restore, NOT set/clear: frozen, this forward is a pure function (the snapshot rule).
bool bnWasFrozen = Net.GetBatchNormFrozen();
if(!bnWasFrozen)
Net.SetBatchNormFrozen(true);
bool fwdOk = Net.feedForward(TempData);
if(fwdOk)
Net.getResults(TempData);
if(!bnWasFrozen)
Net.SetBatchNormFrozen(false);
if(!fwdOk)
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return META_GATE_FAIL_OPEN;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
double p = MetaWinProbability();
if(p < 0.0 || !MathIsValidNumber(p))
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return META_GATE_FAIL_OPEN;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
pWin = p;
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
int verdict = (100.0 * p >= bePct) ? META_GATE_APPROVED : META_GATE_VETOED;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
if(live)
m_metaTelemetry.RecordLive(p, bePct, verdict);
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return verdict;
}
//+------------------------------------------------------------------+
//| The gate role's one method: forward to the head that owns it. |
//| Fails open when unbound, on the same doctrine as everything else |
//| on this path - a gate that cannot be reached must not block. |
//+------------------------------------------------------------------+
int CMetaGateAdapter::Evaluate(const bool isLong, const double netVote, double &pWin,
double &bePct, const int barIdx)
{
if(CheckPointer(m_owner) == POINTER_INVALID)
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
{
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
pWin = -1.0;
bePct = -1.0;
return META_GATE_INACTIVE;
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
}
refactor(meta): the veto is a gate, not a virtual every signal carries Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the consensus already cleared and vetoes the ones under the cost-adjusted break-even. The code still said otherwise. LiveMetaGate() was a virtual on CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets, the session and news filters and the risk guard each carried a meta-gate method they had no business having; one class implemented it and a dozen inherited it. The trading pipeline held the gate as a CExpertSignalCustom* - a signal pointer, with a signal's two hundred other methods reachable from the entry path. Expert\Trading\MetaGate.mqh now owns the abstraction: CMetaGate one pure virtual, Evaluate(), and the two static readings of a verdict (Blocks / Scored) META_GATE_* names for the four codes the three call sites used to spell as bare 0/1/2 and test three different ways (`< 0` here, `== 2` there, `else` for the rest). Codes unchanged; only ONE of them blocks, and that asymmetry is now stated where it lives. SMetaGateTelemetry the five m_metaGate* members that were on the AI signal base - inherited by every direction model, meaningful for none of them. One lifetime, one writer, one object; the arm latch and the two counters are a set that clears together. g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head cannot also BE one (it already extends the AI base for the net, the era loop, the feature windows, the label caches and persistence), so it owns a bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView uses for the same reason. LiveMetaGate() is gone from the signal base. Behaviour unchanged: same codes, same thresholds, same fail-open doctrine, same live-only telemetry rule. The adapter fails open when unbound, on that same doctrine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 15:33:47 -04:00
return m_owner.ScoreProposal(isLong, netVote, pWin, bePct, barIdx);
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
}
//+------------------------------------------------------------------+
//| The gate-time twin of AppendCandidateFeatures: same width, same |
//| slot order, same squash - MUST stay in step with it and with |
//| META_DESC_FEATURES. The differences ARE the proposal: zeroed |
//| one-hot (the vote is not a classic-ladder fire), the caller's |
//| side and net vote, and spread/ATR at barIdx through the same |
//| series-or-zero rule training used (a fallback to the current |
//| quoted spread would feed the net a quantity training never saw |
//| on unavailable bars). |
//+------------------------------------------------------------------+
void CSignalMETA::AppendLiveDescriptor(const char side, const double netVote, const int barIdx)
{
for(int k = 0; k < META_ONE_HOT_SLOTS; k++)
TempData.Add(0.0);
TempData.Add((double)side);
//--- same tanh(nv/20) squash as AppendCandidateFeatures - MQL5 has no MathTanh, via exp
double e2 = MathExp(2.0 * (netVote / 20.0));
TempData.Add((e2 - 1.0) / (e2 + 1.0));
double slMult = 0.0, tpMult = 0.0;
BarrierMultiples(slMult, tpMult);
TempData.Add(slMult);
TempData.Add(tpMult);
double sprAtr = 0.0;
double atr = m_ATR.Main(barIdx);
if(barIdx >= 0 && barIdx < m_spreadSeriesBars && MathIsValidNumber(atr) && atr > 0.0)
sprAtr = (double)m_spreadSeries[barIdx] * m_symbol.Point() / atr;
TempData.Add(sprAtr);
}
//+------------------------------------------------------------------+