Commit graph Warrior_EA/System/AltData.mqh
Author SHA1 Message Date
AnimateDread
a970405042 feat(pool,mi): one feature layout fleet-wide, and the keep-screen stops self-disabling on a cold start
TWO CHANGES, BOTH RETRAIN-FORCING BY INTENT.

1. SP500 was training alone, and one alt-data column was the reason.

   The alt block's width joins the model fingerprint, and the pool reader only
   adopts peer rows whose fingerprint and width match. The exporter gives each
   instrument the series that apply to it - FX 15 columns, metals/oil 14, SP500
   13 - so the fleet ran as three incompatible pools:

     EURUSD/USDJPY/USDCAD  adopt ~57-60k peer rows each
     XAUUSD/XTIUSD         adopt 6.4k / 20.3k
     SP500                 "EVERY peer file was REJECTED, so this chart is
                            training alone" - 0 rows

   SP500 therefore trained on 2279 independent observations against a 600-wide
   input with its first layer floored at 16, printing its own "expect
   overfitting" warning. It is the one chart with no pool and the worst
   capacity ratio in the fleet by a factor of three.

   Fresh models now pin ALTDATA_FLEET_COLUMNS - the 12-column intersection -
   instead of their own file header. An existing model still adopts its .cfg
   pin, so this re-keys nothing that is already trained.

   Intersection rather than union: filling an absent series with its median
   makes that column constant per instrument, which lets a pooled model
   identify the source instrument and stop learning the shared mechanism. It
   is also 6 columns narrower. Cost is six columns whose retained information
   is UNMEASURED - the keep-screen reports a bitmask nothing has mapped back
   to names.

2. The MI keep-screen disabled itself for the whole run on any cold start.

   ReportFeatureLabelInformation set m_miReportDone on ENTRY. On a cold start
   the label cache is allocated before it is filled, so BuildMiSample finds no
   row carrying a resolved label and returns 0 - a sixth exit, and the only
   one the 8c1266d instrumentation did not cover, which is why it printed
   nothing. observed then stayed -1, the permutation loop never iterated, and
   the report emitted "-1.00000 nats over 0 permutations" beside a plausible
   "strongest single feature 0.05979" that was a STALE m_miBestColumn from an
   earlier scoring call. The first ensemble member propagated the latch to
   g_ensembleChartMiReportDone and silenced every member on the chart.

   The flag now latches only once a measurement exists. A short sample is
   reported as a deferral naming the two numbers that identify it (cached bars
   vs bars carrying a resolved label) and retried, up to
   MI_REPORT_MAX_ATTEMPTS.

Build tag -> fleet-pool-v1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 17:41:14 -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
73b6ab9724 refactor(system): TunedPeriods reuses AltDataFileSymbol instead of re-implementing it
TunedPeriodsFileName() inlined the same 9-line StringReplace sanitizer
AltData.mqh already defines once as AltDataFileSymbol(), whose own
comment said it was meant for exactly this. Gave AltData.mqh a proper
include guard (it had none) and included it directly from
TunedPeriods.mqh so the call is safe regardless of include order.

Compile-verified 0 errors/0 warnings.
2026-08-24 01:31:14 -04:00
AnimateDread
b91c7b1f7a refactor(comments): box headers to stdlib length
The //| box blocks were excluded from 0b06f8e and 5efdb48 and were what
remained: 160 of them ran to 10+ lines, the longest to 88. Compressed to their
leading topic sentences - 5 lines for a function header, 8 for a file header -
keeping the box format and the standard MQL5 name/author lines verbatim.

Verified at the BYTE level this time, across every in-scope file: the list of
non-comment lines is byte-identical to HEAD and braces balance. The first check
compared a locale-decoded 'git show' against a UTF-8 read and flagged 25 files
that had not changed at all - every BOM and every non-ASCII line mismatched.

47,696 -> 40,665 lines in scope; comment share 38% -> 26%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 00:30:14 -04:00
AnimateDread
5efdb48de4 refactor(comments): stdlib comment style across the remaining in-scope files
Same pass as 0b06f8e, applied file by file: comment runs of 4+ lines compressed
to their leading topic sentences, capped at 4 lines, whole sentences only.
Warning sentences (NEVER / MUST / trap / would-have) survive the budget.

Every file was checked the same way before committing: the list of non-comment
lines is byte-identical to HEAD, and braces balance. No code was touched.

Panel/, Enumerations/ and the already-terse System headers needed little or
nothing - PooledGate, TradeChecks, BinomialStats and Random came through with
no blocks over the threshold at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 00:25:52 -04:00
AnimateDread
81ad276859 docs(altdata): the as-of comparison is naive-vs-broker-time, and the publication buffer is what makes that safe
Features() binary-searches m_rowTime (StringToTime of a naive "2026.08.21" = 00:00) against
m_Time.GetData(idx), which is BROKER time. A row dated D carries values public from D 00:00 UTC,
so every bar in [D 00:00 broker, D 00:00 UTC) - the first H4 bar of each broker day - reads that
row 2-3 hours before it was public.

Not a leak today: every collector stamps with a conservative buffer on top of the real release,
and the overshoot fits inside all of them. Tightest is the FRED daily series - VIX prints 16:15 ET
and is stamped D+1 00:00 UTC, leaving >=1h45 in the worst DST alignment. COT (Friday 15:30 ET ->
Saturday 00:00 UTC) has no bars to read it before Monday at all; EIA has ~8h.

Recording it because nothing in the file said so, and the safety lives in a number nobody would
think to check before shortening a stamp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 15:01:50 -04:00
AnimateDread
77594ef5fb refactor(stdlib): one quantile definition, from Math\Stat
The codebase had THREE conventions for the same statistic. AltData took a
true median; the barrier horizon and the derived input window took the
upper of the two middle values; the MI terciles and the barrier stop
ladder used nearest-rank indexing. All four now go through MathMedian /
MathQuantile, which is R's type 7 and the library's one answer.

  System\AltData.mqh          column median  -> MathMedian (exact, no change)
  AIBase\Labels.mqh           swing median   -> MathMedian
                              leg-range med  -> MathMedian
                              stop ladder    -> MathQuantile, read in one call
  AIBase\Topology.mqh         window median  -> MathMedian
  AIBase\AutoTune.mqh         MI terciles    -> MathQuantile + MathMin/MathMax
  Signals\SignalSessionFilter DST last Sunday-> CDateTime::DaysInMonth()

gaps[]/legs[] change from int to double so MathMedian can read them; the
values are bar counts either way.

VALUES MOVE. Even-sample medians shift by half a bin and the quantile
reads interpolate, so the barrier geometry and the derived input window
can land on different rungs - re-keying fingerprints and forcing a
retrain. Accepted deliberately: stdlib consistency was the ask, and three
private conventions for one statistic is what it buys out.

Two YAGNI finds fell out of the ladder rewrite. MathQuantile sorts its own
copy, so DeriveBarrierGeometry no longer sorts up[]/dn[] in place - which
means upUnsorted[], a full array copy kept only to undo that sort, is
gone. ArraySort(up) had no consumer needing order at all; it was pure
work. The library call also gets a failure guard the hand-rolled indexing
never needed but the ladder read does.

Verified while here: Math\Stat\Math.mqh's MathAbs/MathMax/MathSqrt/MathPow
and friends are ARRAY overloads, not scalar redefinitions, so pulling it
into the translation unit shadows no builtin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:16:03 -04:00
AnimateDread
552edb5fb1 refactor(yagni): drop 13 accessors nothing called; unify the ATR trailing pair
Verified dead by grep across all first-party sources (references/, Scripts/,
research/ excluded): EraCount, HiddenLayersCount, LstmHiddenSize, ConvFilterCount,
HistoryBars and MinTrainYear setters, PendingBatchSamples, getPrevOutIndex,
BaseCurrency, QuoteCurrency, CurrencyCount, IsLoaded, LastFiredDirection,
DBConfidence, SpecIndex, and the conv Step/WindowOut shape accessors. Every
backing member stays - each is still read internally and several are pinned by
the positional .cfg layout - so this removes surface, not behaviour.

Two comments were asserting the opposite of the code and are now true: the
"No setter: the taper's endpoints are derived" note was directly above three
setters, and the conv shape block claimed EnforceTopologyContract reads all
three accessors when CNet::FirstConvWindow only ever calls Window().

CTrailingATR::CheckTrailingStopLong/Short were byte-identical but for Bid vs Ask
and the isLong flag; both now delegate to one CheckTrailingStop body.

Deliberately NOT removed: the fractal-target branch (TrainTargetFractal,
IsFractalTarget and their label machinery). It reads as dead because the
TrainingTarget input was withdrawn, but Warrior_EA.mq5:836 documents it as a
parked option with a three-line restore path - that is a product call, not a
refactor.

Not compiled - MetaEditor compile pending.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 18:55:36 -04:00
AnimateDread
1cf4c57d57 fix(altdata): median-fill instead of zero-fill, and a one-shot feature-vector autopsy
ALT-DATA AUDIT. The files themselves are healthy - all six symbols, 6,073 daily
rows, 2010-01-01 to 2026-08-17, no constant or degenerate columns, sane tails
(mac_cpi/mac_unemp flat ~47d is monthly data behaving correctly). The problem is
not the data, it is what happens where the data ISN'T.

CAltDataPanel::Features() returned an all-ZERO vector for any bar older than the
file's first row, and left blank cells at 0 too. Both were deliberate ('the block
is additive context and must degrade, never reject the bar') and that reasoning
holds for the CHANGE columns - but half these features are LEVELS: vix, ivol,
mac_y10, mac_cpi, mac_unemp, eia_util. For a level, 0 is not a missing reading,
it is an impossible one far outside the series' range. VIX does not visit zero.

And the spike lands in exactly the wrong place. Every alt file starts 2010-01-01
while the charts run far deeper - USDJPY H4 reaches ~1994, roughly HALF its
history - so 'alt block is all zeros' is precisely the predicate 'this bar is
older than 2010'. The IS/OOS split is chronological, so that predicate covers
~half of IS and none of OOS: an in-sample feature guaranteed to be useless
out-of-sample, and a bimodal input for the first BatchNorm to normalise. Not a
lookahead leak - a distribution corruption, which is quieter and was never
reported anywhere.

Now filled with the column MEDIAN over the covered range. A constant cannot leak
whatever its source - it takes the same value on every pre-coverage bar, so it
carries no information about which of those bars won - which is what makes a
median computed over later data legitimate here. Median not mean because the
series are skewed. Blank cells get the same treatment (eia_stk_idx1y alone has
181 blanks in 6,073 rows) and the count is now logged at load.

THE BACKOFF WAS ALREADY THERE AND WAS DEAD. Training.mqh arms m_coldSweepTick on
m_featureFailTransient, but only the open/ATR guards ever set that flag, so
f0cf659's cold ADMovingAverage looked PERMANENT and the sweep re-ran at full
speed forever. Setting the flag in the indicator guards revives the mechanism
that was already designed for this; no second backoff was needed and the one I
first wrote has been removed in favour of it.

SELF-HEALING, as asked. ReportFeatureHealth() runs once, the first time pass 1
produces usable windows, samples ~400 bars spread across the whole training range
and names every feature slot that is CONSTANT or mostly-zero, tagging alt-block
slots as alt[i]. Both of today's failures were the same shape - a block silently
produces nothing while every downstream number stays plausible - and neither an
accuracy figure nor a model can tell 'this feature is always 0' from 'this
feature is genuinely 0 here'. Evenly spaced sampling so a block that dies only in
deep history is caught as surely as one dead everywhere. A report, not a gate:
a rare-flag feature can be legitimately constant, and refusing to train would
turn a diagnostic into an outage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 12:53:51 -04:00
AnimateDread
3e9b46452b fix(altdata): robustness pass for arbitrary symbol/timeframe - validation + error handling
Systematic audit of the alt-data stack against "any symbol, any timeframe",
prompted by the H4 surprise. Findings, each fixed:

CAPACITY: ALTDATA_MAX_FEATURES was 16 with FX symbols already at 15 - the next
added column would have been silently truncated by a MathMin. Raised to 32,
pin-chars 512 -> 1024.

SYMBOL NAMES: the panel reload path re-derived symbol/timeframe by splitting
the file path on its FIRST underscore - mis-parsing every symbol containing
one (OANDA-style EUR_USD and US_500 are in our own alias lists) and knowing
only three timeframes. It now stores the (symbol, period) Load() was called
with and reuses them verbatim. Path-hostile characters in broker symbols
("EUR/USD") are sanitized by a shared AltDataFileSymbol() used by the panel,
the fetcher and TunedPeriods, so a slash cannot route a write into an
unintended subfolder.

DOWNLOAD VALIDATION: every FRED-family fetch now enforces a per-series
plausibility band (VIX 1-200, yields -5..30, CPI index 20-1000, ...) -
StringToDouble on transport garbage returns 0.0, and one absurd value poisons
every change/percentile feature computed across it (the BatchNorm NaN-latch
incident came from exactly one huge-but-finite input). Rejected rows are
counted and reported, never dropped silently.

LOUD EMPTINESS: a successful response with zero observations on an empty
cache now says so - naming the series (wrong id / format drift) or the COT
predicate (the unverified like-clauses) instead of leaving 0-filled features
unexplained. GEX gains a truncation guard: a day-over-day contract-count
collapse >50% is the fingerprint of a partial 13 MB download, not of markets,
and is skipped rather than recorded as a plausible-but-wrong number.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:59:10 -04:00
AnimateDread
862d0f909f fix(altdata): H4 charts read the daily alt file; multi-chart write races hardened
The user attached H4 charts and the panel found no {SYM}_H4.csv - the fetcher
only writes _D1 files - so the run trained with ZERO alt features, silently.
The daily file is timeframe-agnostic by construction (rows are as-of daily
values, and Features() joins published <= bar open per bar), so the panel now
falls back to {SYM}_D1.csv on any timeframe, logging the substitution. A
per-TF file still takes precedence if one ever exists.

Per-symbol subfolders (the user suggestion) are NOT the fix for multi-chart
concerns: filenames are already symbol-keyed, and the shared caches
(raw_VIXCLS etc.) are shared deliberately - one download serves every chart.
The REAL races were: (1) two charts of one symbol (D1+H4) each caching their
own last-GEX date and double-appending the same day - UpdateGex now re-reads
the file date before spending the download; (2) whole-file rewrites were
truncate-then-write, so a concurrent reader could parse a torn file - SaveRaw
and RebuildFeatures now write a temp and FileMove-swap it into place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:53:41 -04:00
AnimateDread
8ce635ce70 feat(altdata): external feature block wired into the NN feature window
- System\AltData.mqh: CAltDataPanel - publication-stamped CSV panel
  (Common\Files\Warrior_EA\AltData\{SYM}_{TF}.csv), as-of lookup by bar
  open, 0-fill degradation (mirrors cross-asset), hourly live refresh
- Topology: width block AFTER the .cfg name-list pin is pre-read
  (ReadAltDataPinFromCfg) so a grown export can never mismatch a resumed
  model's width or shift its slots
- Persistence: alt pin appended to the .cfg (append-and-length-guard
  convention), adopt-don't-compare on load
- Features: emit block after Wyckoff SBI; EnsureFresh probe in
  BuildFeatureWindow (never fires in tester)
- export.py: fixed a-priori scale constants (never data-fitted)

Widths change SP500 +4 / USDJPY +3 / XAUUSD +1 (fingerprint re-keys ->
fresh models on redeploy); EURUSD exports nothing and resumes unchanged.
Compiles 0 errors / 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 13:39:00 -04:00