Introduce m_useSwingContext flag and FindConfirmedZigZagPivot method to compute normalized swing direction/magnitude/age features from the existing ADZigZag indicator. Only pivots that are at least m_swingConfirmationBars old are trusted, preventing lookahead bias. The SWING_SCAN_CAP_BARS macro limits backward scan depth. Default is off.
Remove overly verbose explanations in Network.mqh comments and InputEnums.mqh enum values. Shorten descriptions to improve readability without losing essential information.
Class-balance correction is now performed entirely via data-level oversampling (duplicating minority-class bars) instead of using a per-occurrence loss-weight multiplier combined with oversampling. The loss-weight mechanism proved ineffective due to Adam's near-invariance to constant gradient rescaling (Kingma & Ba 2015). The CLASS_SAMPLE_WEIGHT_PRESET enum is retained for a possible future supplemental loss weight, but m_maxClassSampleWeight currently has no effect. The MAX_OVERSAMPLE_REPLICAS constant is now the sole bound on correction strength.
Updated comments in InputEnums.mqh and ExpertSignalAIBase.mqh to reflect this change.
- Clarify MAX_OVERSAMPLE_REPLICAS comment: it is an outer safety cap; actual repCount is derived from m_maxClassSampleWeight.
- Introduce m_isTrainQueueWeightScale array to scale per-occurrence class-balance weight by repCount, avoiding independent compounding of frequency and magnitude.
- Add forceRefresh flag to UpdateTrainingStatusLabel to force panel update at era end, fixing one-era-behind display of era number.
Introduce MAX_OVERSAMPLE_REPLICAS=3 to duplicate minority-class (Buy/Sell) bars in the training queue, complementing the existing loss-level reweighting. Adam's update rule largely cancels gradient scaling, so pure loss weighting was insufficient to overcome class imbalance. Duplicating bars ensures Adam's moment estimates see minority gradients more frequently, preventing the Neutral-only collapse observed in practice.
Optimize() scaled lot size off account trade-history streaks with no Magic-number
filter (picked up other EAs'/manual trades) and an unconfigurable m_factor stuck at
1.0 (Factor() was never wired from an input), so a 3-trade streak could triple lot
size or send it negative. It was also entirely disconnected from what the AI model
actually knows about the current setup.
Replaced both AdjustRiskAmount()'s linear confidence-only scale and Optimize()'s
streak multiplier with one edge-based model: p from the empirically calibrated
AI/DB confidence magnitude, b from the trade's real reward:risk ratio (newly
bridged from OpenParams() via g_TradeRewardRiskRatio), quarter-Kelly applied and
clamped so risk% can only ever scale down from its configured ceiling, never above it.
Audit turned up a real gap for a prop-firm-portfolio-manager use
case: nothing in this codebase watched for account-level daily-loss
or max-drawdown breaches - the single most common way a prop-firm
evaluation actually gets failed.
New Signals/SignalRiskGuard.mqh (CSignalRiskGuard), wired into the
exact same filter-composition chain as SignalNewsFilter/
SignalSessionFilter (CreateSignalWithRetry/AddFilterToSignal, no new
architecture). Vetoes new entries only (never closes existing
positions - a materially bigger behavior change, left to the
trader/EA's own SL/TP handling) once either MaxDailyLossPct or
MaxDrawdownPct (new RISK_LIMIT_PCT_PRESET inputs, both default
disabled) is breached. Peak equity and the current broker day's
starting balance persist to a small local per-symbol-per-magic state
file - peak equity in particular must survive a restart to mean
anything, otherwise a restart would silently reset drawdown tracking.
New RISK_LIMIT_PCT_PRESET enum (2/3/4/5/8/10/15/20%) rather than
reusing PERCENTAGE_PRESETS, which steps by 10 starting at 10 - too
coarse for prop-firm-style limits (commonly single-digit daily loss,
~8-10% max drawdown). Compiled clean (MetaEditor, 0 errors/0
warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Price, time, volume, and volatility were already trained-model input
features; the real economic calendar (already used for the live
NewsFilter veto) is now an optional one too, reusing
System/NewsRelevance.mqh's symbol-relevance logic from the prior fix.
New EnableNews/NewsFeatureWindowMinutes inputs gate two features per
bar: minutes-since and minutes-until the nearest symbol-relevant
calendar event, impact-weighted. Deliberately limited to proximity +
impact, not actual-vs-forecast deviation - release schedules are
public knowledge ahead of time (not lookahead bias to use for a
historical training bar), but a release's actual outcome is not.
Wired identically to the existing EnableVolume/EnableTime/EnableATR
toggles: InitIndicators() accounts for the +2 neuron count,
BufferTempDataCompute() appends the two feature values, PAI/CONV/LSTM
all wired in Warrior_EA.mq5. Compiled clean (MetaEditor, 0 errors/0
warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CalendarValueHistory() was called with no country filter at all, so
ANY country's economic calendar event vetoed a trade regardless of
relevance - a JPY release blocked a EURUSD trade just as readily as a
USD one, making NF_MinImpact's fine-tuning far noisier than intended.
Adds System/NewsRelevance.mqh (GetRelevantCountryCodes/
ImpactWeightedProximity), a shared utility that cross-references
CalendarCountries() against the symbol's base/quote currency to get
the actually-relevant ISO country codes, then uses
CalendarValueHistory()'s country_code-filtering overload. Shared so
the upcoming NN news-input feature reuses the same relevance logic
rather than duplicating it.
Compiled clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AI/Network.mqh was 5,805 lines / 18 classes in one file. Investigation
found method implementations for several classes (CNeuronBase/Pool/Conv,
CNeuronBaseOCL) hand-interleaved across thousands of lines - not safe to
split without risky manual reassembly. But 8 classes turned out to be
genuinely self-contained (declaration + every method body physically
contiguous, and only ever depended upon, never depending on anything
declared later): CConnection/CArrayCon, CNeuron, CDirectMLMy (+ its
WarriorDML.dll/WarriorCPU.dll #import blocks), CArrayLayer,
CLayerDescription, CBufferDouble, and CNeuronConvOCL/CNeuronPoolOCL.
Extracted each verbatim, via exact line-range extraction (not manual
retyping) to eliminate transcription risk, into its own AI/*.mqh file,
included from Network.mqh at the exact point each class used to sit -
preserving original declaration order exactly. Mathematically verified
byte-for-byte: reconstructing the original file from the 7 new files'
bodies + Network.mqh's remaining segments is line-for-line identical to
the pre-split git history. Compiled clean (MetaEditor, 0 errors/0
warnings) both before and after.
The remaining tangled classes (CNeuronBase, CNeuronPool, CNeuronConv,
CNet, CNeuronLSTM, CNeuronBaseOCL, CNeuronConvOCL/PoolOCL's shared base,
CNeuronLSTMOCL, COpenCLMy) stay in Network.mqh (now ~4,450 lines) -
splitting those safely needs deliberate per-method surgery, deferred to
a future dedicated pass rather than rushed into this one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CMoneyFixedRisk and CMoneyIntelligent both derived independently from
CExpertMoneyCustom and carried near-identical CalculatePotentialLoss()/
CheckOpenLong()/CheckOpenShort()/CalculateLotSize() bodies, maintained
in two places. New Money/MoneyRiskBase.mqh (CMoneyRiskBase) houses the
shared core, with two virtual hooks - AdjustRiskAmount()/AdjustLotSize()
- at the exact two points CMoneyIntelligent's confidence-scaling and
trade-history Optimize() step used to diverge. CMoneyFixedRisk now has
an empty body (the base's defaults are exactly its old behavior);
CMoneyIntelligent overrides only the two hooks. CMoneyFixedLot is
untouched - it doesn't do risk-based sizing.
Pure reorganization, no behavior change; compiled clean (MetaEditor,
0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Variables/Inputs.mqh: drop the dead commented-out HybridSignals input
line; add a one-line note above the section-header input strings
clarifying they're intentional MetaTrader GUI dividers (consumed by
the terminal, not any MQL5 statement) so a future audit doesn't
re-flag them as unwired.
- Signals/SignalSessionFilter.mqh: replace the never-filled-in MQL5
Wizard template header (ProjectName/CompanyName placeholders) with
this codebase's real header, matching every other Signals/*.mqh file.
- Signals/SignalNewsFilter.mqh: remove the stale NEWS_IMPACT template
macro - it was only ever used as the constructor default, immediately
overridden by the real NF_MinImpact input at wiring time.
- Expert/ExpertSignalAIBase.mqh: guard the SERIES_LASTBAR_DATE read in
ScheduleTrainingIfNeeded() so a failed lookup (0) can't silently be
read as "no new bar pending" and stall training/signal refresh.
Compiled clean (MetaEditor, 0 errors/0 warnings) after each change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
trainingMode, ClassSampleWeight, FocalLossGamma, and SwingConfirmationBars'
inline display comments measured 214-306 chars combined with their
identifier - over MQL5's documented 255-char identifier=value ceiling for
the terminal/testing-agent exchange protocol, risking silent cropping in
the Inputs dialog. Trimmed to a max of 189 chars; full rationale for each
stays in the existing block comments above each input.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
m_isPass2Active going false was ambiguous between "pass 2 hasn't
started yet" and "pass 2 already finished" - both look identical to
a plain if(!m_isPass2Active) check. Once pass 3 (OOS scoring) needed
more than one chunk to finish, every resume into it fell through that
ambiguity on both the pass-1 and pass-2 gates and re-shuffled/replayed
the entire IS training queue from scratch, on top of never-reset
predicted-class counters - observed as era 0 looping the shuffle phase
forever with predicted Buy/Sell collapsing to 0 and predicted Neutral
climbing past the real bar count.
Adds a distinct m_isPass2Done flag so a resume can tell "not started"
apart from "already done" and correctly skip straight into pass 3.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace hardcoded lr and momentum with new input variables for Adam and
SGD+momentum. Add OpenCL kernel LSTM_UpdateWeightsMomentum alongside the
existing Adam kernel. Update comments and revert beta1 to book default 0.9.
Add a new post-training pass (m_isPass3Active) that scores the OOS region chronologically after pass 2 has trained the IS data. This fixes the previous stale scoring (which used pre-era weights) and ensures OOS metrics reflect the trained model.
New member variables track the OOS cursor and start index; UpdateTrainingStatusLabel extracts the inline formatting into a dedicated method to keep the panel updating during all three passes, eliminating the "stuck" appearance during pass 2/3.
Reset m_isPass3Active at era start and remove redundant inline formatting code from Train().
Adam's momentum (b=0.9) has an effective memory of ~1/(1-b)=10 steps, which closely matches the typical 5-bar contiguous same-class label runs around ZigZag pivots. Replaying these runs in the same chronological order every era allowed momentum to lock onto whichever run it was currently passing through, causing the network's end-of-era state to disproportionately reflect the most recent run (recency bias). This led to IS error climbing era-over-era (0.16→0.55+ over 25 eras) and OOS Buy/Sell recall whipsawing between near-0% and 70-100% despite near-equal label counts.
The fix implements a second pass within the resumable-chunk training loop: after pass 1 (sequential feedforward/scoring) queues all IS-eligible bars into an array, pass 2 trains on those bars in a freshly shuffled order – the standard SGD solution to break momentum’s lock on repeating patterns. New member variables `m_isTrainQueue`, `m_isTrainCursor`, and `m_isPass2Active` manage the queue and state transitions between passes, preserving the existing `TRAIN_TIME_BUDGET_MS` chunk-yield mechanism.
- Lowered Adam momentum parameter b1 from 0.9 to 0.8, shortening the effective memory window (~5 steps) to reduce multi-era bias streaks observed in training runs.
- Added FOCAL_GAMMA_PRESET enum and corresponding member variable m_focalGamma to apply focal loss modulation (Lin et al. 2017) on top of class-balance weighting, targeting the same streak issue by downweighting already-confident examples.
- Updated comments to document the rationale and experimental nature of both changes.
Replace the hardcoded `MAX_CLASS_SAMPLE_WEIGHT` (3.0) with a tunable member variable `m_maxClassSampleWeight`, controlled by the new `CLASS_SAMPLE_WEIGHT_PRESET` input enum. This allows adjusting the ceiling on rare-class sample weights to prevent the previously observed anti-correlated Buy/Sell recall whipsaw (where a high multiplier caused one era's gradients to overwrite another class's separability). Default is CSW_15 (1.5x), which is gentler than the old 3.0x; lower values down to 1.0x disable rebalancing to train on raw label distribution, while higher values (up to 3.0x) converge faster but risk instability. This tuning can now be done without recompiling.
- Removed TEST_HOLDOUT_PRESET enum and its associated member variables (m_testHoldoutPct, dTestForecast, m_testSamples) from ExpertSignalAIBase to simplify code and eliminate unused test slice logic.
- Added m_lastNonNeutralSignal member to track the last non-neutral signal during live inference, preventing LongCondition()/ShortCondition() from opening a second consecutive same-direction trade (which would be a false fire based on training patterns where valid reversals always alternate direction).
- This live-only mechanism does not affect historical training or backpropagation.
- Ensure `tick_volume` array is set as series in ADShorteningOfThrust.mq5 to prevent future-data leak in volume calculations.
- Ensure `open` array is set as series in ADWyckoffFailedStructure.mq5 to prevent future-data leak in structure detection.
- Add missing PReLU gradient scaling (multiply by 0.01 for negative outputs) in CPU_CalcHiddenGradient and DirectML shader to match expected derivative behavior across all backends.
Replace the raw debug dump (event call, prevSignal, studied time) in the status label with a clean, user-friendly summary showing era count, training state, and forecast. This provides clearer on-screen feedback when training stops, pauses, or completes.
The training progress display now includes the in-sample split percentage (100 - oosSplitPct) next to the accuracy, replacing the previously generic "IS Acc" label. This makes it clearer what proportion of data is used for in-sample training.
The training status panel was too tall and wordy, displaying detailed recall, precision, and continual-learning OOS simulation metrics that are not needed for at-a-glance monitoring. These metrics are still tracked internally for convergence gating, but removed from the on-chart display to keep the panel compact. Also shortened label names (e.g., "[IS] Accuracy" -> "IS Acc") to save space without reducing clarity.
TextGetSize returns uint values, so dummyW, dummyH, textW, and textH
changed to uint. Added explicit casts to int where used in comparisons
or arithmetic to prevent signed/unsigned mismatch warnings.
Replace the static per-character width approximation with dynamic word-wrapping using
`TextGetSize()` to measure actual rendered pixel widths. This prevents status label text
from being clipped at the chart edge while its oversized background rectangle extended
beyond. Each line is now greedily wrapped to fit within the chart pane minus margins,
ensuring the measured string matches what is drawn. Also extracts font constant and adds
`WrapLineInto()` helper for reuse.
OBJ_LABEL does not render embedded '\n' as line breaks, causing multi-line status text to appear as one truncated line. This commit splits the input text by newlines, creates a separate OBJ_LABEL and tightly-fit OBJ_RECTANGLE_LABEL background for each line, ensuring every line remains legible regardless of chart background.
Migrate all per-era classification counts, OOS confusion metrics, and training progress output from the legacy Comment() function to a dedicated StatusLabel object. This provides cleaner UI integration and avoids blocking the chart's normal info line during prolonged training cycles. A new include for StatusLabel.mqh has been added, and all related documentation comments updated accordingly.
Previously, on a successful checkpoint load (`netLoaded == true`), `m_labelCachePrebuilt` was set to `true`, skipping the eager prebuild scan. This caused every bar to fall back to `ComputeLabelForBar()`, a dead stub returning `buy=false/sell=false`, effectively labeling the entire era Neutral. Now we unconditionally set `m_labelCachePrebuilt = false`, ensuring the real ZigZag-based labeling logic runs via `AdvanceZigZagLabelState()` during the prebuild, regardless of checkpoint state.
- Add inline threshold (512) in WarriorCPU to avoid thread-pool overhead for small dispatches; run small workloads inline on the calling thread.
- Reduce training time budget from 500ms to 120ms in ExpertSignalAIBase to keep the UI reactive while training.
All __global buffers, local vectors, and constants (MAX_WEIGHT, MIN_ACTIVATION_DERIVATIVE, WEIGHT_DECAY, MAX_WEIGHT_DELTA) now use `float` instead of `double`. This avoids the 1/16th throughput penalty on consumer GPUs (e.g. AMD Polaris/RX 580) while remaining numerically safe: weight/delta clamps stay within ±100, gradients and activations fit float32, and the Adam epsilon guard is not sensitive to underflow. The removed `cl_khr_fp64` directive now causes an explicit compile failure if any double slips back in.
The network uses PReLU (leaky ReLU) activations for all hidden layers, but the previous initialization used LeCun-uniform scaling (1/sqrt(fan_in+1)), which is tuned for saturating activations like tanh/sigmoid. The new He-uniform scaling (sqrt(2/fan_in)) better accounts for the half-zeroing effect of ReLU variants, reducing the risk of early saturation and improving gradient flow during training. Applied uniformly across all layers via the shared Init() method, as the output layer (3 neurons) is negligibly affected and the more precise alternative would require risky threading of activation awareness through every neuron subtype.
- Extend `CNet::getResults` and `CLayer::Load` to support `defNeuronConvOCL`, `defNeuronPoolOCL`, and `defNeuronLSTMOCL` types (previously only `defNeuronBaseOCL` was handled), preventing potential crashes or incorrect outputs with those neuron types.
- Increase `ind_Periods` default from `PERIOD_5` to `PERIOD_20` because `PERIOD_5` was smaller than ADZigZag's `InpDepth=12`, making it structurally impossible for the model to see enough bars to recognize swing pivots, likely causing the erratic Buy/Sell OOS recall observed during training runs.
- Added MAX_CLASS_SAMPLE_WEIGHT (3.0) to cap the sample weight multiplier, preventing anti-correlated Buy/Sell recall whipsaw observed when per-era ratio overwrites previous separability.
- Added MIN_OOS_CLASS_SAMPLES_FOR_GATE (10) to require a minimum number of true OOS samples before trusting class recall, avoiding degenerate false-convergence cases.
- Increased TRAIN_TIME_BUDGET_MS from 80 to 500 to improve throughput under MT5's custom event dispatch model (which determines actual pacing); trades UI responsiveness (~500ms lag) for roughly 6× more bars processed per event.
The exact-bar-only label scheme caused OOS Buy/Sell recall stuck at 0-3% because the feature window cannot distinguish a pivot bar from its near-identical neighbor. By extending each confirmed pivot into a +-LABEL_WINDOW_BARS neighborhood (default 2), the network is trained to recognize bars near a real reversal rather than only the exact tick. The spread uses a frozen snapshot of the original scan to prevent cascading propagation.
A freshly-initialized network's weights are plain random init with no
informed prior, so its argmax is close to uniform noise across the 3
classes - on this project's typically heavily Neutral-skewed label
distribution, that means era 0 fires far more spurious Buy/Sell calls
on the very first (oldest) bars than the true base rate warrants,
until enough backProp steps correct it.
The label-cache prebuild already computes the real upfront Buy/Sell/
Neutral tally before era 0 starts. Added CNet::SeedOutputLayerBias()
(AI/Network.mqh) to overwrite just the output layer's bias term (the
per-input weights stay randomly initialized and still carry the real
learning signal) with a fixed +-3.0 push toward whichever class
actually dominates - reaches one layer back from the output layer,
since that's where the weight block producing it is stored. Wired in
at the end of AdvanceLabelCachePrebuild(), fresh-network-only.
ADCumulativeDelta's CumulativeDelta buffer now outputs
cumulativeDelta/sumVolume clamped +/-2 instead of a raw, unbounded,
tick-volume-scale running sum - also fixes the indicator's own
declared -2..2 min/max property, which the raw sum silently violated.
Re-added to the feature set now that it's properly scaled, as a
distinct order-flow-imbalance signal from Pressure (same term plus
Initiative/Absorption adjustments).
Added an explicit +1/-1/0 bullish/bearish/doji flag alongside the
ATR-normalized price features, so the network gets candle direction
as a clean standalone signal instead of having to disentangle it from
(close-open)/atr's combined direction+magnitude encoding.
The training log showed the model had genuinely collapsed to always-predict-
Neutral: 100+ consecutive eras with Buy/Sell OOS recall flat at 0% and IS
error frozen exactly at 0.14, not a "still early" transient. Root cause is
the 33:1 Buy/Sell-vs-Neutral label imbalance combined with the oversample
replay cap - capped at 3x specifically because more identical back-to-back
backProp() calls fed Adam highly-correlated gradients and caused runaway
momentum (a past incident: OOS accuracy diving from 90%+ to single digits
within ~20 eras). That cap meant minority classes never got enough gradient
influence to matter once the model settled into all-Neutral.
Replaced the N-times replay with a single backProp() call per example, with
its output-layer gradient scaled by inverse class frequency (maxCount/
trueCount from the previous era's true label distribution, uncapped - the
existing MAX_WEIGHT_DELTA per-step clip already bounds how far any single
step can move a weight regardless of gradient magnitude, so there's no
repeated-gradient momentum risk left to cap against).
AI/Network.mqh: CNet::backProp()/backPropOCL() take a new optional
sampleWeight parameter (default 1.0, so every other caller is unaffected).
For the OCL/DirectML path, since WarriorCPU.dll/WarriorDML.dll/Network.cl
have no notion of per-sample weighting, the raw gradient computed by the
native CalcOutputGradient call is read back into MQL5, scaled, and written
back via a new CNeuronBaseOCL::setGradient() before the hidden layers read
it - no changes needed to any of the 3 compute backends themselves.
Also fixed: a run that "converged" at era 63 only because that specific
era's small OOS sample happened to contain zero true Buy/Sell examples
(recall shows n/a and auto-passes the gate when a class is absent from an
era's sample) - the model had already fully collapsed several eras earlier;
this was a lucky/unlucky sampling fluke, not real convergence. Not fixed in
this commit (separate, narrower issue - the gate's n/a auto-pass exists to
avoid deadlocking on a genuinely rare class, and distinguishing that from a
collapsed model needs its own follow-up).
Needs a fresh retrain like the prior structural fixes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AI/Network.mqh: CNet::feedForward() was adding sin(i)/cos(i) (alternating by
index parity) directly onto every single input value, on both the CPU and
GPU/DirectML paths - an undocumented, uncontrolled +/-1 offset baked into
already-normalized features that mostly live in a much smaller range. No
comment anywhere explained it, unlike the rest of this codebase; removed.
Expert/ExpertSignalAIBase.mqh, data normalization cleanup:
- Volume relative-change feature is now clamped to +/-5 - unlike the
ATR-normalized price features, it had no ceiling, so a thin previous bar
(e.g. 1 tick) followed by a normal one could feed the network an outlier
input an order of magnitude past every other feature's range.
- Dropped ADCumulativeDelta's raw CumulativeDelta buffer (1) from the feature
set - it's an unbounded, tick-volume-scale running sum, unlike every sibling
buffer in that same indicator (all explicitly clamped +/-2). Pressure
(buffer 0) is this same signal already normalized, so nothing is lost.
- Dropped ADWyckoffEventStream's EventPhase buffer (1) - confirmed via source
it's a byte-for-byte duplicate of EventCode (same underlying variable), not
a distinct phase reading; StructuralPhase (buffer 5) is the real phase
signal and stays.
m_neuronsCount updated accordingly (topology changes, needs a fresh retrain
same as the earlier window-offset fix).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moving the per-bar window/feedForward step before the label-check/backprop
step (previous commit) meant the label-check block now runs LAST each
iteration, clearing TempData and refilling it with the smoothed target label
(0.1/0.9) for backProp - so by the time the next iteration's Comment() call
read TempData[0..2] to display "Buy/Sell/Neutral Neuron", it was showing the
PREVIOUS bar's true label, not the network's actual prediction. Since ~94% of
bars are Neutral-labeled, this mostly looked like "Neutral stuck near 0.9-1,
Buy/Sell stuck near 0.1" regardless of what the model was really outputting.
Snapshot the softmax probabilities into local vars right after they're
computed (before the label-check block can clobber TempData), and moved the
whole Comment() build to run right after that snapshot instead of at the top
of the loop, so it now shows the CURRENT bar's own genuine prediction.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RefreshLatestSignal(), AdvanceOosSimulationChunk(), and Train()'s per-bar window
build all constructed the historyBars-wide feature window starting historyBars
bars past the target bar (r = i + historyBars) instead of ending at it (r = i) -
so the model was always blind to the most recent historyBars bars of price
action right before the bar it was being asked to call, both in training and
live inference. This likely explains why Buy calls fire but land poorly timed
and Sell almost never clears its recall floor.
Also reordered Train()'s main loop so each bar's own freshly-built window/
prediction is what gets checked against that bar's label immediately (matching
AdvanceOosSimulationChunk's existing predict-then-learn pattern), instead of
training against the previous iteration's stale prediction.
Also added a Print() when training genuinely converges (m_objectiveMet &&
m_oosStable) - this was previously silent, only visible via the persisted
TrainingComplete flag.
Existing trained weights encode the old, shifted task and need a full retrain.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TargetCPULoad=max was only ever getting ~half the cores (e.g. 6 of 12)
even outside HYBRID mode, because every CNet (including the live net's
own EMA shadow net, which every signal has) counted as a "peer" that
divided the requested CPU budget.
That division was solving a problem that can't actually happen: MQL5
gives one chart's EA a single execution thread, and every WarriorCPU.dll
entry point blocks that thread until its own ParallelFor() completes,
so only one CNet's worker pool is ever actively computing regardless of
how many are alive (live, shadow, or - under HYBRID - PAI/CONV/LSTM's
own pairs). The idle ones sit blocked on a condvar at ~0% CPU. Dividing
the budget across "peers" that never contend just capped whichever pool
happened to be running at a fraction of the cores actually available.
Removes the now-pointless g_netPeerCount/EffectiveCpuLoadPercent()/
PeerNetworkCount() machinery entirely; SetCpuLoadPercent() now just
takes TargetCPULoad directly.
Recall (directionalRecallOK) was only ever printed on a regression
event, so once OOS accuracy alone cleared the target there was no way
to tell from the Journal whether recall was the thing still blocking
m_objectiveMet. Now shown on every ~5s progress line.
Two issues compounded to keep the network oscillating for 350+ eras
without ever reaching m_objectiveMet, despite good OOS accuracy:
- CNet::backPropOCL() zeroed the diagnostic error contribution from
any output neuron whose target was exactly 0, unlike backProp()'s
CPU-fallback path. For the one-hot 3-class classification head this
meant dError only ever reflected the true class's own calibration,
never whether the other two classes were correctly suppressed -
purely a reporting bug, the real gradient (CPU_CalcOutputGradient
in WarriorCPU.cpp) was already correct and unaffected.
- Train()'s convergence gate required dError < 0.1 unconditionally.
That RMS-error floor is reachable for the single-neuron regression
head, but for 3 one-hot outputs it demands near-perfect confident
calibration on every bar - unreachable under normal market label
noise, and redundant with the classification-native gates already
in place (dOosForecast >= target, per-class directionalRecallOK).
Now skipped when m_outputNeuronsCount == 3.