Commit graph Warrior_EA/Expert/Chart
Author SHA1 Message Date
AnimateDread
781ae3a702 perf(deinit): I/O-free chart cleanup, dead-panel purge, skip clean weight saves
The 18:23 terminal close (20260825.log) killed two of six charts inside
OnDeinit: they printed "shutting down" then nothing for 5.9 s until
"Abnormal termination", stranding ~700 objects each - including the one
family no prefix sweep can reach, the control panel (CAppDialog names
its 15 objects <numeric instance id><control>, and a re-attach mints a
new id, so a killed panel is a permanent ghost; XTIUSD carried one
across sessions). The stall sat in the two file writes that preceded
all visible cleanup while the four sibling charts flooded the same
2013-era disk - the ~4x18MB-per-chart shutdown weight saves.

Three changes:

1. OnDeinit touches no file until the chart is clean. CVoteArrowStore
   splits Save() into Snapshot() (the chart scan, in memory) and
   WriteSnapshot() (the disk half, consuming). New order: status label,
   vote-arrow snapshot, prefix sweep, panel destroy - all object ops -
   then member sidecars, final sweep, timings, and only then the
   visibility file, the vote-arrow write and the weight saves.

2. PurgeOrphanedPanelObjects() at OnInit: deletes numeric-prefix
   CAppDialog ghosts by name (6 chrome + 9 buttons), qualifying a
   prefix only when >=4 of OUR button names carry it, so a foreign
   dialog sharing stock chrome names is never touched.

3. m_netDirty: set by every net mutation (both backProp sites, both
   RestoreWeights sites, online learning conservatively, panel reset),
   cleared only on a successful Net.Save. Shutdown AND the per-bar
   autosave now skip the ~18MB write when the net is provably unchanged
   - for converged ensembles that is every save - which removes the
   very flood that starved the sibling charts. .stats still writes
   every time (small; carries the vote record and calibration). A
   skipped save leaves the .nnw header dtStudied stale, which is the
   already-handled attach-after-offline-gap case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 18:46:51 -04:00
AnimateDread
5aec69fe4c feat(vote): replay pass rebuilds a deployed model's ladder without retraining
The previous commit persisted the tier ladder, which fixes this going
forward but did nothing for models whose .stats predates WST7 - they
still had to retrain to mint one. They never did. Every number a
converged model needs in order to vote is a pure function of weights
already on disk plus labels derivable from the chart, so replay them:

  stage 1  build the label cache   (existing chunked prebuild)
  stage 2  rescan history          (existing chunked rescan, deployed net)
  stage 3  score + rank + persist  (one walk over two arrays)

ScoreReplayFromCache() walks m_arrowSignalCache against
m_labelCacheBuy/Sell, fills the same m_oosTierFired/Hits and per-class
totals pass 3 fills, and hands them to RankTiersFromOos() - deliberately
feeding the existing ranker rather than reimplementing it. The shrinkage,
the chance reference and the module trust weight are subtle enough that a
second copy would drift, and a ladder measured by a slightly different
rule would be silently incomparable with every ladder training produced.

AdvanceDeployedRebuild() sequences the three stages off the timer. It has
to be a sequence: stages 1 and 2 are each minutes of work draining in
time-boxed slices, and stage 2's output is meaningless until stage 1 has
labels to score against. The previous version ran the rescan with no
labels at all, which is why it could only ever rebuild arrows and never
the ladder - the thing actually blocking the vote.

The result is written to .stats immediately. The failure being repaired
is state that lived in memory and was never written down; recomputing it
and not saving it would repeat that exactly.

Also routes every rescan completion through one hook, so there is a
single place that knows what a finished rescan means - republish for a
manual one, score and rank for a rebuild.

Compile-verified in _claude_stage: 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 15:01:24 -04:00
AnimateDread
b92e233b88 fix(chart): a deployed model rescans history to rebuild its vote arrows
The sidecar added in 484a9d8 restores the vote arrows from the previous
session - but there was no previous session to restore from, and a
deployed ensemble could never produce one.

The overlay that draws the vote layer replays each member's
m_overlaySigSnap, published in exactly one place: RankTiersFromOos, at
pass-3 completion. A converged model runs no further eras. So after a
restart every member's snapshot was empty, would never fill, the sweep
had nothing to replay and the chart stayed blank permanently - no route
back by any path.

The chart rescan is the route: it runs the DEPLOYED net forward over
history and rebuilds the per-bar cache, which is the same quantity pass 3
produces, obtained without training. It already existed for the panel's
Show-Signals button; it just never handed its result to the overlay, so
on the default filtered view a rescan rebuilt only the RAW per-member
layer - the one that is hidden - and appeared to do nothing.

- PublishOverlaySnapshotFromCache() extracted from RankTiersFromOos, so
  the era end and a completed rescan publish through one implementation.
- A completed rescan now calls it, which also arms the sweep.
- PollTraining auto-arms one rescan for a model that is converged, has no
  snapshot, and is on the filtered view. One-shot: a model that
  legitimately calls Neutral everywhere must not rescan forever chasing a
  snapshot that is correctly empty. On the timer, not in OnInit - it is a
  full feedForward per bar over up to 5000 bars and drains in the same
  time-boxed slices as a manual rescan.

Together with the sidecar this closes both halves: the rescan covers the
first session and any chart whose file was lost or invalidated by a
threshold change; the sidecar covers every session after one is saved.

Compile-verified in _claude_stage: 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:11:36 -04:00
AnimateDread
484a9d8b0f fix(panel,arrows): one deploy predicate, a deployed-only readout, and persist the vote arrows
Four reported symptoms, three of them one root cause: the ensemble's
certified record was session-scoped and written ONLY at pass-3
completion. A deployed ensemble runs no further eras, so every restart
lost the aggregate win rate, the aggregate panel line and the overlay
snapshots - and could never regenerate them, because regeneration only
happens at an era end that will never come.

THE SELF-CONTRADICTION. Member rows read "Live - learning from new bars"
(from m_trainingComplete) while the line under them read "training, not
tradable yet" (from `prospective`, which means "this number came from
ProspectiveVote() rather than a real Direction() call" - what happens on
any bar where every member abstains, and which says nothing whatever
about training state). Both now resolve through one predicate:
WarriorChartModelsDeployed(), fed by members publishing their own state
on the same slot and cadence as their vote. Adds a third verdict word,
"armed (bar still open)", for a deployed model on a prospective
recompute - the case that used to claim it was training.

DEPLOYED PANEL. Once every published model is converged the per-member
rows are dropped: what ships is the aggregate vote win rate, the live
vote, and the verdict. While training the rows stay - they are the only
way a collapsed or lagging member is visible, since a collapsed member
abstains and so is invisible in the aggregate by construction.

ACCURACY NOW RESPECTS THE ENTRY THRESHOLD. The panel's "precision 65%"
came from m_cumOosCorrect/m_cumOosTotal, which counts every bar a model
called Buy or Sell - threshold-blind, and per-model rather than
per-vote. The correct number already existed (votePrecPct: bars where
|vote| >= threshold and the direction policy allows) and is now what the
panel shows, with the threshold named in the text because the number is
meaningless without it.

VOTE ARROWS PERSIST. With DrawUnfilteredSignals off - the default - the
chart shows SIG_VOTE_PREFIX arrows, and nothing saved them:
CChartUI's .arrows sidecar is member-scoped and never saw that layer.
New CVoteArrowStore mirrors them to a chart-keyed sidecar and restores
them progressively at init, on the same budgeted non-blocking path.
The header stores the open/close thresholds; a mismatch on load DISCARDS
the arrows rather than redrawing a picture of a strategy no longer
configured - stale arrows are worse than none, because none is visibly
empty and stale is confidently wrong.

Also: .stats bumped to WST7 carrying the ensemble record (guarded on
threshold match, most-complete-copy-wins), and the loader's version
tests collapsed from an or-chain to ">=" - the magics are ASCII 'WST1'..
'WST7' so they are already ordered, and a missed arm in that chain reads
the NEXT field's bytes into this one, which fails as plausible numbers
rather than as an error.

Compile-verified in _claude_stage: 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:00:15 -04:00
AnimateDread
1baa13c5b4 refactor(meta): remove meta-labeling entirely - RETRAIN-NEUTRAL
~2,300 lines. META had real, repeatedly measured ranking skill and ZERO
operating points that ever cleared break-even (0/350 H1 eras, 1/999 H4
pre-2-sigma, 0/8 pooled fitted points). The clinching arithmetic was edge x
width = 0.095 ATR/trade against spread 0.099 ATR/trade, and the
dose-response showed the high-conviction tail is temporally unstable -
the precision-vs-threshold slope flips sign between calib and test on 3 of
4 symbols, so no ex-ante threshold rule exists. It shipped default-off and
never gated a live entry. The self-measured tier weights are what actually
rank the vote, and all six H4 instruments converged on them alone.

RETRAIN-NEUTRAL, and that is the property that made this safe:

  - The weights fingerprint emitted "|TGT:META2" or "|TGT:SWG1" from an
    if/else. Every direction model already took the SWG1 arm, so
    collapsing it to an unconditional append is byte-identical. No .nnw or
    .cfg is orphaned or re-keyed.
  - NetInputWidth() lost its "+ MetaDescWidth()" term. MetaDescWidth()
    returned 0 for every direction model, so the input layer is unchanged.
  - DbLegacyAiSlot()'s slot 5 was reachable only with all four Use_* NNs
    off AND meta on - a config that never shipped. Every existing .db keeps
    its filename.

Deleted outright: Signals/SignalMETA.mqh, Expert/Trading/MetaGate.mqh (the
directory is now empty), Expert/Training/{MetaCorpus,MetaCandidateStore,
MetaFamilies}.mqh, Tests/Test_MetaFamilies.mq5, Meta_Labeling_Design.md.

Unwound in place, the delicate part: Training.mqh carried four
IsMetaTarget() branches whose else-arm WRAPPED the direction body (pass 1
queueing, pass 2 backprop, pass 2.5 calibration, pass 3 OOS scoring). Each
wrapper is removed and the direction body promoted back to its original
nesting - the bodies were never re-indented when the wrappers were added,
so the promoted code is byte-identical to what ran before META existed.
Also gone: the ensemble verdict's meta-veto replay and its
approved/vetoed/unscored counters, the per-family/per-side OOS
decomposition arrays, the m_isTrainQueueCand parallel queue and its
lockstep shuffle, and the S2 era report.

Also removed: the CMetaGate abstraction and the live CheckOpenPosition
veto; m_gates plus AddFilter's non-voter routing and IsVotingSignal()
(META was the only non-voting child, so m_gates was always empty);
m_parentSignal/SetParentSignal (existed only to reach the root's gate);
SweepPrepare/SweepPrepareIndicator (only caller was the corpus sweep);
IsMetaTarget() from all four view interfaces and their adapters;
Use_MetaLabeling, EnableMETA, Meta_ExportDataset, m_trainTarget.

EvalShift is KEPT - HistoricalNetVote() uses it for the filtered overlay,
not just the corpus sweep; only its comment changed. The 2-output softmax
arm in NetForward.mqh is kept too: it costs nothing and is the reusable
binary-head path, now commented as unclaimed rather than as META's.

Compile-verified in _claude_stage: 0 errors, 0 warnings, matching the
pre-edit baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 09:44:52 -04:00
AnimateDread
b5d34a82b4 feat(panel): one live vote line, no stale era count, no per-model HUD
Two chart-display fixes reported after watching a converged 4-model
ensemble: the ensemble panel's trailing "(era 69, 4 models,
DEPLOYING)" was frozen at whatever era the ensemble happened to
deploy on, and the separate top-right HUD (one line per model, raw
B/S/N + weight + era + error) was clutter once the vote itself is
what matters.

Root cause of the freeze: g_ensembleVoteLine is written once per era,
at pass-3 completion. A deployed/converged ensemble runs no further
eras (ScheduleTrainingIfNeeded's trainingComplete branch skips
Train() entirely), so that line could never update again - the era
count and "DEPLOYING" marker were permanent set-dressing from the
deploying era, not a live reading.

- EnsembleScoreCombinedVote() drops the era/DEPLOYING tail once
  g_ensDeployApproved - nothing left there worth freezing.
- UpdateVoteReadout() (the aggregate "VOTE ..." line, previously its
  own top-right chart object) now writes g_liveVoteLine instead of
  drawing anything. Both status-label builders - PublishEnsembleStatus
  for the ensemble panel, PublishStatus's choke point for the solo
  panel - append it as one line, refreshed every tick/timer exactly
  as the old HUD was, so the live vote replaces the frozen era tail
  in the same visual slot.
- RefreshVoteReadout()'s per-member loop (DisplayHudLine, one
  ObjectLabel per model) is deleted outright rather than folded in -
  the operator asked for the aggregate only, "without telling me each
  individual network".

Follow-on dead-code removal, since DisplayHudLine was the only
caller: the DispProb/DispSignal/MetaGateArmedNow/MetaHasScore/
MetaLastP/MetaLastBe/MetaApproved/MetaVetoed leg of IChartView (and
its AIBaseChartView/AIBaseChartViewImpl/ExpertSignalAIBase forwards)
had no other reader. The underlying data survives untouched -
m_metaTelemetry is still populated live by SignalMETA.mqh,
m_dispSignal still feeds ProspectiveVote - only the chart-view
forwarding that existed solely to reach the deleted HUD is gone.

Compile: 0 errors, 0 warnings (stage).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 22:27:41 -04:00
AnimateDread
8f2164698b feat(target): delete the barrier/geometry stack - the label is the verdict
Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE
target and the era verdict is precision + recall per class against the
label's own base rate - no win rate, no break-even, no expectancy, no
geometry anywhere in training.

DELETED
- Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep,
  FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives
  in Labeling/LabelOverlap.mqh), 3 test EAs.
- TripleBarrierLabel + walk, fractal label, geometry derivation/scan/
  adoption, exit-policy replay, excursion MI targets, the drift verdict
  (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry,
  the barrier defines, the .cfg geometry adopt (slots kept as zeros for
  the positional layout), the derived-geometry live-order override.
- TRAINING_TARGET input/enum: direction models are always swing; META2
  re-keys the meta head onto label agreement (descriptor loses its two
  geometry slots).

REWORKED
- Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with
  FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is
  never cached, so it can never freeze as a false Neutral; training,
  calibration, OOS scoring and online learning all skip unresolved bars.
- SDeployVerdict: significance-only; SOosTally chance = larger
  directional class share; pooled gate poolability = timeframe (record v2).
- Purge/embargo/declustering gaps: the measured mean label resolution
  lag (LabelResolutionBars), not a barrier horizon.
- Pool purge key + backfill DB rows: marked at the bar the label
  resolved on (m_labelResolveAge), not a fabricated barrier touch.
- Online learning frontier: finality, not a horizon delay.
- m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced ->
  m_erasSinceBest, ensemble vote outcome arrays -> label arrays.

STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection
are inputs again - trade management is the tester GA's search space.

Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional,
CUT token gone); META1 -> META2. Full retrain, as planned.

Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs,
0 errors, 0 warnings each.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
AnimateDread
ada8ddac22 refactor(chart): rename methods for consistency and clarity in CAIBaseChartView and ExpertSignalAIBase 2026-08-23 20:24:54 -04:00
AnimateDread
053d704a84 refactor(chart): ChartUI is a real collaborator, not a raw-include partial (S2)
Expert/AIBase/ChartUI.mqh was 869 lines of method bodies of
CExpertSignalAIBase, #included after the class declaration - free to touch
any of its ~500 members. First of the eleven AIBase/*.mqh partials to come
out (fewest inbound edges - see the SOLID campaign session order), using
the same view+adapter shape already proven for CTrainingDataView.

CChartView (Expert/Chart/IChartView.mqh) is the abstract read/behaviour
surface a chart-rendering collaborator needs - identity, bar/model access,
the prediction cache, and the training/vote/meta scalars the panel and HUD
line summarise. CAIBaseChartView is the adapter the signal owns and binds
to itself (MQL5 gives a class exactly one base, so CExpertSignalAIBase
cannot implement the view directly). CChartUI is the real collaborator: it
owns the arrow-restore queue, the rescan queue/tally, the last-arrows-saved
count and the purge-mismatch latch as its own fields (verified via grep to
be touched nowhere else in Expert/), and reaches everything else - including
StartChartSignalRescan, moved in from its old inline home in the header
since it drives the exact same rescan state machine AdvanceChartSignalRescan
drains - through the view.

m_arrowSignalCache and m_signalClusterWindow stay on the signal: Training.mqh
writes the cache directly every era and the training-data view already reads
it, so moving it would mean rewriting Training.mqh's write sites too - out of
scope here. CChartUI reaches it through four bounds-checked accessors instead
of a raw member poke. All 10 public methods keep their exact signatures and
become one-line forwards on the signal, so no other file's call sites change
except Training.mqh's one era-end status refresh, which now reads
RefreshStatusLabel() rather than reaching into CChartUI's now-private
last-displayed-neuron cache directly.

Verified structurally, not compiled (never compile - the operator does, in
MetaEditor): brace balance checked on every touched/new file against HEAD,
and the view/adapter/impl method lists cross-diffed to confirm all 59
accessors match 1:1 across the interface, the adapter declaration and the
adapter body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 20:03:52 -04:00