forked from animatedread/Warrior_EA
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
36e8463310 | refactor: derive history bars for input sequences and update related configurations | ||
|
|
923addf574 |
feat: pin the cross-asset pair set train->serve + warm the sync at init
The reference-pair set was re-discovered from Market Watch on every build, so adding or removing a terminal symbol silently changed what a trained model's six cross-asset features meant - the last open train/serve parity gap from the 2026-08-11 audit. The set a model's FIRST successful build actually used is now stamped into its .cfg (append-and-length-guard, adopt-don't-compare - the derived-barrier pattern) and every later build constructs the panel from exactly that list; a pinned pair that is temporarily unavailable is skipped, never substituted. Also warms SymbolSelect/SeriesInfo for every reference symbol at InitNeuralNetwork, so the terminal's ~minute of async cross-symbol download starts at init instead of when the first Build() trips over an unselected symbol - the source of the startup 'only 0 usable reference pairs' console failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0c85c54a5b |
fix: a restart no longer loses the measured geometry or the training window
Terminal restart, 22:25: all four resumed models sat on empty windows with enum 2:6 barriers. Three interlocking causes, all visible in one log excerpt: 1) THE PRE-SCAN WINDOW WAS SIZED BY THE SAVED WATERMARK. A resumed model's dtStudied sits at its last studied bar, so Bars(dtStudied, now) ~ 0 and the resumed-model MI pre-scan built a zero-bar "complete" label cache - logged as "Buy: 0 | Sell: 0 | Neutral: 0". Train()'s own era start RESETS dtStudied to the training-window rule before computing its window; the pre-scan did not. The rule is now factored into TrainWindowStart() and both use it. The scan also refuses to arm before SERIES_SYNCHRONIZED (it ran in the same second as OnInit), and deployed models keep their watermark - for them it gates inference recency, not a training window. 2) THE HORIZON LATCHED ON AN INDICATOR WARM-UP. ComputeBarrierHorizonBars ran against a ZigZag with 0 calculated legs, fell back, and EnsureBarrierHorizon latched fallback(32) x slMult x tpMult = 384 for the process lifetime. A leg-starved horizon is now PROVISIONAL: re-resolved on the next rebuild, the label cache wiped if it moved (labels from two horizons answer different questions), and the geometry deriver refuses to run from it - a pair derived over a warm-up window would get PINNED. 3) THE DERIVED GEOMETRY WAS NEVER PERSISTED. The .cfg is written at model creation and at weights-reset - both BEFORE era 0 derives - so the measured pair lived only in memory: every restart read back zeros, adopted nothing, fell back to the enum barriers, and the era-0-only gate meant a resumed model could NEVER re-derive. A full day of training on 3.33/1.62 resumed as 2:6. Now: the settled pair is pinned to the .cfg the moment derivation completes (one-shot, atomic write), and the derive gate accepts any model with no pinned pair, not just era 0 - mid-run stability is carried by m_geometryDerived itself, which never allows a second derivation. Both build variants compile 0 errors, 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9a7c37f334 |
fix: live trades now use the geometry the gate certifies; perf: BN kernels
Three changes, one theme: the trade placed, the trade graded, and the trade computed are now the same trade. 1) GEOMETRY WIRE (correctness, the ranked #1 open issue). The measured barrier pair reached the LABELS only - OpenParams still placed orders at the enum geometry (2*ATR/6*ATR), so the deploy gate certified "reaches 1.62*ATR before 3.33*ATR above break-even" about trades the EA never placed. Published via g_DerivedSlAtrMult/g_DerivedTpAtrMult (ConfidenceBridge, same same-tick contract as the confidence globals, because OpenParams runs on the root signal which has no pointer to the AI filter). Two writers: DeriveBarrierGeometry at era 0, and the .cfg adoption a deployed model takes. Overrides both legs and both Intelligent modes - the certificate is exact or it is nothing. TP is ATR-anchored like the label, NOT risk-relative, so a floor-widened stop cannot reshape the certified target. 2) BATCH NORM RUNS DEVICE-SIDE ON OPENCL. Four kernels in Network.cl - forward, hidden gradient, gamma/beta accumulate, gamma/beta apply - each a line-for-line transcription of the host implementation (NormalizeHost / HiddenGradHost / StepGammaBeta) including every NaN guard, clamp, and the exact moment-write ordering. The host copies remain the runtime for the DLL and pure-MQL5 tiers and the reference the kernels must match. Because this box has no OpenCL platform, the safety story is layered: - shim validation: kernels compiled as C and driven against a fp64 host transcription over NaN-poisoned stats, NaN gamma, over-clamp inputs, the frozen path, both optimizers, 3 batches - ALL PASS, worst normalized diff 0.132 vs tolerance 1.0 - in-situ self-check: each kernel is compared against its host twin ON FIRST USE on the real device (SelfCheckBn*), covering what the shim cannot - arg indices and buffer bindings. Any disagreement resyncs from the good copy, latches all BN kernels off process-wide, and training continues host-side. A transcription bug costs a warning and some speed, never a poisoned .nnw. - sync discipline: BatchOptions is now a CBufferDouble with explicit authority tracking (m_bnDeviceAuthoritative). Checkpoints/saves pull read-only; restores/loads/resets push; a mid-batch handover drains the device gamma/beta accumulator into the host arrays so no sample is lost. 3) SMALL FIXES. Apply-kernel build failure now latches the dispatch path at init (one warning instead of warning + failed Execute). Build tag bumped to win-scoring-gpu-v1 - first tag change since expectancy-stop-v1 despite five binary-changing commits. Both build variants compile 0 errors, 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
19dfb91108 |
feat: fitted directional confidence threshold - selectivity gets a mechanism
The training loss and the selection metric wanted different things and only the second one knew it. Logit-adjusted cross-entropy has no term for "how often should I trade", so the head calls a direction on 87-91% of bars. The selection metric is precision x coverage credit, saturating at the coverage floor - above the floor extra calls earn NOTHING and only precision counts. So selection wanted few good calls, the loss produced many mediocre ones, and all selection could do was pick the least-bad era out of what it was handed. Nothing pushed the model toward selectivity. This gives the decision RULE the policy instead of distorting the loss (which is estimating class probabilities correctly, and a probability estimate should not be bent to encode a trading policy - Elkan 2001: estimate, then choose the operating point separately). AdjustedSignalFromSoftmax now abstains unless the winning direction's softmax margin over its best rival clears a fitted threshold. Margin, not the winning probability: the latter moves with overall calibration rather than with how close the decision actually was. Fitted on IS, applied to OOS and live. Pass 2 already forward-passes every IS sample, so the margin histogram is harvested there for free (primary occurrences only, so the oversampled replay queue cannot skew the operating point); the fit runs at the end of pass 2, BEFORE pass 3, so the deploy gate grades the thresholded model on bars the threshold never saw. Fitting on pass 3's own predictions would be choosing the operating point on the data being graded - the best-of-N error corrected in five other places here. Objective: maximise IS directional precision subject to still clearing the SAME coverage floor the deploy gate uses (base rate x 0.25, re-derived locally so the two cannot drift apart). Swept top-down in one pass; ties go to the LOWER threshold, since equal precision for less coverage is strictly worse. Under DIR_CONF_MIN_FIT_CALLS (200) it runs unthresholded rather than on a guess. The threshold is part of the MODEL, not the run: captured with Net.CaptureWeights(), restored with the weights at both restore sites, and appended to the .cfg under the same length-guard convention so a deployed model reloads at the operating point its gate actually cleared. A pre-2026-08-09 .cfg reads 0.0, which is exactly the behaviour it was trained under. Per-era line now prints "@margin>=X.XX" next to coverage, so a coverage drop can be attributed to the operating point rather than guessed at. Both build variants compile 0 errors / 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bfc1da9de1 |
fix: the sequence models were reading the window backwards
BuildFeatureWindow() replaces eight hand-rolled copies of the same loop
and feeds the window OLDEST BAR FIRST. Every copy fed it newest-first,
because MQL5 timeseries indices run backwards and `r + b` with b ascending
walks into the past.
Harmless for PAI and CONV - a dense layer learns a weight per position
either way, a conv learns time-mirrored kernels. Not harmless for the
recurrent stacks:
- LSTM_SeqStepForward reads `inputs + t*Iw`, so step t is block t.
- It writes output[] only when t == steps-1: the visible output IS the
last hidden state.
- c_t = f*c_{t-1} + i*g decays toward the start of the sequence.
lstm_seq_flowcheck.cpp measured block 0's influence on the output at
1.2e-2 of block T-1's, at the shipped forget bias of 1.0.
So the bar being PREDICTED sat at the far end of the decay and the output
was handed to the OLDEST bar in the window - the exact inverse of what the
window is for. ~80x backwards on LSTM and HYBRID, on all three tiers
(OpenCL kernel, CPU DLL, pure-MQL5 inference), which is why it never
surfaced as a backend discrepancy.
This does not create edge - the MI diagnostics read at the noise floor
(p=0.4975) with a working positive control. It makes the one hypothesis
those diagnostics explicitly do NOT cover testable: they are marginal and
per-bar, and state they "cannot rule out one that only exists in
combination or across time". The sequence model is the instrument for
across-time structure and it has been crippled, so that hypothesis has
never been honestly tested.
Fingerprint gets an unconditional |WIN:2 - the vector keeps its shape and
its features, so a stale .nnw would load cleanly and run a model fitted to
one ordering against the other, silently. Re-keying every config is the
point, not collateral damage. FORCES A FULL RETRAIN.
Also: the now-relative bar caches are re-keyed on the two live paths.
EnsureBarCachesCapacity() was only ever called from training paths, but
once m_trainingComplete is set ScheduleTrainingIfNeeded() routes every bar
to RefreshConvergedSignal() and Train() is never re-entered - so nothing
cleared the feature cache again for the life of the process. A chart that
trained to convergence kept replaying the rows computed for the last
training era's bar grid: the live signal froze at its convergence-time
value, and OnlineLearnStep() backpropped those stale features against
freshly resolved labels. Backtests were never affected (an inference-only
process never allocates the arrays, so every read recomputes).
Compiles clean: 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a7701f032b |
feat: derive the ATR multiples from measured excursions - no hardcoded geometry
The barrier was still two constants. SL_Mode/TP_Mode left the Inputs tab in |
||
|
|
3482b6c238 |
feat: entry/SL/TP stop being inputs - the barrier geometry is measured
Three enums left the Inputs tab. They were three things a user had to pick and, in the tester, three more axes for a genetic optimization to overfit. Entry_Multiplier is pinned to MARKET. Its pending modes place the entry at a LEVEL while the rest of the pipeline measures from the bar open - the exact mismatch that manufactured the +0.097 R "retail fade" result later retracted as a fill artifact. This codebase's fill model cannot honestly simulate a pending entry, so it is no longer offered. SL_Mode/TP_Mode become a STARTING pair. ReportBarrierGeometryScan now ADOPTS its winner instead of printing "set SL_Mode/TP_Mode to X and retrain": - only when it clears the family-wise gate from |
||
|
|
34d6aa42a4 |
feat(ai): real conv receptive field + the reference's channel pool
CONV's convolution used window = step = one bar, which is a per-bar projection - a 1x1 conv with a temporal receptive field of ONE BAR. It never mixed information across time, so "convolutional" described the layer type and nothing about what it computed. Same finding that sank HYBRID's LSTM. Pooling was removed on 2026-07-29 for being misconfigured against the conv output's memory layout. That removal was right; leaving the conv at a one-bar window was not. The two belong together: the NeuroNet_DNG reference (references\MQL5\Experts\EDL\Trajectory.mqh layers 2-5, kernels byte-identical to ours) pairs conv(window=2, step=1, window_out=4) with pool(window=4, step=4), and the pool only earns its place because a conv with a real receptive field sits above it. The input is bar-major (BufferTempData appends m_neuronsCount contiguous features per bar), so a flat window of k*m_neuronsCount spans exactly k bars - the receptive field needed NO kernel change. The conv output is position-major, so window == step == window_out is a clean max-over-channels, which is what the reference does and what the existing pool kernels already implement correctly. New chain at H1 defaults (420 = 20 bars x 21): conv1 w=42 s=21 out=8 -> 19 pos x 8 = 152 pool w=8 s=8 -> 19 conv2 w=2 s=1 out=8 -> 18 pos x 8 = 144 (effective field: 3 bars) We deliberately stop before the reference's SECOND pool: a channel pool emits one scalar per position, so a trailing pool would hand the dense stack 18 values and force it to fan out 18 -> 64. That is a bottleneck below every learnable layer - the same class of mistake the 2026-07-29 removal was about. Fixes a latent sizing bug this exposed: CNet's conv/pool position cursor tracked sliding POSITIONS, but a conv's real width is units_count * window_out. Any pool stacked on a conv would therefore have sized against a width window_out times too small and silently built the wrong shape. Both branches now read the built layer's actual Neurons(), which is what the batch-norm branch already did for the same reason. Also closes the architecture-pinning trap: a .nnw persists the window each conv was built with, so an existing CONV/HYBRID model would have loaded cleanly and gone on training under the OLD architecture. The conv weight tensor is (window+1)*window_out, so this cannot be repaired in place - EnforceTopologyContract now detects it, reports both shapes, and retrains. Conv chain shape is derived in one place (ConvReceptiveFieldBars / ConvFirstStagePositions / HasSecondConvStage / ConvOutputPositions / ConvOutputWidth) and consumed by AddConvStage, LstmFanIn and the startup config line, so what is built and what is logged cannot drift. Both builds compile 0 errors, 0 warnings. Forces a CONV and HYBRID retrain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
45b35b3d1d |
feat(nn): derive dense depth, train on all history, pin the shape in .cfg
Completes the derived-topology work. Three inputs removed. AIType loses its depth suffix - AI_MLP/AI_CONV/AI_LSTM/AI_HYBRID, five entries instead of eight. Depth is now derived from the two endpoints the taper already has to connect (derived first-layer width, output-tied final width) at a 2x per-layer compression target, clamped [2..5]. Asking a user to pick a layer count while the code derives the widths those layers taper between was asking for half a decision: at 64 units tapering to 12, four layers compress by 1.4x per step and five by 1.3x, so the extra depth bought no abstraction. On the shipping H1/10y default the derivation lands on 3 layers - the depth that actually won Run 2. StudyPeriods removed. There is no case for training on less data than the broker provides at a ~6% directional base rate; the honest generalization read comes from the OOS holdout, not from withholding history. Training now starts at the earliest available bar, floored by MinTrainYear, which answers a different question (excluding dubious pre-history) and stays. That required closing the hazard the old code documented: the capacity budget now MEASURES the symbol's real bar count, and a topology derived from a measurement would widen as history downloads. Both ends are now pinned. Every derived value left the weights-filename fingerprint - keying a filename on a measured quantity means the EA looks for a file that does not exist, starts from era 0 and orphans a trained model, silently, because a missing cache is the normal first-run state. The shape lives in the .cfg instead, where LoadAndCompare now ADOPTS the four derived fields rather than diffing them; a mismatch there would discard a fully-trained model over nothing the user did. Two fields appended to the .cfg for the conv/LSTM stages, length-guarded on read because FileReadInteger past EOF returns 0 with no error. ForceHiddenLayers, a compile-time constant like DebuggingMode, pins depth for diagnostic comparisons. It joins the fingerprint only when non-zero, so forced depths get their own files - sequential comparisons only, not simultaneous from one .ex5. Derived shape, H1/10y defaults (21 features x 20 bars): first layer 64, 3 dense, 8 conv filters, 16 LSTM units. The LSTM block halves from ~58k to ~28k weights. Both builds compile 0 errors, 0 warnings. Re-keys existing models. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
65dc1bddb4 |
fix(ai): freeze batch-norm statistics when comparing two forward passes
With normalization enabled a forward pass is not a pure function of its input - it also advances the running mean/variance. ValidateCpuInference compares the live backend net against a throwaway pure-MQL5 clone loaded from the just-saved .nnw, so its own reference pass left the live model one EMA step ahead of the file the clone reads. The check would then have been measuring its own side effect, and a marginal result decides whether buyers' backtests are allowed to run DLL-free. Adds CNet::SetBatchNormFrozen / CNeuronBatchNormOCL::SetStatsFrozen - classic batch-norm inference semantics, statistics used but not updated - and freezes both sides for the duration of the comparison. Not persisted: it is a transient evaluation mode, not model state. Default stays adaptive, which is what the rest of the system (online continual learning) is built around. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1aa7df9096 |
fix: stop a .nnw from pinning a superseded architecture
A .nnw persists the ARCHITECTURE, not just the weights: Save writes (int)activation per neuron and Load reads it straight back. The activation chosen in BuildFreshTopology() therefore only ever reached a brand-new topology - every reload restored the file's value and the next save wrote it back out, so a wrong value could never heal while the source read as though it were already fixed. That is how five models kept training with an unbounded NONE classification head for a full day after the 07-28 revert to SIGMOID. Confirmed by parsing the binaries: 848cb42c.nnw / 2e754b43.nnw carry `act=NONE` on the 3-neuron output layer, while a genuinely reset model of the same config carries act=SIGMOID. In the log it showed as negative "OOS raw out" values - impossible under sigmoid - escalating to a 4.14e13 logit spread with all three classes numerically identical (input-independent output) and balanced accuracy pinned on the 33.3% one-class floor. - OutputLayerActivation() is now the single source of truth, called by both BuildFreshTopology() and the new load-time repair, so the two can no longer diverge the way a duplicated literal did. - CNet::EnforceOutputActivation() re-asserts it after Load and reports the stale value; CExpertSignalAIBase::EnforceTopologyContract() logs the repair loudly, since weights learned under the old head may not be worth keeping even once the head is corrected. - Hidden layers are deliberately left alone: they legitimately differ per stage (PRELU dense/conv, NONE pool, TANH LSTM). Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2de93539d4 |
refactor: split CExpertSignalAIBase implementation by responsibility
ExpertSignalAIBase.mqh was 8216 lines: the class declaration followed by 87 method bodies covering training, labelling, feature extraction, persistence, chart drawing, online learning, the GA auto-tuner and inference, all in one file. Train() alone is 1492 lines; a change to arrow drawing meant scrolling past the era loop. Moved the bodies into Expert\AIBase\, included at the bottom of the original after the class declaration: Training.mqh 1607 era loop, plateau ladder, checkpoint select, deploy Features.mqh 1093 indicator creation + per-bar input feature vector ChartUI.mqh 634 arrows, arrow persistence, status panel, cleanup Persistence.mqh 492 .stats/.cfg sidecars, CPU-inference validation, copy OnlineLearning.mqh 461 live continual learning, EMA shadow, OOS simulator Labels.mqh 309 ZigZag pivot labels, async label-cache prebuild AutoTune.mqh 275 genetic tuner (population, crossover, halving) Inference.mqh 235 softmax, prior calibration, class priors ExpertSignalAIBase.mqh 8216 -> 3131 (declaration + topology build only) This is a pure relocation - verified mechanically, not by eye: HEAD's file reconstructed from the eight partials plus the surviving remainder is byte-identical to HEAD, span for span (scratchpad verify_split.py). No declaration moved, no signature changed, no code rewritten, so behaviour is unchanged by construction. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |