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"
|
|
|
|
|
#include "..\Expert\AIBase\MetaCorpus.mqh"
|
|
|
|
|
// 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. |
|
|
|
|
|
//| |
|
|
|
|
|
//| Sample: the signal-DB corpus (built by an 18-year backtest |
|
|
|
|
|
//| with UseDatabaseRanking on - per-side journaling means |
|
|
|
|
|
//| the DB IS the candidate stream, uncensored). |
|
|
|
|
|
//| 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 |
|
|
|
|
|
//| LongCondition/ShortCondition return 0. Live gating of fired |
|
|
|
|
|
//| candidates via the per-side hooks is S3. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
//--- 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)
|
|
|
|
|
|
|
|
|
|
class CSignalMETA : public CExpertSignalAIBase
|
|
|
|
|
{
|
|
|
|
|
protected:
|
|
|
|
|
//--- The corpus: every journaled pattern instance, loaded ONCE per attach from the largest signal
|
|
|
|
|
//--- DB on disk (see LoadMetaCorpus for why largest-by-rows rather than the chart's own config
|
|
|
|
|
//--- fingerprint). GMT times are fixed; bar INDICES are re-resolved every era (they shift).
|
|
|
|
|
datetime m_corpusGmt[];
|
|
|
|
|
char m_corpusSide[];
|
|
|
|
|
double m_corpusNetVote[];
|
|
|
|
|
short m_corpusFamily[];
|
|
|
|
|
short m_corpusPattern[];
|
|
|
|
|
double m_corpusEntry[]; // touchable price at fire time - the offset oracle
|
|
|
|
|
int m_corpusCount;
|
|
|
|
|
bool m_corpusLoaded;
|
|
|
|
|
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;
|
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:
|
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 bool InitIndicators(CIndicators *indicators) override;
|
|
|
|
|
virtual int MetaDescWidth(void) const override { return META_DESC_FEATURES; }
|
|
|
|
|
virtual bool MetaPrepareEra(const int bars) override;
|
|
|
|
|
virtual void AppendCandidateFeatures(const int candId) override;
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
bool LoadMetaCorpus(void);
|
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: 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
|
|
|
long CountDbPatternRows(const int db);
|
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: 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
|
|
|
|
|
{
|
|
|
|
|
//--- MA 0-3, RSI 4-7, MACD 8-13, Ichimoku 14-25 - matches MetaFamilyPatterns' 4/4/6/12
|
|
|
|
|
int base = -1;
|
|
|
|
|
switch(family)
|
|
|
|
|
{
|
|
|
|
|
case 0: base = 0; break;
|
|
|
|
|
case 1: base = 4; break;
|
|
|
|
|
case 2: base = 8; break;
|
|
|
|
|
case 3: base = 14; break;
|
|
|
|
|
}
|
|
|
|
|
if(base < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
int slot = base + pattern;
|
|
|
|
|
return (slot >= 0 && slot < META_ONE_HOT_SLOTS) ? slot : -1;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Constructor |
|
|
|
|
|
//+------------------------------------------------------------------+
|
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
|
|
|
CSignalMETA::CSignalMETA(void) : m_corpusCount(0), m_corpusLoaded(false), m_prepareReported(false),
|
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
|
|
|
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");
|
|
|
|
|
//--- 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
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Create indicators and bootstrap/load the network. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CSignalMETA::InitIndicators(CIndicators *indicators)
|
|
|
|
|
{
|
|
|
|
|
return InitNeuralNetwork(indicators);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Total rows across the 52 pattern tables of an open DB handle. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
long CSignalMETA::CountDbPatternRows(const int db)
|
|
|
|
|
{
|
|
|
|
|
long rows = 0;
|
|
|
|
|
for(int f = 0; f < 4; f++)
|
|
|
|
|
for(int p = 0; p < MetaFamilyPatterns(f); p++)
|
|
|
|
|
for(int s = 0; s < 2; s++)
|
|
|
|
|
{
|
|
|
|
|
string table = MetaFamilyName(f) + "_Pattern_" + IntegerToString(p) +
|
|
|
|
|
(s == 0 ? "_Buy" : "_Sell");
|
|
|
|
|
int stmt = DatabasePrepare(db, "SELECT COUNT(*) FROM " + table);
|
|
|
|
|
if(stmt == INVALID_HANDLE)
|
|
|
|
|
continue; // table absent (family disabled in the populating run)
|
|
|
|
|
long c = 0;
|
|
|
|
|
if(DatabaseRead(stmt))
|
|
|
|
|
DatabaseColumnLong(stmt, 0, c);
|
|
|
|
|
DatabaseFinalize(stmt);
|
|
|
|
|
rows += c;
|
|
|
|
|
}
|
|
|
|
|
return rows;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Load the candidate corpus from the LARGEST signal DB on disk. |
|
|
|
|
|
//| |
|
|
|
|
|
//| Deliberately NOT through dbm/the chart's own config fingerprint: |
|
|
|
|
|
//| the DB filename hashes the journaling inputs, so a training |
|
|
|
|
|
//| chart whose inputs differ by one journal setting from the |
|
|
|
|
|
//| corpus-building tester run would open a different (empty) file |
|
|
|
|
|
//| and silently train on nothing - the exact procedural trap that |
|
|
|
|
|
//| burned four corpus-build attempts. The corpus is DATA, not |
|
|
|
|
|
//| config identity; the biggest coherent set of candidates on disk |
|
|
|
|
|
//| is the right training set, and the pick is logged so the run is |
|
|
|
|
|
//| auditable. All files in the folder share one semantics era - |
|
|
|
|
|
//| the DatabaseVersion wipe guarantees it. |
|
|
|
|
|
//| |
|
|
|
|
|
//| Read-only open: this must never write, prune, or lock the DB |
|
|
|
|
|
//| the journaling side owns. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CSignalMETA::LoadMetaCorpus(void)
|
|
|
|
|
{
|
|
|
|
|
const string folder = "Warrior_EA\\Databases\\Signals\\";
|
2026-08-13 11:11:38 -04:00
|
|
|
//--- Only THIS symbol's + THIS timeframe's corpora are candidates. The DB filename is
|
|
|
|
|
//--- <symbol>_<period>_<fingerprint>.db, and "largest on disk" without this filter would happily
|
|
|
|
|
//--- hand an H4 chart the (bigger) H1 corpus - whose rows then resolve onto the wrong bars - or an
|
|
|
|
|
//--- SP500 chart another symbol's DB entirely. Candidates journaled on a different grid are not
|
|
|
|
|
//--- this chart's candidates.
|
|
|
|
|
const string mustPrefix = _Symbol + "_" + IntegerToString((int)_Period) + "_";
|
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
|
|
|
string bestFile = "";
|
|
|
|
|
long bestRows = 0;
|
|
|
|
|
string fname;
|
|
|
|
|
long find = FileFindFirst(folder + "*.db", fname, FILE_COMMON);
|
|
|
|
|
if(find != INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
do
|
|
|
|
|
{
|
2026-08-13 11:11:38 -04:00
|
|
|
if(StringFind(fname, mustPrefix) != 0)
|
|
|
|
|
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
|
|
|
int db = DatabaseOpen(folder + fname, DATABASE_OPEN_READONLY | DATABASE_OPEN_COMMON);
|
|
|
|
|
if(db == INVALID_HANDLE)
|
|
|
|
|
continue;
|
|
|
|
|
long rows = CountDbPatternRows(db);
|
|
|
|
|
DatabaseClose(db);
|
|
|
|
|
if(rows > bestRows)
|
|
|
|
|
{
|
|
|
|
|
bestRows = rows;
|
|
|
|
|
bestFile = fname;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
while(FileFindNext(find, fname));
|
|
|
|
|
FileFindClose(find);
|
|
|
|
|
}
|
|
|
|
|
if(bestFile == "" || bestRows <= 0)
|
|
|
|
|
{
|
2026-08-13 11:11:38 -04:00
|
|
|
Print(ID + ": META CORPUS UNAVAILABLE - no signal DB matching " + mustPrefix + "*.db with pattern"
|
|
|
|
|
" rows found under Common\\Files\\" + folder + ". Build one for THIS symbol+timeframe first:"
|
|
|
|
|
" wipe the Signals folder, then run a long backtest on this chart's symbol AND timeframe"
|
|
|
|
|
" with UseDatabaseRanking=true and DB_MaxRowsPerTable raised (see Meta_Labeling_Design.md S1).");
|
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;
|
|
|
|
|
}
|
|
|
|
|
int db = DatabaseOpen(folder + bestFile, DATABASE_OPEN_READONLY | DATABASE_OPEN_COMMON);
|
|
|
|
|
if(db == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
Print(ID + ": failed to reopen corpus DB " + bestFile + " (error " +
|
|
|
|
|
IntegerToString(GetLastError()) + ")");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
ArrayResize(m_corpusGmt, (int)bestRows);
|
|
|
|
|
ArrayResize(m_corpusSide, (int)bestRows);
|
|
|
|
|
ArrayResize(m_corpusNetVote, (int)bestRows);
|
|
|
|
|
ArrayResize(m_corpusFamily, (int)bestRows);
|
|
|
|
|
ArrayResize(m_corpusPattern, (int)bestRows);
|
|
|
|
|
ArrayResize(m_corpusEntry, (int)bestRows);
|
|
|
|
|
m_corpusCount = 0;
|
|
|
|
|
for(int f = 0; f < 4; f++)
|
|
|
|
|
for(int p = 0; p < MetaFamilyPatterns(f); p++)
|
|
|
|
|
for(int s = 0; s < 2; s++)
|
|
|
|
|
{
|
|
|
|
|
string table = MetaFamilyName(f) + "_Pattern_" + IntegerToString(p) +
|
|
|
|
|
(s == 0 ? "_Buy" : "_Sell");
|
|
|
|
|
int stmt = DatabasePrepare(db, "SELECT year, month, day, hour, minutes, netVote, entryPrice"
|
|
|
|
|
" FROM " + table);
|
|
|
|
|
if(stmt == INVALID_HANDLE)
|
|
|
|
|
continue;
|
|
|
|
|
while(DatabaseRead(stmt) && m_corpusCount < (int)bestRows)
|
|
|
|
|
{
|
|
|
|
|
long y = 0, mo = 0, d = 0, h = 0, mi = 0;
|
|
|
|
|
double nv = 0.0, ep = 0.0;
|
|
|
|
|
DatabaseColumnLong(stmt, 0, y);
|
|
|
|
|
DatabaseColumnLong(stmt, 1, mo);
|
|
|
|
|
DatabaseColumnLong(stmt, 2, d);
|
|
|
|
|
DatabaseColumnLong(stmt, 3, h);
|
|
|
|
|
DatabaseColumnLong(stmt, 4, mi);
|
|
|
|
|
DatabaseColumnDouble(stmt, 5, nv);
|
|
|
|
|
DatabaseColumnDouble(stmt, 6, ep);
|
|
|
|
|
MqlDateTime t;
|
|
|
|
|
t.year = (int)y;
|
|
|
|
|
t.mon = (int)mo;
|
|
|
|
|
t.day = (int)d;
|
|
|
|
|
t.hour = (int)h;
|
|
|
|
|
t.min = (int)mi;
|
|
|
|
|
t.sec = 0;
|
|
|
|
|
m_corpusGmt[m_corpusCount] = StructToTime(t);
|
|
|
|
|
m_corpusSide[m_corpusCount] = (char)(s == 0 ? 1 : -1);
|
|
|
|
|
m_corpusNetVote[m_corpusCount] = nv;
|
|
|
|
|
m_corpusFamily[m_corpusCount] = (short)f;
|
|
|
|
|
m_corpusPattern[m_corpusCount] = (short)p;
|
|
|
|
|
m_corpusEntry[m_corpusCount] = ep;
|
|
|
|
|
m_corpusCount++;
|
|
|
|
|
}
|
|
|
|
|
DatabaseFinalize(stmt);
|
|
|
|
|
}
|
|
|
|
|
DatabaseClose(db);
|
|
|
|
|
m_corpusLoaded = (m_corpusCount > 0);
|
|
|
|
|
Print(ID + StringFormat(": meta corpus loaded from %s - %d candidates across the pattern tables"
|
|
|
|
|
" (largest of the DBs found; corpus rows are GMT-stamped, resolution to"
|
|
|
|
|
" server bars happens per era).", bestFile, m_corpusCount));
|
|
|
|
|
return m_corpusLoaded;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Resolve the corpus onto this era's bar grid. |
|
|
|
|
|
//| |
|
|
|
|
|
//| DB rows are GMT; bar history is server time (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(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;
|
|
|
|
|
int cap = bars * 2;
|
|
|
|
|
ArrayResize(m_corpusGmt, cap);
|
|
|
|
|
ArrayResize(m_corpusSide, cap);
|
|
|
|
|
ArrayResize(m_corpusNetVote, cap);
|
|
|
|
|
ArrayResize(m_corpusFamily, cap);
|
|
|
|
|
ArrayResize(m_corpusPattern, cap);
|
|
|
|
|
ArrayResize(m_corpusEntry, cap);
|
|
|
|
|
m_corpusCount = 0;
|
|
|
|
|
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 && m_corpusCount + 2 <= cap; i--)
|
|
|
|
|
{
|
|
|
|
|
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")
|
|
|
|
|
{
|
|
|
|
|
m_corpusGmt[m_corpusCount] = bt;
|
|
|
|
|
m_corpusEntry[m_corpusCount] = bo;
|
|
|
|
|
m_corpusSide[m_corpusCount] = 1;
|
|
|
|
|
m_corpusFamily[m_corpusCount] = (short)m_srcFamily[s];
|
|
|
|
|
m_corpusPattern[m_corpusCount] = (short)StringToInteger(StringSubstr(pl, 8));
|
|
|
|
|
m_corpusNetVote[m_corpusCount] = nv;
|
|
|
|
|
m_corpusCount++;
|
|
|
|
|
}
|
|
|
|
|
if(ps != "NULL")
|
|
|
|
|
{
|
|
|
|
|
m_corpusGmt[m_corpusCount] = bt;
|
|
|
|
|
m_corpusEntry[m_corpusCount] = bo;
|
|
|
|
|
m_corpusSide[m_corpusCount] = -1;
|
|
|
|
|
m_corpusFamily[m_corpusCount] = (short)m_srcFamily[s];
|
|
|
|
|
m_corpusPattern[m_corpusCount] = (short)StringToInteger(StringSubstr(ps, 8));
|
|
|
|
|
m_corpusNetVote[m_corpusCount] = nv;
|
|
|
|
|
m_corpusCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Print(ID + StringFormat(": candidate sweep done - %d candidates from %d bars in %.1fs (no tester"
|
|
|
|
|
" corpus run needed; DB corpus not used).", m_corpusCount, deepest,
|
|
|
|
|
(GetTickCount() - t0) / 1000.0));
|
|
|
|
|
m_corpusLoaded = (m_corpusCount > 0);
|
|
|
|
|
return m_corpusLoaded;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
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.
|
|
|
|
|
if(!m_corpusLoaded && !BuildCorpusBySweep() && !LoadMetaCorpus())
|
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;
|
|
|
|
|
ArrayResize(m_metaCandHead, bars);
|
|
|
|
|
ArrayInitialize(m_metaCandHead, -1);
|
|
|
|
|
ArrayResize(m_metaCandBar, m_corpusCount);
|
|
|
|
|
ArrayResize(m_metaCandSide, m_corpusCount);
|
|
|
|
|
ArrayResize(m_metaCandNetVote, m_corpusCount);
|
|
|
|
|
ArrayResize(m_metaCandFamily, m_corpusCount);
|
|
|
|
|
ArrayResize(m_metaCandPattern, m_corpusCount);
|
|
|
|
|
ArrayResize(m_metaCandNext, m_corpusCount);
|
|
|
|
|
m_metaCandCount = 0;
|
|
|
|
|
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;
|
|
|
|
|
for(int r = 0; r < m_corpusCount; r++)
|
|
|
|
|
{
|
|
|
|
|
int bestSh = -1, bestOff = -1;
|
|
|
|
|
double bestDiff = DBL_MAX;
|
|
|
|
|
for(int off = 0; off <= 4; off++)
|
|
|
|
|
{
|
|
|
|
|
int sh = iBarShift(_Symbol, per, m_corpusGmt[r] + off * 3600, true);
|
|
|
|
|
if(sh < 0)
|
|
|
|
|
continue;
|
|
|
|
|
double diff = MathAbs(iOpen(_Symbol, per, sh) - m_corpusEntry[r]);
|
|
|
|
|
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);
|
|
|
|
|
double tol = (MathIsValidNumber(atr) && atr > 0.0) ? 0.15 * atr : 0.0005 * m_corpusEntry[r];
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
int id = m_metaCandCount;
|
|
|
|
|
m_metaCandBar[id] = bestSh;
|
|
|
|
|
m_metaCandSide[id] = m_corpusSide[r];
|
|
|
|
|
m_metaCandNetVote[id] = m_corpusNetVote[r];
|
|
|
|
|
m_metaCandFamily[id] = m_corpusFamily[r];
|
|
|
|
|
m_metaCandPattern[id] = m_corpusPattern[r];
|
|
|
|
|
m_metaCandNext[id] = m_metaCandHead[bestSh];
|
|
|
|
|
m_metaCandHead[bestSh] = id;
|
|
|
|
|
m_metaCandCount++;
|
|
|
|
|
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",
|
|
|
|
|
ID, m_metaCandCount, m_corpusCount, offCount[0], offCount[1], offCount[2],
|
|
|
|
|
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_metaCandCount > 0)
|
|
|
|
|
ExportMetaDataset();
|
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 m_metaCandCount > 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
|
|
|
//| 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);
|
|
|
|
|
int fh = FileOpen(base + ".f32", FILE_BIN | FILE_WRITE | FILE_COMMON);
|
|
|
|
|
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_metaCandCount, base));
|
|
|
|
|
int width = NetInputWidth();
|
|
|
|
|
int rows = 0, skipLabel = 0, skipWindow = 0;
|
|
|
|
|
for(int cd = 0; cd < m_metaCandCount; cd++)
|
|
|
|
|
{
|
|
|
|
|
int idx = m_metaCandBar[cd];
|
|
|
|
|
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_metaCandFamily[cd]);
|
|
|
|
|
FileWriteInteger(fh, (int)m_metaCandPattern[cd]);
|
|
|
|
|
FileWriteInteger(fh, (int)m_metaCandSide[cd]);
|
|
|
|
|
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;
|
|
|
|
|
int mh = FileOpen(base + ".meta.csv", FILE_CSV | FILE_WRITE | FILE_COMMON, ',');
|
|
|
|
|
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(candId < 0 || candId >= m_metaCandCount)
|
|
|
|
|
return;
|
|
|
|
|
int idx = m_metaCandBar[candId];
|
|
|
|
|
int slot = OneHotSlot(m_metaCandFamily[candId], m_metaCandPattern[candId]);
|
|
|
|
|
for(int k = 0; k < META_ONE_HOT_SLOTS; k++)
|
|
|
|
|
TempData.Add(k == slot ? 1.0 : 0.0);
|
|
|
|
|
TempData.Add((double)m_metaCandSide[candId]);
|
|
|
|
|
//--- 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_metaCandNetVote[candId] / 20.0));
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|