Warrior_EA/Variables/Inputs.mqh

769 lines
66 KiB
MQL5
Raw Permalink Normal View History

feat(ai): logit-adjusted loss, replacing oversampling and the post-hoc prior Menon et al. 2021 (ICLR), "Long-tail learning via logit adjustment": add tau*log(prior_c) to each class logit inside the training gradient. Softmax CE on adjusted logits is consistent for BALANCED error - the metric checkpoint selection already ranks on - so the loss and the deploy decision finally optimize the same thing. The engine already computed a true softmax + categorical-CE gradient and wrote it over the per-neuron sigmoid delta, so this is an offset added to three logits in the two places that gradient is built (backProp scalar path and backPropOCL). No backend, kernel or DLL change; the forward pass and every inference path are untouched, which is the point - the network learns to absorb the offset, so its raw argmax becomes the balanced-optimal decision with nothing applied at inference. Replaces rather than stacks. Minority replay is disabled while this is on, and the post-hoc inference prior is forced off. Stacking is not a theoretical worry: simulated on the measured 1118/1119/34298 distribution in the weak-signal regime, plain CE collapses to Neutral (33.4% balanced, Buy 0%), replay reaches 48.1%, logit adjustment 50.9% with better balance - and BOTH together score 45.4% with Neutral recall at 0%, worse than either alone. Buda et al. 2018 predicts exactly that. Motivation from the six-chart run: every topology took one direction to ~50% recall and abandoned the other, the direction chosen arbitrarily (the batch-norm control went Buy 1% / Sell 42%, the inverse of the other five). One era in 1,301 cleared the per-class recall floor. Fingerprinted conditionally, so the converged 60.7% models on disk keep their filenames and stay loadable as the fallback. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:05:14 -04:00
//+------------------------------------------------------------------+
//| Inputs.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "AnimateDread"
#property link "https://www.mql5.com"
#include "..\Enumerations\InputEnums.mqh"
//--- Each `input string *_Settings` below is a GUI-only section divider: MetaTrader renders an input
//--- string whose value equals its comment as a header. Never read by MQL5 code - that's expected, not
//--- dead wiring. Sections are ordered most-used first: General, Money, Trade, Classic Signals,
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- Neural Network, AI Input Features, Filters, Trade Journal - then NN Optimizer / Performance LAST.
//--- The Neural Network block sits directly ABOVE AI Input Features because that is the reading order a
//--- user actually needs: choose the architecture, then choose what it sees. NN Optimizer / Performance
//--- must remain the final divider in this file - the Adam/Sgd inputs are declared in AI\Network.mqh and
//--- render immediately after it, so anything added below would land inside that group.
//==================================================================================================
// GENERAL
//==================================================================================================
input string Expert_Settings = "General"; // General
input ulong Expert_MagicNumber = 2024; // Magic number (unique EA id)
input bool Expert_EveryTick = false; // Calculate on every tick
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- NO LONGER AN INPUT (2026-08-01). The detailed panel/journal is a developer view: a buyer does not
//--- care which plateau stage the ladder is on, and every row in the Inputs tab is a row they have to
//--- read past to reach something that matters. Same reasoning that already applied to DebuggingMode
//--- below, so the two now sit together. Flip to true and recompile to work on the EA.
const bool VerboseMode = false;
fix(ui): unique chart tag, product-grade panel, responsive under load Three separate reports from one deploy. 1. CONV, LSTM and HYBRID all came back tagged [4109]. The weights fingerprint omits the topology type on purpose - the file path already separates it (State\CONV\ vs State\LSTM\ vs State\HYB\) and hashing a value that is constant within a folder buys nothing while re-keying every trained model into a forced retrain. So the files were never at risk, but the tag could not do its one job. Prefixing the short id makes it unique on the display side only; the hex half still greps straight to the .nnw inside the folder the prefix names. 2. The default panel read like a training console. Six lines down to three, each answering a question an owner actually has. The deploy internals (best score, eras-since-best, ladder stage) were developer diagnostics describing a recall floor that no longer decides anything, and were already in the era-end journal line. In-sample accuracy left the panel too: it grades the model on bars it trained on, so it always flatters, and showing it beside the honest number invites reading the wrong one. New compile-time DebuggingMode constant - deliberately not an input - carries the IS/OOS pair and the resolved model path into the journal instead. No extra Inputs row, no extra Market description line, no user-reachable firehose. 3. Panel drag and buttons stuttered under training load, exactly as the 2026-07-26 note raising the chunk budget to 200ms warned they might. Backed off to the documented 120ms - worst-case click latency is that budget - and the derived topology (~292k weights to ~29k) makes the throughput this costs far cheaper than when that note was written. Also halved the panel redraw rate to 2.5 Hz: ChartRedraw repaints the whole chart, so its cost scales with accumulated arrows, and 5 Hz was the larger half of the stutter. Era-end still force-refreshes. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:05:58 -04:00
//--- DELIBERATELY NOT AN INPUT. Development diagnostics: dumps the training internals that used to sit on
//--- the on-chart panel (plateau-ladder stage, eras-since-best, the deploy gate, selection internals) into
//--- the Experts journal instead, where they cost the user nothing. This is a commercial product - the
//--- default panel has to read like a product, not like a training console, so anything a buyer cannot act
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- on belongs in a log. Flip to true and recompile when diagnosing a training run.
fix(ui): unique chart tag, product-grade panel, responsive under load Three separate reports from one deploy. 1. CONV, LSTM and HYBRID all came back tagged [4109]. The weights fingerprint omits the topology type on purpose - the file path already separates it (State\CONV\ vs State\LSTM\ vs State\HYB\) and hashing a value that is constant within a folder buys nothing while re-keying every trained model into a forced retrain. So the files were never at risk, but the tag could not do its one job. Prefixing the short id makes it unique on the display side only; the hex half still greps straight to the .nnw inside the folder the prefix names. 2. The default panel read like a training console. Six lines down to three, each answering a question an owner actually has. The deploy internals (best score, eras-since-best, ladder stage) were developer diagnostics describing a recall floor that no longer decides anything, and were already in the era-end journal line. In-sample accuracy left the panel too: it grades the model on bars it trained on, so it always flatters, and showing it beside the honest number invites reading the wrong one. New compile-time DebuggingMode constant - deliberately not an input - carries the IS/OOS pair and the resolved model path into the journal instead. No extra Inputs row, no extra Market description line, no user-reachable firehose. 3. Panel drag and buttons stuttered under training load, exactly as the 2026-07-26 note raising the chunk budget to 200ms warned they might. Backed off to the documented 120ms - worst-case click latency is that budget - and the derived topology (~292k weights to ~29k) makes the throughput this costs far cheaper than when that note was written. Also halved the panel redraw rate to 2.5 Hz: ChartRedraw repaints the whole chart, so its cost scales with accumulated arrows, and 5 Hz was the larger half of the stutter. Era-end still force-refreshes. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:05:58 -04:00
const bool DebuggingMode = false;
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>
2026-07-30 10:05:40 -04:00
//--- ALSO NOT AN INPUT, and for a stronger reason than DebuggingMode. Pins the dense-taper depth instead
//--- of deriving it (ComputeHiddenLayerCount), purely so a depth comparison can still be run while
//--- working on the EA. 0 = derived, which is the only value that should ever ship. A user who picks a
//--- depth is contradicting the first-layer width and the taper the code derived around it - that
//--- contradiction is exactly what the MLP_3L/MLP_4L presets used to allow.
//--- NOTE the limitation: this is compile-time, and it feeds the weights-filename fingerprint only when
//--- non-zero, so two forced depths get their own model files but cannot run SIMULTANEOUSLY from one
//--- .ex5. Depth comparisons are sequential unless you deploy two separately-compiled builds.
const int ForceHiddenLayers = 0;
//==================================================================================================
// MONEY MANAGEMENT
//==================================================================================================
input string MM_Settings = "Money Management"; // Money Management
input MONEY_MANAGEMENT_STRATEGY MM_STRATEGY = FIXED_RISK; // MM strategy
input MONEY_RISK_PERCENT_PRESET Money_Risk_Percent = RISK_PCT_1; // Risk % of balance per trade
input double Money_FixLot_Lots = 0.01; // Fixed lot size [0.01-10]
//==================================================================================================
// TRADE MANAGEMENT (entry / stop / target / trailing / exit)
//==================================================================================================
input string Entry_Settings = "Trade Management"; // Trade Management
input TRADING_DIRECTION tradingdirection = BOTH; // Trade direction
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 04ee2e1 (beat the null of the MAXIMUM, not merely the incumbent). This is why that gate had to land first: without it, removing the inputs would hand a noise-picked geometry direct control over the training target with no human in the loop - strictly worse than the input it replaced. On SP500 H1 today it does NOT clear (p=0.1463), so 2:6 is what you get - now chosen by measurement rather than assumed. - only at m_eraCount == 0. Relabelling a partly-trained net moves the target out from under weights already fitted to the old one. THE GEOMETRY LEFT THE WEIGHTS-FILENAME HASH, because it is now measured. Same rule that moved the horizon and the derived topology values out: a filename keyed on a measured quantity changes the moment the measurement does - a few more bars shift which pairing wins - and the EA then looks for a file that does not exist, starts from era 0 and orphans a trained model silently. It is PINNED IN THE .cfg instead: appended at the end (the only backward-safe change), length-guarded like the 2026-07-30 derived pair, and ADOPTED on load rather than compared, so a trained model keeps the barriers it actually learned and never re-measures. Two traps closed while wiring it, neither of which announces itself: - m_barrierHorizonResolved latches the horizon ONCE PER PROCESS. Adopting 2:8 (wants ~192 bars) after it settled for 2:6 (128) would label the new target against the old ceiling - the truncation fixed in 168422f, where every model learned "target within 128 bars" while the EA holds to SL/TP. It lands in Neutral, not in the timeout counter watching for it. Unlatched on adoption, along with the label cache the old barriers filled. - the .cfg adopt runs at init, before the horizon latches and before any label is computed, so a resumed model has its pinned pair in place first. Verified, not assumed. FORCES A FULL RETRAIN: the fingerprint change orphans every existing .nnw. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 09:39:30 -04:00
//--- ENTRY / STOP / TARGET ARE NO LONGER INPUTS (2026-08-07). They were three enums the user had to pick,
//--- and in the tester they were three more axes for a genetic optimization to overfit. The barrier
//--- geometry is now MEASURED (ReportBarrierGeometryScan picks the SL:TP pairing that carries the most
//--- entry-time information about its own outcome, and only adopts it when it clears a family-wise
//--- significance gate - otherwise these defaults stand). Kept as named constants rather than deleted so
//--- every existing reference still reads the same, and so the fallback is stated in one place.
//---
//--- Entry is pinned to MARKET deliberately. The pending-order modes place the entry at a LEVEL while the
//--- rest of the pipeline measures from the bar open, which is precisely the mismatch that manufactured
//--- the +0.097 R "retail fade" result later retracted as a fill artifact - a pending entry cannot be
//--- honestly simulated by this codebase's own fill model, so it is not offered.
const ENTRY_MULTIPLIER Entry_Multiplier = MARKET; // Entry type/offset (fixed - see above)
//--- STARTING geometry only. The scan may replace this pair at era 0 on a fresh model; a model that has
//--- already been trained reads its pinned pair back out of the .cfg and never re-measures, so the labels
//--- a run started with are the labels it finishes with.
feat: remove Min_Risk_Reward_Ratio - a guess was overriding a measurement The barrier geometry is derived from the instrument's own excursion distribution (stop at q75 of adverse travel, target at q50 of favourable), and then a 1:2 floor was applied on top, raising the target to twice whatever the stop happened to be. On SP500 H1 that pushed the target to 6.66*ATR, reached on 3.3% of bars inside the horizon - so the label became "almost never a win" and every topology was trained to predict an event that essentially does not occur. A measured target has to stay measured. The ratio never bought what it was believed to buy. A reward:risk floor does not create expectancy; it trades hit rate against payoff at a break-even the geometry already fixes - which this project has separately MEASURED (payoff 0.92 -> 5.72 with expectancy flat). What it did buy was two outages: four consecutive Market validation rejections for "no trading operations" when it rejected 100% of setups, and the label corruption above. Removed: - the input and the RISK_REWARD_RATIO enum (deleted, not left dangling - a live enum with no input behind it is the shape of the stale-.set incident that trained ~250 eras on the wrong target) - the forced target raise in the label geometry - the rrOK eligibility gate in the barrier-geometry scan, so every unclamped pairing now competes on the measurement alone. Clamping stays disqualifying for its own unrelated reason. - the reward < minRR*risk veto in OpenParams Kept: g_TradeRewardRiskRatio still computed and still bridged to Kelly sizing in MoneyIntelligent - the ratio as a SIZING input was always the sound use. Risk stays bounded where it actually is - account risk % and CRiskBudget. The low-reachability warning survives but is re-aimed: with nothing inflating the target, a target the market rarely reaches can only mean the horizon is truncating the excursions the geometry is derived from. Both build variants compile 0 errors / 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:51:59 -04:00
//---
//--- MIN REWARD:RISK IS GONE (2026-08-09). It was the last place a GUESS could override a MEASUREMENT.
//--- The barrier geometry is derived from the instrument's own excursion distribution - stop at q75 of
//--- adverse travel, target at q50 of favourable - and then a 1:2 floor was applied on top, raising the
//--- target to whatever twice the stop happened to be. On SP500 H1 that turned a reachable target into
//--- 6.66*ATR, which only 3.3% of bars reach inside the horizon: the label became "almost never a win",
//--- and the model was trained to predict an event that essentially does not occur.
//---
//--- The ratio never bought anything it was believed to buy. A reward:risk floor does not create
//--- expectancy - it trades hit rate for payoff at a fixed break-even (see the barrier-geometry log
//--- line, which prints that break-even next to the ranking precisely to make this visible), and this
//--- project has already MEASURED that exit shape moves payoff without moving expectancy at all. What
//--- it did buy was two outages: four consecutive Market validation rejections for "no trading
//--- operations" when it rejected 100% of setups, and the label corruption above.
//---
//--- Risk is controlled where risk is actually controlled - the per-trade account risk percentage and
//--- CRiskBudget's daily/total drawdown enforcement - not by a ratio filter at the door.
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 04ee2e1 (beat the null of the MAXIMUM, not merely the incumbent). This is why that gate had to land first: without it, removing the inputs would hand a noise-picked geometry direct control over the training target with no human in the loop - strictly worse than the input it replaced. On SP500 H1 today it does NOT clear (p=0.1463), so 2:6 is what you get - now chosen by measurement rather than assumed. - only at m_eraCount == 0. Relabelling a partly-trained net moves the target out from under weights already fitted to the old one. THE GEOMETRY LEFT THE WEIGHTS-FILENAME HASH, because it is now measured. Same rule that moved the horizon and the derived topology values out: a filename keyed on a measured quantity changes the moment the measurement does - a few more bars shift which pairing wins - and the EA then looks for a file that does not exist, starts from era 0 and orphans a trained model silently. It is PINNED IN THE .cfg instead: appended at the end (the only backward-safe change), length-guarded like the 2026-07-30 derived pair, and ADOPTED on load rather than compared, so a trained model keeps the barriers it actually learned and never re-measures. Two traps closed while wiring it, neither of which announces itself: - m_barrierHorizonResolved latches the horizon ONCE PER PROCESS. Adopting 2:8 (wants ~192 bars) after it settled for 2:6 (128) would label the new target against the old ceiling - the truncation fixed in 168422f, where every model learned "target within 128 bars" while the EA holds to SL/TP. It lands in Neutral, not in the timeout counter watching for it. Unlatched on adoption, along with the label cache the old barriers filled. - the .cfg adopt runs at init, before the horizon latches and before any label is computed, so a resumed model has its pinned pair in place first. Verified, not assumed. FORCES A FULL RETRAIN: the fingerprint change orphans every existing .nnw. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 09:39:30 -04:00
const STOP_LOSS_MODE SL_Mode = SL_ATR_x2; // Stop-loss mode (measured - see above)
const TAKE_PROFIT_MODE TP_Mode = TP_ATR_x6; // Take-profit mode (measured - see above)
input TRAILING_STRATEGY TrailingStrategy = TRAILING_STRATEGY_NONE; // Trailing stop
input BARS_EXPIRATION Signal_Expiration = BARS_X3; // Pending order expiry (bars)
input CONFIDENCE_SOURCE Confidence_Source = CONF_AI; // AI confidence source (SL/TP/trail/exit/MM)
//--- UNIFIED conviction gates - ONE pair of thresholds governing BOTH engines, classic and AI. There
//--- used to be a second, AI-only pair in the Neural Network section (Min AI confidence / Min AI exit
//--- confidence) duplicating these: four inputs for what is really two decisions, where a trader could
//--- set the vote gate and still be silently overruled by the AI floor (or the reverse). Merged here.
//--- Everything is expressed on the same 0-100 conviction scale: a classic filter contributes its
//--- pattern weight (10-100), an AI signal contributes its confidence tier (80-100), and
//--- CExpertSignalCustom::Direction() averages the filters that voted before
//--- CheckOpenPosition/CheckClosePosition threshold that average.
//--- Open - aggregate conviction required to ENTER, and NOTHING else. It has exactly one meaning for
//--- both engines: the averaged vote across the filters that voted must reach it.
//--- It used to do two further jobs on the AI side - an entry floor on the winning softmax
//--- probability, and the base the 4 AI confidence tiers were quartiled from - which put one
//--- number on two incompatible scales. A 3-class argmax winner is arithmetically >= 1/3, so
//--- as a floor every setting from 0 to 33 gated precisely nothing, while every setting above
//--- that ALSO silently moved the tier boundaries. Both jobs are gone. The AI now expresses
//--- confidence the way a classic signal does - as the WEIGHT of the vote it casts, 25/50/75/
//--- 100 across its four tiers, quartiled from the head's own structural floor (1/3 for the
//--- 3-class softmax, 0.5 for the regression head - see CExpertSignalAIBase::ConfidenceTier).
//--- So this input now reads, for the AI voting alone: 25 = trade any directional call,
//--- 50 = tier 1 and up, 75 = tier 2 and up, 100 = only near-certain calls. A weak AI call is
//--- no longer blocked inside the AI - it votes weakly and is filtered here, exactly like a
//--- weight-10 classic confirmation.
//--- NOTE in a hybrid setup this is an AVERAGE: a tier-3 AI vote of 100 alongside two
//--- weight-10 classic confirmations averages to 40, not 100. Raising this input while several
//--- low-weight classic signals are enabled suppresses strong AI calls by dilution - that is
//--- inherent to averaging, and it is the same arithmetic the classic-only path has always had.
//--- Close - OPPOSITE conviction required to EXIT. It drives BOTH exit routes, at the same conviction:
//--- the averaged rule-based vote, and the AI early exit (how strongly the AI must have flipped
//--- AGAINST an open position before that alone closes it). There is deliberately no separate
//--- "Early AI exit" switch any more - it was a third input for what these two routes already
//--- express, and it could be left off while Close was set, silently discarding the exit the
//--- trader had just asked for. The two routes are NOT redundant with each other and both are
//--- needed: the AI's normal vote is one-shot (LongCondition/ShortCondition consume the
//--- m_lastNonNeutralSignal alternation gate when they fire) and is then AVERAGED with every
//--- other filter, so an AI reversal that gets diluted below Close on the bar it happens is
//--- consumed and never re-offered, leaving the position open indefinitely. The early-exit
//--- route reads the AI's LIVE signed confidence every bar, undiluted, and so still fires.
//--- Set Close = Disabled to switch off vote-driven exits entirely (SL/TP/trailing only) -
//--- that turns off both routes at once, since 101 is unreachable on either scale. See
//--- VOTE_CLOSE_PRESETS in Enumerations\InputEnums.mqh.
//--- Close defaults ABOVE Open deliberately: a position is an existing commitment with real cost to
//--- abandon, so reversing out of one should demand more conviction than opening it did, and a signal
//--- hovering either side of the entry gate must not be able to churn a position open and shut. Both
//--- were once hardcoded to 10/10 - one value for BOTH directions of the decision, pinned at the LOWEST
//--- weight any pattern can carry - so with MA/RSI Pattern_0 (weight 10) firing on nearly every bar on
//--- whichever side of the MA price sits, one cross flipped the average from +10 to -10 and closed the
//--- position on the very next bar. The stock MQL5 wizard makes the same asymmetric choice, 50 to open
//--- against 100 to close.
feat(vote): thresholds become confidence percentages, on ONE scale everywhere User request: "the entry/exit thresholds are manual numbers, I would like them to be confidence percentages, so the current 20 would be only 20% confidence in a profitable trade." WHY 20 WAS EVER SENSIBLE. Under UseDatabaseRanking both factors of a filter's contribution are win rates: the pattern weight is that pattern's measured win rate (UpdateSignalsWeights -> ApplyPatternWeight) and m_weight is the filter's average win rate over its patterns, /100. Dividing the sum by the VOTER COUNT therefore produced a mean of PRODUCTS of two win rates - a genuinely 60%-accurate filter firing a 60% pattern scored 0.60 x 60 = 36. The number was never on a probability scale, so its magnitude meant nothing on its own. Dividing by Sum(m_weight) instead makes it a weighted MEAN of win rates, which is a win rate: result = Sum(w_i*p_i)/Sum(w_i). Every voter at 60% now reads 60; MACD's double-divergence pattern (weight 100) voting alone reads 100. m_weight stops being a discount on the probability and becomes how much a filter's opinion COUNTS - which is what a module weight should always have been. Default Min_Vote_Open 20 -> 50: not a tightening, the same bar re-expressed. ONE SCALE, EVERYWHERE - the part that made this bigger than a rescale. Three other places compared against a 0..1 softmax confidence and would each have become a fresh currency mismatch the moment the input changed meaning: * the AI early-exit route (LiveSignedConfidence vs m_ai_exit_threshold) now reads m_lastAiVote - the AI filters' own weighted mean, undiluted by the classic side, which is the only reason that route exists - against the same m_threshold_close the averaged vote uses. m_ai_exit_threshold is retired rather than left dangling. * m_oosDecisionSeries now carries the vote, not the confidence, so the exit SIMULATION stops modelling a close rule the EA does not run. * ExitPolicy() clamped anything > 1.0 to zero. Passing the unscaled input through that would have silently switched vote exits off in the simulation while live went on running them - found before it shipped; the bound now tracks the scale. LiveSignedConfidence() is deliberately untouched and still 0..1: MM sizing, SL/TP scaling and the intelligent trailing want a model confidence, not a win rate. CALIBRATION CAVEAT, stated in the code where the claim is made: this is only a real probability to the extent the pattern weights are. A pattern with fewer than MIN_TRADES_FOR_WIN_RATE journaled trades keeps its DEFAULT weight - a designed prior (25/50/75/100 for the AI tiers), not a measurement. Until the signal DB fills, "60" means "the designed conviction of the patterns that fired". Closing that gap is the next commit. Also corrects VOTE_CLOSE_PRESETS' comment, which documented the two scales this removes. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:52:08 -04:00
//--- BOTH THRESHOLDS ARE CONFIDENCE PERCENTAGES as of 2026-08-18 (user request: "I would like them to
//--- be confidence percentages, so the current 20 would be only 20% confidence in a profitable
//--- trade"). The vote is a WEIGHTED MEAN of the firing patterns' weights, and under
//--- UseDatabaseRanking each of those weights is that pattern's measured win rate - so 60 reads as
//--- "the patterns backing this trade won 60% of the time". See the normalization comment in
//--- CExpertSignalCustom::Direction() for why it used to be a mean of PRODUCTS of two win rates,
//--- which is what made the old default of 20 sensible: the number was not on a probability scale.
//---
feat(chart): on-chart vote readout, and Min_Vote_Open 50 -> 40 THRESHOLD. 40 is a measured correction, not a preference. Once RankTiersFromOos() replaced the designed tier priors with each model's real held-out win rate, the vote converges on that win rate - logged 2026-08-18 as pooled 23-36% across four members on three symbols - so a 50% bar could not be reached by anything on offer and the ensemble gate fired on 0 of 4,865 OOS bars. 40 clears the ~34% break-even those same lines report without being unreachable. The comment says plainly not to copy the number: break-even is a function of the barrier geometry, so read the gate's own "needs >N%" for the config in front of you. READOUT. One line, top-right: VOTE SELL 37.2% peak 44.1% need 40% 3 voter(s) -> no trade Every other number on the chart is downstream of the weighted mean the open threshold is compared against, and that was the one quantity never displayed. A chart with no arrows could mean the models abstained, the vote was diluted, or the threshold is unreachable - and telling those apart meant waiting for an era to end and reading the gate line, which is how the last two sessions went. PEAK is the part that earns its space. A threshold above what the vote ever attains can never fire, and that is not knowable from a single bar - it is precisely the "unreachable gate vs merely unmet gate" confusion this project has paid for twice. Colour carries the verdict rather than the direction: green/red ONLY when the vote would actually place an order, grey otherwise. Green-for-buy would make a below-threshold buy look like a trade, which is the specific misreading the display exists to prevent. Guarded on `total > 0` for the same reason the normalization is: Direction() is inherited as-is by every leaf filter, so without it each filter would write its own opinion into the one shared label and the last to run would win - the reader would be looking at an arbitrary member's number believing it was the vote. Drawn after the +-100 range check, so it shows what the threshold is actually tested against. CORNER_RIGHT_UPPER: the status lines, control panel and ensemble panel all live on the left. Registered in WarriorChartPrefixes() explicitly even though the "Warrior" catch-all already reaches it - that catch-all exists because the list has drifted twice, not to make entries optional. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 18:22:45 -04:00
//--- DEFAULT 20 -> 50 -> 40. The first move was not a tightening, just the same bar re-expressed on
//--- the new scale. The second is a MEASURED correction: once RankTiersFromOos() replaced the
//--- designed tier priors with each model's real held-out win rate, the vote converges on that win
//--- rate - logged 2026-08-18 as pooled 23-36% across four members on three symbols - so a 50%
//--- bar could not be reached by any model on offer and the gate fired on 0 of 4,865 OOS bars.
//--- 40 sits above the ~34% break-even those same lines report without being unreachable. THIS IS
//--- NOT A NUMBER TO COPY: break-even is a function of the barrier geometry, so read the "needs
//--- >N%" figure the ensemble gate prints for YOUR config and set this above it.
//--- (original note) It is not a tightening - it is the same bar
feat(vote): thresholds become confidence percentages, on ONE scale everywhere User request: "the entry/exit thresholds are manual numbers, I would like them to be confidence percentages, so the current 20 would be only 20% confidence in a profitable trade." WHY 20 WAS EVER SENSIBLE. Under UseDatabaseRanking both factors of a filter's contribution are win rates: the pattern weight is that pattern's measured win rate (UpdateSignalsWeights -> ApplyPatternWeight) and m_weight is the filter's average win rate over its patterns, /100. Dividing the sum by the VOTER COUNT therefore produced a mean of PRODUCTS of two win rates - a genuinely 60%-accurate filter firing a 60% pattern scored 0.60 x 60 = 36. The number was never on a probability scale, so its magnitude meant nothing on its own. Dividing by Sum(m_weight) instead makes it a weighted MEAN of win rates, which is a win rate: result = Sum(w_i*p_i)/Sum(w_i). Every voter at 60% now reads 60; MACD's double-divergence pattern (weight 100) voting alone reads 100. m_weight stops being a discount on the probability and becomes how much a filter's opinion COUNTS - which is what a module weight should always have been. Default Min_Vote_Open 20 -> 50: not a tightening, the same bar re-expressed. ONE SCALE, EVERYWHERE - the part that made this bigger than a rescale. Three other places compared against a 0..1 softmax confidence and would each have become a fresh currency mismatch the moment the input changed meaning: * the AI early-exit route (LiveSignedConfidence vs m_ai_exit_threshold) now reads m_lastAiVote - the AI filters' own weighted mean, undiluted by the classic side, which is the only reason that route exists - against the same m_threshold_close the averaged vote uses. m_ai_exit_threshold is retired rather than left dangling. * m_oosDecisionSeries now carries the vote, not the confidence, so the exit SIMULATION stops modelling a close rule the EA does not run. * ExitPolicy() clamped anything > 1.0 to zero. Passing the unscaled input through that would have silently switched vote exits off in the simulation while live went on running them - found before it shipped; the bound now tracks the scale. LiveSignedConfidence() is deliberately untouched and still 0..1: MM sizing, SL/TP scaling and the intelligent trailing want a model confidence, not a win rate. CALIBRATION CAVEAT, stated in the code where the claim is made: this is only a real probability to the extent the pattern weights are. A pattern with fewer than MIN_TRADES_FOR_WIN_RATE journaled trades keeps its DEFAULT weight - a designed prior (25/50/75/100 for the AI tiers), not a measurement. Until the signal DB fills, "60" means "the designed conviction of the patterns that fired". Closing that gap is the next commit. Also corrects VOTE_CLOSE_PRESETS' comment, which documented the two scales this removes. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:52:08 -04:00
//--- re-expressed. The old 20 on the product scale corresponds to roughly a coin flip once the
//--- derating is removed, and a threshold below break-even cannot be a filter. Break-even itself is
//--- computable from the barrier geometry (the ensemble gate already prints it as "need N%"), so
//--- set this ABOVE that number, not by feel: at a 2:6 ATR stop/target break-even is 25%, at 1:1 it
//--- is 50%. 80-100 is usable and very selective - MACD's double-divergence pattern carries weight
//--- 100 by default, so a lone high-conviction classic vote can still reach the top of the scale.
feat(chart): on-chart vote readout, and Min_Vote_Open 50 -> 40 THRESHOLD. 40 is a measured correction, not a preference. Once RankTiersFromOos() replaced the designed tier priors with each model's real held-out win rate, the vote converges on that win rate - logged 2026-08-18 as pooled 23-36% across four members on three symbols - so a 50% bar could not be reached by anything on offer and the ensemble gate fired on 0 of 4,865 OOS bars. 40 clears the ~34% break-even those same lines report without being unreachable. The comment says plainly not to copy the number: break-even is a function of the barrier geometry, so read the gate's own "needs >N%" for the config in front of you. READOUT. One line, top-right: VOTE SELL 37.2% peak 44.1% need 40% 3 voter(s) -> no trade Every other number on the chart is downstream of the weighted mean the open threshold is compared against, and that was the one quantity never displayed. A chart with no arrows could mean the models abstained, the vote was diluted, or the threshold is unreachable - and telling those apart meant waiting for an era to end and reading the gate line, which is how the last two sessions went. PEAK is the part that earns its space. A threshold above what the vote ever attains can never fire, and that is not knowable from a single bar - it is precisely the "unreachable gate vs merely unmet gate" confusion this project has paid for twice. Colour carries the verdict rather than the direction: green/red ONLY when the vote would actually place an order, grey otherwise. Green-for-buy would make a below-threshold buy look like a trade, which is the specific misreading the display exists to prevent. Guarded on `total > 0` for the same reason the normalization is: Direction() is inherited as-is by every leaf filter, so without it each filter would write its own opinion into the one shared label and the last to run would win - the reader would be looking at an arbitrary member's number believing it was the vote. Drawn after the +-100 range check, so it shows what the threshold is actually tested against. CORNER_RIGHT_UPPER: the status lines, control panel and ensemble panel all live on the left. Registered in WarriorChartPrefixes() explicitly even though the "Warrior" catch-all already reaches it - that catch-all exists because the list has drifted twice, not to make entries optional. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 18:22:45 -04:00
input PERCENTAGE_PRESETS Min_Vote_Open = PCT_40; // Min confidence to open (%) - AI + classic
feat(vote): thresholds become confidence percentages, on ONE scale everywhere User request: "the entry/exit thresholds are manual numbers, I would like them to be confidence percentages, so the current 20 would be only 20% confidence in a profitable trade." WHY 20 WAS EVER SENSIBLE. Under UseDatabaseRanking both factors of a filter's contribution are win rates: the pattern weight is that pattern's measured win rate (UpdateSignalsWeights -> ApplyPatternWeight) and m_weight is the filter's average win rate over its patterns, /100. Dividing the sum by the VOTER COUNT therefore produced a mean of PRODUCTS of two win rates - a genuinely 60%-accurate filter firing a 60% pattern scored 0.60 x 60 = 36. The number was never on a probability scale, so its magnitude meant nothing on its own. Dividing by Sum(m_weight) instead makes it a weighted MEAN of win rates, which is a win rate: result = Sum(w_i*p_i)/Sum(w_i). Every voter at 60% now reads 60; MACD's double-divergence pattern (weight 100) voting alone reads 100. m_weight stops being a discount on the probability and becomes how much a filter's opinion COUNTS - which is what a module weight should always have been. Default Min_Vote_Open 20 -> 50: not a tightening, the same bar re-expressed. ONE SCALE, EVERYWHERE - the part that made this bigger than a rescale. Three other places compared against a 0..1 softmax confidence and would each have become a fresh currency mismatch the moment the input changed meaning: * the AI early-exit route (LiveSignedConfidence vs m_ai_exit_threshold) now reads m_lastAiVote - the AI filters' own weighted mean, undiluted by the classic side, which is the only reason that route exists - against the same m_threshold_close the averaged vote uses. m_ai_exit_threshold is retired rather than left dangling. * m_oosDecisionSeries now carries the vote, not the confidence, so the exit SIMULATION stops modelling a close rule the EA does not run. * ExitPolicy() clamped anything > 1.0 to zero. Passing the unscaled input through that would have silently switched vote exits off in the simulation while live went on running them - found before it shipped; the bound now tracks the scale. LiveSignedConfidence() is deliberately untouched and still 0..1: MM sizing, SL/TP scaling and the intelligent trailing want a model confidence, not a win rate. CALIBRATION CAVEAT, stated in the code where the claim is made: this is only a real probability to the extent the pattern weights are. A pattern with fewer than MIN_TRADES_FOR_WIN_RATE journaled trades keeps its DEFAULT weight - a designed prior (25/50/75/100 for the AI tiers), not a measurement. Until the signal DB fills, "60" means "the designed conviction of the patterns that fired". Closing that gap is the next commit. Also corrects VOTE_CLOSE_PRESETS' comment, which documented the two scales this removes. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:52:08 -04:00
input VOTE_CLOSE_PRESETS Min_Vote_Close = VOTE_CLOSE_DISABLED; // Min opposite confidence to close (%) - AI + classic
feat(chart): filtered view - one arrow per trade the bot would actually take Adds DrawUnfilteredSignals (default OFF) and, with it off, replaces the per-model arrow layer with the decision the EA would really have made. THE FILTERED ARROW IS DRAWN AT THE ORDER, NOT AT THE THRESHOLD. Clearing Min_Vote_Open is not the same as trading: a setup can pass the vote and still never reach the broker (invalid SL/TP, stops-level, ATR warm-up, unsynced swing history), and every one of those lands in OpenParams' failure branch. So DrawVoteArrow() fires only after the order parameters validate, and the failure branch withdraws any arrow already standing on that bar. One arrow is one entry the EA would have placed - carrying the vote, the threshold it cleared, and the SL/TP the order would have had. Classic signals now draw too, under their own name and weight, so a chart running MA/RSI/MACD/Ichimoku alongside the nets reads the same way an ensemble chart does. They can only be drawn from the aggregate's once-per-bar pass, because unlike the AI members they have no cached per-bar scan. Two subtleties that would each have produced a quietly wrong chart: - The raw classic draw sits AFTER filter.Direction(), not beside the journaling block. GetActivePattern*() are CONSUMING reads holding the PREVIOUS evaluation - "one tick later", which at Expert_EveryTick=false is one BAR later. Keyed off those and placed at StartIndex(), every classic arrow would have been drawn one bar early, which on a chart is indistinguishable from a model that genuinely leads. Peek*() accessors (non-consuming) let pattern, weight and bar come from one evaluation. - CExpertSignalAIBase::DrawObject() early-returns instead of gating its five call sites, so the switch cannot be honoured in three passes and missed in the fourth. Its delete counterparts stay ungated so flipping the input off and rescanning clears the raw layer rather than stranding it. SIG_ARROW_PREFIX and g_signalsVisible move from ExpertSignalAIBase.mqh down to ExpertSignalCustom.mqh - the nearest common ancestor - because the classic signals cannot see the AI header (it is included later in Warrior_EA.mq5). The vote layer gets SIG_VOTE_PREFIX under the same bare prefix, so WarriorChartPrefixes()' purge still reaches every arrow without knowing they exist. NOT YET BUILT: the reconstructed history behind attach. Filtered arrows currently start where the EA starts. See the next commit. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:41:28 -04:00
//--- WHAT THE ARROWS ON THE CHART MEAN. Two genuinely different questions, and one switch:
//---
//--- OFF (default) - THE FILTERED VIEW: "how would the whole bot have traded". One arrow per position
//--- the EA would open, after the weighted vote is averaged across every voting filter (AI members
//--- AND enabled classic signals), after UseDatabaseRanking has re-weighted each pattern by its
//--- measured win rate, and after Min_Vote_Open. Forward of attach these are drawn at the real
//--- decision point, so they also carry the prohibition signal, the trade-direction restriction and
//--- SL/TP validation - one arrow is one order the EA would have placed. Behind attach they are
//--- RECONSTRUCTED from each model's cached per-bar decision plus a replay of the classic ladders,
//--- which reproduces vote+ranking+threshold but cannot replay a broker-side rejection.
//---
//--- ON - THE RAW VIEW: every model's own opinion, per model, ignoring the vote, the ranking and the
//--- threshold entirely. This is the pre-2026-08-18 behaviour and it is the DIAGNOSTIC view: it is
//--- how you see that one ensemble member has collapsed to Neutral or gone one-sided, which the
//--- filtered view cannot show you because a collapsed member simply stops appearing in it. Classic
//--- signals draw here too, under their own name, exactly as the AI members do.
//---
//--- Neither view is a performance measurement - most of the chart during training is in-sample, and
//--- the honest numbers are the deploy gate's OOS figures and a tester run. This switch decides which
//--- QUESTION the chart answers, not how good the answer is.
input bool DrawUnfilteredSignals = false; // Draw raw per-model signals (bypass vote/ranking/threshold)
//==================================================================================================
// CLASSIC SIGNALS (rule-based MA/RSI votes - trade alongside or instead of the neural network)
//==================================================================================================
input string Classic_Settings = "Classic Signals"; // Classic Signals
//--- ALL FOUR CLASSIC FAMILIES DEFAULT OFF 2026-08-16 (user request, alt-data campaign): the EA is
//--- AI-first, classic votes are an opt-in experiment (the user may try them in the vote later). The
//--- WARRIOR_MARKET_BUILD branches are gone with the marketplace variant (private-use pivot) - one
//--- default per flag again. History: private defaults were flipped ON 2026-08-13 as META corpus
//--- candidate sources; the sweep corpus builder still needs them ON, which is a per-chart Inputs-tab
//--- choice on a META chart, not a shipping default.
input bool EnableMA = false; // MA classic vote
input bool EnableRSI = false; // RSI classic vote
input bool EnableMACD = false; // MACD classic vote
input bool EnableIchimoku = false; // Ichimoku classic vote
//--- MA/RSI PERIODS ARE NO LONGER INPUTS (2026-08-16) - same treatment MACD/Ichimoku got 2026-08-01
//--- and the AD/Wyckoff block got earlier today, closing the set: ALL indicator parameters are now
//--- tuner-owned. These constants are only the SEED; the auto-tuner searches from them (gated), and
//--- the adopted values persist chart-level in TunedPeriods_{SYM}_{TF}.cfg (Variables\TunedPeriods.mqh)
//--- which BOTH consumers read at init - the classic votes and the AI features - so the two can never
//--- run different periods for the same concept. An operator who must hand-set a period edits these
//--- constants (deliberate speed bump: hand-set values bypass the tuner's family-wise gate).
const MA_PERIOD_PRESETS PeriodMA = MA_PERIOD_50; // MA period seed
const MA_TYPE_PRESETS MA_Type = MA_TYPE_SMA; // MA type seed
const RSI_PERIOD_PRESETS PeriodRSI = RSI_PERIOD_14; // RSI period seed
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- MACD AND ICHIMOKU PERIODS ARE NO LONGER INPUTS (2026-08-01). Six dropdowns, pinned here at the
//--- textbook values every reference uses (12/26/9 and 9/26/52), for three reasons:
//--- 1. They were six of the largest contributors to the Inputs tab, for indicators that both ship
//--- DISABLED. Rows a user must scroll past to reach the AI settings are a real cost.
//--- 2. As optimizer inputs they are an overfitting surface. A genetic sweep across 6 period
//--- dimensions on one symbol's history will always find a combination that looks excellent and
//--- generalizes to nothing - and it costs nothing to discover, which is what makes it dangerous.
//--- 3. They are the SEED for the AI's own auto-tuner (AutoTuneIndicators), which searches from these
//--- values against a held-out objective. That search is the supported way to move them: it is
//--- validated, it is per-model, and it cannot silently overfit the way a raw optimizer pass can.
//--- Left as named constants rather than deleted because they are still read in both roles (classic
//--- vote periods AND auto-tune starting points), and because the classic textbook values are the
//--- correct fixed answer for a vote that exists mainly to satisfy marketplace validation.
const MACD_FAST_PRESETS MACD_PeriodFast = MACD_FAST_12;
const MACD_SLOW_PRESETS MACD_PeriodSlow = MACD_SLOW_26;
const MACD_SIGNAL_PRESETS MACD_PeriodSignal = MACD_SIGNAL_9;
const ICHIMOKU_TENKAN_PRESETS Ichimoku_PeriodTenkan = ICHI_TENKAN_9;
const ICHIMOKU_KIJUN_PRESETS Ichimoku_PeriodKijun = ICHI_KIJUN_26;
const ICHIMOKU_SENKOU_PRESETS Ichimoku_PeriodSenkou = ICHI_SENKOU_52;
//==================================================================================================
// NEURAL NETWORK (training)
//==================================================================================================
input string NNetworks_Settings = "Neural Network"; // Neural Network
//--- AIType default depends on the build, via the same WARRIOR_MARKET_BUILD compile-time flag that
//--- strips the DLL import block for Market submissions (see Warrior_EA.mq5's top-of-file comment) - not
//--- an input value itself (that can't be set programmatically), only which default the Inputs tab
//--- starts on:
//--- - WARRIOR_MARKET_BUILD defined (Market submission): OFF - a fresh install trades from Classic
//--- Signals (MA/RSI) out of the box with no AI warm-up, satisfying MQL5's automated check for live
//--- trade activity within its test window.
//--- - Not defined (private/live build): MLP - this build runs AI-only from the start, with Classic
//--- Signals defaulting off too (see EnableMA/EnableRSI), so no per-run manual input changes are
//--- needed switching between preparing a submission and running the real thing.
//--- Either way, still a normal input - freely changeable per-run from the Inputs tab.
#ifdef WARRIOR_MARKET_BUILD
input AI_CHOICE AIType = AI_NONE; // AI architecture preset (or Disabled)
#else
//--- AI_HYBRID is the ENSEMBLE preset since 2026-08-15 (see AI_CHOICE): all four direction NNs
//--- train, self-gate and vote on the one chart - the drop-on-D1-chart fractal campaign gets every
//--- topology's verdict from a single attach, and only gate-certified members ever vote. Solo
//--- architectures remain selectable per-run as always. Cost note: four nets train per chart, so on
//--- sub-daily timeframes prefer a solo preset unless the machine budget allows it.
feat(ai): TrainingTarget input - fractal-direction label for the direction models User direction (2026-08-15): back to predicting swing turns, D1 charts, fractals over ZigZag pivots (their call - balances classes, matches the reference library target, and a 5-bar fractal confirms 2 bars after its extreme so labels resolve nearly to the present with no repaint embargo). - TRAINING_TARGET enum + TrainingTarget input: TARGET_BARRIER (Market default - existing models keep their meaning and fingerprints) or TARGET_FRACTAL (private default). - FractalDirectionLabel (Labels.mqh): per-bar 3-class label = direction from the bar close to the next confirmed strict 5-bar fractal extreme, costs charged in the same bid-series convention as the barrier label, Neutral when the move cannot clear max(2 spreads, 0.10 ATR) or on an outside bar (both-extreme bars are unorderable within OHLC). - The barrier walk still runs in full: measured SL/TP geometry, the expectancy scan, excursion caches and the era gate all keep scoring what a trade at the EA's own stop/target actually collected - only the TRAINING label changes. NOT the pre-b4a704d "is this bar the pivot" form; that target's 31:1 imbalance stays retired. - Fingerprint token |TGT:FRA1 so switching targets trains a separate model; AI_META unaffected (guarded setter). - Private defaults: AIType back to AI_HYBRID (direction topology needed) + TrainingTarget=TARGET_FRACTAL = drop-on-D1-chart workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 04:44:10 -04:00
input AI_CHOICE AIType = AI_HYBRID; // AI architecture preset (or Disabled)
#endif
feat(target): withdraw the TrainingTarget option - barrier is the only live one NOT COMPILED - user compiles. The private build still DEFAULTED to TARGET_FRACTAL, so every fresh attach was training the target adjudicated dead that morning (5,700 model-eras flat at -2pp, best-of-243 p=0.17). The campaign closed; the default was never flipped back. Rather than re-default it, the input is withdrawn entirely (user: "remove the option if there is only one choice for now"). An input offering a single live choice is worse than no input - it presents a dead option as supported, and an operator picking it silently trains a model already known to carry nothing. Direction models are now unconditionally triple-barrier. Removed: the input, the TrainTargetFractal() call in the signal setup, and the HoldToBarrier() exit-policy block (which existed only because the fractal vote flips at swing-marker cadence, ~3-5 bars, far inside the barrier's travel time - barrier-target models keep vote exits and always did, their label IS the vote's horizon). Verified no code reference to TrainingTarget survives; the four remaining mentions are comments. Kept deliberately, so a rerun is a re-enable and not a rebuild: the TRAINING_TARGET enum, the fractal label itself, its |TGT:FRA1 fingerprint token, its conditional barrier-geometry derivation, HoldToBarrier()/m_holdToBarrier, and the campaign's trained models on disk. Three lines bring it back; Inputs.mqh names them. ALSO CORRECTS THE RECORD from 1b5a412. I claimed the live run was on the barrier target, "confirmed" by break-even 34.3% matching the 0.62/1.18 geometry. That proved nothing - break-even comes from BarrierMultiples, which grades wins identically under either target. Neutral's share is the real tell: ~31% would say barrier, the measured 10.6% says fractal. So the imbalance finding stands and its mechanism is unchanged, but the cause of Neutral being rare was the FRACTAL target, not the triple-barrier relabel. Neutral fell twice - 94% under the old exact-pivot ZigZag label, ~31% under triple-barrier, 10.6% under fractal - and the correction was re-checked at neither step. The fractal campaign was chosen FOR its balanced classes and did balance Buy vs Sell (48.3/41.1) while quietly making Neutral the thin residual the correction then subsidised. Under the barrier target the same geometry gives roughly 34/34/31, where Neutral is neither rare nor dominant, so 1b5a412's fix should be close to a no-op there - which is the right answer when there is nothing to correct. It stays: never subsidising the abstain class is correct under both targets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 22:53:04 -04:00
//--- Training target for the direction models - see TRAINING_TARGET's declaration comment.
//---
//--- 2026-08-16: the private build's default returns to TARGET_BARRIER. It had pointed at
//--- TARGET_FRACTAL for the 2026-08-15 campaign (predict the next confirmed swing extreme's
//--- direction - the reference library's per-bar target, chosen for its ~balanced classes). That
//--- campaign was ADJUDICATED DEAD the following day: 5,700 model-eras flat at -2pp, best-of-243
//--- p=0.17. The default was never flipped back, so every fresh private-build attach kept training
//--- a target already known to carry nothing - a live trap, since nothing in the run says so.
//---
//--- The campaign also had a second cost that only surfaced when the collapse was traced. Choosing
//--- the fractal target FIXED the Buy/Sell balance (48.3 / 41.1 measured) and, unnoticed, made
//--- Neutral a thin 10.6% residual - so the class-imbalance correction, built when Neutral was the
//--- 94% majority, began subsidising it by 1.20 logits and the model collapsed onto it. See
//--- ApplyLogitAdjustment. Under the barrier target the same geometry (stop 0.62 / target 1.18)
//--- gives roughly 34/34/31, where Neutral is neither rare nor dominant and the correction is close
//--- to a no-op - which is the right answer when there is nothing to correct.
//---
//--- THE INPUT IS WITHDRAWN, not merely re-defaulted (user, 2026-08-16: "remove the option if there
//--- is only one choice for now"). With the fractal campaign closed there is exactly one live target,
//--- and an input offering a single real choice is worse than no input: it presents a dead option as
//--- a supported one, and every operator who picks it silently trains a model already adjudicated to
//--- carry nothing. The triple-barrier label is now unconditional for direction models.
//---
//--- TO RE-ENABLE for a rerun of the campaign, three lines come back: this input (the TRAINING_TARGET
//--- enum is deliberately KEPT in Enumerations\InputEnums.mqh for exactly that), the
//--- TrainTargetFractal() call in Warrior_EA.mq5's signal setup, and the HoldToBarrier() exit-policy
//--- block further down it. Nothing else was deleted - the label itself, its |TGT:FRA1 fingerprint
//--- token, its conditional barrier-geometry derivation and the campaign's trained models are all
//--- still on disk and still correct, so a rerun is a re-enable rather than a rebuild.
//--- SGD or ADAM weight update (honored by PAI/CONV/LSTM/HYBRID). SGD rate/momentum are AI\Network.mqh inputs.
//--- A third "DFA" option was briefly the default (2026-07-28) and has been removed - it was a
//--- deterministic index-parity sign flip on the gradient, i.e. permanent gradient ASCENT on half of
//--- every weight tensor, and its backward pass was structurally incompatible with the OpenCL/DirectML
//--- neuron model. See ENUM_OPTIMIZATION's comment in AI\Network.mqh.
input ENUM_OPTIMIZATION TrainingOptimizer = ADAM; // Weight optimizer
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- OUTPUT TYPE IS NO LONGER AN INPUT (2026-08-01). The regression head (1 tanh output) was an option
//--- that never made sense for the question this system asks. Since the triple-barrier relabel the
//--- target is explicitly an EVENT - "does a trade opened here reach its target before its stop" - and
//--- the right output for an event is its probability, which is what the 3-class softmax head produces.
//--- A regression head would have to predict a continuous quantity that the label does not even contain,
//--- and every downstream consumer already speaks probability: the confidence tiers quartile the softmax
//--- winner, dir-precision is a win rate over called bars, and the class priors calibrate a distribution.
//--- The regression path is still IMPLEMENTED throughout (m_outputNeuronsCount == 1 branches, the 0.50
//--- magnitude cutoff in DoubleToSignal) and is left in place deliberately: it costs nothing dormant and
//--- removing it would touch every scoring path at once for no gain. It is simply no longer selectable.
const OUTPUT_NEURONS_COUNT OutputNeuronsCount = OUTPUT_CLASSIFICATION;
refactor(ai): derive the first dense layer's width instead of asking for it InitialNeurons was an input whose only defensible value depends on two things the user cannot see when picking from a dropdown: how wide the input vector ended up after feature selection, and how much in-sample data the study period actually yields. Left to a hand-picked constant it was badly wrong - 500 units against a 420-wide input is 210,500 weights, 72% of a 292,583-weight model, against ~36,500 training bars of which only ~2,236 are directional. That is 6.6 weights per training bar, and it EXPANDS a set of highly correlated inputs rather than compressing them. The symptom was already in the logs and had been read as a depth problem: the shallowest topology consistently beat the deepest (perceptron 52.7% balanced, hybrid 41.3%). Over-parameterization predicts that ordering just as well as covariate shift does, and only one of the two had been addressed. ComputeFirstLayerWidth() budgets roughly one first-layer weight per in-sample bar. Measured across the configurations in use: M15 10y -> 256 units, 129,071 weights, 0.73 per bar H1 10y -> 64 units, 28,727 weights, 0.65 per bar H4 10y -> 16 units, 7,559 weights, 0.68 per bar Two design points that matter: - It estimates in-sample bars from the STUDY PERIOD and timeframe, not from Bars(). What is downloaded grows over a terminal's lifetime, and a topology that widened as history filled in would re-key its own weights file and discard a trained model. - The result is snapped down to a coarse power-of-two ladder, so the estimate would have to be wrong by ~2x to change the answer. Every field it reads is already part of the weights-filename fingerprint, so the derived value needs no fingerprint entry of its own. The public setter is removed - it could only have been called after construction, and would either be ignored or silently re-key the model mid-run. Where the data cannot support even the floor (D1 over 10 years is under 2,000 bars) it now says so and names the fixes, rather than quietly training a model with more weights than examples. The DB config fingerprint drops the term too, which re-keys existing pattern databases once - correct, since a model an order of magnitude smaller should not inherit the old one's win-rate history. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 13:01:16 -04:00
//--- No "first layer neurons" input any more. Its only defensible value depends on two things the user
//--- cannot see - the input-vector width after feature selection, and how much in-sample data the study
//--- period yields - so it is derived at topology-build time instead. See
//--- CExpertSignalAIBase::ComputeFirstLayerWidth(). The old default (500) was ~8 parameters per training
//--- sample and expanded a 420-wide correlated input rather than compressing it.
feat(nn): derive conv filter count and LSTM hidden size from the data Same defect the first-layer width had before 2026-07-29: both were inputs whose defaults were fixed constants picked with no reference to the input they sit on, which is the only thing that decides whether either number is sane. The conv layer is a per-bar projection - AddConvStage sets window = step = one bar's features - so its filter count should be read against the per-bar feature count. Sixteen filters COMPRESSED a 50-feature configuration 3x but EXPANDED a minimal 4-feature one 4x, and the expanding case adds parameters below every learnable layer without adding information. Now derived as half the per-bar feature count, snapped down a power-of-two ladder. The LSTM stage was the bigger miss. Its weight count is exactly 4*H*(H+inputs+1) (CNeuronLSTMOCL::SetInputs) and AddLstmStage feeds it the whole flattened vector, so the shipped 32 units against a 540-wide input is ~73k weights - more than DOUBLE the entire derived dense taper it feeds. It was the one stage the capacity budget never covered, which is why deriving the dense stack alone did not stop LSTM and HYBRID from being over-parameterized. Now solved from the same one-weight-per-in-sample-bar budget the first layer spends. Factored EstimatedInSampleBars() out of ComputeFirstLayerWidth so all three decisions spend one budget rather than each guessing at the training-set size separately. Both new values are assigned alongside the first-layer width, before the fingerprint that hashes them, and are functions of inputs already in that hash - so they need no entry of their own, and the same reasoning removes them from the DB config key. Both builds compile 0 errors, 0 warnings. Re-keys existing models. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:22:11 -04:00
//--- No "LSTM hidden size" or "CONV filter count" inputs either, removed 2026-07-30 for exactly the
//--- reason above: both defaulted to a fixed constant (32 units, 16 filters) chosen without reference to
//--- the input they sit on, which is the one thing that decides whether either number is sane.
//--- The conv layer is a per-bar projection (window = step = one bar's features), so 16 filters
//--- COMPRESSED a 50-feature configuration but EXPANDED a minimal 4-feature one 4x - adding parameters
//--- below every learnable layer without adding information. The LSTM block is worse: its weight count
//--- is 4*H*(H+inputs+1), so 32 units against a 540-wide input is ~73k weights, more than double the
//--- entire derived dense taper it feeds, and it was the one stage the capacity budget never covered.
//--- Both are now derived from the per-bar feature count and the same one-weight-per-training-bar budget
//--- the first layer uses. See ComputeConvFilterCount()/ComputeLstmHiddenSize().
fix(ai): drop the conv pooling stage - it reduced across filters, not time FeedForwardConv emits POSITION-MAJOR output, matrix_o[out + window_out * i], so one bar's window_out filter responses are contiguous and consecutive bars sit window_out apart. Both pooling implementations (FeedForwardProof and CPU_FeedForwardProof) slide FLAT over that buffer - pos = i * step, reducing `window` CONSECUTIVE elements. On a position-major layout those neighbours are different FILTERS of the same bar, never one filter across time. At the shipped 3/2 the pool computed max(bar0_f0, bar0_f1, bar0_f2), then max(bar0_f2, bar0_f3, bar0_f4), with every 8th window straddling a bar boundary. So it collapsed unrelated feature detectors into whichever fired hardest, passed gradient to that winner only, and halved the feature map while doing it - all below every learnable layer, where nothing above can recover it. The removed inputs' own labels ("3 Bars") show time-axis pooling was the intent throughout. Measured cost: CONV sat pinned at ~40% balanced accuracy for 510 eras with Sell recall 0%, while plain MLPs on the same data reached 57-61%. HYBRID, which also carried this stage, came second-worst of the batch-norm group. Not fixable in the topology: pooling one filter across time needs a stride of window_out BETWEEN samples within a window, which a consecutive-window kernel cannot express at any window/step. That needs a stride-aware kernel in Network.cl + WarriorCPU.cpp + WarriorDML.cpp and a DLL rebuild, and is only worth doing if a conv front-end earns its place without downsampling first - with 20 sliding positions there is little to gain by halving them. ConvPoolWindow/ConvPoolStep and their enums are removed with it, along with the |CP: fingerprint term added earlier today. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:28:44 -04:00
//--- ConvPoolWindow / ConvPoolStep removed 2026-07-29. The pooling stage they configured reduced
//--- across FILTER channels rather than across time - a consequence of the conv layer's position-major
//--- output layout that no window/step pair can correct. See AddConvStage() in Expert\ExpertSignalAIBase.mqh.
refactor(ai): derive the dense taper's shape, not just its first layer Deriving the first layer's width left NeuronsReduction and MinNeuronsCount behind as inputs calibrated for something that no longer exists. Against a hand-picked 500-wide first layer "keep 30%, floor at 20" produced a genuine funnel - 500 -> 150 -> 45. Against the derived 64 it degenerates to 64 -> 20 -> 20: the reduction factor stops mattering after one step, and "minimum neurons per layer" silently becomes the width of every layer but the first. Two knobs whose labels no longer describe what they do. The taper now runs geometrically from the derived first-layer width down to a final hidden layer sized off the output count, spread evenly over however many layers the chosen AIType implies: MLP_3L 64 -> 28 -> 12 -> 3 29,151 dense weights MLP_4L 64 -> 37 -> 21 -> 12 -> 3 30,450 CONV/LSTM/HYBRID_2L 64 -> 12 -> 3 27,763 and it stays a funnel at the floor, where the old rule could not: D1 (first layer floored to 16) 16 -> 14 -> 12 -> 3 Both inputs are removed. With the width derived there is no freedom left in the taper, so keeping either would only let the user contradict the derivation. The layer COUNT stays selectable, because it is bundled into AIType alongside the conv/LSTM front-end - depth is an architecture choice, not a data-derived quantity, and pairing them means the two cannot contradict each other. m_minNeuronsCount / m_neuronsReduction survive as frozen members: nothing reads them to build a topology any more, but they hold positional slots in the .cfg sidecar and the weights fingerprint, and changing either value would re-key every model on disk for no behavioural reason. The DB config fingerprint drops both terms. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:03:42 -04:00
//--- No "min neurons" / "reduction per layer" inputs either. With the first layer's width derived
//--- (ComputeFirstLayerWidth) the taper has no freedom left: it runs geometrically from that width down
//--- to a final hidden layer sized off the output count, spread over the layer count the chosen AIType
//--- implies. Keeping either knob would let the user contradict the derivation - and both were
//--- calibrated for the old hand-picked 500-wide first layer, where they gave 500->150->45; against the
//--- derived 64 they degenerate to 64->20->20. See BuildFreshTopology()'s taper block.
feat(ai): batch normalization between dense layers The only bounded stage in the entire forward path was the sigmoid classification head - every hidden stage is PRELU. That is a network with no internal scale control, and the failure ordered exactly by depth: on SP500 H1 the shallow perceptron held ~52% balanced accuracy while the deepest topology sat on the 33.3% one-class floor, with the per-bar logit spread decaying monotonically (0.45 -> 0.38 over ~200 eras) until the evidence tilt fell under the class-prior tilt. That is the signature of internal covariate shift, which chapter 6.1 of the reference book is entirely about and which the NeuroNet_DNG engine addresses with a layer this project never had. Two mechanisms make this the right fix rather than more hyperparameter nudging: - it decouples WEIGHT_DECAY from the learned function (van Laarhoven 2017) - with a normalized layer downstream, decay can no longer grind the discriminative signal away, it only rescales the effective learning rate; - it is the precondition for ever running an unbounded logit head here. The 2026-07-27 attempt blew up (IS error 5.6e15) precisely because nothing upstream constrained scale. Implementation notes: - CNeuronBatchNormOCL computes host-side rather than as a fourth copy of a kernel across Network.cl + WarriorCPU.cpp + WarriorDML.cpp. The math is elementwise O(n); this way it behaves identically on all four compute tiers, needs no DLL rebuild, and cannot drift between backends. Same precedent as the softmax+CCE gradient and the per-sample loss weighting, both computed in MQL5 for that reason. - Statistics are exponential moving, not a stored mini-batch: training is pure online SGD, one update per sample, so there is no batch to average over. BatchNormWindow is an EMA window length. - gamma/beta are excluded from weight decay, deliberately - decaying gamma toward zero is the exact pathology being fixed. - The layer self-sizes from whatever sits below it, because a conv/pool stage's output width is derived inside the CNet constructor and is not knowable to the topology builder. - Checkpoint capture/restore/blend carry gamma/beta and the running statistics alongside the dense matrix, so the plateau ladder cannot restore a mismatched pair. - SeedOutputLayerBias accepted only an exact defNeuronBaseOCL as the weight-carrying penultimate layer; with normalization enabled that is the batch-norm layer, so the cold-start bias seed would have silently stopped being applied. - Refuses to build, loudly, if a topology asks for normalization with no compute backend at all - rather than quietly training a different architecture than the one requested. EnableBatchNorm (default on) and BatchNormWindow (1000 samples) are inputs so the effect can be A/B'd without a recompile. Both feed the weights-filename fingerprint, appended conditionally so existing non-BN configs keep their fingerprints and are not forced to retrain. Verified: analytic gradients match finite differences to 1.5e-7 relative over 200 random cases; a faithful port of the full forward/backward chain collapses to the 33.3% floor by era 4 without this layer and holds 36-43% with it. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:34:29 -04:00
//--- Batch normalization (Ioffe & Szegedy 2015) between every pair of dense layers, including just
//--- before the classification head. ON by default: without it the only bounded stage in the whole
//--- forward path was the sigmoid head, and the observed failure mode ordered exactly by depth - the
//--- shallow perceptron held ~52% balanced accuracy while the deepest topology sat on the 33.3%
//--- one-class floor. It also decouples WEIGHT_DECAY from the learned function, which is what stops
//--- the slow monotonic decay of the per-bar logit spread that preceded every collapse.
//--- Left as an input rather than hardcoded so the effect can be A/B'd without a recompile. It is part
//--- of the weights-filename fingerprint, so flipping it starts a separate model rather than resuming
//--- an incompatible one. See AI\NeuronBatchNorm.mqh.
feat: make batch normalization mandatory, and record the run-3 results EnableBatchNorm and BatchNormWindow demoted from inputs to constants. Batch norm is required, not optional: measured on identical MLP_3L topologies it was worth +11.3 points of balanced accuracy (57.0% with, 45.7% without), stable across 150+ and 200+ eras, and the no-BN control converged to ~5% IS and OOS accuracy with no chart signals at all. A user cannot make a good decision here and can easily make a ruinous one, so the choice is not offered. BatchNormWindow goes with it - a running-statistics window in samples has no meaningful setting a trader could reason about, and its only other reachable state (<=1) silently disables the layer. Kept as named constants rather than deleted: the topology builder, the weights fingerprint and the .cfg guard all read them, and a constant keeps those paths - and the ability to flip one for a diagnostic rebuild - intact. Fewer knobs also means a shorter Market description and less room for a buyer to misconfigure. EXPERIMENTS.md records runs 2 and 3, since the MT5 logs are wiped between runs and these measurements are what the design decisions rest on. Run 3 (12h, uncapped tau=1.0) is a write-off: zero eras out of 1,993 across the five batch-norm charts ever called a direction on fewer than half of all bars, at a median precision equal to the ~6.1% base rate. The damage was present at era 1 and never recovered over 292-766 eras. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:51:10 -04:00
//--- NOT an input. Batch normalization is required, not optional: measured 2026-07-29 on identical
//--- MLP_3L topologies it was worth +11.3 points of balanced accuracy (57.0% with, 45.7% without),
//--- stable across 150+ and 200+ eras, and the no-BN control converged to ~5% IS and OOS accuracy
//--- with no chart signals at all. A user cannot make a good decision here and can easily make a
//--- ruinous one, so the choice is not offered. Kept as a named constant rather than deleted: the
//--- topology builder, the weights fingerprint and the .cfg guard all read it, and a constant keeps
//--- those paths (and the ability to flip it for a diagnostic rebuild) intact.
const bool EnableBatchNorm = true; // AI: batch normalization
feat(ai): batch normalization between dense layers The only bounded stage in the entire forward path was the sigmoid classification head - every hidden stage is PRELU. That is a network with no internal scale control, and the failure ordered exactly by depth: on SP500 H1 the shallow perceptron held ~52% balanced accuracy while the deepest topology sat on the 33.3% one-class floor, with the per-bar logit spread decaying monotonically (0.45 -> 0.38 over ~200 eras) until the evidence tilt fell under the class-prior tilt. That is the signature of internal covariate shift, which chapter 6.1 of the reference book is entirely about and which the NeuroNet_DNG engine addresses with a layer this project never had. Two mechanisms make this the right fix rather than more hyperparameter nudging: - it decouples WEIGHT_DECAY from the learned function (van Laarhoven 2017) - with a normalized layer downstream, decay can no longer grind the discriminative signal away, it only rescales the effective learning rate; - it is the precondition for ever running an unbounded logit head here. The 2026-07-27 attempt blew up (IS error 5.6e15) precisely because nothing upstream constrained scale. Implementation notes: - CNeuronBatchNormOCL computes host-side rather than as a fourth copy of a kernel across Network.cl + WarriorCPU.cpp + WarriorDML.cpp. The math is elementwise O(n); this way it behaves identically on all four compute tiers, needs no DLL rebuild, and cannot drift between backends. Same precedent as the softmax+CCE gradient and the per-sample loss weighting, both computed in MQL5 for that reason. - Statistics are exponential moving, not a stored mini-batch: training is pure online SGD, one update per sample, so there is no batch to average over. BatchNormWindow is an EMA window length. - gamma/beta are excluded from weight decay, deliberately - decaying gamma toward zero is the exact pathology being fixed. - The layer self-sizes from whatever sits below it, because a conv/pool stage's output width is derived inside the CNet constructor and is not knowable to the topology builder. - Checkpoint capture/restore/blend carry gamma/beta and the running statistics alongside the dense matrix, so the plateau ladder cannot restore a mismatched pair. - SeedOutputLayerBias accepted only an exact defNeuronBaseOCL as the weight-carrying penultimate layer; with normalization enabled that is the batch-norm layer, so the cold-start bias seed would have silently stopped being applied. - Refuses to build, loudly, if a topology asks for normalization with no compute backend at all - rather than quietly training a different architecture than the one requested. EnableBatchNorm (default on) and BatchNormWindow (1000 samples) are inputs so the effect can be A/B'd without a recompile. Both feed the weights-filename fingerprint, appended conditionally so existing non-BN configs keep their fingerprints and are not forced to retrain. Verified: analytic gradients match finite differences to 1.5e-7 relative over 200 random cases; a faithful port of the full forward/backward chain collapses to the 33.3% floor by era 4 without this layer and holds 36-43% with it. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:34:29 -04:00
//--- EMA window the running mean/variance are estimated over, in TRAINING SAMPLES (bars replayed),
//--- not eras. Training here is pure online SGD - one update per sample - so there is no mini-batch to
//--- average over and this stands in for the batch size. Long enough to be a stable estimate of the
//--- feature distribution, short enough to track a genuine regime change. 1000 is ~3% of a typical
//--- 36k-bar in-sample window.
feat: make batch normalization mandatory, and record the run-3 results EnableBatchNorm and BatchNormWindow demoted from inputs to constants. Batch norm is required, not optional: measured on identical MLP_3L topologies it was worth +11.3 points of balanced accuracy (57.0% with, 45.7% without), stable across 150+ and 200+ eras, and the no-BN control converged to ~5% IS and OOS accuracy with no chart signals at all. A user cannot make a good decision here and can easily make a ruinous one, so the choice is not offered. BatchNormWindow goes with it - a running-statistics window in samples has no meaningful setting a trader could reason about, and its only other reachable state (<=1) silently disables the layer. Kept as named constants rather than deleted: the topology builder, the weights fingerprint and the .cfg guard all read them, and a constant keeps those paths - and the ability to flip one for a diagnostic rebuild - intact. Fewer knobs also means a shorter Market description and less room for a buyer to misconfigure. EXPERIMENTS.md records runs 2 and 3, since the MT5 logs are wiped between runs and these measurements are what the design decisions rest on. Run 3 (12h, uncapped tau=1.0) is a write-off: zero eras out of 1,993 across the five batch-norm charts ever called a direction on fewer than half of all bars, at a median precision equal to the ~6.1% base rate. The damage was present at era 1 and never recovered over 292-766 eras. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:51:10 -04:00
//--- Also not an input, for the same reason plus one more: this is a running-statistics window in
//--- SAMPLES, and nothing on the Inputs tab tells a trader what a good value is. It only ever had
//--- two meaningful settings - large enough to be a stable estimate, or <=1 which silently disables
//--- the layer entirely. The first is the only correct one.
const int BatchNormWindow = 1000; // AI: batch-norm window (samples)
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>
2026-07-30 10:05:40 -04:00
//--- No "training years" input either, removed 2026-07-30. There is no case for training on less data
//--- than the broker actually provides: this is a weak signal at a ~6% directional base rate, every extra
//--- year is more of the minority class, and the honest generalization read comes from the out-of-sample
//--- holdout below rather than from withholding history. Training now starts at the earliest available
//--- bar (floored by MinTrainYear, which exists to exclude a broker's dubious pre-history, not to size
//--- the run). The topology's capacity budget reads the REAL bar count that yields - see
//--- CExpertSignalAIBase::EstimatedInSampleBars(), and the note there on why that measurement is taken
//--- exactly once and then pinned.
input OOS_SPLIT_PRESET OOSSplit = OOS_30; // Out-of-sample holdout
//--- There is deliberately NO "target accuracy" input. Training runs until it stops improving and then
//--- deploys its own best model: after a stretch of eras with no new best it tries to escape the plateau
//--- (learning-rate warm restart, then focal-gamma anneal), and if neither finds anything better it
//--- finalises the best checkpoint it found. See the PLATEAU_* ladder in Expert\ExpertSignalAIBase.mqh.
//--- An absolute target could only ever be wrong in one of two directions: set above what a given
//--- symbol/timeframe can reach and the run never converges (it burns to the era cap and deploys the same
//--- checkpoint hours later anyway); set below and it stops a run that was still getting better.
//--- MinRecall stays, and is NOT a performance target - it is the anti-collapse floor that makes
//--- auto-deploy safe. Buy, Sell AND Neutral must each be recognised this well on held-back data before a
//--- checkpoint is eligible to ship, so a model that quietly gives up on one direction can never deploy.
//--- It is an OOS CLASSIFICATION metric (3-class), NOT a trade win rate: random guessing is ~33%.
fix(training): escape the recall-gate catch-22 that let runs decay unchecked Evidence (MQL5\Logs, SP500 H1, 2026-07-29): Perceptron era 61 Buy 32% Sell 27% Neut 94% bal 51% LSTM era 160 Buy 16% Sell 11% Neut 98% bal 42% (peaked 49% @ era 44) Hybrid era 179 Buy 5% Sell 2% Neut 99% bal 35% (peaked 41%) CONV era 228 Buy 2% Sell 4% Neut 99% bal 35% (peaked 40% @ era 122) Every model peaks early then decays monotonically toward Neutral, and nothing stops it: the restore-best-weights + decay-eta handler is gated on m_bestPassedRecall, which stays false forever when no checkpoint ever clears the per-class floor. CONV ran 228 eras with eta pinned at its 0.000300 start. The plateau ladder cannot end such a run either (stage 3 refuses to deploy without a recall pass, so it resets ~27 times), making it a 1000-era one-way trip. The gate's own justification had expired. It was written when the pre-pass tiebreak was blended-accuracy-only, where "best" really did mean "called Neutral most confidently". The balanced-selection change replaced that with `balancedOosEra > m_bestBalancedOos` plus an isFullyCollapsedEra exclusion, so a Neutral-only era now scores ~33% - the FLOOR of the balanced metric - and cannot anchor the checkpoint at all. Pre-pass "best" now means "most class-balanced so far", which is worth defending; and isWorseEra is itself a balanced-accuracy regression, so it cannot fire merely for trading Neutral calls for Buy/Sell. The original concern still holds while the best-so-far IS near-collapse, so the escape is margin-guarded: defend the checkpoint only once balanced accuracy sits more than BALANCED_WORTH_DEFENDING_MARGIN_PCT (5pp) above the one-class floor of 100/3. Against the run above that engages for all three stuck topologies (42.3/41.3/50.0 vs a 38.3 threshold) while a genuinely collapsed run still explores freely. Two inputs restored to the regime that actually produced a deploy: - MinRecall 60 -> 40. The one successful auto-deploy in the logs (Hybrid, 28th 00:50, best balanced 66.0%) ran against a 40% floor. 60 has never been shown reachable here - a floor above what the config can reach is the same "target set too high" failure the surrounding comment already warns about. - OversampleParity 60 -> 90. 60 overcorrected. Runs now START Neutral-dominant (Buy 0-11% recall at era 1) and call Buy/Sell on 0-4% of bars against a ~6% true base rate - under-calling, with no headroom to converge down from. The deploying run began at Buy 90% / Sell 36%, 24% of bars called, and settled into the floor from above. Raw over-calling is the intended starting condition; live calls are base-rate-calibrated by AILogitPriorStrength, which is why the input's own note says to judge over-calling by live-fired precision, not raw counts. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 07:51:08 -04:00
//--- 2026-07-29: 60 -> 40. 60 was never demonstrated reachable on this data. The ONE successful
//--- auto-deploy in the logs (Hybrid, SP500 H1, 28th 00:50, best balanced 66.0%) ran against a 40%
//--- floor; every run since has been gated at 60 and none has come close - CONV/LSTM/Hybrid peaked at
//--- 40/49/41% balanced and then decayed, so stage 3 refused to deploy and reset the ladder ~27 times,
//--- turning a converged run into a 1000-era one-way trip. A floor above what the configuration can
//--- reach is exactly the "absolute target set too high" failure the comment above warns about, just
//--- expressed per-class. Raise it again only after a run actually clears it with headroom.
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- 2026-08-01: NO LONGER AN INPUT. This is a safety floor, not a preference, and the one direction a
//--- user can move it is the harmful one - raising it past what the configuration reaches does not
//--- produce a better model, it produces NO model (nothing clears the gate, stage 3 refuses to deploy,
//--- and the run burns to the era cap). That failure was observed repeatedly at 60 and is the exact
//--- catch-22 this floor was nearly deleted over. 40 is the value the only successful auto-deploy in the
//--- project's history ran against. It also no longer decides what SHIPS - deployability moved to
//--- directional precision with a derived coverage floor - so it now only drives the diagnostic recall
//--- line, which makes exposing it even harder to justify.
const PERCENTAGE_PRESETS MinRecall = PCT_40;
//--- There are deliberately NO AI-only confidence inputs here any more. The AI entry floor and the AI
//--- early-exit threshold are the SAME two numbers the classic votes use - Min vote to open / Min
//--- opposite vote to close (Trade Management section) - so one pair of inputs governs both engines;
//--- see their declaration comment for how the 0-100 scale maps onto AI softmax confidence and tiers.
refactor(ai): nine class-imbalance inputs down to two The imbalance section offered nine controls for one job. Audited against the code, five of them did not do what their names said at the shipped defaults: AILogitPriorStrength DEAD - Inference.mqh's post-hoc prior early-returns whenever the adjusted loss is on, which is default. OversampleParity DEAD in training - Training.mqh gated the replay loop on !useLogitAdjustedLoss (correctly, citing Buda et al. 2018). Live only in the online-learning path. EnableMinorityReplay DEAD as replay. It survived ONLY as a focal-gamma damper - "replay minority bars through pass-2 oversampling" was a focal-loss switch. ConstrainReplay DEAD as a cap; it only chose damper 0.125 vs 0.25. UseStaticPrior An exact duplicate of FreezePriorCalibration - the two were OR'd together in the single place either is read. So they were not five mechanisms fighting; they were one mechanism plus eight knobs that mostly described machinery that no longer ran. That is worse than a real conflict, because the log agreed with the names: the label-cache line printed "reps up to 28x (90% parity) (seeding era 0's class-balance oversampling)" on every run, describing an oversampling pass that had been switched off. It is fixed here too - it cost this session a wrong diagnosis. The one genuine redundancy was focal loss, running at gamma*0.125 alongside the adjusted loss: two corrections on the same axis, the exact stacking failure this file already cited Buda et al. for in two other places, damped by a replay flag whose replay path was itself dead. Removed rather than re-tuned. The plateau ladder is unaffected - its escape is the learning-rate warm restart; the gamma anneal beside it only ever stepped toward zero. WHAT REMAINS is logit-adjusted loss (Menon et al. 2021) plus a prior freeze: LogitAdjustTau 0 = off; replaces the separate EnableLogitAdjusted- Loss boolean, since a strength dial where 0 already means off does not need an on/off switch beside it. FreezePriorCalibration unchanged. It is the only one of the six corrections with a consistency guarantee, and it is consistent for exactly the balanced-error metric checkpoint selection already ranks on - so the loss and the deploy decision optimize one thing. The online continual-learning path keeps its own alpha-balanced focal weight, now as constants pinned to the removed inputs' shipped defaults, so its behaviour is unchanged. It legitimately needs its own correction: ApplyLogitAdjustment() only runs inside a training run, so a deployed model that was reloaded carries no logit offsets and would otherwise stream 31:1 data into itself uncorrected. The weights-filename fingerprint is BYTE-IDENTICAL. The focal slot was a double fed to a %d conversion and had always emitted a literal 0; the |MR: segment is written as the constant its shipped defaults produced. Dropping either would have re-keyed every model and forced a from-scratch retrain of the one topology currently converged and trading. Also removed as orphans: FOCAL_GAMMA_PRESET, MAX_OVERSAMPLE_REPLICAS, OVERSAMPLE_PARITY_FRACTION, PLATEAU_GAMMA_STEP, and the now-unreachable "neutralized by prior correction" diagnostic. Both builds compile 0 errors, 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:46:57 -04:00
//==================================================================================================
// CLASS IMBALANCE - ONE MECHANISM, ONE KNOB
//==================================================================================================
//--- The directional base rate here is ~3% Buy / ~3% Sell / ~94% Neutral (a ~31:1 imbalance), so the
//--- loss needs SOME correction or the optimum is "always predict Neutral". This section used to offer
//--- NINE inputs for that one job. They were consolidated on 2026-07-31 because, audited against the
//--- code, five of them did not do what their names said at the shipped defaults:
//--- AILogitPriorStrength DEAD - Inference.mqh's post-hoc prior early-returns when the adjusted
//--- loss is on, because the offsets are already trained into the weights.
//--- OversampleParity DEAD in training - Training.mqh gates the replay loop on
//--- !useLogitAdjustedLoss (correctly: Buda et al. 2018 on why stacking
//--- oversampling with an analytic correction double-counts the imbalance).
//--- EnableMinorityReplay DEAD as replay; it survived ONLY as a focal-gamma damper (x0.125).
//--- ConstrainReplay DEAD as a cap; it only chose between damper 0.125 and 0.25.
//--- UseStaticPrior an exact duplicate of FreezePriorCalibration - the two were OR'd
//--- together in the single place either was read.
//--- Focal loss was the one real redundancy: it ran at gamma*0.125 alongside the adjusted loss, i.e.
//--- two corrections on the SAME axis, which is what the codebase's own Buda et al. citation warns
//--- against. Removed rather than re-tuned - the plateau ladder's escape is its learning-rate warm
//--- restart, and the gamma anneal it also performed was only ever a monotone step toward zero.
//---
//--- WHAT REMAINS is LOGIT-ADJUSTED LOSS (Menon et al. 2021, ICLR, "Long-tail learning via logit
//--- adjustment"): add tau*log(prior_c) to each class logit inside the TRAINING gradient only. The
//--- network learns to absorb the offset, so at inference its RAW argmax is already the
//--- balanced-error-optimal decision - no second correction at read time, by construction. Minimizing
//--- softmax cross-entropy on adjusted logits is consistent for BALANCED error, which is the metric
//--- checkpoint selection already ranks on, so the loss and the deploy decision optimize the same
//--- thing. It is the only one of the six with a consistency guarantee, which is why it is the one kept.
//---
//--- tau: 100% = tau 1.0, the paper's default and the only value carrying the guarantee. 0 = OFF, which
//--- is now the honest way to disable the correction entirely (it replaces the old EnableLogitAdjusted-
//--- Loss boolean - a separate on/off switch beside a strength dial where 0 already means off is two
//--- controls for one decision). NOTE the runtime auto-caps tau so the offsets cannot swamp the output
//--- head's usable logit range; the startup line reports the capped value actually used.
input LOGIT_PRIOR_STRENGTH_PRESETS LogitAdjustTau = LOGIT_PRIOR_100; // AI: class-imbalance correction (tau, 0=off)
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- Stop EMA-updating the measured class priors after the first real measurement. NOT AN INPUT as of
//--- 2026-08-01: it answers a question a user has no way to evaluate ("should the correction track this
//--- era's tally or the first one's"), and after the triple-barrier relabel the priors barely move
//--- between eras anyway - the labels are near-balanced and stable, which is the whole point of the
//--- relabel. Letting them track is the correct default; freezing exists for a symbol whose class
//--- distribution genuinely shifts mid-run, which is a developer's diagnostic, not a product setting.
const bool FreezePriorCalibration = false;
//--- SWING CONFIRM BARS IS NO LONGER AN INPUT (2026-08-01), but the constant is still load-bearing and
//--- must not be deleted. It stopped gating the LABELS with the triple-barrier relabel - that lookahead
//--- is now the barrier horizon, which is measured (ComputeBarrierHorizonBars) rather than configured.
//--- It still gates the swing-context INPUT FEATURES (EnableSwingContext, on by default, 9 features):
//--- ZigZag revises its most recent legs, so a feature that read the raw current buffer would be reading
//--- a value the live bar could not actually have had yet. That is straight lookahead into the feature
//--- vector, so this embargo stays - it simply has no reason to be user-facing, because the correct
//--- value is a property of the ZigZag indicator's own recalculation depth, not of anyone's preference.
//--- Kept in the weights fingerprint at its shipped value, so pinning it re-keys nothing.
const SWING_CONFIRMATION_PRESET SwingConfirmationBars = SC_100;
//--- Continual learning: after the model is deployed, keep adapting it on a LIVE chart to newly-RESOLVED
//--- market structure - the same supervised triple-barrier task it was trained on, waiting the full
//--- barrier horizon so a bar whose outcome is not yet decided is never learned from. The deployed model
//--- only moves toward the update while a rolling-accuracy guardrail holds; if accuracy decays the blend
//--- FREEZES (live keeps trading the last-good shadow while the net recovers), so drift cannot reach the
//--- account. No effect in the Strategy Tester/optimizer - the model is held fixed there by design.
//--- ON BY DEFAULT AND NO LONGER AN INPUT (2026-08-01). Adapting to a changing market is not an optional
//--- extra for a model that will be attached for months, it is the thing that keeps it from going stale,
//--- and the guardrail above is what makes it safe to leave on. See the caveat in the release checklist:
//--- this had never been forward-tested on a live feed at the time it was made default.
const bool EnableOnlineLearning = true;
//--- Default 6 -> 3 and NO LONGER AN INPUT (2026-08-01). Declustering existed because exact-pivot ZigZag
//--- labels make a same-direction repeat provably redundant; barrier labels answer every bar
//--- independently, so consecutive Buy setups inside a trend are real trades and suppressing them throws
//--- signal away - which argued for 0. It is not 0 because on D1 and above a 6-bar window spans more than
//--- a trading week, and two arrows a day apart on a weekly-scale move really are one event. 3 keeps the
//--- immediate-neighbour duplicate off the chart on slow timeframes while leaving genuine consecutive
//--- setups intact on fast ones. Display/emission only either way - the raw per-bar recall/precision
//--- metrics are never declustered, so this cannot flatter a model's measured numbers.
feat: 10-bar decluster window + alternation on every signal consumer SignalClusterWindow 3 -> 10 for all topologies. On H1 a 3-bar window collapsed only the tightest runs and left visible clusters at every turn; 10 bars is closer to the spacing of genuinely distinct setups. ALTERNATION. Rule 1 only collapses a same-direction run INSIDE the window; past it a second Buy is emitted with no Sell between, giving Buy/Buy/Buy/Sell. With both directions tradeable that sequence is the model re-entering a move it is already in rather than finding a new one. The kept sequence must now alternate: the first signal passes, and after that a direction passes only if the last KEPT signal was the opposite one. Added to ALL THREE consumers, with identical logic, because they must agree: - NmsLiveAccept -> the live trade - pass 3's OOS replay -> the tally the deploy gate grades - PruneDirectionalClusters -> the drawn history A rule applied to only some of these certifies one strategy and trades another - the same defect class as the geometry the gate certified while OpenParams placed something else (9a7c37f) - and would draw the user arrows the EA would never have taken. Deliberately NOT applied to the LABEL. The barrier target has no "must flip" invariant: consecutive Buy labels are routinely correct, and an earlier alternation gate was removed with the triple-barrier relabel for exactly that reason. This filters what is ACTED ON, which is what "applies to training" can honestly mean here - pass 3's declustered tally is the training-side number that decides deployment. BothDirectionsTradeable() is the stated precondition (with one side disabled there is no opposite to wait for, so alternation would suppress everything after the first call). This build has no long-only/short-only input, so it is constant true - kept as a named predicate so a future direction restriction has one place to change rather than three call sites silently assuming both sides. Build tag -> nms-alternate-v4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 14:26:12 -04:00
//--- 10 bars (2026-08-10, was 3). On H1 a 3-bar window collapsed only the tightest runs and left
//--- visible clusters around every turn; 10 bars is a third of a session and closer to the spacing of
//--- genuinely distinct setups on this timeframe. Applies to every topology - it is not per-network.
const int SignalClusterWindow = 10;
feat: excursion-size head (Stage 1, measurement only) Direction is closed - normalised asymmetry fails on three instruments with a working positive control, and the classifier's own best-of-999 era-cap test agrees (+0.9pp = 1.48 sigma, family-wise p=1.0000). SIZE is a different question and RANGE clears at ~4x its null. Checked the denomination before building on that, since the source memo warns to: m_excUpCache holds (maxHigh - fill)/ATR, so "RANGE is predictable" is a claim about travel RELATIVE to current ATR, not a restatement of "ATR is autocorrelated". It is exactly the part a fixed multiple (stop 3.31*ATR, target 1.64*ATR) discards. A second small CNet, 760 -> 24 -> 32 sigmoid outputs = P(price reaches ladder rung k) upward and downward. Survival parameterisation rather than regressing the multiple, because it needs nothing new from CNet: sigmoid outputs and the per-neuron delta the `total != 3` branch already applies (a quantile head would need a linear activation and a pinball gradient in Network.mqh, Network.cl and the DirectML path, on a class four topologies share). Targets are free - m_ladderUpAt already records first-touch age per rung with 0 meaning never reached. Separate net, not extra outputs on the classifier: more outputs would change m_outputNeuronsCount, the .nnw shape and the fingerprint, and push the count off 3 - the exact condition backProp uses to select the joint softmax gradient the 3-class head depends on. The classifier is bit-for-bit unaffected and this is removable without trace. STAGE 1 PLACES NO ORDERS. It reports a Brier skill score against the constant per-rung base rate - the baseline a fixed ATR multiple already assumes - with both predictors fitted IS and evaluated OOS, so neither gets a look at the test set. Positive skill justifies Stage 2 (drive SL/TP and sizing off ExcursionQuantile, which is defined and deliberately uncalled). Zero or negative means ATR already carries everything and Stage 2 must not be built. Trains only on primary occurrences: the replay queue oversamples for CLASS balance, and a direction-balanced sample is a biased SIZE sample. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 07:40:01 -04:00
//--- EXCURSION-SIZE HEAD (Expert\AIBase\Excursion.mqh). A second small net that predicts how FAR price
//--- travels within the horizon - never which way, which is measured-closed on three instruments.
//--- STAGE 1 IS A MEASUREMENT: it trains beside the classifier and prints a Brier skill score against
//--- the constant base rate a fixed ATR multiple already assumes. It places no orders and moves no
//--- stops, so leaving it on costs only era time and leaving it off changes nothing else.
//--- NOT in the weights fingerprint: it is a separate network with its own weights, so it cannot alter
//--- the classifier's shape - the rule that keeps TRAIN_BATCH_SIZE out for the same reason.
const bool UseExcursionHead = true;
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- Era cap. NOT AN INPUT as of 2026-08-01: it is a runaway backstop, not a training control. Training
//--- decides its own ending (the plateau ladder deploys the best checkpoint once escalation stops finding
//--- anything better), so in a healthy run this number is never reached and choosing it changes nothing;
//--- in an unhealthy one the useful response is to read the era log, not to raise a cap.
const MAX_ERAS_PRESET MaxErasPerRun = ME_10000;
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//==================================================================================================
// AI INPUT FEATURES (the data the neural network sees each bar)
//==================================================================================================
input string AISignals = "AI Input Features"; // AI Input Features
//--- ind_Periods IS NO LONGER AN INPUT (2026-08-11). The number of bars per input sequence is now
//--- DERIVED - median confirmed swing leg over recent history, snapped to a coarse ladder and capped
//--- (see DeriveHistoryBars) - then pinned in the model's .cfg and ADOPTED on every later load, the
//--- same measure-once contract as the barrier geometry. Picking it by predictive skill instead was
//--- ruled out by the 2026-08-06 lag profile (no information at any lag 0-20): a best-of-N window
//--- scan would only ever mine noise. The ATR feature/barrier-unit lookback it also used to set is
//--- deliberately DECOUPLED and pinned at the old default below: the ATR indicator is created before
//--- the .cfg can be adopted, so deriving its period would let init ordering change the unit the
//--- pinned SL/TP multiples are expressed in.
#define ATR_FEATURE_PERIOD 20
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
input ENUM_APPLIED_VOLUME VolumeData = VOLUME_TICK; // Volume data type (tick / real)
input bool EnableVolume = true; // Feature: volume
input bool EnableTime = true; // Feature: time
input bool EnableATR = true; // Feature: volatility (ATR)
//--- MA/RSI as network input features, independent of the Classic Signals votes above (you can feed MA
//--- to the model without it voting, or vice versa). Uses PeriodMA/MA_Type/PeriodRSI (Classic Signals)
//--- as the starting period, then auto-tuned from there when Auto-tune indicators is on.
input bool EnableMAFeature = true; // Feature: Moving Average
input bool EnableRSIFeature = false; // Feature: RSI
//--- MACD adds 3 inputs/bar (main, signal, histogram - all ATR-normalized); Ichimoku adds 8 (distances to
//--- Tenkan/Kijun/both cloud edges, the TK spread, cloud thickness here and projected, and the Chikou
//--- displacement). Widths are per BAR, so each is multiplied by Bars to analyse before it reaches the
//--- first layer - Ichimoku at the default 20 bars is 160 extra inputs on its own. Worth it for the
//--- multi-timescale structure nothing else in the vector carries, but enable deliberately, not by habit.
input bool EnableMACDFeature = false; // Feature: MACD
input bool EnableIchimokuFeature = false; // Feature: Ichimoku
//--- Confirmed ZigZag swing direction/magnitude/age - lookahead-safe (repainting embargo applied, see
//--- SWING_CONFIRMATION_BARS in Expert\ExpertSignalAIBase.mqh).
input bool EnableSwingContext = true; // Feature: ZigZag swing context
//--- ORDER-FLOW/WYCKOFF FEATURES DEFAULT OFF 2026-08-16 (user request): the alt-data campaign makes
//--- externally-measured features the default information diet; the Wyckoff stack (28-36 features/bar)
//--- is opt-in per chart. The toggles stay - Wyckoff CONTEXT is the one price-derived family that ever
//--- replicated out of sample - but a shipping default should carry the feature set with measured
//--- incremental value, and today that is price basics + alt data.
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
input bool EnableADCumulativeDelta = false; // Feature: Cumulative Delta
input bool EnableADShorteningOfThrust = false; // Feature: Shortening of Thrust
input bool EnableADWyckoffEventStream = false; // Feature: Wyckoff Events
input bool EnableADWyckoffFailedStructure = false; // Feature: Wyckoff Failed Structure
input bool EnableADWyckoffSignificantBarInversion = false; // Feature: Wyckoff Bar Inversion
feat: expose the AD/Wyckoff parameters; default the indicator tuner off AutoTuneIndicators now defaults to FALSE, and the 33 AD/Wyckoff parameters it used to search are now inputs. WHY THE DEFAULT FLIPPED - not because the search is broken. It is correct, and its own Sidak gate is what proves it: 324 candidates per model on SP500 H1, "no improvement" on all four topologies (0.00236 -> 0.00236 on the AD configs, 0.00370 -> 0.00370 on PAI), winner rejected at p=1.0000. It cannot do better here by construction - it ranks candidates by MARGINAL MI, and the headline MI is 0.00370 nats against a shuffled null of 0.00379 +/- 0.00061 (p=0.4975), so every candidate is a noise draw and the maximum over N of them is noise too. The cost is 45-56 min per model in one synchronous call with no yield, and it was the amplifier for the handle leak fixed in 33f106d. The EA's own report says it plainest: "no per-feature indicator retuning will help." THE INPUT STAYS. TuneIndicatorsByFilter is one function of twelve in AIBase/AutoTune.mqh; the other eleven are the MI/lag/excursion/geometry diagnostics that produced every verdict this project relies on, and they run regardless of this flag. Removing the input invites removing the file. WHY THE INPUTS WERE NEEDED. All 33 were literals in CADIndicatorTuner's constructor with no input of any kind, while MA/RSI/MACD/Ichimoku have had their periods exposed from the start. On the AD configs those indicators contribute 28 of 64 features per bar. Survivable while the tuner searched them; indefensible with it off, where they would freeze at values nobody chose. CONSOLIDATED 33 -> 18. volClimax/volHigh/rangeClimax/rangeSignificant/ stVolRatio/atr were duplicated verbatim across CumulativeDelta, Wyckoff Events, Failed Structure and Bar Inversion - the same constants restated 3-4 times. One concept, one input. They are SEEDS: each fans out to the indicator's own struct field, so with the tuner on it retains full per-indicator freedom to move them apart. Same contract as PeriodMA. NO RETRAIN. Every default is byte-identical to the literal it replaces, and the fingerprint's new ADP token is appended ONLY on deviation (MACD/Ichimoku/BN/XA convention), gated on the AD features being enabled. At defaults the token is absent, so every model on disk keeps its filename and stays loadable. Without that guard, merely EXPOSING these parameters would have re-keyed every config and forced a from-scratch retrain of all four topologies for a change that alters no number anywhere. All-or-nothing rather than per-input, so the token can never encode a partial picture of what the features were built from. Compiles clean: 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:37:21 -04:00
//==================================================================================================
// AD / WYCKOFF INDICATOR PARAMETERS
//==================================================================================================
//--- ADDED 2026-08-08, because AutoTuneIndicators now defaults to false and these 33 values had NO input
//--- of any kind - they were literals in CADIndicatorTuner's constructor. MA/RSI/MACD/Ichimoku have had
//--- their periods exposed since the beginning; the order-flow and Wyckoff indicators, which contribute
//--- 28 of the 64 features per bar on the AD configs, were operator-invisible. With the tuner on that was
//--- survivable (it searched them); with it off they would be frozen at values nobody chose.
//---
//--- SEEDS, exactly like PeriodMA/MA_Type. When AutoTuneIndicators is on, the search still starts here and
//--- is still free to move each indicator's copy independently - collapsing the shared thresholds below
//--- into one input each constrains only what the OPERATOR sets, never what the tuner may explore.
//---
//--- CONSOLIDATED 33 -> 18 on purpose, following the imbalance-input precedent. volClimax/volHigh/
//--- rangeClimax/rangeSignificant/stVolRatio/atr were duplicated verbatim across CumulativeDelta, Wyckoff
//--- Events, Failed Structure and Bar Inversion - the same four constants restated 3-4 times each. They
//--- are one CONCEPT per row ("what counts as climactic volume", "what counts as a significant range"),
//--- so they get one input per concept. Eighteen knobs an operator can reason about beats thirty-three
//--- that invite inconsistent settings for the same idea.
//---
//--- Every default below is byte-identical to the literal it replaces, so this ships as a pure no-op:
//--- see the ADP token in ConfigFingerprint(), which is appended ONLY when something actually differs,
//--- leaving every existing model's filename - and therefore its trained weights - untouched.
#define WYK_VOL_CLIMAX_DEF 2.5
#define WYK_VOL_HIGH_DEF 1.5
#define WYK_RANGE_CLIMAX_DEF 1.8
#define WYK_RANGE_SIGNIF_DEF 1.2
#define WYK_ST_VOL_RATIO_DEF 0.6
#define WYK_ATR_MULT_DEF 0.5
#define ADCD_LOOKBACK_DEF 50
#define SOT_THRUST_LOOKBACK_DEF 30
#define SOT_MIN_IMPULSES_DEF 3
#define SOT_THRESHOLD_DEF 0.30
#define WES_LOOKBACK_DEF 50
#define WES_ZIGZAG_DEF 3
#define WES_TOUCH_ATR_DEF 0.5
#define WES_AR_MIN_ATR_DEF 1.0
#define WES_MAX_RANGE_BARS_DEF 200
#define WFS_LOOKBACK_DEF 50
#define WFS_ZIGZAG_STRENGTH_DEF 3
#define WSBI_LOOKBACK_DEF 50
//--- PRUNED FROM THE MENU 2026-08-16 (user request: smaller input list, tuner-owned values). The 18
//--- inputs that lived here for eight days become compile-time aliases of their own defaults, so every
//--- consumer (CADIndicatorTuner's seeds, ConfigFingerprint's ADP token) is untouched and the values are
//--- byte-identical to what the menu shipped. The OPERATOR path to these numbers is now the auto-tuner:
//--- it defaults ON (see AutoTuneIndicators below), searches from these seeds under a family-wise gate,
//--- and persists winners inside the .nnw next to the weights. Anyone who genuinely needs to hand-set a
//--- value edits the _DEF constant above - a deliberate speed bump, because hand-set values bypass the
//--- gate that keeps noise out of the feature stack.
#define Wyk_VolClimaxMult WYK_VOL_CLIMAX_DEF
#define Wyk_VolHighMult WYK_VOL_HIGH_DEF
#define Wyk_RangeClimaxMult WYK_RANGE_CLIMAX_DEF
#define Wyk_RangeSignificantMult WYK_RANGE_SIGNIF_DEF
#define Wyk_ShortTermVolRatio WYK_ST_VOL_RATIO_DEF
#define Wyk_AtrMult WYK_ATR_MULT_DEF
#define ADCD_Lookback ADCD_LOOKBACK_DEF
#define SOT_ThrustLookback SOT_THRUST_LOOKBACK_DEF
#define SOT_MinImpulses SOT_MIN_IMPULSES_DEF
#define SOT_Threshold SOT_THRESHOLD_DEF
#define WES_Lookback WES_LOOKBACK_DEF
#define WES_ZigZag WES_ZIGZAG_DEF
#define WES_TouchATR WES_TOUCH_ATR_DEF
#define WES_ARMinATR WES_AR_MIN_ATR_DEF
#define WES_MaxRangeBars WES_MAX_RANGE_BARS_DEF
#define WFS_Lookback WFS_LOOKBACK_DEF
#define WFS_ZigZagStrength WFS_ZIGZAG_STRENGTH_DEF
#define WSBI_Lookback WSBI_LOOKBACK_DEF
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- News LAST in this list on purpose: it is the only feature whose data comes from outside the price
//--- series (the terminal's economic calendar), so it is the one a user is most likely to want to reason
//--- about separately - and the only one with a companion setting. Proximity/impact only, never
//--- actual-vs-forecast, which is not knowable ahead of the release.
input bool EnableNews = false; // Feature: news proximity
input NF_LOOKBACK_PRESETS NewsFeatureWindowMinutes = M60; // News feature window
fix(signals): revive a dead MA model, and demote Sanyaku from state to event Two defects surfaced by research/test_classic.py, both verified fixed by re-running the transcription against 178k bars of EURUSD H1. CSignalMA model 1 could never fire. For any recursive average - and MA_TYPE_EMA is the shipped default - MA(i) = a*Close(i) + (1-a)*MA(i+1), so DiffMA(i) = a * (Close(i) - MA(i+1)) DiffCloseMA(i) = (1-a) * (Close(i) - MA(i+1)) are positive multiples of one quantity and always share a sign. Model 1 asks for a close BELOW a RISING average, which is precisely the combination that identity forbids: 0.000% of bars, either direction, any symbol. The MQL5 standard library this was ported from defaults to MODE_SMA, where the two are merely correlated - the bug arrived with the EMA default, not with the port. Reading the slope one bar back (DiffMAPrev) breaks the tie for every MA type while keeping the model's stated meaning. Now fires on 7.92% of bars. CSignalIchimoku model 11 fired on 27% of bars at weight 100. Sanyaku is three standing STATES conjoined with no transition term, so it held across long stretches - and being last in the if-chain at the top weight, the module's highest-conviction reading was also its most common one, overwriting all eight event models below it on a quarter of all bars. The old comment rejected an event form because "demanding all three flip on the same bar would fire almost never" - true, but that is not the alternative. Kouten is the TURN: the ALIGNMENT transitions, and only one role need change for it to. Testing !Sanyaku(idx+1) fires once per aligned stretch. Now 2.17%, in line with Kumo breakout (2.4%) and the strong TK cross (1.1%). DataReady() extended one bar deeper to cover the lookback. Neither pattern showed edge before or after; this is about the models meaning what they say and the vote not being dominated by a constant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 17:14:34 -04:00
//--- Cross-asset, after News for the same reason: its data also comes from outside this symbol's own
//--- series - in fact it is the ONLY feature here that does so without leaving the price domain. Every
//--- other block above, News included, is either a transform of this one instrument's OHLCV or a
//--- timing overlay on it. Measured end to end, that whole family sits at the noise floor
//--- (research/test_classic.py), which is precisely why this exists. Builds a currency-strength panel
//--- from the FX pairs in Market Watch and feeds the traded pair's base/quote strength plus the
//--- divergence between the pair and its own two currencies. Needs >= 2 usable FX pairs in Market
//--- Watch; degrades to a neutral 0-fill with one logged line if it cannot build, never blocks training.
input bool EnableCrossAsset = true; // Feature: cross-asset currency strength
feat(ai): spread as a volatility-regime feature, and fix a stale-index cache in both new blocks Adds spread/ATR and the spread change ratio as network inputs (EnableSpreadFeature, default on). Spread is the one microstructure channel that is both FX-available and genuinely historical in the Strategy Tester - "during testing, the spread is not modeled but is taken from historical data" - so unlike swap, signed tick flow or depth of market it is something a backtest can honestly validate. What it encodes, stated precisely because the raw measurement overstates it. research/test_spread.py found spr/atr the strongest single feature in this codebase, on 5 of 8 instrument/geometry cells at 2-4x any volume feature. But the barrier LABEL charges the spread inside its own barriers, so a wide-spread bar is mechanically likelier to resolve as a loss and the feature would partly be predicting its own cost model. Relabelling at zero cost and re-measuring the identical feature showed 20-40% of it WAS that tautology and the majority was not (XAUUSD retained 97%). What survives is a volatility-regime reading: spread is near-fixed while ATR is not, so the ratio runs high exactly when realised volatility is below its own ATR estimate, which genuinely predicts whether ATR-scaled barriers get reached. It is UNSIGNED - Neutral-vs-directional only, never a side. Also fixes a stale-index bug I introduced with the cross-asset panel and had just repeated in the spread series. Both cached on length alone: if(m_crossAsset.Bars() >= bars) return true; MQL5 series indices are relative to NOW, so one new closed candle shifts every index by one. Keyed only on length, the panel keeps serving its index 0 as a bar that is no longer the newest, and every cross-asset value is read one bar out of step with the price features sitting beside it in the same vector - silently, with no error and no shape change. This is the same class of defect as the dtStudied watermark behind the zero-direction backtests. Both now carry a datetime anchor on m_Time.GetData(0), the same invalidation key the label/feature bar caches already use. And a performance fix that fell out of it: with correct invalidation the panel rebuilds on every new bar, and RefreshConvergedSignal runs per bar - which in the tester would mean one full multi-symbol resample per simulated bar at training depth. Inference only reads bars 0..m_historyBars-1 plus the panel's own slow window, so it now requests exactly that. The cache check is >=, so a deeper panel left from training still satisfies it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 17:42:40 -04:00
//--- Spread: the only microstructure channel that is both FX-available and genuinely historical in
//--- the Strategy Tester, so the only one a backtest can honestly validate. Encodes a volatility
//--- REGIME (spread is near-fixed while ATR is not, so the ratio runs high exactly when realised
//--- volatility is below its ATR estimate), which predicts whether ATR-scaled barriers get reached.
//--- Unsigned, like volume - it informs Neutral-vs-directional and can never pick a side.
input bool EnableSpreadFeature = true; // Feature: spread / volatility regime
//--- Alternative data: the externally-collected, publication-stamped block (COT positioning, VIX
//--- complex, macro) - the only feature family here whose information does not exist anywhere in the
//--- terminal. Per-symbol feature sets are decided by the research screens (family-wise + incremental
//--- gates, research/altdata/DESIGN.md) and served/maintained per System\AltDataFetch.mqh; this toggle
//--- only gates CONSUMPTION. With it on and no file for this symbol, the block contributes 0 features
//--- and the topology is unchanged - so it is safe ON everywhere. Turning it OFF on a model trained
//--- WITH alt features is a config change (input width shrinks) and correctly starts a fresh model.
input bool EnableAltData = true; // Feature: alternative data (COT / VIX / macro)
//--- API keys travel WITH the EA as input defaults so wiping Common\Files\Warrior_EA (the usual
feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols EIA (user directive: "the NN might find patterns in it for both oil and regular symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR, field production, refinery utilization - three features (1y percentile, 4w change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not the screen, decides whether a model trained on them trades. Publication stamp observed+6d mirrors research/altdata/eia.py. Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24 instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC Markets naming, with prefix matching for the broker suffix zoo (US500.cash, XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are named by CANONICAL so two brokers' names for one contract share a download. Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog + dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and "No alternative data" is a recorded choice, not a nag. Non-blocking by design: an unmapped symbol contributes 0 features and must never hold up a chart. Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the wildcard and an unescaped one corrupts the query; docs/ gains the whitelist URLs, an API-key backup, and the catalog reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:18:29 -04:00
//--- start-fresh ritual) cannot silently kill a source again (the 2026-08-16 incident: COT
//--- fetched, FRED skipped keyless, feature files never built). A keys.txt in the AltData folder
feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols EIA (user directive: "the NN might find patterns in it for both oil and regular symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR, field production, refinery utilization - three features (1y percentile, 4w change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not the screen, decides whether a model trained on them trades. Publication stamp observed+6d mirrors research/altdata/eia.py. Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24 instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC Markets naming, with prefix matching for the broker suffix zoo (US500.cash, XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are named by CANONICAL so two brokers' names for one contract share a download. Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog + dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and "No alternative data" is a recorded choice, not a nag. Non-blocking by design: an unmapped symbol contributes 0 features and must never hold up a chart. Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the wildcard and an unescaped one corrupts the query; docs/ gains the whitelist URLs, an API-key backup, and the catalog reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:18:29 -04:00
//--- is only consulted if an input is blanked. COT needs no key. EIA feeds the exploratory
//--- petroleum block on every catalog symbol (user directive; screened null on WTI, so the
//--- deploy gate - not the screen - decides whether models trained on it trade).
input string FredApiKey = "9640c07ff6574c1c23a17393b735fd36"; // FRED API key (VIX/USD features)
feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols EIA (user directive: "the NN might find patterns in it for both oil and regular symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR, field production, refinery utilization - three features (1y percentile, 4w change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not the screen, decides whether a model trained on them trades. Publication stamp observed+6d mirrors research/altdata/eia.py. Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24 instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC Markets naming, with prefix matching for the broker suffix zoo (US500.cash, XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are named by CANONICAL so two brokers' names for one contract share a download. Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog + dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and "No alternative data" is a recorded choice, not a nag. Non-blocking by design: an unmapped symbol contributes 0 features and must never hold up a chart. Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the wildcard and an unescaped one corrupts the query; docs/ gains the whitelist URLs, an API-key backup, and the catalog reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:18:29 -04:00
input string EiaApiKey = "oeSZu7EaZxG5Icjm6q78yUIXaH2EKGhIwVsdTj76"; // EIA API key (petroleum features)
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- Searches the per-bar parameters of every ENABLED input feature above (the order-flow/Wyckoff
//--- MA/RSI feature periods) for the combination that trains best - see ADIndicatorTuner.mqh.
//--- The TRIAL COUNT IS NO LONGER AN INPUT (2026-08-01). It was a number the user had no basis to pick:
//--- the right budget depends on how many parameters are actually being searched and how wide each
//--- one's range is, both of which the code knows at runtime and the user does not. Asking for it
//--- guaranteed either a wasted search (too many trials on two narrow parameters) or a blind one (32
//--- trials against a space of millions). Now derived - see ComputeTuneTrialBudget().
//--- DEFAULT HISTORY, kept because each flip was measured, not vibed:
//--- * Flipped to false 2026-08-08: the sweep scored candidates against the BARRIER (direction)
//--- label, whose headline MI read 0.00370 nats against a null of 0.00379 +/- 0.00061 (p=0.4975).
//--- Every candidate was a noise draw, the Sidak gate rejected every winner (p=1.0000 after 324
//--- candidates), and 45-56 min per model bought nothing. That was the gate working - on a target
//--- with nothing to find.
//--- * FLIPPED BACK TO true 2026-08-16, because the OBJECTIVE changed, not the gate: the sweep now
//--- scores against the excursion RANGE target (MI_TUNE_TARGET), which carries measured signal
//--- (4x its null, p=0.005, with a working positive control) - the landscape has a slope, so the
//--- search finally has something to climb. Simultaneously the 18 AD/Wyckoff menu inputs were
//--- pruned to constants (see above), making the tuner the ONLY path by which those values move -
//--- off would mean frozen-at-default forever. Winners still need the family-wise gate; a noise
//--- instrument still correctly tunes nothing.
//--- The sweep is one function of twelve in AIBase\AutoTune.mqh - the other eleven are the MI/lag/
//--- excursion/geometry diagnostics behind every verdict this project relies on, and they run on their
//--- own path regardless of this flag (see TuneIndicatorsAndTrain's else-branches).
input bool AutoTuneIndicators = true; // Auto-tune indicator params (gated, era 0)
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//==================================================================================================
// FILTERS
//==================================================================================================
input string SF_Settings = "Session Filter"; // Session Filter
perf(autotune): replace the genetic search with a filter score - hours to seconds MEASURED COST OF THE GA, which is what retired it. Per generation: rung 0: 8 cand x 3 seeds x 3 eras = 72 eras rung 1: 4 cand x 3 seeds x 8 eras = 96 rung 2: 2 cand x 3 seeds x 20 eras = 120 = 288 eras/generation x 4 generations = 1152 eras BEFORE the winner's real training began. Against the observed era times on SP500 H1: PAI 29.1 s/era -> 9.3 h (matches the observed 00:37 -> 09:22) CONV 41.3 s/era -> 13.2 h LSTM 150.4 s/era -> 48.1 h HYBRID 154.6 s/era -> 49.5 h Two days to tune is not a first-run experience, and it is the phase in which the panel goes quiet, which is what made it look like a hang. It also bought nothing. The space is 90 points (10 MA periods x 9 MA types), so 1152 evaluations revisited each point ~13 times; and rungs of 3 and 8 eras cannot separate two MA periods at all. The 2026-08-01 run proves it: every finalist scored 25.0-25.9% balanced accuracy - below the 33.3% one-class floor, i.e. indistinguishable noise - and the search then "deployed the winner" of that. THE ERROR WAS THE SCORING FUNCTION, not its constants. Using a full training run to choose a feature's period is a wrapper method paying wrapper prices for a decision that does not need one. The reference book does not do this: ch. 3.3 selects inputs by measuring each candidate indicator's CORRELATION with the target and dropping the ones with none, with no network involved. So: rank candidates by the MUTUAL INFORMATION between the resulting feature vector and the triple-barrier label. MI rather than correlation because the label is 3-class categorical and the features are not monotonically related to it. Equal-FREQUENCY binning (rank-based), because these features are ATR-normalised and heavy-tailed - fixed-width bins put nearly everything in one bucket and report ~0 information for a genuinely useful feature. Scoring is arithmetic over the feature cache, so it costs seconds and its cost is independent of topology: LSTM now tunes as fast as the MLP. Coordinate sweep, not product sweep - cost is the SUM of per-parameter candidate counts, so enabling every indicator stays affordable - with a second pass that breaks early once nothing moves. Sampling is IS-ONLY. Letting the OOS window influence which indicator settings ship would mean the holdout had been used for selection and had stopped being a holdout. HONEST LIMIT, recorded because it is the price: MI is marginal, so a parameter that only pays off in combination with another can be missed (Guyon & Elisseeff 2003, filter vs wrapper). Given the wrapper it replaces was ranking pure noise at 48 h a run, this is strictly better. Deleted with it: GaRungEras/GaExtract/GaStore/GaMutate/GaRandomCandidate/ GaBlockCrossover/GaSortAliveByScoreDesc/GaBreedNextGeneration, 14 m_ga* members, the GA_*/TUNE_POP_* constants, and ComputeTuneTrialBudget. AND m_evalMode/m_evalEraBudget, because nothing set them any more - 28 read sites all permanently inert. That is not a tidy-up: the `if (!m_evalMode)` guard on UpdateClassPriors is exactly what silently disabled the imbalance correction for entire runs two commits ago. Dead machinery that still reads like live machinery is this codebase's most expensive recurring bug, and leaving 28 more instances of it would have been indefensible. The panel's tuning-progress state goes too - tuning no longer takes long enough to need one. Both builds compile 0 errors / 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:29:31 -04:00
input bool EnableSessionFilter = false; // Signal: Session filter
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- All three ON by default. The filter is evaluated once per BAR (Expert_EveryTick=false ships as the
//--- default), so on a slow timeframe there are very few evaluations per day and a single-session
//--- default can starve the EA of entries entirely - on D1 there is exactly ONE evaluation, at the bar
//--- open, and whether that instant falls inside a narrow session window depends purely on the broker's
//--- server offset. Enabling all three spans 00:00-22:00 GMT so only genuinely dead hours are excluded;
//--- narrow it deliberately per-chart rather than inheriting it as an accident of the default.
input bool SF_trade_LondonSession = true; // Trade London session
input bool SF_trade_TokyoSession = true; // Trade Tokyo session
input bool SF_trade_NewYorkSession = true; // Trade New York session
//--- Scheduled flat-close. Deliberately its OWN group rather than part of the Session Filter above:
//--- CExpertCustom::OnTick() (Expert\ExpertCustom.mqh) evaluates this schedule unconditionally, so it
//--- fires whether EnableSessionFilter is on or off - grouping it under the session filter implied a
//--- coupling that has never existed in the code. Set Close-all day = Disabled to switch it off.
input string CA_Settings = "Scheduled Close-All"; // Scheduled Close-All
input CLOSE_DAY_OF_WEEK targetDayOfWeek = CLOSE_FRIDAY; // Close-all day
input CLOSE_HOUR_OF_DAY targetHour = CH_23; // Close-all hour
input CLOSE_MINUTE_OF_HOUR targetMinutes = CM_45; // Close-all minute
//--- INTRADAY TIME FILTER REMOVED ENTIRELY 2026-08-01 (5 inputs, plus Signals\SignalITF.mqh).
//--- Two of its five inputs were raw BITMASKS ("hours to avoid" as an integer), which is not a setting a
//--- trader can reasonably compute - it is an implementation detail exposed as a control, and it shipped
//--- disabled so essentially nobody ever got it right. More importantly the job is now covered three
//--- times over by things that learn or are declarative: the Session Filter handles "when may I trade"
//--- explicitly, the time-of-day/day-of-week features (EnableTime) let the NETWORK discover which hours
//--- are good on this instrument instead of being told, and the trade journal ranks by time bucket.
//--- A hand-specified hour mask is the least informed of the four and the hardest to use.
input string NF_Settings = "News Filter"; // News Filter
input bool EnableNewsFilter = true; // Signal: News filter
input NF_LOOKBACK_PRESETS NF_LookMinutes = M60; // News avoid window (min)
input NF_IMPACT_PRESETS NF_MinImpact = HOLIDAYS; // Min news impact to avoid
//--- MARKET DEPTH FILTER REMOVED ENTIRELY 2026-08-01 (5 inputs, plus Signals\SignalMarketDepth.mqh).
//--- It needs real level-2 DOM data, which this development broker does not provide and which most
//--- retail MT5 brokers do not provide either - so the module has never been executed against real data
//--- even once. Shipping four tuning dropdowns for an UNTESTED code path is worse than shipping nothing:
//--- the only users who could enable it are the ones whose broker supplies a book, and they would be
//--- the first people ever to run it, in live trading, with no validation behind it. If DOM support is
//--- wanted later it should return as a feature fed to the network rather than as a rule-based veto with
//--- its own hand-tuned thresholds - the imbalance is data, and data belongs in the input vector.
input string RiskGuard_Settings = "Risk Guard"; // Risk Guard
input bool EnableRiskGuard = true; // Signal: Risk Guard
//--- FREE-ENTRY PERCENTAGES, replacing the RISK_LIMIT_PCT_PRESET dropdown these two used to be
//--- (that enum is gone - see Enumerations\InputEnums.mqh). Every funded/prop programme sets its own
//--- numbers and they are not always integers, so a fixed ladder of presets could not express them;
//--- 4.5% or 3.75% were simply unreachable. Enter the limits from YOUR account agreement, and enter
//--- them slightly TIGHTER than the contract if you want margin for slippage past a stop.
//--- 0 disables a rule. Enforced live at quote frequency by Variables\RiskBudget.mqh - not once per
//--- bar, which is all the old guard could manage.
input double MaxDailyLossPct = 4.0; // Daily loss limit % (0 = off)
input double MaxDrawdownPct = 8.0; // Max total drawdown % (0 = off)
//--- TRUE: max drawdown is measured down from the highest equity ever reached (trailing DD, the
//--- stricter and more common funded-account rule). FALSE: measured from the equity this EA first
//--- saw on the account (static DD). Pick whichever your programme actually uses - a trailing rule
//--- applied to a static challenge halts trading long before it has to.
input bool MaxDrawdownIsTrailing = true; // Max DD trails the equity peak
//--- Broker-server hour at which the firm's trading day (and therefore the daily loss allowance)
//--- resets. Broker time here is NOT your local time; if the firm quotes the reset in another zone,
//--- convert it. A misaligned window hands the allowance back hours early or late.
input int RiskDayResetHour = 0; // Risk day reset hour (broker time, 0-23)
//--- Ceiling on what ONE trade may risk, as a share of the allowance that is genuinely left after
//--- subtracting every open position's remaining loss-to-stop. This is the fix for the real breach
//--- mode: without it a trade at 3.2% into a 4% day still sized for a full risk unit and a routine
//--- stop-out went through the limit. At the default 50% a full stop-out spends at most half of
//--- what is left, so even a stop that slips to twice its distance lands inside the limit.
input double RiskPerTradeOfBudget = 50.0; // Max % of remaining budget per trade
//--- Blocking new entries cannot stop an ALREADY-OPEN position from running through the limit, which
//--- is the way a hard daily loss rule is actually breached. Turn this on to close this EA's own
//--- positions (matching symbol + magic) the moment a limit is hit. Default OFF because closing
//--- positions is a materially bigger behaviour change than declining to open them - but leaving it
//--- off means the limits above are advisory, not enforced.
input bool RiskGuardFlatten = false; // Close own positions on breach
feat: expectancy stop - halt when the measured result says the strategy loses The daily (4%) and total (8%) rules bound how FAST an account can lose. Nothing noticed WHETHER it was losing. A negative-expectancy signal traded at 1% inside that envelope breaches no rule and still arrives at zero - it just takes longer, with every limit green the whole way down. That is the realistic way this EA destroys an account, and no existing guard could see it. THE ARITHMETIC THIS ENFORCES. Expected value per trade is p*TP - (1-p)*SL - cost. With no directional edge p equals SL/(SL+TP), which is also the break-even rate, so the payoff terms cancel exactly and EV = -cost. Expected P&L is -(trades) x cost: strictly negative, proportional to activity. Measured here: directional precision 23-24% against a 25% break-even, flat across every confidence tier, with 58 points of spread on SP500. Sizing, stop placement and trailing move variance around that mean; none of them changes its sign. So every closed position now reports its result in R (net profit over money actually at risk) and the running mean is tested against zero. Above the configured minimum sample, if mean + sigma*SE < 0, new entries stop. - SIGNIFICANTLY below, not merely below. A run of losers is ordinary variance even for a profitable system; halting on the raw mean would be the same act-on-noise error the MI gates exist to prevent. Using the standard error means a wide spread simply demands more trades before the rule can fire. - NET of swap and commission (ResolveClose already sums all three). Deliberate and load-bearing: when the edge is zero, cost IS the expectancy, so a gross version would measure a strategy nobody can trade. - Reported in R so symbols, lot sizes and balances share one scale and one mean. Trades without a stop are not scored rather than assigned a guessed R. - LATCHED across restarts, like the daily halt and for the same reason: a latch a reattach clears is not a latch. Clearing it means deleting the risk state file, deliberately, after looking at why. State is appended to the risk file length-guarded, so files written before this still load and start their sample at zero rather than misreading. Defaults 40 trades / 2 sigma; ExpectancyMinTrades = 0 disables it. This does not make the strategy profitable and is not meant to. It stops paying tuition on one the results say is losing, and does it on measurement rather than on a drawdown limit finally being reached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:20:00 -04:00
//--- EXPECTANCY STOP. The daily and total limits bound how FAST the account can lose; neither notices
//--- WHETHER it is losing. A negative-expectancy signal traded inside a 4%/8% envelope breaches no rule
//--- and still arrives at zero - it just takes longer. This tests the realised mean result per trade
//--- against zero and stops opening new positions once it is significantly below.
//--- Significantly, not merely below: a run of losers is ordinary variance even for a profitable system,
//--- so the test uses the standard error of the mean and a wide spread simply demands more trades before
//--- it can fire. Measured in R (net profit over money risked), so symbols and lot sizes share one scale,
//--- and net of swap and commission - when the directional edge is zero, cost IS the expectancy.
//--- 0 trades = off. The halt is LATCHED and survives a restart; clearing it means deleting the risk
//--- state file, deliberately, after looking at why.
input int ExpectancyMinTrades = 40; // Halt if losing: min closed trades first (0 = off)
input double ExpectancySigma = 2.0; // ...and mean must be this many std errors below zero
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//==================================================================================================
// TRADE JOURNAL / PATTERN RANKING
//==================================================================================================
input string Journal_Settings = "Trade Journal / Ranking"; // Trade Journal / Ranking
//--- Enables the per-pattern win-rate database: scales each signal's vote by its historical win rate,
//--- records every trade, and powers the Export Trade Journal Report button (see the control panel).
feat(meta): self-contained corpus - the META chart sweeps the real classic ladders over its own history The user should not need a tester corpus run per symbol. Every pattern condition in Signals\Signal{MA,RSI,MACD,Ichimoku}.mqh anchors its reads on `int idx = StartIndex()` with zero hardcoded indices (verified), so a name-hiding StartIndex override + EvalShift(i) on CExpertSignalCustom makes the EXACT live ladder code answer "what would you have fired at bar i" - the silent-divergence trap that justified the DB corpus does not exist on this path, and neither do the GMT-offset ambiguity, the DB row caps, or the wipe procedure. - CExpertSignalCustom: m_evalShift + StartIndex()/EvalShift() + SweepPrepare(bars) (deep-resizes the shared price series); the four classic signal classes override SweepPrepare to deep-resize their own indicator buffers. - CSignalMETA::BuildCorpusBySweep: per bar x per source filter, run Direction() shifted, harvest the per-side pattern slots + netVote into the same corpus arrays the DB loader fills; entry=bar open so MetaPrepareEra's resolution matches at offset +0 with zero price error. DB corpus remains the fallback when classic filters are disabled. - Warrior_EA.mq5: META gets the enabled classic filters as candidate sources (family ids match the descriptor one-hot). - UseDatabaseRanking default false -> true (user request): a META chart journals + ranks out of the box. Workflow per symbol is now: attach ONE chart with AIType=META (optionally Meta_ExportDataset=true for the offline pool) - candidates, labels, training and export all happen in place, ~10 seconds of sweep instead of a tester run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:19:43 -04:00
//--- Default TRUE since 2026-08-13 (user request): a META chart should journal + rank out of the box,
//--- and the classic-only configuration benefits from ranked weights as soon as history accumulates.
input bool UseDatabaseRanking = true; // Weight filters by DB win-rate
//--- Row cap per pattern table (oldest row pruned past it). 1000 is plenty for live ranking; a
//--- META-LABEL CORPUS BUILD (Meta_Labeling_Design.md, stage S1: a long backtest whose signal DB
//--- becomes the training set) needs it raised so a 15-20 year run isn't pruned away - 20000 holds
//--- ~3x the densest pattern's 20-year stretch count. A high cap costs nothing until rows exist.
input int DB_MaxRowsPerTable = 1000000; // Max rows kept per pattern table
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
//--- META DATASET EXPORT (cross-sectional pooling, Meta_Labeling_Design.md). With AIType=META, the
//--- chart writes its full training set - every resolved candidate's feature window + setup
//--- descriptor + triple-barrier label - to Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32
//--- (float32 rows; sidecar .meta.csv carries width/geometry/BE) once per attach, then trains as
//--- normal. Pooled training across symbols happens OFFLINE on these files; the EA itself is
//--- unchanged. Costs one pass-1-sized sweep (~a minute) at attach. Default ON in the private build
//--- (the pooling campaign's drop-on-chart workflow); OFF for Market.
#ifdef WARRIOR_MARKET_BUILD
feat(meta): dataset export for offline cross-sectional pooled training Meta_ExportDataset input: with AIType=META the chart writes its complete training set once per attach - every resolved+labeled candidate as [barTime|family|pattern|side|won|NetInputWidth floats] using the SAME window builder, descriptor and label caches pass 2 trains on, so offline examples are byte-equivalent to the EA's own. Sidecar .meta.csv carries layout + the geometry/BE the labels were computed at. Files land in Common\Files\Warrior_EA\MetaExport\<sym>_<period>.f32. This is the pooling architecture decision: multi-symbol training INSIDE the per-chart God-class would be the riskiest surgery this codebase has seen; instead each chart exports, the pooled head trains offline (small dense+BN net, minutes on this box), is validated per-symbol under the same chronological splits and coverage x (p - BE) gate, and only a WINNER gets written back into a .nnw for the EA to load natively (format fully mapped). Also turns every future meta experiment from a 20-minute tester cycle into minutes of offline iteration. Cost-model note for the record (user challenge, verified): spread is 0.099 ATR = ~2% of the 4.74 ATR trade width - tiny per bar, but expressed in win-rate points it is 0.099/4.74 = 2.1pp, which is the measured base-vs-BE gap and the size of the entire observed skill lift. Zero-spread relabeling would put base == BE by construction. Multi-day holds additionally pay swap, which the label does NOT charge - the true bar is higher, not lower. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:14:12 -04:00
input bool Meta_ExportDataset = false; // META: export training dataset at attach
#else
input bool Meta_ExportDataset = true; // META: export training dataset at attach
#endif
refactor(perf): pin CPU threads per network, drop the TargetCPULoad input Dividing a machine budget by the live chart count was wrong twice over. The count is a snapshot taken when each net's pool is built, and charts attach one at a time: five charts measured 10/6/5/4/4% of the same budget, because the first only ever saw itself and the last saw all five. So the earliest chart got several times the threads of the latest - skewing any cross-topology comparison run on those charts, which is the exact thing the setting existed to make fair. Nothing rebalanced afterwards either, and rebalancing would mean tearing down a DLL context under a live trainer. Both problems disappear once the answer stops depending on how many charts are running. Each net now asks for a fixed 2 worker threads, converted to the percentage the DLL wants from the detected core count. Two is not a compromise: since the topology became data-derived the widest dense layer is 64 units, so each ParallelFor has almost nothing to split and per-dispatch overhead dominates. An MLP era cost ~66s at a wildly oversubscribed 12 threads and ~80s at 1 thread - a 20% spread across a 12x difference in thread count. Two per net also lands six concurrent charts exactly on a 12-core box. Removing the input costs nothing on the product side: a Market build has no DLL tier at all, so it was already compiled out to a constant there and no buyer could reach it. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:33:37 -04:00
//--- Header only. The AI\Network.mqh optimizer inputs (Adam*, Sgd*) are declared in that library
//--- header; because this Inputs file is included FIRST (see Warrior_EA.mq5), those
//--- render immediately AFTER this divider - grouping them here instead of leading the Inputs tab.
input string NNPerf_Settings = "NN Optimizer / Performance"; // NN Optimizer / Performance
//--- Chart-level tuned-period state rides with the inputs (guarded, so the explicit include in
//--- Warrior_EA.mq5 stays harmless): every translation unit that sees the seed constants above also
//--- sees the g_Tuned* globals that supersede them - the tuner ctor reads those.
#include "TunedPeriods.mqh"