forked from animatedread/Warrior_EA
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0e1e952b96 |
feat(topology): cap the input window at 6 bars for capacity - 588 inputs -> 294
Three charts (SP500, XAUUSD, XTIUSD) sat on the FIRST_LAYER_MIN_WIDTH floor
even after pooling took SP500 from 4.1 to 1.8 weights per independent
observation. ComputeFirstLayerWidth needs width <= ~331 to clear it; 49 columns
x 12 bars = 588.
TWO QUESTIONS, AND THE WINDOW IS NOW THE SMALLER ANSWER. The ZigZag ladder
answers "how far back is a swing worth looking" and says 12. The capacity
budget answers "how far back can this much data support" and says 6. Taking the
min stops the first writing a cheque the second cannot cover.
WHY THE LAG AXIS AND NOT THE COLUMN AXIS - the choice was between this and a
per-column mask (designed, parked on feature/column-mask):
- On the LAG axis there is a measured null. The corrected lag profile finds no
linear structure at any lag within +/-50, on all six charts, family-wise
p=1.0000, argmax scattered across different columns and lags per chart.
- On the COLUMN axis the two measures that would justify a mask - marginal MI
retention and variance share - are explicitly blind to joint and temporal
structure, and the columns they would delete include the entire price core,
which is the one place such structure would plausibly live.
Cutting where there is a measured null beats cutting where the instrument
cannot see. Corroborating: PAI/CONV/LSTM/HYBRID score within ~1pp of each
other, so the temporal machinery is not visibly earning the deeper lags.
THE CAP IS A FLEET CONSTANT, NOT A PER-CHART DERIVATION. Pool rows are keyed on
`bars x columns`, so a capacity cap computed from a chart's own observation
count would differ across the fleet by construction and hand every chart its
own layout, its own fingerprint and its own pool of one - exactly what orphaned
SP500. Set from the most starved chart; every chart shares it.
Conv survives: CONV_RECEPTIVE_FIELD_BARS is 3, so a 6-bar window still leaves 4
sliding positions. LSTM sequence length becomes 6.
RETRAIN-FORCING and POOL-INVALIDATING: width changes, so old .nnw and old
TrainPool rows are both incompatible. Wipe both - which puts the fleet back in
the cold-start condition
|
||
|
|
1e6d00e602 |
fix(vote): a resumed converged model passed the eligibility test and then abstained on every bar
Found by restarting the terminal against three charts that had just deployed - the exact scenario |
||
|
|
bbe26a09fb |
fix(diag): the lag profile's first-ever run produced a spectacular false positive
Resurrecting the diagnostic in
|
||
|
|
4113afd293 |
fix(diag): the linear lag profile never ran once - it started at the newest bar
Every chart, every era, on every run in the logs: "linear lag profile skipped - only 0 contiguous OOS bars." That reads as "not enough data". It was not. The walk never started. ReportLinearLagProfile walks newest-first from r=0 and breaks on the first row without a label, to avoid splicing across a hole. But r=0 IS the newest bar, and a forward-looking swing-pivot label cannot be resolved there by construction - the opposite pivot has not committed yet. So HasLabel(0) is false, the loop breaks on its first iteration, and n=0. Permanently. The leading gap is SYSTEMATIC (always about the label resolution), not a hole in the middle of the series, so stepping over it splices nothing. Contiguity is still enforced from the first labelled row onward. WHY THIS MATTERS BEYOND THE DIAGNOSTIC: the input window is 12 bars, and the capacity budget divides by width = columns x bars. Cutting the window is the largest lever left for the three charts still pinned to the 16-unit first-layer floor, and there has been no measurement of whether the deeper lags carry anything - because the one diagnostic that would answer it has never produced a number. The old lag verdict in memory predates the pivot-event label. The skip message now reports where the walk ran out, so "0 from r=0" (never started) is distinguishable from "0 from r=37" (genuinely short window). Build tag -> lagprofile-v1. NOT a feature-layout change: no retrain, models resume from their weights and the training pool stays valid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
00699e1af8 |
fix(chart): stale combined-vote arrows survived every wipe, because two files lived outside Warrior_EA\
Operator report: arrows labelled as restored from a previous session on a fleet training from era 0. Confirmed - all six charts restored 115-431 combined-vote arrows drawn by models that no longer exist. TWO INDEPENDENT DEFECTS, either of which alone causes it. 1. CVoteArrowStore::Discard() HAD NO CALLER. The member-scoped .arrows file is cleared by ClearPersistedChartSignals on a fresh topology. The CHART-scoped .votearrows store has an equivalent Discard(), written for exactly this, and nothing ever called it. The store is keyed on the DB config fingerprint, which does not move when a model is wiped, so it reloaded across any reset - fresh topology, panel weight reset, or a model-file wipe. A vote is a claim made by a specific set of members. If any member rebuilt from scratch this run, the whole stored history is void, so g_warriorFreshTopologyThisRun is now raised wherever a member discards weights or builds a fresh topology, and the store Discards instead of Loads. 2. TWO WARRIOR FILES LIVED OUTSIDE Warrior_EA\. .sigvis and .votearrows were written to the ROOT of Common\Files, outside the one directory that "wipe the Warrior EA files" has always meant. Two consecutive wipes this session left them standing untouched, and neither wipe was as fresh as reported. Both now live under Warrior_EA\ChartState\. A wipe that does not remove all of a program's state is not a wipe, and nothing in the log told the operator which files were missed. NOTE for anyone re-running the wipe: pre-existing WarriorVote_*.votearrows and Warrior_EA_*.sigvis in the Common\Files ROOT are orphaned by this change and should be deleted once. Build tag -> fleet-pool-v3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
afe1038d11 |
fix(topology): stop a training-alone size becoming permanent, and stop the keep-screen latching underpowered
1. THE POOL FIX WAS LANDING ON A TOPOLOGY THAT COULD NOT SEE IT. ComputeFirstLayerWidth budgets against EstimatedInSampleBars, which counts this chart's own bars PLUS the training pool. On a COLD fleet start every chart derives and pins its topology BEFORE any chart has published a pool file - measured on the 18:13 start, model creation at 18:13:21 against a first publish at 18:13:48. All six sized as if training alone, wrote that into .cfg, and adopted it back on every later start even with the pool full. SP500 ran a first layer floored to 16 while adopting 30229 peer rows. Adopt-don't-compare exists to protect weights shaped by those sizes. It was also running for a model with NO .nnw, where there is nothing to protect and the .cfg is just a record of one unlucky moment. The four derived sizes are now re-measured when no weights exist. Safe on all three counts that matter: free (nothing to discard), cannot loop (once weights exist the .cfg is authoritative again), and cannot fragment the pool - the derived width is NOT in BuildModelFingerprint, which keys only on the FEATURE layout. Verified: field 2 of the fingerprint is LEGACY_HISTORY_BARS_SLOT, not the first-layer width. TO TAKE EFFECT the weights must be wiped while the TrainPool is KEPT - the census has to be non-empty at derivation time. A full wipe empties the pool and reproduces the original condition exactly. 2. THE KEEP-SCREEN LATCHED ON AN UNDERPOWERED SAMPLE. MI_MIN_SAMPLES is a floor for "can this be computed", and it was being used as the bar for "is this answer final". The screen fired on the first era clearing 200 rows and latched, measuring at 202-773 samples where a warm chart gives ~2065. Columns kept then tracked SAMPLE SIZE rather than information - EURUSD kept 0 of 49 at n=202, SP500 kept 15 at n=773, and the ordering across all six charts was very nearly monotone in n. A thin sample is still measured and printed, but it no longer closes the question: below MI_GOOD_SAMPLE_FRACTION of the target the result is labelled underpowered and a later era supersedes it, bounded by the same attempt budget. An underpowered screen that latches is worse than one that waits, because it looks like a result. Build tag -> fleet-pool-v2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a970405042 |
feat(pool,mi): one feature layout fleet-wide, and the keep-screen stops self-disabling on a cold start
TWO CHANGES, BOTH RETRAIN-FORCING BY INTENT.
1. SP500 was training alone, and one alt-data column was the reason.
The alt block's width joins the model fingerprint, and the pool reader only
adopts peer rows whose fingerprint and width match. The exporter gives each
instrument the series that apply to it - FX 15 columns, metals/oil 14, SP500
13 - so the fleet ran as three incompatible pools:
EURUSD/USDJPY/USDCAD adopt ~57-60k peer rows each
XAUUSD/XTIUSD adopt 6.4k / 20.3k
SP500 "EVERY peer file was REJECTED, so this chart is
training alone" - 0 rows
SP500 therefore trained on 2279 independent observations against a 600-wide
input with its first layer floored at 16, printing its own "expect
overfitting" warning. It is the one chart with no pool and the worst
capacity ratio in the fleet by a factor of three.
Fresh models now pin ALTDATA_FLEET_COLUMNS - the 12-column intersection -
instead of their own file header. An existing model still adopts its .cfg
pin, so this re-keys nothing that is already trained.
Intersection rather than union: filling an absent series with its median
makes that column constant per instrument, which lets a pooled model
identify the source instrument and stop learning the shared mechanism. It
is also 6 columns narrower. Cost is six columns whose retained information
is UNMEASURED - the keep-screen reports a bitmask nothing has mapped back
to names.
2. The MI keep-screen disabled itself for the whole run on any cold start.
ReportFeatureLabelInformation set m_miReportDone on ENTRY. On a cold start
the label cache is allocated before it is filled, so BuildMiSample finds no
row carrying a resolved label and returns 0 - a sixth exit, and the only
one the
|
||
|
|
869cd1b40c |
fix(pool): defer the atomic promotion to the timer instead of spinning on the tick
REPLACES the in-line retry from
|
||
|
|
72dba892cc |
fix(chart): configure the vote-arrow layer with the PINNED threshold
Second instance of the same regression |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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. |
||
|
|
ccfe5563e3 |
perf(tester,bn): no sub-second timer in the tester + BN kernels on the DLL tier
THE OPTIMIZER ("0.1% an hour per agent", 0 of 39 passes in 78 min,
12 agents): the tester fires OnTimer on SIMULATED time, so the live
chart's 500ms EventSetMillisecondTimer over a 2016-2026 pass is ~600
MILLION OnTimer calls - each walking 4x PollTraining, the vote
readout's string build, the overlay advance and the deployed census.
None of it serves an inference-only pass: training never runs, per-bar
inference is driven by OnTickHandler off the tick stream, the risk
budget re-checks in OnTick, and there is no chart to keep fresh.
StepSetTimer now arms EventSetTimer(3600) in tester/optimizer/forward
(~2,600 calls per pass) and keeps the 500ms timer for live charts.
Plus a TESTER PASS SELF-PROFILE: per-tick buckets (pre / Expert.OnTick
/ journal) and the timer total, printed once at the pass's OnDeinit -
so if a pass is still slow it names its own consumer instead of being
diagnosed from outside.
OFFLOAD (operator: "as much calculation as possible to DLL/OpenCL"):
batch norm was the ONE stage still host-side on the DLL tier - the
device path was OpenCL-only, so every sample crossed the bus twice per
BN layer and normalized in interpreted MQL5 (and every model runs
batchnorm ON). Four new exports mirror AI\Network.cl's BatchNorm*
kernels 1:1 in DOUBLE precision (closer to the host reference than
the float OpenCL kernels): forward with running stats + frozen flag,
hidden gradient with the clamp derivative, gamma/beta accumulate, and
the batch-mean apply (no weight decay, moments-before-skip ordering,
sqrt-stored v). BnDeviceEligible/EnsureBnDeviceBuffers/all four
Dispatch* now route by backend; the EXISTING in-situ self-checks
(host-vs-device on the first real sample, latch-off + host fallback on
mismatch) verify the DLL kernels exactly as they verified OpenCL ones.
batch_accum_check regression: ALL CHECKS PASSED on the rebuilt DLL.
Same deployment coupling as
|
||
|
|
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> |
||
|
|
4e4bff51d4 |
feat(vote): backfill the ensemble win-rate record from the overlay sweep
"Vote win rate: measuring..." never resolved on a deployed chart whose .stats predate the WST7 ensemble record: g_ensCumOosTotal is fed only by the era-end combined-vote scorer (Training.mqh), and a deployed ensemble runs no further eras. The replay pass rebuilt every MEMBER's ladder (64-71% each, per the 16:12 log) but nothing ever scored the COMBINED vote, so the aggregate line sat on "measuring" while 300+ arrows drew. The overlay sweep already reconstructs the vote per bar with the live threshold and direction policy - so it now also tallies, BEFORE declustering (NMS thins arrows, not calls), each threshold-clearing bar against the inline swing-pivot label (same resolution ScoreReplayFromCache uses, same window-mismatch reason). On sweep completion Warrior_EA.mq5 harvests the tally through a consuming one-shot read and adopts it ONLY when the record is empty and the models are deployed - a training-time sweep can never pre-empt the era scorer, and a restored record always wins. The result is persisted immediately into every member's .stats. Also verified against the same log: the sweep does NOT ignore DrawUnfilteredSignals - 4986 voter bars -> ~300 arrows, all gated on the 25% open threshold. The arrow increase vs the restored set (41-312 saved) is the replay-minted ladder reading stronger (partly in-sample), plus the reconstruction deliberately not replaying order validation/session hours (tooltip says so); the backfilled record carries the same caveat and is labelled so in the log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
6d48fdb4cd |
perf(tester): stop agents doing chart work on deinit; enforce Expert_EveryTick
Three related changes, all aimed at work being repeated at a frequency nobody chose. 1. OnDeinit gets a tester/optimizer fast path. Everything in the live teardown exists to leave a CHART clean and a live model's state on disk. An optimization agent has neither. It was still running, on EVERY pass: a per-signal arrow-sidecar WRITE (ShutdownChartCleanup -> PersistAndClearChartSignals) plus two full chart-object scans plus a ChartRedraw. At optimization scale that is hundreds of thousands of pointless file writes per agent, against a ~4,500 ms budget MetaTrader force-terminates on - the shape of thing that stalls an agent rather than failing it. The fast path keeps MarkShutdown() and FlushTrainRun() (so a killed pass never leaves a half-written era) and still calls dbm.Deinit() and Expert.Deinit() - leaking the signal tree or a handle across passes is its own way to accumulate into a stall. The two now-unreachable !isTesterRun guards further down are folded away. 2. All four tester handlers are present and documented by WHERE THEY RUN. OnTesterInit/OnTesterPass/OnTesterDeinit run in the CONTROLLING TERMINAL once per session; only OnTester runs on the agent, per pass. OnTesterPass was missing entirely - added empty and deliberately so: it only fires for passes that shipped FrameAdd() data, which this EA never sends, and reading frames there would put per-pass work on the terminal's critical path. Declared so that adding frame-sending later fails loudly instead of silently dropping every frame. 3. Expert_EveryTick is now actually enforced. It was passed to Expert.Init() and only ever reached StartIndex() - which bar a signal READS. The whole pipeline still ran on every quote. It now gates m_signal.SetDirection() in CExpertCustom::Processing(): that call drives Direction(), which is a TRANSACTION (NN forward passes, DB rows, chart arrows, one-shot vote state), and re-running it on every tick of a 4-hour bar repeats all of it. Scoped deliberately. Everything after that line still runs per tick - CheckReverse/CheckClose/CheckTrailingStop and pending-order maintenance are risk management, and a stop that only trails at bar boundaries is a different strategy, not a faster one. The scheduled close-all in OnTick() matches a +-1 MINUTE window, so bar-gating it on H4 would step straight over the thing 100% of label timeouts already resolve against. g_riskBudget.Update() also stays at quote frequency, by design. System/NewBar.mqh becomes CNewBar, a class. The free function it replaced had zero callers and kept its watermark in a `static`: ONE watermark shared by every caller, so the first caller each tick consumed the transition and every other caller was told "no new bar" for a bar that had just opened. Per-instance state fixes that; first observation counts as new, so a fresh attach acts immediately instead of idling up to a full bar. Compile-verified in _claude_stage: 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
0005cbad1f |
refactor(init): switch-based dispatch for InitializeTrailing/InitializeMoneyManagement
Both selected on an enum via nested if/else-if chains 2-3 levels deep,
inconsistent with the switch-dispatch style the rest of the file uses
(HandleControlPanelAction,
|
||
|
|
f1179304bc |
refactor(init): dedupe InitializeMoneyManagement's create+InitMoney boilerplate
All three MM_STRATEGY branches repeated new+null-check+Expert.InitMoney()+ null-check verbatim. Added CreateAndInitMoney<TMoney>(functionName), the same template-helper shape as the existing CreateSignalWithRetry<TSignal> a few hundred lines up. Pure relocation - same error text, same control flow, only the branch-specific setter calls (Percent/Lots/UseAIConfidence...) stay inline. |
||
|
|
e2a1f8d8e3 |
refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
|
||
|
|
2f1951e142 |
refactor(panel): split HandleControlPanelAction's 9-branch switch into per-action handlers
The ~185-line switch mixed chart-UI toggling, AI training-lifecycle dispatch, weight save/load/reset and DB/report admin in one function with subtly different guard conditions per branch. Each CP_ACTION_* case is now its own HandleCp*() free function (matching this file's existing procedural style - ConfirmDestructiveAction, RefreshControlPanelLabels, etc. are already standalone functions over the same globals); the switch is now a one-line-per-case dispatch table. Every guard/confirm/ Alert/Print sequence is preserved verbatim (break -> return only change; verified via quoted-string-set diff = empty and if/Alert/ Print/DispatchSignalCommand counts identical against the original). Compiled 0 errors, 0 warnings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
91c0f2c1a2 |
refactor(ea): OnInit's two hand-rolled retry loops reuse the existing helpers
Expert.Init() and the main signal's `new CExpertSignalCustom` each had their own 5-retry loop, hand-rolled, while CreateSignalWithRetry<T>() and RetryInitStep() already exist and are used for every OTHER signal/init step in this same function. Expert.Init() -> a StepExpertInit() shim through RetryInitStep (same pattern as StepInitTrailing/StepInitIndicators/etc. - and picks up RetryInitStep's fatal-reason fast-fail, which the hand-rolled loop didn't have: a permanent AcquireConfigLock refusal now fails immediately instead of blindly retrying 5 times). `new CExpertSignalCustom` -> CreateSignalWithRetry<CExpertSignalCustom>(maxRetryOnError, true), the exact template already used for PAI/CONV/LSTM/HYBRID/META/MA/RSI/MACD/ Ichimoku/NewsFilter/SessionFilter/RiskGuard. The dbm.OpenDatabase/BeginTransaction/ CommitTransaction/CloseDatabase loop stays hand-rolled - it's a multi-step transactional retry with different per-step cleanup, not a single-op retry, so it does not fit either existing helper's shape. Compiled clean (0 errors, 0 warnings). |
||
|
|
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 (
|
||
|
|
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>
|
||
|
|
8c64ec7018 |
refactor(meta): the signal tree owns its gate - no global
g_warriorMetaGate was a file-scope mutable pointer, and it did not need to be. The root CExpertSignalCustom - the one CExpert actually calls CheckOpenLong/Short on - now holds the gate as a member, and children reach it through a parent back-pointer AddFilter sets on adoption. That was the last piece of the meta veto that behaved like ambient state: - CheckOpenPosition reads MetaGate() instead of a global. - EnsembleEraVerdict's replay reads the same MetaGate(). It sits deep in the training code inside an AI filter and had no route up the tree; a global WAS that route. m_parentSignal is now, and a back-pointer is safe for the same reason the gate adapter's owner pointer is - m_filters and m_gates free their children, so a parent always outlives them. - The stale-pointer hazard is gone by construction. The global had to be hand-cleared at every re-init because an input change re-enters OnInit in the same program instance and frees the old head; the root signal is new'd fresh each time, so nothing survives one. That reset line is deleted, not moved. Note what did NOT need doing: the tree already owned the meta head itself. AddFilter routes non-voters into m_gates, so it has been a gate child of the root since the S3 wiring - it was only the VETO that lived outside. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3d2ee517ca |
refactor(meta): the veto is a gate, not a virtual every signal carries
Since S3 (
|
||
|
|
a699bd597d |
refactor(meta): one owner for the pattern taxonomy and its table names
MetaCorpus was already a class, so the raw-include problem was not the
one here. The problem was that the rule for naming a signal-DB pattern
table
MetaFamilyName(f) + "_Pattern_" + p + ("_Buy" | "_Sell")
was written out FOUR times, each wrapped in its own identical
family/pattern/side triple loop: the corpus loader, the stale-DB guard,
META's row counter and META's exporter. Four chances for a rename to
leave three of them querying an absent table and reporting it as
"family disabled" - which is what that code says when a table is
missing, so the failure would have looked like normal operation.
CMetaFamilies now owns the taxonomy and that rule. Callers walk ONE
flat index over all 52 tables and never spell a name:
for(int ti = 0; CMetaFamilies::TableAt(ti, table, f, p, isBuy); ti++)
Enumeration order is unchanged - Buy then Sell within a pattern,
families in order - so the corpus is assembled in exactly the same
sequence as before.
META's OneHotSlot had a second hardcoded 0/4/8/14 ladder with a comment
reading "matches MetaFamilyPatterns' 4/4/6/12" - a note asking a reader
to keep two constants in step by hand. The ladder is now summed from
the pattern counts, so they agree by construction. The bound against
META_ONE_HOT_SLOTS stays in META: the head's input width is that
class's business, and a taxonomy grown past it must be caught rather
than silently truncated.
Caught while re-reading the rewritten loop: my first counter was `t`,
and the body declares `MqlDateTime t`. Renamed to `ti` at all four
sites before it reached a compile.
MetaCorpus.mqh moves to Expert\Training\ with the other real classes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
14f7718d35 |
refactor(signal): only true signalers are filters - META becomes a gate
Operator's call: "META should be removed or implemented directly into
CExpertSignalBase. Only true signalers needs to be filters."
A meta head never votes - its Long/ShortCondition are structurally 0 and
its verdict reaches the pipeline through LiveMetaGate(), not through the
vote. Keeping it in m_filters meant every consumer of that list needed a
special case, and each one was a bug waiting: VoteCapableWeight() had to
return 0 for it or it would park a permanent abstainer in the consensus
divisor. The replay's divisor bug (
|
||
|
|
788115970c |
fix(vote): unranked members voted with the stock 25/50/75/100 ladder
Two defects behind "arrows drawn while members are still mid-era". 1. THE DRAW. The filtered overlay armed on the FIRST member to finish pass 3 and leaned on a 60 s rate limit to "collapse the burst", assuming members finish seconds apart. They do not - on USDJPY one member was at sample 10496 of pass 2 while another was at 2304, minutes apart. A member with no era-end snapshot returns false from SnapshotVoteAt, and the sweep's `if(!hasData) continue;` skips it BEFORE `den += ModuleWeight()`, so the one finished model's tier weight became the entire vote and was drawn as a consensus arrow. An abstention is a member that looked at the bar and said nothing; a missing snapshot is a member that has not looked. The first must dilute the vote, the second must suppress the draw. The arm is now a readiness MASK - one bit per m_ensembleIndex, set at that member's pass-3 completion, cleared when a sweep arms - and a sweep waits for every enrolled member. Bounded at 10 minutes so a member that stops cannot freeze the chart, and the partial draw PRINTS which members were missing: the |
||
|
|
b91c7b1f7a |
refactor(comments): box headers to stdlib length
The //| box blocks were excluded from |
||
|
|
5efdb48de4 |
refactor(comments): stdlib comment style across the remaining in-scope files
Same pass as
|
||
|
|
2de62241a5 |
fix(init): the OnInit failure the operator reads did not name the reason it failed
Two same-config charts collide on the config lock. AcquireConfigLock prints a precise REFUSED line, but CExpert::InitIndicators then returns a bare false, RetryInitStep retries it five times - a lock held by a live chart gives the same answer every time - and the last line on screen is "Failed to initialize Indicators after retries", which names neither the lock nor the owner. The explanation is six lines further up, under identical-looking retry noise. A refusal that cannot change on a retry now says so and stops: AcquireConfigLock records the reason, RetryInitStep repeats it and returns immediately. Found because a second SP500 H4 chart was started to run Run_Alglib_Baselines. That input is a diagnostic and is deliberately not in the fingerprint, so both charts resolved to the same filename - the guard was correct and the message was not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
667f2bcb6b |
revert(labels): drop the one-sided exit target; measure the calibration drift instead
Reverts |
||
|
|
a86379621c |
feat(labels): on a one-sided book the blocked side's class is retargeted from an entry it can never take to the EXIT of the one it holds
User request: "when an asymmetry is noticed in a market (like sp500 upward drift) ... it does not need to predict shorts, but exit points. a sell signal needs to be preceded by a buy so that it can say I predict we must close that long." Until now a LONG_ONLY verdict only BLOCKED short entries. The network went on being trained to predict them - a third of its output capacity spent learning an answer the direction policy guarantees it can never act on, while the question the book actually faces (when to get out of the long) was never asked. The two are not the same event: "a short pays" needs price to travel the SHORT's target before the SHORT's stop, and at any geometry where reward != risk that is a different bar from "this long hits its stop first". The exit is the second one. So on a one-sided book TripleBarrierLabel re-cuts all three classes around the only position the book can hold: Buy = it reaches its target, Sell = it reaches its STOP first, Neutral = the horizon expired with it still open. Both come off the allowed side's own barriers, which the walk already computed - this reads longLost where it used to read shortWon, so it costs nothing. Label lifespan and the timeout flag follow the allowed side too, so the overlap correction is sized on the window this label actually spans. DECIDED ONCE, AT ERA 0, AND PINNED. m_exitTargetSide goes in the .cfg beside the derived geometry under the same doctrine and for the same reason: it decides what Buy and Sell MEAN, and a target that moved mid-run would retrain a fitted model against something it never saw. A .cfg from before this ends early and reads 0/0 - "not decided, symmetric" - which is exactly what every existing model was trained as, so nothing needs migrating. The weights fingerprint keys on the INPUT only (explicit Long only / Short only); under Intelligent the measured verdict must never reach a filename, or the model is orphaned the moment more history downloads. THE DRIFT VERDICT HAD TO MOVE OFF THE LABELS FIRST, and it turns out it was measuring the wrong thing anyway. It counted m_labelCacheBuy/Sell and called them "always-long vs always-short win rate", but the label pair is the COLLAPSED first-touch verdict: a bar where both sides reached their target carries only the side touched first, so long wins were undercounted by the both-won-goes-to-short share. m_winLongCache/m_winShortCache are the actual per-side win rates, published before the collapse, and that is what it reads now. Necessary as well as more correct - deriving the verdict from labels the verdict shapes is a feedback loop, since Sell-as-exit is near complementary to Buy and would close the very gap that produced it. The gap's SE now leans conservative rather than anti-conservative for the same reason. LIVE. The retargeted class is wired to close the position, or training it would be pointless: CheckClosePosition's "never vote-exit a certified position" rule keeps governing symmetric books and gains a one-sided exception, and the replay reads the identical rule through one LiveVoteExitThreshold() so certified and traded cannot describe different policies. Armed only when the operator picks a close threshold (Signal_ThresholdClose ships Disabled) AND the model's own pin says its blocked-side class means "close" - a model trained symmetric never fires it, whatever the verdict has since become. This does trade a different game from the one the win-rate certificate grades; the era's EXIT-POLICY REPLAY line already reports expectancy in R for exactly this case and says so in words. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
506626b381 |
feat(panel): commands reach signals down the filter tree, not through a registry
The control panel drove training by looping g_aiSignals[] - a
hand-maintained, MAX_AI_SIGNALS-capped, AI-only registry that had
already dropped an ensemble member on the floor once (
|
||
|
|
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
|
||
|
|
90c6e26e94 |
feat(rng): ALGLIB's L'Ecuyer generator replaces MathRand, and a seed collision goes with it
MQL5's MathRand() is the 15-bit MSVC LCG - 32768 distinct values and the lattice structure that shape of generator has. Two places here actually lean on randomness and both were hurt by it: WEIGHT INIT. Six He/LeCun-uniform sites drew ((MathRand()+1)/32768.0 - 0.5) * 2 * scale, so a first dense layer of ~250k weights had only 32768 possible values and thousands of connections started byte-identical. Breaking that symmetry is the whole job of random init. SHUFFLING. ShuffleRandomIndex() already had to splice TWO MathRand() draws to reach 30 bits, and its own comment documented the residual modulo bias it still carried. HQRndUniformI() is rejection-sampled and exactly uniform, so the splice and the bias note both go. CHighQualityRand is L'Ecuyer's combined multiplicative congruential generator - two differenced streams, 31-bit output, period ~2.3e18 - and it ships with the terminal. AND A BUG THE MIGRATION EXPOSED. The three MathSrand(GetTickCount()) calls sit immediately before "build a fresh topology", once per model. GetTickCount() steps in ~15.6 ms on Windows and an ensemble builds every member inside one OnInit, so members could be handed the SAME seed and draw the SAME weights wherever their shapes coincide - and members that start identical are not an ensemble. WarriorRandSeed() takes a salt (the model id) plus a never-reset call counter, so a collision is impossible rather than merely unlikely, while the tick keeps the run itself genuinely unrepeatable the way those call sites asked for. Seeds are masked positive rather than trusted: HQRndSeed computes s % (M-1) + 1 and MQL5's % keeps the sign, so a negative seed leaves the generator in a state its own assertions reject. GetTickCount() is a uint and goes negative as an int after ~24 days of uptime - a fault that would surface as "training is broken" on a long-running terminal and nowhere else. The indicator tuner's 52 draws move across too: its random search is where sample quality earns its keep. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d1f08eda13 |
refactor(dry): one retry-and-report for OnInit's five init steps
Trailing, money management, settings validation, indicator setup and timer registration each carried their own copy of the same 18-line retry loop - a bool, a counted loop, a RandomSleep backoff, and two Print lines - differing only in which call they made and what they called it. Five places for the retry count, the backoff and the failure wording to drift apart, and OnInit was 635 lines partly because of it. RetryInitStep() is now the only copy. `what` completes both sentences the loop printed, so the journal reads exactly as it did; `caller` is passed in rather than read from __FUNCTION__ so the line still names OnInit and not the helper. The five steps become one-line wrappers because MQL5 function pointers bind neither a method call nor an argument, and these are two of each (Expert.ValidationSettings/InitIndicators, and the timer's interval). That interval moves to WARRIOR_TIMER_INTERVAL_MS beside its wrapper, taking its full rationale with it instead of leaving it stranded in the middle of OnInit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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> |