Commit graph Warrior_EA/Expert/Training/TrainingPool.mqh
Author SHA1 Message Date
AnimateDread
2e22e714c6 fix(pool): length-prefix the fingerprint - the cross-instrument pool was inert
STrainPoolHeader wrote its fingerprint into a FILE_BIN stream as
FileWriteString(h, fingerprint + "\n") and read it back with
FileReadString(h) - no length argument. In binary mode FileWriteString
emits the characters raw: no length prefix, no terminator, and "\n" is
just another character rather than a delimiter anything honours. The
reader had nothing to stop at, over-read into the float rows that follow,
and returned the fingerprint plus a few bytes of binary garbage - so
`fingerprint != wantFp` could never succeed between two genuinely
identical models.

Verified in the bytes rather than inferred: xxd on a v1 file shows three
ints then the fingerprint starting immediately at offset 12 with no count
in front of it, and EURUSD/USDJPY/USDCAD all stored width 624 with
byte-identical fingerprints while each one's log rejected the other two as
"different model fingerprint". The StringReplace on "\n" is the tell that
a delimiter was intended.

Cross-asset-class peers really are incompatible and always will be - FX
majors carry XA:6, indices/metals/oil carry XA:6:IDX2, giving widths
600/612/624 - which is why the reject list looked plausible and this went
unread. The three FX majors were always poolable and never pooled.

Length-prefixes the string, bounds-checks the count before sizing a read
from it, and bumps TRAINPOOL_RECORD_VERSION 1 -> 2 so existing files are
refused by the version gate with a reason instead of being misread.

Also documents, without changing, why Signal_ThresholdOpen is now a
unanimity rule: the vote is a weighted mean of tier weights, those fell
from ~70 to ~30 with the pivot-event label, so PCT_25 went from ~36% of
the reachable ceiling to ~83%. Measured: all 6 symbols clear their
precision bar, 4 of 6 fail only on coverage, and coverage decays 6.8% ->
2.2% over 35 eras as the models specialise - which shrinks effN and so
RAISES the deploy bar at flat precision. PCT_20 (a 3-of-4 quorum) is the
indicated change but is left unmade: MT5 stores input values per chart in
profiles\Charts\*\chart*.chr, so an already-attached EA ignores this
default entirely - confirmed by a full close/recompile/relaunch cycle
after which the log still read "fired at vote>=25%".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:41:41 -04:00
AnimateDread
b2784b5a4d Enhance Feature and Topology Interfaces with Bulk Operations and Cache Management
- Added bulk read/write methods for feature caches in IFeaturesView and its implementations to optimize performance.
- Introduced LabelCacheInvalidateAll method to manage label cache invalidation alongside feature cache.
- Implemented PooledIndependentBars method in topology interfaces to account for additional independent observations.
- Enhanced risk budget management with throttling for peak-equity updates to reduce unnecessary file operations.
- Improved error handling and logging for ATR trailing stops to ensure better visibility of issues.
- Updated alt-data handling to prevent unnecessary operations during testing and optimization phases.
2026-08-25 22:51:50 -04:00
AnimateDread
9c31625aae fix(training-pool): say why a peer was rejected instead of adopting nothing in silence
Two charts (SP500 H4 + USDJPY H4) ran with the pool enabled and produced no
TrainPool directory, no adopted rows and not one journal line. The pool was
inert and there was no way to tell that from "the feature is off".

It could never have fired: the fingerprint is not symbol-invariant. It hashes
NeuronsCount, which counts the alt-data columns - and those are per-symbol
(SP500 carries cot_spec_net, the FX majors cot_idx_1y/3y/chg_4w) - and the
cross-asset block appends ":IDX2" when base currency == profit currency, true
of an index and false of a pair. SP500 came out 50 features wide under
XA:6:IDX2, USDJPY 52 wide under XA:6. Compatible() gates on both, so adoption
was zero by construction.

- STrainPoolHeader::MismatchReason() replaces the bare Compatible() predicate
  and names the mismatch; Compatible() now delegates to it, so "may I adopt"
  and "why not" can never drift apart.
- CTrainPoolReader::Adopt() reports its own verdict - adopted, alone, or every
  peer rejected with the reason per file - and reports it on CHANGE only. An
  era over a warm feature cache runs in a fraction of a second here, so a
  per-era line would bury the journal. The duplicate Print in RunPass2 is gone;
  pool state is now reported from exactly one place.
- CTrainPoolWriter::Publish() rate-limits to TRAINPOOL_MIN_PUBLISH_SEC (300s).
  Every era re-derives the same rows from the same in-sample span, so per-era
  publishing rewrote a multi-megabyte file continuously for no new information.
  The first publish is never delayed.

Compile-verified in the staging copy: 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 14:43:12 -04:00
AnimateDread
e7b4442999 feat(training): TrainingPool - cross-instrument training rows, compile-verified
PooledGate pools the DECISION; this pools the DATA. Measured in research/edge.py with both arms
sharing calendar folds, exit-time purge, benchmark and scoring so training breadth is the only
variable: H4 k=2 gap +2.02pp at t_mkt 3.97, which CLEARS the Sidak bar of 3.69 over five feature
sets at df=6, replicated independently at D1 k=1 (+2.03pp, t_mkt 2.79). The per-instrument arm
was NEGATIVE on every feature set at both timeframes - it loses to "always take the drift side".
This EA trains one net per chart, which is that arm.

Rows, not symbols. Pointing the feature stack at another symbol needs per-symbol indicator
handles and this project has been bitten there twice - the handle leak that never released the
old handle, and the twelve "dead" handles that were one shared refcounted iMA. Each chart
instead computes its own features with its own handles and shares the NUMBERS. Sound only
because FeatureBuilder already ATR-normalises every price-unit feature, for exactly this reason
("instead of feeding e.g. 0.0005 on EURUSD").

Not a fingerprint participant: pooling changes what the model is trained ON, not what it IS, so
adding it would re-key every .nnw to record something outside the model's identity. The
fingerprint instead GATES adoption - it is the assertion that column k means the same thing in
both files - alongside a width check (a fingerprint match with a width mismatch means one side
pinned an older layout) and an exit-TIME purge, since a bar index cannot be compared across
instruments that each have their own calendar.

Writer and reader are separate classes: different reasons to change, different lifecycles, and
one class would carry the export buffers through every read. The file layout lives in one
STrainPoolHeader used by both sides so a layout change cannot be applied to the writer and
missed in the reader. Staging goes through System\AtomicFile rather than a second hand-rolled
temp-and-rename.

Compile-verified in isolation: 0 errors, 0 warnings. Staging junctions and harness removed; the
deployed .ex5 was never touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 13:46:58 -04:00