Commit graph Warrior_EA/Variables
Author SHA1 Message Date
AnimateDread
ad4ae58814 feat(vote): exit-on-reversal boolean, pin the threshold, retry the atomic rename
THE EXIT KNOB. Exit_On_Reversal_Vote (default false) replaces the deleted
Signal_ThresholdClose with one boolean: false pins the close threshold to an
arithmetically unreachable 101, true pins it to the SAME threshold the entry
uses - the seed at first, then the derived value, republished together whenever
it moves. A second threshold was always redundant; "the bot now says the other
way" is one question.

It also arms CExpertSignalCustom::m_holdToBarrier, which was DEAD CODE:
HoldToBarrier(bool) had no caller anywhere in the build, so the flag had been
permanently false and the disabled close threshold was carrying the whole
hold-to-barrier policy alone. Both halves now move together.

Default stays false because the reason is statistical: the gate certifies
P(label agrees | vote fired) against a label that runs to the barrier, so an
early close trades something never measured. Turning it on is a different
strategy, not a tightening of this one.

THE PIN. The live threshold now moves only when an era's weights become the
checkpoint, and freezes once g_ensDeployApproved. Every era still derives its own
rung - that is how the best one is found - but the rung that TRADES belongs to
the checkpoint, exactly as the weights do. Two reasons, one measured and one
structural: the per-era rung moves on 6-34% of steps (the live run flapped
SP500 15 -> 10 -> 15 within a minute of starting), and without the pin a later
era's rung could end up applied to an earlier era's deployed model. A ladder
restart releases the pin, since clearing the checkpoint clears what it pinned.
The era line now prints the rung its own numbers came from, so it stays honest
when that differs from the pinned one.

THE ATOMIC RENAME retried zero times. Six charts share the TrainPool and AltData
directories, so a publish regularly lands while a peer chart holds the
destination open and FileMove returns 5004 - 27 times in one day on the live
fleet. Nothing was lost (the temp keeps the new content, the old file stays
intact) but the row did not update until the next publish. Now four attempts at
25ms, on the FAILURE PATH ONLY - a successful rename never sleeps - and skipped
in the tester, where the contention cannot happen and Sleep would distort a pass.
A rescued retry is logged, so worsening contention is visible.

Retrain-neutral. Compiled clean; NOT yet run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:53:56 -04:00
AnimateDread
34f1e09372 feat(magic): assign the magic number once, then remember it
Expert_MagicNumber = 0 (the new default) means "draw one and write it down".
On first attach the EA picks a random magic in a distinctive band, persists it
to MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on
every later start. Unique without anyone typing it, and STABLE.

Stability is the whole point. The magic is how the EA recognises its own
positions - a fresh one per start would leave every open position invisible to
the scheduled close-all, the risk-budget flatten and the journal's MAE/MFE walk:
trades still running that no code would ever manage again. So the value is
persisted before it is ever used to trade.

Stored TERMINAL-LOCAL rather than in Common\Files\Warrior_EA, on purpose: that
folder is the one wiped for a retrain, and positions outlive retrains. It also
gives two terminals on the same symbol different magics, which a chart-identity
hash could not.

Fallbacks, both of which stay stable without a file:
  * tester/optimizer/forward use a magic derived from chart identity, so two
    identical passes cannot differ.
  * an unwritable file falls back to that same derived value, and says so.

Books occupy EVEN slots only, so one chart's short book (base+1) can never land
on another chart's long book.

WarriorOwnsMagic() now also recognises the legacy 2024/2025 pair permanently.
Without it, switching an existing chart to 0 while a position was open would
orphan that position. Every caller also matches the symbol, so claiming those
values can only reach positions on this EA's own chart.

Existing charts are untouched: MT5 stores inputs per chart, so the six live
charts keep the 2024 they already have and keep managing what they hold.

Compiled clean; NOT yet run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:35:58 -04:00
AnimateDread
17270ab308 feat(trade): two books per symbol, and delete the vote exit
Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA
an independent long book and short book on its symbol: at most one long and at
most one short, each opened on its own side's vote and each held to its own
barrier. On a netting account, or with the input off, the original
single-position path runs bit-for-bit unchanged and init says which one is live.

WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies
P(label agrees | vote fired) and the label runs to the barrier, so closing early
on a reversal makes the realised outcome stop being the labelled one - the
certified precision no longer describes what is traded. Opening the other side
acts on the new signal and leaves the old position's certification intact, and
costs no more than reversing: both pay the new side's spread, the difference is
only that the existing position runs on to a barrier already measured as
positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned,
along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an
arithmetically unreachable 101 (the stock default of 100 is reachable by a
weighted mean of values capped at 100).

Note the two books can never both fill from one signal: CheckOpenLong and
CheckOpenShort test opposite signs of the same m_direction, so at most one clears
per tick. A hedge only forms when a LATER opposite vote fires - which is what
keeps it from being a guaranteed-loss wash pair.

The mechanism is a SelectPosition() override keyed on the active book's magic;
every inherited close/trail path then operates on that book untouched. The long
book keeps Expert_MagicNumber, so no existing position, journal row or
risk-budget state file is re-addressed. Short book is +1.

Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(),
or the short book would have been invisible to the code that must reach it:
the scheduled close-all (positions and orders), the risk budget's emergency
flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT
gated on Allow_Hedging - turning the input off while a short-book position is
open would otherwise orphan it with nothing left to close it.

Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(),
which counts every position regardless of magic, so the second book is sized
inside what the first one left. Conservative for a hedged pair, which cannot
lose both stops - the safe direction.

Retrain-neutral: neither input is in BuildModelFingerprint() or
ComputeDbConfigFingerprint(). Compiled clean; NOT yet run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
AnimateDread
c6eb9085d5 feat(vote): derive the threshold instead of configuring it
Signal_ThresholdOpen becomes a seed. The era verdict now picks the HIGHEST
sweep rung whose vote still clears the whole deploy gate - coverage floor,
exact-binomial precision bar and two-sidedness together - computes the era's
verdict AT that rung, and publishes it to the live signal's m_threshold_open
so the bar the gate certifies is the bar the EA trades.

Measured on 619 era verdicts across all six live charts:

  * every era on every symbol had at least one rung clearing the full gate.
    At the fixed 25% the fleet was actually running, four of six symbols had
    none, ever. The threshold, not the models, was the blocker.
  * walk-forward (rung derived on era N, scored on era N+1): 10.2% coverage /
    31.8% precision, against an oracle re-picking on N+1 of 10.3% / 31.7%.
    Near-zero shrinkage - a measurement, not a fit. It holds because the
    binding constraint is COVERAGE, a near-deterministic step function of the
    vote distribution, not precision.
  * vs a fixed 15% (best global value): +0.6pp precision, 3.4pp less coverage.
    vs a fixed 20%: deployable on all six rather than four of six.

Selection on the highest PASSING rung, never on the best-precision rung - that
is a best-of-6 on a noisy statistic and this project has crowned noise that way
four times. The multiplicity that remains is paid for: nTried in
EnsembleSurvivesSelection is now eras x rungs. Costs nothing - all six charts
clear it by 6.5-12 sigma even forming z on effective rather than raw calls.

Also fixes, in the same path: the direction-policy gate is hoisted above the
per-rung tally so every rung is scored on the population the gate certifies.

Retrain-neutral: not in BuildModelFingerprint(), no .nnw re-keyed.
Compiled clean; NOT yet run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:02:58 -04:00
AnimateDread
51620fe9e0 fix(vote): Signal_ThresholdOpen 25 -> 15, from the sweep's own numbers
The quorum model that said 20 was wrong. It reasoned from the vote's
quantisation - 4 members at tier weight ~30, so 3-of-4 agreeing gives
22.5 and PCT_20 admits it - and simultaneous agreement turns out to be
rarer than a per-member coverage of ~27% implies. Measured on real rows
by the threshold sweep added in the previous commit:

  symbol  floor    15% cov/prec    20% cov/prec    25% cov/prec (active)
  EURUSD   6.7%    15.7 / 32.6     12.5 / 33.7      4.1 / 35.0
  SP500    6.9%     9.8 / 32.7      4.0 / 34.4 X    1.0 / 34.7 X
  USDCAD   7.2%    18.9 / 32.2     12.8 / 32.6      4.0 / 34.7 X
  XAUUSD   6.7%    12.8 / 29.2      4.4 / 33.9 X    1.0 / 26.5 X
  XTIUSD   6.9%    11.4 / 34.3      7.8 / 35.7      1.7 / 38.4 X

15 clears the coverage floor on every symbol; 20 fails SP500 and XAUUSD;
25 fails all of them. The precision surrendered is about 2pp, because
precision is nearly flat across these rungs while coverage moves 10-20x -
the high thresholds were buying almost nothing for the coverage they
cost. The rule this encodes is: take the cheapest rung whose coverage
clears the floor, not the best precision. Precision above the bar earns
nothing extra; coverage below the floor makes the era undeployable
regardless.

Not in BuildModelFingerprint(), so every trained .nnw survives.

DOES NOT MOVE THE RUNNING FLEET. MT5 stores input values per chart in
profiles\Charts\*\chart*.chr, so this only takes effect on a fresh
attach; the live charts have to be changed in each one's EA properties.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 04:24:37 -04:00
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
15827a6b77 refactor(trade-mgmt): remove all confidence-scaled trade management
Five modes went, all of them staking real risk on the model's confidence:
Intelligent entry (ENTRY_INTELLIGENT), stop (SL_INTELLIGENT), target
(TP_INTELLIGENT), trailing (CTrailingIntelligent) and lot size
(CMoneyIntelligent's quarter-Kelly). With them, the Confidence_Source
input and the CONFIDENCE_SOURCE enum, whose only job was choosing which
number those five read.

The reason is calibration, not correctness: the confidence magnitude is
known to be miscalibrated against the label prior, so every one of these
modes multiplied money by a quantity whose units were never established.
The DB arm had a second, independent defect - since the tester DB guard
(SignalDatabaseActive) it reads 0 in tester and optimizer but non-zero
live, so any backtest of CONF_DB/CONF_BLENDED could not reproduce live
trading. And what the DB produces is a filter-RANKING win rate, not a
per-trade win probability.

Both confidence numbers are still recorded per trade (aiConfidence /
dbConfidence) and still bucketed against outcome in TradeJournalReport.
Recording is what keeps the question answerable; acting on it was the
part with no evidence behind it. ConfidenceBridge.mqh now carries an
explicit telemetry-only rule at the top.

ENUM ORDINALS PINNED. Removing a member vacated a value in four enums at
once and MT5 does not validate an enum input replayed from a saved .set
or a stored optimization pass. TRAILING_STRATEGY and
MONEY_MANAGEMENT_STRATEGY now carry explicit values so the survivors keep
the numbers they were saved as, and ValidateBarrierInputs is widened into
ValidateTradeManagementInputs covering SL_Mode, TP_Mode,
Entry_Multiplier, TrailingStrategy and MM_STRATEGY. Without that gate a
chart saved with the Intelligent stop would feed SL_Mode = -1 into a
multiplier now used verbatim, placing the stop on the wrong side of entry.

RETRAIN-NEUTRAL: neither SL_Mode nor TP_Mode appears in
BuildModelFingerprint() or ComputeDbConfigFingerprint() since the
swing-pivot target replaced the barrier labels. No .nnw, .cfg or .db
re-keys. Also drops the now-dead g_TradeRewardRiskRatio bridge, the
CMoneyRiskBase::AdjustRiskAmount hook and the unsigned AIConfidence().

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 10:10:20 -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
ad5c2542ec perf(tester): skip the signal DB in tester/optimizer, drop ExportFeaturesOnly
Two removals of work that a backtest was paying for and never using.

1. SignalDatabaseActive() gates the signal DB off in tester/optimizer.

   A backtest opened the fingerprinted SQLite DB under FILE_COMMON - and so
   did every parallel optimization agent, against the same file, with the
   per-tick journal Update() behind them. Measured 2026-08-25 on a 12-agent
   SP500 H4 run: zero passes completed in 75 minutes.

   It bought nothing, for a reason specific to this EA's current shape: the
   DB's only effect on a trading decision is ApplyPatternWeight overriding a
   filter's module weight, and that is declined for any self-ranking filter
   (CExpertSignalCustom's !filter.SelfRanked() guard). The AI members
   self-rank once their tiers are measured, and the classic votes that DID
   consume the ranking are gone - so a tester run's DB was written and never
   read. Skipping it changes no decision.

   One predicate, not two inline guards: OnInit asks the question twice
   (InitDatabaseAndJournal, then VerifyDatabaseTransactionCycle) and a run
   where those disagreed would try to open a database it never initialised.
   The tester now takes journal.InitTrackingOnly(), so close detection,
   MAE/MFE and the expectancy-stop feed still run - only the SQLite half is
   dropped, and Update() already skipped its INSERT when there is no DB.

   Caveat recorded at the predicate: if a future filter consumes DB ranking
   WITHOUT self-ranking, this needs revisiting - a backtest would then stop
   reproducing live.

2. ExportFeaturesOnly and its two exporters are gone.

   Research-only CSV dumps (feature matrix + a hardcoded 8-symbol x 5-TF raw
   rates grid), superseded by the research/ python path that reads its own
   data. Removed the input, m_exportFeaturesOnly, the setter, both method
   declarations, ExportFeatureMatrix()/ExportRawRates() (111 lines in
   AutoTune.mqh), the OnTick early-return, and the ctor initialiser.

   The config-lock bypass it owned collapses to the plain tester test:
   `if(!inTesterOrOpt && !AcquireConfigLock())`. Shared helpers it called -
   ServableBars, EnsureBarCachesCapacity, ResizeBuffers, RefreshData - all
   have other callers and are untouched.

Compile-verified in _claude_stage: 0 errors, 0 warnings, identical to the
baseline taken before either edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 09:32:44 -04:00
AnimateDread
e035a3076d feat(pool): default cross-instrument training pooling on
Use_Training_Pool gates a fully-built, fully-wired mechanism
(Expert\Training\TrainingPool.mqh + the Add/Adopt/Publish call sites
already in Training.mqh) that shipped false. Nothing to build - the
writer/reader/atomic-file/compat-gate/age-gate/lookahead-purge were
all already there, measured +2.02pp of paired skill at H4 (research/
edge.py, 2026-08-24). Flipping the default is the whole change.

Compile: 0 errors, 0 warnings (stage).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 22:38:38 -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
8c945bf752 feat(target): swing is the default, and tau is measured, not chosen
- TrainingTarget defaults to TARGET_SWING.
- LogitAdjustTau input, preset enum and all plumbing deleted: tau is fixed
  at 1.0 (the full log-prior, Menon et al.'s consistent value); the
  delivered strength is capped to the head's usable logit range from the
  priors the prebuild measures. The CAPPED journal line is the step-1
  measurement. |LA💯BS becomes a frozen legacy fingerprint slot, so no
  existing model re-keys.
- The swing label measures its own resolution lag (idx - P2, the earliest
  bar P1 can be final on) into the overlap/SE machinery, capped at
  SWING_SCAN_CAP_BARS instead of a barrier horizon it does not have.
- The prebuild line is target-aware: both-won, timeout and horizon-lifespan
  fragments are barrier-walk facts and no longer decorate swing counts.

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 19:33:37 -04:00
AnimateDread
579e8b45ef feat(target): swing-pivot direction label, and drop the ADZigZag name
TARGET_SWING: the direction models learn which way the next CONFIRMED SWING
PIVOT lies from the current close. Geometry-free - the label owes nothing to a
stop, target or horizon - which is what lets trade management be tuned
separately instead of being baked into what the net learns.

SwingPivotDirectionLabel reuses the ZigZag pivot the horizon and leg-size
measurement already walk, so there is ONE notion of "pivot" in the codebase. It
walks forward in time and stops at m_swingConfirmationBars: a pivot nearer than
that is still repainting, so its label is not knowable yet and the bar stays
Neutral. That boundary is the whole lookahead control for this target.

TrainingTarget input is back (TARGET_BARRIER default, unchanged behaviour) with
TARGET_FRACTAL and TARGET_SWING beside it; |TGT:SWG1 joins the fingerprint so
switching trains a separate model rather than relabelling an existing one.

ADZigZag was renamed to ZigZag throughout (30 identifiers). It has loaded
MetaTrader's stock Examples\ZigZag at its stock defaults for some time - the
migration was done, only the name was left behind, and a name that says "AD"
about a stock indicator is exactly the legacy pointer this codebase should not
carry. No behaviour change: same #resource, same params.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 18:26:25 -04:00
AnimateDread
9883b209c7 feat(deploy): ship on positive EXPECTANCY, and let the chart draw before convergence
TWO CHANGES, both of which turn a permanent "nothing happens" into a decision.

1. THE DEPLOY GATE ASKS THE WRONG QUESTION. tradeable required the win rate to
   clear chance by EDGE_MIN_SIGMAS - "can I PROVE an edge exists" from one OOS
   window. On H4 that asks ~66% against a market supplying ~53%, so it is
   unreachable by construction and no run has ever deployed through it.

   SDeployVerdict now also carries the economics of the geometry actually being
   traded - cost-adjusted break-even and reward:risk, both from the new
   CostAdjustedGeometry() so a spread convention cannot be applied to one and
   missed on the other - and derives

       E[R] = (p - p*) * (1 + RR)

   which is exactly zero at break-even by construction, so "profitable" and
   "beats break-even" can never disagree. Under DeployOnExpectancy (new input,
   default ON) tradeable becomes E[R] > 0 and selectionScore ranks eras by
   expectancy instead of precision. Coverage and both-sides-live still gate
   both: an expectancy over a handful of one-sided calls is not tradeable.

   The struct also publishes scoreSE - the SE of selectionScore IN THE SCORE'S
   OWN UNITS - because the score changes units with the objective (win-rate
   points vs R). Both plateau bands now read it instead of precSE, which was
   right for one objective and dimensionally wrong for the other.

   Setting DeployOnExpectancy=false restores the previous behaviour exactly.

2. THE FILTERED VIEW COULD NOT DRAW WHILE ANY MODEL WAS TRAINING.
   HistoricalNetVote built its divisor from VoteCapableWeight(), which answers
   "may this member move real money" and returns 0.0 for an AI member until the
   whole run converges. So the reconstruction's divisor was zero on EVERY bar,
   every bar was skipped as "nobody looked", and the chart drew nothing at all -
   for the entire training run, which before the plateau noise band was forever.
   Reported as "no signals drawn since the refactor".

   New ReconstructionWeight(): the same weight WITHOUT the converged-run
   requirement, overridden on the AI member to ModuleWeight() gated on
   SelfRanked() only. The overlay is a picture of what the vote WOULD have
   shown, which a mid-training model can answer - the chart HUD already says so
   with its "(trn)" marker. Live Direction() still uses VoteCapableWeight(), so
   no untrained model gains a say in an order.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 16:15:06 -04:00
AnimateDread
906c60e227 feat(training): wire TrainingPool into pass 2 - peer rows contribute gradient only
Peer rows join m_isTrainQueue as NEGATIVE sentinels before the shuffle, so they interleave with
this chart's samples instead of training in a block at one end. A block would be a curriculum:
whatever the optimizer saw last would decide where it landed.

TrainPoolStep is a separate path on purpose. Everything in pass 2's local branch after the
forward pass reaches for something indexed by a LOCAL bar - m_labelCache, m_winLongCache, the
excursion target, the arrow cache, m_Time - and a peer row has none of those. Sharing the path
would mean inventing values for all of them, which is how another instrument's outcomes end up
inside m_cumIsCorrect and the operating point gets fitted to them. The IS-vs-OOS gap is read as
THE overfitting signal, so polluting the IS side would not crash anything; it would just quietly
stop meaning what it says.

The purge key reuses the label walk's own two bounds - the horizon and NextScheduledCloseAll -
rather than approximating with a bar offset. A second horizon model here would drift from the
real one, and this project already measured that the close-all, not the nominal horizon, is what
actually terminates labels. Cutoff is the OLDEST OOS BAR'S TIME, in wall clock, because bar
indices cannot be compared across instruments that each have their own calendar.

Contribution happens while the window is still in TempData and before the forward pass
overwrites it, and is gated to direction models: the meta head trains a different target on a
wider input, which the fingerprint gate alone would NOT catch, since a meta model's fingerprint
matches its own peers perfectly well.

Use_Training_Pool ships false and does nothing until a second chart runs a matching fingerprint.

Compile-verified against a BASELINE of the same tree without the wiring: both produce 12
errors, all error 313 invalid-resource-path from #resource directives that cannot resolve in a
headless staged build (stock Controls res\*.bmp, plus the pre-existing Network.cl). Code errors
0, warnings 0, identical to baseline. Staging copy and junctions removed; the live .ex5 was
never touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 14:00:21 -04:00
AnimateDread
6974fb03af ditch(features): remove the eight dead feature groups from the input matrix
RSI, MACD, Ichimoku and the five AD/Wyckoff indicators (CumulativeDelta,
ShorteningOfThrust, WyckoffEventStream, WyckoffFailedStructure,
WyckoffSignificantBarInversion). All eight inputs shipped false and each carries a
closed verdict: the three oscillators are the same patterns that measured at chance
as entries, and the Wyckoff family returned zero out-of-sample on five independent
instruments - which is what closed the context score.

RETRAIN-NEUTRAL, and this one is worth stating precisely because the change looks
larger than it is. Every removed group contributed `flag ? N : 0` to the input
width, and every flag was false, so the width was ALREADY zero for all eight: no
.nnw's input layer changes. On the fingerprints, UseRSI and the five AD flags were
hashed unconditionally and become literal 0 legacy slots (the convention the
m_focalGamma slot above them already uses); UseMACD/UseIchimoku were appended only
when enabled, so their segments simply never appear - byte-identical to every
fingerprint ever produced, since neither ever shipped on.

CADIndicatorTuner IS DELIBERATELY NOT SHRUNK. Its flat parameter array is persisted
inside every .nnw, and Unflatten() rejects a size mismatch by falling back to
constructor defaults - so dropping the dead fields would silently revert the tuned
MA period of every model on disk while keeping its trained weights. That is the
feature/weight mismatch this project has already paid for twice, and it is not
worth 200 lines. AD_TUNE_PARAM_COUNT stays 42, the dead slots are still written and
read, and AutoTune's ParamOwner gate now matches only owner 5 (MA) so nothing
searches them. The class comment says all of this at the declaration.

Also renamed ReInitADIndicators -> ReInitTunableIndicators: it rebuilds exactly one
indicator now, and a name saying "AD" for the MA handle is the kind of stale label
that gets believed later. Its release-AFTER-recreate ordering is untouched - that
is a documented fix, not bookkeeping.

Compile-verified in the stage copy: 0 errors, 0 warnings, against the same 0/0
baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:21:03 -04:00
AnimateDread
ed919194a4 ditch(signals): remove the four classic votes - all 26 patterns measured at chance
research/classic.py transcribed all 26 shipped vote patterns (MA 4, RSI 4, MACD 6,
Ichimoku 12) with their constructor weights and tested them as entries on 178k-bar
histories, four instruments x three barrier geometries. Nothing separated from
chance - not one pattern, not the averaged vote at any threshold 10-70, not a
2/3/4-module quorum, not event-plus-confirmation. Residual E[R] everywhere was
-0.01 to -0.08 R, which is approximately the spread. The +4 sigma reading that had
once justified the set was two bars of lookahead: closing it took MACD_p4 on EURUSD
from +5.05pp to -0.02pp.

All four inputs have shipped false ever since, so this deletes dormant code rather
than changing behaviour.

RETRAIN-NEUTRAL, deliberately. EnableMA and EnableRSI were hashed UNCONDITIONALLY
into the DB config fingerprint, so they become literal 0 legacy slots - the same
treatment the ind_Periods slot two lines above already uses, and every existing
database keeps its key. EnableMACD/EnableIchimoku were appended only when enabled,
so with both gone the segment simply never appears, which is byte-identical to
today. No .nnw or .db is orphaned.

WHAT THIS COSTS, STATED PLAINLY: these four were CSignalMETA's only wired candidate
sources, so the on-chart ladder sweep (BuildCorpusBySweep) now has nothing to sweep
and a META chart is no longer self-contained. That is survivable rather than fatal
because MetaPrepareEra already falls back to CMetaCorpus::LoadLargestOnDisk, and its
own comment names this exact case - "charts whose classic filters are disabled".
Use_MetaLabeling ships false regardless. SignalMETA.mqh is otherwise UNTOUCHED, and
its 26-slot one-hot stays at 26: a tester-built corpus on disk still encodes those
pattern ids, and narrowing the descriptor would invalidate every stored corpus.

  Signals/SignalMA.mqh SignalRSI.mqh SignalMACD.mqh SignalIchimoku.mqh   deleted
  Signals/OscillatorDivergence.mqh   deleted - RSI and MACD were its only users
  Classic_Shift                      deleted - the four votes were its only readers

Compile-verified in the stage copy: 0 errors, 0 warnings, against a 0/0 baseline
taken before any edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:08:43 -04:00
AnimateDread
899bba1c51 refactor(risk): CRiskBudget::SaveState routes through AtomicFile.mqh
Same doctrine as AltDataFetch's SaveRaw/RebuildFeatures (a8e6572): a plain
FileOpen(FILE_WRITE) truncates on open, and this file carries the daily/total halt
LATCHES - a crash mid-write left a truncated file that LoadState()'s magic/size guard
rejects, silently re-seeding from live equity and clearing a compliance halt. Staged
through a temp file + atomic rename; each FileWrite* call's return value is now
checked so a write failure sets ok=false instead of silently corrupting the state
alongside a successful-looking FileClose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 04:39:28 -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
909f2385bc refactor(build): retire the WARRIOR_EXPORT_FEATURES compile flag
Last surviving compile-time feature switch in the codebase - the same pattern
already killed for the MARKET build and DirectML tier (02766b5): one build,
configured at runtime like every other module (inputs + getters/setters, set
in ConfigureAISignal during OnInit), not a second code path that only existed
if someone remembered to define a macro before compiling.

Replaced with `input bool ExportFeaturesOnly = false` (Variables/Inputs.mqh)
and a plain m_exportFeaturesOnly member + setter, matching AutoTuneIndicators'
exact shape. Four call sites converted from #ifdef to a runtime read of the
same variable:
  - Warrior_EA.mq5 OnTick() - reads the input directly (this check has to
    stand before any per-signal object exists)
  - Topology.mqh's config-lock skip and ExportFeatureMatrix() call - read
    m_exportFeaturesOnly, now set by ConfigureAISignal before InitIndicators()
    runs (same init-order guarantee AutoTuneIndicators already relies on)
  - ExportFeatureMatrix()/ExportRawRates() declarations - always compiled now,
    called conditionally instead of not existing as symbols

No change to what the flag does when off (the state of every build that
exists today, since the macro was never defined anywhere in-repo) or when on;
only how it's set. Verified: WARRIOR_EXPORT_FEATURES fully gone from every
#ifdef/#endif in the tree; brace and ifdef/endif counts balance in every
touched file; ConfigureAISignal runs before StepInitIndicators in OnInit's
linear init chain, so the flag reaches InitNeuralNetwork() in time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 19:14:55 -04:00
AnimateDread
02766b590b chore(build): delete the MQL5 Market build - there is only one build now
Market rule IV forbids DLL calls, and the DLL compute tier plus the
WebRequest alt-data fetch are what make this bot work. If it is ever sold
it goes through its own channel with the DLLs intact, so a no-DLL build has
nothing left to be for (operator, 2026-08-23).

Removed:
- Warrior_EA.mq5      the //#define toggle and the #resource block behind it
- Variables/Inputs.mqh    two #ifdef pairs whose market arms forced every
                          Use_* NN input and Meta_ExportDataset to false
- AI/NeuronDirectML.mqh   the 62-line market stand-in CDirectMLMy whose every
                          method returned false so the chain fell through to
                          plain MQL5
- IndicatorResources.mqh  WARRIOR_CI(name), which had already collapsed to
                          (name) - a switch with one position is not a switch.
                          Its five call sites in Features.mqh name the
                          indicators directly now.

Behaviour is the private build's, unchanged: bare indicator names loaded
from <MQL5>\Indicators\, all four NN votes defaulting on, dataset export on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 16:40:27 -04:00
AnimateDread
a0f9cfa3e0 refactor(baselines): the first real module - a class, not an #included partial
Baselines was 951 lines of CExpertSignalAIBase method bodies in a file
that only looked like a module. It is now CBaselineComparator: a class
the signal OWNS, which reads a CTrainingDataView and prints. It does
not name the signal anywhere in its code.

What the seam forced out into the open:

- Thirty-odd ArraySize() bounds tests, each carried by its caller, are
  now one test per accessor next to the data. The two `hasValueN` and
  one `arrowN` locals are gone with them.
- The -2.0 "never scored" sentinel on the arrow cache was tested at the
  call site. It is now inside DataDirectionalCall, where it cannot be
  read as a small confidence.
- DoubleToSignal needs m_outputNeuronsCount, so a raw double could not
  be turned into a side by any reader. The view answers
  DirectionalCall(bar, isBuy, magnitude) instead - the conversion
  happens where the head width lives, and the module no longer needs
  ENUM_SIGNAL at all.
- m_baselineDone was a latch on the signal for a decision only this
  module makes. It is m_done, private, where it belongs.

Correction to my own earlier claim: I said Baselines had nine exclusive
members "polluting the signal class". It had none. m_x, m_f, m_ngrad,
m_AvgCE and the rest are FIELDS OF ALGLIB REPORT OBJECTS (state.m_x,
mrep.m_AvgCE) that my `\bm_\w+` scan matched after the dot. The module
needs no private state but its view pointer and that latch - which is
why it came out this cleanly.

The include sits below the g_ens* vote globals and the Alglib headers
it reads, because unlike the AIBase\*.mqh partials this is a real class
declaration compiled where it stands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 12:00:18 -04:00
AnimateDread
2ea753cb40 diag(baselines): turn the forest/linear comparison on, and deflate its SE
Run_Alglib_Baselines = true. It answers the question the campaign is
actually stuck on - is a flat result the architecture or the matrix - by
fitting an Alglib forest, MLP and OLS on the net's OWN windows, labels,
split and gate arithmetic. Nothing trades on it and no model is saved.

Not in BuildModelFingerprint, so this does NOT re-key anything: no
retrain, it can go out with the next compile. One-shot per chart
(m_baselineDone latches first, and g_ensembleChartBaselinesDone stops
the other three members re-measuring the identical fit), bounded by
BASELINE_BUDGET_MS = 45 s, which it spends frozen because the EA is
single threaded - it says so when it stops early.

FIXED WHILE TURNING IT ON: ReportBaselineModel sized its SE on RAW call
count while the NN's own DEPLOY BAR deflates by the mean label lifespan.
The whole point of this line is a like-for-like comparison against the
net on the same windows, and it was holding the baseline to the more
permissive standard - which is how the last forest run's +8.4pp read as
significant when it was +1.0 SE after deflation. Now uses
EffectiveSampleSize and prints how many independent calls that leaves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 10:47:25 -04:00
AnimateDread
e372ce60a9 fix(vote): "0 fired" on USDJPY meant the threshold is above the highest vote the ensemble can cast
USDJPY has taken no trades in 66 eras and its highest vote ever seen is 13
against a 25% threshold. Not a bug and not undertrained models - arithmetic.

Direction() divides the summed contributions by the CAPABLE weight, so a
unanimous vote returns the capability-weighted mean of the tier weights, which
is roughly the pooled holdout win rate. USDJPY's members pool at 15.6-19.4%
(its label base rate is 14.0% against SP500's 25.4%, because its derived
geometry resolves far fewer bars directionally: Buy 10.3% Sell 11.2% Neutral
78.6%). So the ensemble's CEILING is ~19 and the threshold is 25. Coverage can
never leave 0, and no amount of training moves it, because the ceiling IS the
win rate.

The report now computes that ceiling - every member voting at its best tier -
and says so when the threshold sits above it, instead of printing "0 fired at
vote>=25%" which reads as "the models are unsure".

Same class as the excursion head's disjoint gate (ee4d459) and the reason
ReportDetectability exists: a configuration that cannot reach its own bar has
to say that, not report a number that looks like evidence.

Also: VerboseMode and Run_Alglib_Baselines back to false. The per-era cadence
was for reading the horizon break-even and the excursion sigmas; both are
settled, and TrainLogDue still prints them every 25 eras. The baselines cost a
45 s single-threaded freeze at every attach and their forest row turned out to
be one deterministic observation that does not survive overlap deflation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 08:16:11 -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
20b7d9967e chore(test-run): defaults for the horizon-break-even measurement, and give the replay line a cadence
Two diagnostics on, neither in the fingerprint, so no model is re-keyed and the run resumes:

  VerboseMode          false -> true   per-era journal instead of every 25th
  Run_Alglib_Baselines false -> true   verifies 3d81fed and finally reaches the OOS scoring

And the fix that makes the run worth doing. ReportExitPolicyDivergence printed ONCE per run while
vote exits are off, justified by "then it is arithmetically guaranteed to agree with the
certificate". That is the claim 4070c5c disproved: the certificate scores win rate against a
GEOMETRIC break-even while this line replays the real payoff including horizon timeouts, and the
two disagree by ~6.5pp. The timeout share is a MEASURED quantity that moves with geometry and
volatility - one print per run is the wrong cadence for it. Now on TrainLogDue(), the same density
as every other era line, with m_exitReplayReported still guaranteeing at least one.

What to read: BREAK-EVEN geometric X% vs horizon-aware Y%, and the timeout share and mean R beside
it. Those two numbers decide whether LiveMetaGate and BarrierMinReachPct get repointed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 20:13:03 -04:00
AnimateDread
2ba0f348c0 feat(ui): thresholds pick from a dropdown, and the finder arrows are back beside the level lines
Two UX changes the operator asked for.

THRESHOLDS. Signal_ThresholdOpen/Close were raw ints with the legal range
written in the label ("[0...100, 101 = never]") - the one input style this
codebase converted away from everywhere else. Open now takes the existing
PERCENTAGE_PRESETS, whose comment already declared itself to be "Signal_
ThresholdOpen's scale" but was never wired to it; Close takes a new
SIGNAL_CLOSE_PRESETS carrying the same rungs plus CLOSE_DISABLED = 101, which
is why it cannot just reuse the other enum. Member names are prefixed because
MQL5 enum members share ONE flat namespace - a bare PCT_25 in the second enum
would silently resolve to the first one's, warning only. Values are unchanged,
so existing .set files keep their settings. Both call sites now cast
explicitly at the CExpertSignal boundary rather than leaning on an implicit
enum-to-int conversion that only warns.

ARROWS. 2026-08-19 replaced the low/high arrows WITH trigger-price lines; that
was a swap where it should have been an addition, and it cost the zoomed-out
view. A mark is now both objects: the line is the precise entry/exit level,
the arrow off the candle's extreme is the finder that says there is something
here to zoom into. The arrow's name is the line's plus a suffix, so it stays
inside SIG_ARROW_PREFIX and every prefix-scoped purge already reaches it.

The two type-filtered sweeps had to widen or they would clear one half and
leave the other: the Hide/Show visibility loop and the pre-rescan scoped
delete both walked OBJ_TREND only. Both are typed-blind and prefix-scoped now
- the same widening this file's 2026-08-09 note describes, for the same reason
it gives. Deletes go through one WarriorDeleteSignalMark() so an arrow cannot
outlive the line it belongs to, and the sidecar deliberately still records one
row per mark off the line (the half carrying the price), with the restore
redrawing the pair.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 13:50:33 -04:00
AnimateDread
21263cfd01 refactor(comments): Inputs.mqh and the AI base header move to stdlib comment style
Box header per function/class, section banners, and 1-3 line notes for
variables - not the paragraphs that had accumulated. Both files verified
mechanically rather than by eye: every declaration in Inputs.mqh (159 of
them) and every non-comment line in ExpertSignalAIBase.mqh is byte-
identical before and after.

Variables/Inputs.mqh   881 -> 355 lines (704 -> 179 comment lines)
Expert/ExpertSignalAIBase.mqh  4795 -> 4388, first ~800 lines converted

What was cut is narration - the history of what a constant used to be,
paragraphs restating the line below them, and commentary about earlier
versions of the comment itself. What was kept is every constant, every
measured number, and the traps worth a warning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 09:49:33 -04:00
AnimateDread
c171eace78 feat(schedule): close-all defaults to market close minus 5 minutes
targetHour CH_23 -> CH_MARKET_CLOSE, targetMinutes CM_45 -> CM_5.

A fixed 23:45 was a guess at one broker's server offset. It is silently
wrong on any other feed, and wrong twice a year on the same one.
"Market close" resolves per day from the symbol's own session table and
backs off the minute value, so it is correct on every symbol and on both
sides of a DST switch with no number for anyone to maintain.

THIS RE-KEYS EVERY FINGERPRINT and forces a fresh run from era 0. That
is correct rather than collateral: the close-all schedule is where the
label walk stops treating a trade as holdable
(Expert\AIBase\Labels.mqh), so changing it changes every label, and
models trained against the 23:45 barrier are confounded for this one.
The CUT: field in BuildModelFingerprint() catches it automatically -
verified present rather than assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 08:39:26 -04:00
AnimateDread
4346dd3c24 refactor(stdlib): the vote thresholds are ints on the library's scale, not "confidence %"
The MECHANISM was already stdlib and is untouched: ThresholdOpen() ->
m_threshold_open, tested as `m_direction >= m_threshold_open` exactly as
CExpertSignal does it. What was wrong was the presentation. Both inputs
were preset ENUMS labelled "Min confidence to open/close (%)", which
names the wrong quantity - m_direction is a WEIGHTED MEAN OF PATTERN
WEIGHTS, not a probability, and nothing in this path is a confidence.

They are now plain ints named the way the MQL5 wizard names them:

  input int Signal_ThresholdOpen  = 25;   // [0...100]
  input int Signal_ThresholdClose = 101;  // [0...100, 101 = never]

Values are exactly what shipped, so behaviour is unchanged. 101 rather
than the library's default of 100 for close: a weighted mean of pattern
weights cannot REACH 101, which is how the shipped config disables the
vote exit, and quietly lowering it to 100 would re-arm a live exit route
as a side effect of a naming change.

VOTE_CLOSE_PRESETS is deleted (its only user is gone). PERCENTAGE_PRESETS
stays - MinRecall genuinely is a percentage.

** ACTION NEEDED ON DEPLOYED CHARTS: the inputs are RENAMED, so saved
.set files no longer match and charts fall back to the defaults above.
Those defaults are the current shipped values, so a chart on 25/Disabled
needs nothing; a tuned one does.

Comment cleanup in the same pass, and this part was not cosmetic - three
blocks documented mechanisms that no longer exist:
- the AI early-exit route (deleted in 38a12a2) described as live and
  still firing every bar;
- the m_lastNonNeutralSignal alternation gate (removed 2026-08-01)
  described as consuming the AI's vote;
- 16 lines of VOTE_CLOSE_PRESETS documentation orphaned by that enum's
  deletion, ending with "see that enum's note directly above" pointing
  at nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 08:39:01 -04:00
AnimateDread
ce2c324527 feat(baselines): Alglib forest + linear on the NN's own matrix
Every direction verdict so far was measured through one architecture
family, so "flat" has two readings that no topology tuning can separate:
the net is the wrong learner, or the matrix carries no directional
information.

Two learners with completely different inductive biases - Alglib's
random decision forest and an ordinary least-squares fit - now train on
the SAME feature windows (BuildFeatureWindow, the net's own function, so
there is no second feature implementation to drift), the SAME labels,
the SAME IS/OOS split with both purges, and are scored through the SAME
precision-against-always-one-direction comparison and the same Sidak
family-wise arithmetic the deploy gate uses. If both also land at
chance, the matrix is the limit.

Deliberate choices, each of which could have made the comparison a
different question wearing this one's name:
- LRBuild, not LRBuildZ: the intercept absorbs the class imbalance, and
  a baseline handicapped by a forced zero intercept would flatter the
  net for the wrong reason.
- Raw call counts in the SE, matching the live gate's known-permissive
  test rather than correcting it here - both sides must face the same
  bar.
- No threshold sweep on the linear fit: a threshold fitted on the slice
  being scored is the calibration leak the purged band exists to avoid.
- Uniform stride when a cap bites, not the newest N rows, so a score
  difference cannot be a regime difference. What was dropped is logged.

Ships off (Run_Alglib_Baselines = false): it is a measurement, not a
trading feature, nothing trades on the answer and no model is saved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 23:45:03 -04:00
AnimateDread
38a12a240b refactor(kiss): drop the AI sub-vote early-exit route; certified == traded
First of the AI vote layers to go. CheckClosePosition had two exit routes:
the stock blended vote, and an AI-only one reading the AI members' sub-vote
undiluted. The second existed because an AI reversal averaged in with the
classic filters could be diluted below the threshold before it could close
a position.

It is gone, and with it m_lastAiVote and the aiResult/aiWeightSum pair
Direction() carried to feed it.

This CLOSES the certified-vs-traded gap rather than widening it. The
deploy gate certifies a win rate measured on hold-to-resolution outcomes,
and CheckClosePosition already gated the blended route off whenever an AI
model's derived geometry was on the order - so the AI route was the only
vote exit an AI-certified trade could take, and the exit replay existed to
reproduce it. With it removed, an AI-certified position holds to its
barrier by construction instead of by reconstruction, so Warrior_EA.mq5
now pushes ExitPolicy(0.0, true) unconditionally. Previously it forwarded
Min_Vote_Close and relied on Disabled arriving as 1.01 to switch the
simulated exit off by arithmetic - correct at the shipped default, and one
input change away from the simulation and the live path describing
different games.

Min_Vote_Close keeps its meaning for the classic route and is now
documented as inert wherever an AI certificate governs, rather than
appearing to drive an exit it can no longer reach.

Comment debt cleared while here: a tombstone block for m_ai_exit_threshold
(a member deleted 2026-08-18) still sat in the header, and four sites still
named LiveSignedConfidence's "two consumers" - it had one, the intelligent
trailing stop, since that same date.

NOT touched, and deliberately: NMS declustering is NOT a quality layer. It
gates the live signal at Inference.mqh:226 (NmsLiveAccept), and the
undeclustered population is ~8x what the EA trades. Removing it would
multiply live position count, not simplify a scoring path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:27:28 -04:00
AnimateDread
61c0d19ca9 feat(indicators): run the built-in iMA and MetaTrader's ZigZag; add a classic-vote shift
MA: CustomIndicators\ADMovingAverage is replaced by the built-in iMA (CiMA) on
both consumers - the classic vote and the NN MA input feature. This drops the
five advanced types ALMA/DEMA/ZLEMA/T3/Kalman, which have no iMA equivalent;
MA_TYPE_PRESETS is now ENUM_MA_METHOD's own codes and the tuner searches all
four. It also removes a documented failure mode: a custom indicator's depth is
bounded by TERMINAL_MAXBARS, and m_MA was the one whose feature block REJECTS
the bar on a short read - the "feature 25 fails on every bar" incident of
2026-08-17. A built-in is served at any depth.

MIGRATION. SMA moves from code 5 to 0, so persisted type codes change meaning.
SanitizeMaType() is the single validity rule; TunedPeriods records now carry a
version field and a v1 record remaps 5..8 -> 0..3, falling back to SMA for a
stored advanced type (unrecoverable - old 0..4 are indistinguishable from valid
new codes). Existing .nnw files re-key on their own, because MA_Type is hashed
into the topology fingerprint, so models retrain rather than silently running
on different MA values. EXPECT A FULL RETRAIN.

ZigZag: ADZigZag was a byte-identical rename of MetaQuotes' Examples\ZigZag -
verified by normalising identifiers and stripping comments, 233 significant
lines each with only renamed symbols differing. It now loads the stock one, so
nothing is bundled and MetaQuotes' fixes arrive without a rebuild here. Both
#resource entries are gone.

Classic_Shift: a new input, the BAR the four classic votes evaluate on (0 =
forming, 1 = last closed, default 1). One implementation on CExpertSignalCustom,
inherited by all four rather than repeated per module. Defaults to a sentinel
meaning "unset", so the AI signals and the aggregate keep the stock every_tick
rule and their feature/label alignment is untouched. The META corpus sweep still
takes precedence. CExpertBase::StartIndex turns out to be virtual, so this is a
real override, not the name-hiding the old comment claimed.

Not compiled - MetaEditor compile pending.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:09:58 -04:00
AnimateDread
9a0d063da4 fix(inputs): the Neural Networks group header was singular
Typo, and more wrong than it was: the group now holds four independent NN toggles rather
than one architecture selector.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 14:23:37 -04:00
AnimateDread
70eaa8a498 feat(direction): Intelligent becomes the shipped default for Trade direction
User request after the first ensemble run under the per-NN build: the drift verdict was
measured and printed every era while the input sat at BOTH, so it gated nothing - the
worst of both, because an authoritative-looking log line described a policy that was not
in force.

Safe as a default: the verdict fails open to BOTH (it drops a side only when the gap
clears 2 combined SEs on the overlap-deflated sample AND the weaker side sits below
cost-adjusted break-even), so an instrument with no measurable drift behaves exactly as
before. Verified it appears in no fingerprint or DB key - trade policy, not label
definition - so this changes no model identity and resets no training.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 14:02:56 -04:00
AnimateDread
f64e0f8b67 feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate
User design (2026-08-19): 'remove the enum menu that selects neural networks... individual
inputs for every NN just like classic signals... the META NN should be integrated into the
voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.'

- AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/
  Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to
  any subset because the consensus divisor is the enabled capable weight. Two or more
  enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so
  existing weight files keep loading); one = the old solo preset; none = classic-only.
- Use_MetaLabeling un-couples META from the direction NNs (the old selector made them
  mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry
  (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR;
  pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and
  vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly.
- COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the
  meta target - solo-only until today, a trained META would otherwise sit in the consensus
  divisor as a permanent abstainer and shrink every vote by its module weight.
- CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same
  g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own
  time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K
  unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class,
  calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not
  model the veto - the standing solo-gate caveat, documented at the input.
- DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType;
  DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member
  subsets get 100+bitmask, outside the legacy range) so no existing database re-keys.
  filterID becomes the enabled roster via one EnabledNNSummary().
- HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the
  armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not
  only when an entry happens to be proposed.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
AnimateDread
1a46dfdad9 feat(sessions): market-hours entry gate + "Market close" close-all option, both live from the symbol's session table
Two user requests, one authority: SymbolInfoSessionTrade, read fresh on
every call so DST and per-symbol schedule changes track themselves.

- WarriorMarketOpenNow(): CheckOpenPosition refuses entries outside the
  symbol's trading sessions (Sunday reopen, index CFDs' daily breaks) -
  a vote can no longer fire into a closed book and collect a broker
  error. ENTRIES ONLY: exits, SL/TP and the scheduled close-all stay
  unguarded - closing risk must never be blocked by a session boundary.
- CH_MARKET_CLOSE = 24 (appended, .set-safe): the close-all fires
  "Close-all minute" minutes before that day's LAST session close.
  Friday + Market close + xxH05 = flatten 5 minutes before Friday's
  actual close. Resolved identically in three places: the live executor
  (CExpertCustom::OnTick), the label walk's vertical barrier
  (NextScheduledCloseAll - the symbol's CURRENT table stands in for
  history; MT5 keeps none, and a fixed hour is wrong by more), and the
  fingerprint (the |CUT: token already carries hour=24, so switching to
  the dynamic mode re-keys the model exactly like any schedule change).

Training itself is deliberately NOT gated on market hours: weekend
compute is free and labels only ever exist on real bars - what the
session table gates is order placement and, via the close-all barrier,
what the labels may count as holdable.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 11:34:27 -04:00
AnimateDread
348492fb3b feat(direction): INTELLIGENT trade direction - the measured drift picks the side(s)
SQX EdgeFinder precedent (user request): adjust for the drift instead of
fighting it. The 2026-08-19 telemetry found the models leaning SHORT
(Buy recall 21% vs Sell 40%) against a long-favored market (always-long
34.3% vs always-short 29.5% at the adopted geometry).

TRADING_DIRECTION gains INTELLIGENT = 3 (appended, explicit value,
.set-safe). It resolves at runtime from the label cache's per-side win
rates - the Buy/Sell shares ARE the win rates of taking every bar
long/short at the REAL stop/target with spread charged. A side is
dropped only when BOTH hold: the drift gap clears 2 combined SEs on the
overlap-deflated effective sample (EffectiveSampleSize - labels overlap
~18x), AND the weaker side sits below cost-adjusted break-even (a side
that still clears costs is kept; drift tilt alone is not a reason to
refuse a profitable side). Fails open to BOTH: unmeasured, tiny
effective n (<30), insignificant gap, or classic-only charts (no label
cache).

One resolution point - WarriorEffectiveDirection() - feeds all three
gates so they cannot drift apart: CheckOpenLong/Short (live entries),
the filtered-view sweep (a blocked side falls into the delete branch,
mirroring live), and the vote HUD's "-> TRADE" verdict. The verdict
re-derives at every label-cache rebuild, prints only on change, and is
computed even when the input is not Intelligent (marked informational).

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 11:10:05 -04:00
AnimateDread
f30e342a1d feat(logs): throttle the settled per-era diagnostics - measured 22MB/9.5h of confirmed-working systems
Measured from the journal (2026-08-19): the era deep-dive line (~2KB) plus
the excursion verdict, tier re-rank, calibration move, barrier hold and
selection-regressed note each printed EVERY era for EVERY member - ~940
eras/member/day - long after the systems they watch were confirmed
working. Yesterday's file was 1.3GB (70% of it the news-filter calendar
spam the sweep fix already removed).

VerboseMode returns as an INPUT (demoted 2026-08-01 for the marketplace;
that track is dead since the 2026-08-16 pivot) and gains a second job:
false throttles each settled per-era print to eras 0-3 plus every
TRAIN_LOG_EVERY_ERAS-th (25 ~= one deep-dive per ~15min per member);
true restores the per-era firehose, flippable live.

Never throttled: anything that marks a CHANGE - new bests, restores +
eta decays, plateau stage transitions, deploy approvals, warnings,
errors, the label-cache/adoption one-shots, and the combined-vote gate
line (the active system's primary telemetry, still every era).

Semantic fixes over blanket gating:
- barrier hold now ARMS silently and prints only when the hold outlasts
  the 2-min report interval - a brief hold every era is the design, the
  long hold is the watchdog case the line exists for;
- the ensemble deploy REFUSAL prints immediately when its reason
  changes (that is a finding), on cadence when unchanged;
- the filtered-view census prints when its RESULT moves (drawn count,
  or strongest vote by >=2pp) and at least every 10th sweep.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 09:36:15 -04:00
AnimateDread
c85bd9f516 feat(inputs): 5-point vote-threshold steps below 50 - the consensus rungs sit between the old 10s
Under CONSENSUS arithmetic the vote is quantized by agreement: with four
members at pooled tier weights ~29, unanimity reads ~29, 3-of-4 ~22,
2-of-4 ~14.5. The 10-point dropdown straddled every one of those rungs -
20 admitted 3-of-4, 30 admitted nothing - so the thresholds an operator
actually wants (between rungs, e.g. 25 = "unanimity or a top-tier 3-of-4")
did not exist. PERCENTAGE_PRESETS and VOTE_CLOSE_PRESETS both gain
5/15/25/35/45; members are only ever ADDED (explicit values, .set-safe),
never removed - MT5 does not validate saved enum values.

Min_Vote_Open default 40 -> 25: the 40 was priced under union arithmetic
and now sits above the unanimity ceiling (~29), i.e. a fresh attach would
silently never trade - the same fired-on-0-bars defect the 50 -> 40 move
fixed once already.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 08:25:49 -04:00
AnimateDread
8ab8cf6b92 feat(chart): on-chart vote readout, and Min_Vote_Open 50 -> 40
THRESHOLD. 40 is a measured correction, not a preference. Once
RankTiersFromOos() replaced the designed tier priors with each model's real
held-out win rate, the vote converges on that win rate - logged 2026-08-18 as
pooled 23-36% across four members on three symbols - so a 50% bar could not be
reached by anything on offer and the ensemble gate fired on 0 of 4,865 OOS
bars. 40 clears the ~34% break-even those same lines report without being
unreachable. The comment says plainly not to copy the number: break-even is a
function of the barrier geometry, so read the gate's own "needs >N%" for the
config in front of you.

READOUT. One line, top-right:

  VOTE SELL  37.2%  peak  44.1%  need 40%  3 voter(s)  -> no trade

Every other number on the chart is downstream of the weighted mean the open
threshold is compared against, and that was the one quantity never displayed.
A chart with no arrows could mean the models abstained, the vote was diluted,
or the threshold is unreachable - and telling those apart meant waiting for an
era to end and reading the gate line, which is how the last two sessions went.

PEAK is the part that earns its space. A threshold above what the vote ever
attains can never fire, and that is not knowable from a single bar - it is
precisely the "unreachable gate vs merely unmet gate" confusion this project
has paid for twice. Colour carries the verdict rather than the direction:
green/red ONLY when the vote would actually place an order, grey otherwise.
Green-for-buy would make a below-threshold buy look like a trade, which is the
specific misreading the display exists to prevent.

Guarded on `total > 0` for the same reason the normalization is: Direction()
is inherited as-is by every leaf filter, so without it each filter would write
its own opinion into the one shared label and the last to run would win - the
reader would be looking at an arbitrary member's number believing it was the
vote. Drawn after the +-100 range check, so it shows what the threshold is
actually tested against.

CORNER_RIGHT_UPPER: the status lines, control panel and ensemble panel all
live on the left. Registered in WarriorChartPrefixes() explicitly even though
the "Warrior" catch-all already reaches it - that catch-all exists because the
list has drifted twice, not to make entries optional.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 18:22:45 -04:00
AnimateDread
4858507146 feat(vote): thresholds become confidence percentages, on ONE scale everywhere
User request: "the entry/exit thresholds are manual numbers, I would like
them to be confidence percentages, so the current 20 would be only 20%
confidence in a profitable trade."

WHY 20 WAS EVER SENSIBLE. Under UseDatabaseRanking both factors of a filter's
contribution are win rates: the pattern weight is that pattern's measured win
rate (UpdateSignalsWeights -> ApplyPatternWeight) and m_weight is the filter's
average win rate over its patterns, /100. Dividing the sum by the VOTER COUNT
therefore produced a mean of PRODUCTS of two win rates - a genuinely
60%-accurate filter firing a 60% pattern scored 0.60 x 60 = 36. The number was
never on a probability scale, so its magnitude meant nothing on its own.

Dividing by Sum(m_weight) instead makes it a weighted MEAN of win rates, which
is a win rate: result = Sum(w_i*p_i)/Sum(w_i). Every voter at 60% now reads 60;
MACD's double-divergence pattern (weight 100) voting alone reads 100. m_weight
stops being a discount on the probability and becomes how much a filter's
opinion COUNTS - which is what a module weight should always have been.
Default Min_Vote_Open 20 -> 50: not a tightening, the same bar re-expressed.

ONE SCALE, EVERYWHERE - the part that made this bigger than a rescale. Three
other places compared against a 0..1 softmax confidence and would each have
become a fresh currency mismatch the moment the input changed meaning:
  * the AI early-exit route (LiveSignedConfidence vs m_ai_exit_threshold) now
    reads m_lastAiVote - the AI filters' own weighted mean, undiluted by the
    classic side, which is the only reason that route exists - against the
    same m_threshold_close the averaged vote uses. m_ai_exit_threshold is
    retired rather than left dangling.
  * m_oosDecisionSeries now carries the vote, not the confidence, so the exit
    SIMULATION stops modelling a close rule the EA does not run.
  * ExitPolicy() clamped anything > 1.0 to zero. Passing the unscaled input
    through that would have silently switched vote exits off in the
    simulation while live went on running them - found before it shipped;
    the bound now tracks the scale.
LiveSignedConfidence() is deliberately untouched and still 0..1: MM sizing,
SL/TP scaling and the intelligent trailing want a model confidence, not a win
rate.

CALIBRATION CAVEAT, stated in the code where the claim is made: this is only a
real probability to the extent the pattern weights are. A pattern with fewer
than MIN_TRADES_FOR_WIN_RATE journaled trades keeps its DEFAULT weight - a
designed prior (25/50/75/100 for the AI tiers), not a measurement. Until the
signal DB fills, "60" means "the designed conviction of the patterns that
fired". Closing that gap is the next commit.

Also corrects VOTE_CLOSE_PRESETS' comment, which documented the two scales
this removes.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:52:08 -04:00
AnimateDread
282b535037 feat(chart): filtered view - one arrow per trade the bot would actually take
Adds DrawUnfilteredSignals (default OFF) and, with it off, replaces the
per-model arrow layer with the decision the EA would really have made.

THE FILTERED ARROW IS DRAWN AT THE ORDER, NOT AT THE THRESHOLD. Clearing
Min_Vote_Open is not the same as trading: a setup can pass the vote and still
never reach the broker (invalid SL/TP, stops-level, ATR warm-up, unsynced
swing history), and every one of those lands in OpenParams' failure branch.
So DrawVoteArrow() fires only after the order parameters validate, and the
failure branch withdraws any arrow already standing on that bar. One arrow is
one entry the EA would have placed - carrying the vote, the threshold it
cleared, and the SL/TP the order would have had.

Classic signals now draw too, under their own name and weight, so a chart
running MA/RSI/MACD/Ichimoku alongside the nets reads the same way an
ensemble chart does. They can only be drawn from the aggregate's once-per-bar
pass, because unlike the AI members they have no cached per-bar scan.

Two subtleties that would each have produced a quietly wrong chart:
- The raw classic draw sits AFTER filter.Direction(), not beside the
  journaling block. GetActivePattern*() are CONSUMING reads holding the
  PREVIOUS evaluation - "one tick later", which at Expert_EveryTick=false is
  one BAR later. Keyed off those and placed at StartIndex(), every classic
  arrow would have been drawn one bar early, which on a chart is
  indistinguishable from a model that genuinely leads. Peek*() accessors
  (non-consuming) let pattern, weight and bar come from one evaluation.
- CExpertSignalAIBase::DrawObject() early-returns instead of gating its five
  call sites, so the switch cannot be honoured in three passes and missed in
  the fourth. Its delete counterparts stay ungated so flipping the input off
  and rescanning clears the raw layer rather than stranding it.

SIG_ARROW_PREFIX and g_signalsVisible move from ExpertSignalAIBase.mqh down
to ExpertSignalCustom.mqh - the nearest common ancestor - because the classic
signals cannot see the AI header (it is included later in Warrior_EA.mq5).
The vote layer gets SIG_VOTE_PREFIX under the same bare prefix, so
WarriorChartPrefixes()' purge still reaches every arrow without knowing they
exist.

NOT YET BUILT: the reconstructed history behind attach. Filtered arrows
currently start where the EA starts. See the next commit.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:41:28 -04:00
AnimateDread
94019f363e feat(gate): grade OOS calls on the exit policy actually in force, and move vote combining out of the members and into the orchestrator
Option (a) from the exit-policy question: the certified number must be the traded
number. Plus the modularity correction the user called for on 778b6c0.

1. VOTE COMBINING BELONGS TO THE ORCHESTRATOR, NOT TO A MEMBER.

778b6c0 fixed the last-writer-wins bug on g_LiveAISignedConfidence by having a
member average its siblings through g_warriorEnsemble. That trades a scheduling bug
for a coupling bug, and it is the wrong shape for this EA: every signal runs in its
own instance, minds its own state, and VOTES to the orchestrator, which is the only
thing allowed to combine opinions.

Replaced with a publish/aggregate pair in Variables\ConfidenceBridge.mqh whose split
is enforced by shape rather than by convention:
  - PublishAIVote(slot, conf)  - a member writes ONLY its own slot, reads nobody's;
  - AggregateAIVotes()         - called by CExpertSignalCustom::LiveSignedConfidence.
CExpertSignalAIBase::EnsembleLiveSignedConfidence is gone. The orchestrator also
republishes the aggregate into g_LiveAISignedConfidence, because the intelligent
trailing reads that global directly and must act on the same number the exit route
does rather than on a leftover from whichever member ticked last. A solo AI signal
owns slot 0, so the non-ensemble path is unchanged.

2. THE GATE NOW REPLAYS THE REAL EXIT RULE.

SimulateTradeOutcome() walks the same price series with the same fill/barrier/spread
convention as ComputeLabelForBar - deliberately by copy, so a disagreement between
the two can only be a policy effect and never a discrepancy between two pieces of our
own arithmetic - and terminates at the FIRST of stop / target / vote reversal /
horizon. Barriers are tested before the vote on the same bar: intrabar we cannot know
which came first, and the barrier is what the broker executes automatically, so
checking the vote first would credit the exit policy with escapes a real stop would
have taken out of its hands.

It runs AFTER pass 3, not inside it. A vote-flip exit for a trade entered at bar r is
decided by the model's output at bars r-1, r-2, ... - NEWER bars - and pass 3 walks
oldest-to-newest, so at the moment r is graded its own exit does not exist yet. Only
once m_oosDecisionSeries is complete over the whole OOS window can the replay run.
In ensemble mode that series carries the member's adjusted decision and the live exit
reads the ensemble aggregate, which is the coupling the user identified: an LSTM entry
really can be closed by the ensemble turning against it.

3. THE STATISTIC HAS TO CHANGE WITH THE POLICY, AND THAT IS THE REAL FINDING.

A barrier exit pays a fixed R. A vote exit pays whatever the close happens to be. So
the moment vote exits are enabled the payoff is CONTINUOUS, and "win rate vs
break-even" stops being a meaningful test - there is no fixed break-even for a
variable payoff. SimulateTradeOutcome therefore returns R rather than a bool, and the
replay reports expectancy in R with its SE taken from the R distribution (overlap-
deflated on the same EffectiveSampleSize doctrine as every other SE here), not from a
binomial.

This is the same class of error as win-based scoring in 2026-08-09: measuring a
variable-payoff process with a fixed-payoff statistic. Naming it now, while vote exits
are still off, is much cheaper than discovering it after they go on.

4. WHY THIS IS SAFE TO SHIP TODAY.

Min_Vote_Close ships Disabled (101, unreachable on both scales it drives) and reaches
the AI signal through the new ExitPolicy() setter as 1.01, which the setter turns into
0.0 = no vote exit. Under that policy every replayed trade resolves at a barrier and
the simulation is arithmetically the same trade the deploy gate already certifies -
they cannot drift. The report says so explicitly, and prints ONCE per run in that
state; when vote exits are on it prints every era, because then the divergence is the
thing to watch. Nothing about today's numbers moves.

The gate switchover is wired but dormant by construction: it becomes exit-aware the
moment the input is enabled, which is exactly what "the certified number is the traded
number" has to mean.

KNOWN LIMIT, stated rather than hidden: only the AI early-exit route is replayed. The
rule-based averaged-vote close (m_threshold_close) depends on every other filter's
live vote, which pass 3 does not reproduce, so a position the classic filters would
have closed is held to its barrier here. The replay therefore holds LONGER than live
and overstates barrier-reached outcomes. Faithful only while the AI is the dominant
vote - which is the configuration this is being built for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:25:57 -04:00
AnimateDread
34fa0045e3 feat: extend MAX_ERAS_PRESET enum and update MaxErasPerRun to ME_10000 2026-08-16 23:52:22 -04:00
AnimateDread
c0c9f4a285 feat(target): withdraw the TrainingTarget option - barrier is the only live one
NOT COMPILED - user compiles.

The private build still DEFAULTED to TARGET_FRACTAL, so every fresh attach was
training the target adjudicated dead that morning (5,700 model-eras flat at -2pp,
best-of-243 p=0.17). The campaign closed; the default was never flipped back.

Rather than re-default it, the input is withdrawn entirely (user: "remove the
option if there is only one choice for now"). An input offering a single live
choice is worse than no input - it presents a dead option as supported, and an
operator picking it silently trains a model already known to carry nothing.
Direction models are now unconditionally triple-barrier.

Removed: the input, the TrainTargetFractal() call in the signal setup, and the
HoldToBarrier() exit-policy block (which existed only because the fractal vote
flips at swing-marker cadence, ~3-5 bars, far inside the barrier's travel time -
barrier-target models keep vote exits and always did, their label IS the vote's
horizon). Verified no code reference to TrainingTarget survives; the four
remaining mentions are comments.

Kept deliberately, so a rerun is a re-enable and not a rebuild: the TRAINING_TARGET
enum, the fractal label itself, its |TGT:FRA1 fingerprint token, its conditional
barrier-geometry derivation, HoldToBarrier()/m_holdToBarrier, and the campaign's
trained models on disk. Three lines bring it back; Inputs.mqh names them.

ALSO CORRECTS THE RECORD from 1b5a412. I claimed the live run was on the barrier
target, "confirmed" by break-even 34.3% matching the 0.62/1.18 geometry. That
proved nothing - break-even comes from BarrierMultiples, which grades wins
identically under either target. Neutral's share is the real tell: ~31% would say
barrier, the measured 10.6% says fractal. So the imbalance finding stands and its
mechanism is unchanged, but the cause of Neutral being rare was the FRACTAL
target, not the triple-barrier relabel. Neutral fell twice - 94% under the old
exact-pivot ZigZag label, ~31% under triple-barrier, 10.6% under fractal - and
the correction was re-checked at neither step. The fractal campaign was chosen
FOR its balanced classes and did balance Buy vs Sell (48.3/41.1) while quietly
making Neutral the thin residual the correction then subsidised.

Under the barrier target the same geometry gives roughly 34/34/31, where Neutral
is neither rare nor dominant, so 1b5a412's fix should be close to a no-op there -
which is the right answer when there is nothing to correct. It stays: never
subsidising the abstain class is correct under both targets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 22:53:04 -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
0a198fb95f feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols
EIA (user directive: "the NN might find patterns in it for both oil and regular
symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR,
field production, refinery utilization - three features (1y percentile, 4w
change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on
WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not
the screen, decides whether a model trained on them trades. Publication stamp
observed+6d mirrors research/altdata/eia.py.

Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24
instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC
Markets naming, with prefix matching for the broker suffix zoo (US500.cash,
XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are
named by CANONICAL so two brokers' names for one contract share a download.

Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog +
dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and
"No alternative data" is a recorded choice, not a nag. Non-blocking by design:
an unmapped symbol contributes 0 features and must never hold up a chart.

Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the
wildcard and an unescaped one corrupts the query; docs/ gains the whitelist
URLs, an API-key backup, and the catalog reference.

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