Warrior_EA/Variables/Inputs.mqh

374 lines
32 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,
//--- AI Input Features, Filters, Neural Network.
//==================================================================================================
// 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
input bool VerboseMode = false; // Detailed panel + verbose journal (off = simple panel)
//==================================================================================================
// 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
input ENTRY_MULTIPLIER Entry_Multiplier = MARKET; // Entry type/offset
input STOP_LOSS_MODE SL_Mode = SL_ATR_x1; // Stop-loss mode
input TAKE_PROFIT_MODE TP_Mode = TP_PREV_SWING; // Take-profit mode
input RISK_REWARD_RATIO Min_Risk_Reward_Ratio = RR_1x2; // Min reward:risk (reject only)
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.
input PERCENTAGE_PRESETS Min_Vote_Open = PCT_20; // Min vote to open - AI + classic (0-100)
input VOTE_CLOSE_PRESETS Min_Vote_Close = VOTE_CLOSE_DISABLED; // Min opposite vote to close - AI + classic
//==================================================================================================
// CLASSIC SIGNALS (rule-based MA/RSI votes - trade alongside or instead of the neural network)
//==================================================================================================
input string Classic_Settings = "Classic Signals"; // Classic Signals
//--- EnableMA/EnableRSI default depends on the build (see AIType's declaration comment for the full
//--- rationale): ON for a Market submission build (WARRIOR_MARKET_BUILD defined) so a fresh install
//--- trades immediately with no AI warm-up, OFF for the private/live build, which runs AI-only by
//--- default. Either way this is only a compile-time DEFAULT - still a normal input, changeable per-run
//--- from the Inputs tab without recompiling.
#ifdef WARRIOR_MARKET_BUILD
input bool EnableMA = true; // MA classic vote
#else
input bool EnableMA = false; // MA classic vote
#endif
#ifdef WARRIOR_MARKET_BUILD
input bool EnableRSI = true; // RSI classic vote
#else
input bool EnableRSI = false; // RSI classic vote
#endif
//--- MACD and Ichimoku votes. Unlike EnableMA/EnableRSI above these default OFF in BOTH builds,
//--- including the Market one: they are additive to a classic set that already trades out of the box
//--- there, and turning them on by default would silently change the shipped strategy's behaviour rather
//--- than merely widening the choice. MACD contributes a momentum/divergence model (MA reads level, RSI
//--- reads a bounded oscillator - neither carries divergence); Ichimoku contributes multi-timescale
//--- support/resistance structure. Enable per-run from the Inputs tab like any other signal.
input bool EnableMACD = false; // MACD classic vote
input bool EnableIchimoku = false; // Ichimoku classic vote
//--- Indicator parameters below are SHARED: they define the classic votes above AND seed the matching AI
//--- input features (AI Input Features section) as their starting period, which Auto-tune indicators then
//--- searches from. Set once here, used by whichever consumer(s) are enabled.
input MA_PERIOD_PRESETS PeriodMA = MA_PERIOD_50; // MA period
input MA_TYPE_PRESETS MA_Type = MA_TYPE_EMA; // MA type (SMA/EMA/.../T3/Kalman)
input RSI_PERIOD_PRESETS PeriodRSI = RSI_PERIOD_14; // RSI period
//--- Every fast/slow pair from these dropdowns is legal by construction (fast tops out below the lowest
//--- slow), so no combination can fail CSignalMACD::ValidationSettings() - see InputEnums.mqh.
input MACD_FAST_PRESETS MACD_PeriodFast = MACD_FAST_12; // MACD fast EMA period
input MACD_SLOW_PRESETS MACD_PeriodSlow = MACD_SLOW_26; // MACD slow EMA period
input MACD_SIGNAL_PRESETS MACD_PeriodSignal = MACD_SIGNAL_9; // MACD signal period
//--- Same all-combinations-legal design against Tenkan < Kijun < Senkou B.
input ICHIMOKU_TENKAN_PRESETS Ichimoku_PeriodTenkan = ICHI_TENKAN_9; // Ichimoku Tenkan-sen period
input ICHIMOKU_KIJUN_PRESETS Ichimoku_PeriodKijun = ICHI_KIJUN_26; // Ichimoku Kijun-sen period
input ICHIMOKU_SENKOU_PRESETS Ichimoku_PeriodSenkou = ICHI_SENKOU_52; // Ichimoku Senkou Span B period
//==================================================================================================
// AI INPUT FEATURES (the data the neural network sees each bar)
//==================================================================================================
input string AISignals = "AI Input Features"; // AI Input Features
//--- ind_Periods is the number of bars per input sequence fed to the network (and the ATR lookback).
//--- Must stay >= ADZigZag Depth (12) so a full swing leg is visible to the model.
input IND_PERIODS_PRESETS ind_Periods = PERIOD_20; // Bars to analyse
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 = false; // 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 (same repainting embargo as labels).
input bool EnableSwingContext = true; // Feature: ZigZag swing context
//--- News event proximity/impact only (not actual-vs-forecast, which isn't knowable ahead of time).
input bool EnableNews = false; // Feature: news proximity
input NF_LOOKBACK_PRESETS NewsFeatureWindowMinutes = M60; // News feature window
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
//--- 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.
input bool AutoTuneIndicators = true; // Auto-tune indicator params
input TUNE_TRIALS_PRESET IndicatorTuneTrials = TT_32; // Auto-tune trials
//==================================================================================================
// FILTERS
//==================================================================================================
input string SF_Settings = "Session Filter"; // Session Filter
input bool EnableSessionFilter = true; // Signal: Session filter
//--- 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
//--- Own divider so the hours/days filter doesn't render under the Close-All group above (before that
//--- group existed it sat under the Session Filter header, which was equally misleading).
input string ITF_Settings = "Intraday Time Filter"; // Intraday Time Filter
input bool EnableITF = false; // Signal: Intraday time filter
input ENTRY_HOUR_OF_DAY ITF_GoodHourOfDay = -1; // Preferred hour
input int ITF_BadHoursOfDay = 0; // Hours to avoid (bitmask)
input TIME_FILTER_DAY_OF_WEEK ITF_GoodDayOfWeek = -1; // Preferred day
input int ITF_BadDaysOfWeek = 0; // Days to avoid (bitmask)
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
input string DOM_Settings = "Market Depth Filter"; // Market Depth Filter
//--- Live-only rule-based confirmation/veto (needs real broker DOM); verified once in OnInit().
input bool EnableMarketDepth = false; // DOM imbalance filter (live)
input DOM_DEPTH_LEVELS_PRESET DOM_DepthLevels = DOM_LEVELS_5; // DOM book levels/side
input DOM_IMBALANCE_SCALE_PRESET DOM_ImbalanceScale = DOM_SCALE_100; // DOM imbalance influence
input DOM_MAX_SPREAD_MULTIPLE_PRESET DOM_MaxSpreadMultiple = DOM_SPREADMULT_3x; // DOM max spread multiple
input string RiskGuard_Settings = "Risk Guard"; // Risk Guard
input bool EnableRiskGuard = true; // Signal: Risk Guard
//--- Blocks new entries only (never closes positions). 0 = disabled.
input RISK_LIMIT_PCT_PRESET MaxDailyLossPct = RISK_LIMIT_2; // Max daily loss % (halt entries)
input RISK_LIMIT_PCT_PRESET MaxDrawdownPct = RISK_LIMIT_15; // Max drawdown % (halt entries)
//==================================================================================================
// 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).
input bool UseDatabaseRanking = false; // Weight filters by DB win-rate
//==================================================================================================
// 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
input AI_CHOICE AIType = HYBRID_2L; // AI architecture preset (or Disabled)
#endif
//--- 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
input OUTPUT_NEURONS_COUNT OutputNeuronsCount = OUTPUT_CLASSIFICATION; // Output type
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.
//--- LSTM only - its own recurrent hidden-unit count, independent of the AI preset (which already
//--- selects the dense taper stacked after the LSTM layer). Used by LSTM and the fused HYBRID model;
//--- no effect on plain MLP/CONV.
input LSTM_HIDDEN_SIZE_PRESET LstmHiddenSize = LSTM_HIDDEN_32; // LSTM hidden size
//--- CONV only - its own convolutional output-filter count, independent of the AI preset (which already
//--- selects the dense taper stacked after the Conv+Pool stage). Used by CONV and the fused HYBRID
//--- model; no effect on plain MLP/LSTM.
input CONV_FILTER_COUNT_PRESET ConvFilterCount = CONV_FILTERS_16; // CONV filter count
//--- Shared Conv pooling span/stride for both CONV and the fused HYBRID model. Kept separate from
//--- ConvFilterCount so the feature-bank width and the downsampling geometry can be tuned independently.
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.
input bool EnableBatchNorm = true; // AI: batch normalization
//--- 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.
input int BatchNormWindow = 1000; // AI: batch-norm window (samples)
input TRAINING_YEARS_PRESET StudyPeriods = YEARS_10; // Training years
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.
input PERCENTAGE_PRESETS MinRecall = PCT_40; // Min per-class OOS recall %
//--- 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.
//--- Post-hoc prior correction for the 3-class AI decision: the network trains on class-balance-
//--- oversampled data so its raw output over-calls the rare Buy/Sell classes; this re-weights each class
//--- by its true base rate at inference so live trading fires at (and performs like) the calibrated rate
//--- shown during training. See LOGIT_PRIOR_STRENGTH_PRESETS. 0=off (raw), 100=full calibration.
input LOGIT_PRIOR_STRENGTH_PRESETS AILogitPriorStrength = LOGIT_PRIOR_25; // AI: prior correction to true base rate
//--- Precision/recall dial for training. Fraction of full class parity the minority (Buy/Sell)
//--- oversampling targets: lower = fewer, higher-precision directional calls (Neutral kept heavier),
//--- higher (toward 100%) = more calls / higher recall / more over-calling. Watch MinRecall: too low
//--- and the model can't meet the per-class recall gate. 70% is the tuned default; try 50-60% to cut
//--- Buy/Sell over-calling. NOTE this shapes the RAW model - live calls are also base-rate-calibrated
//--- by AILogitPriorStrength above, so judge over-calling by the live-fired precision line, not raw counts.
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 -> 90. 60 overcorrected. It was lowered to cut low-precision Buy/Sell over-calling
//--- and it did - too far: the SP500 H1 runs now START Neutral-dominant at era 1 (Buy 0-11% recall) and
//--- decay from there, calling Buy/Sell on 0-4% of bars when the true directional base rate is ~6%.
//--- That is UNDER-calling, and there is no headroom left to converge down from. Contrast the run that
//--- actually deployed (28th, best balanced 66.0%): it began at Buy 90% / Sell 36% recall, 24% of bars
//--- called directionally, and settled INTO the floor from above. Over-calling in the raw model is the
//--- intended starting condition here - the note below is the reason it is safe.
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
//--- LOGIT-ADJUSTED LOSS (Menon et al. 2021, ICLR, "Long-tail learning via logit adjustment").
//--- Adds tau*log(prior_c) to each class logit inside the TRAINING gradient only. Minimizing softmax
//--- cross-entropy on adjusted logits is consistent for BALANCED error - the exact metric checkpoint
//--- selection already ranks on - so for the first time the loss optimizes the same thing the deploy
//--- decision does. When on, it REPLACES two other mechanisms rather than stacking with them:
//--- - minority replay is disabled (see EnableMinorityReplay). Replay duplicated rare bars up to 28x,
//--- which made Buy and Sell compete for the same replicated capacity; measured 2026-07-29 across
//--- six topologies, every model took ONE direction to ~50% recall and abandoned the other, and the
//--- direction was arbitrary (the batch-norm control went Buy 1% / Sell 42%, the exact inverse of
//--- the other five). One era in 1,301 cleared the per-class recall floor.
//--- - the post-hoc inference prior (AILogitPriorStrength) is forced off, because the offsets are
//--- already trained in - applying it again would correct for the same base rate twice.
//--- Turning this OFF restores the previous replay + post-hoc behaviour exactly.
input bool EnableLogitAdjustedLoss = true; // AI: logit-adjusted loss (replaces oversampling)
//--- tau. 100% = full tau=1.0, the paper's default and the only value carrying the consistency
//--- guarantee; lower trades balanced accuracy back toward raw accuracy. Percent, divided by 100.
input LOGIT_PRIOR_STRENGTH_PRESETS LogitAdjustTau = LOGIT_PRIOR_100; // AI: logit-adjust strength (tau)
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
input PERCENTAGE_PRESETS OversampleParity = PCT_90; // AI: oversample parity (lower=fewer Buy/Sell calls)
input FOCAL_GAMMA_PRESET FocalLossGamma = FG_10; // Focal-loss gamma
input bool EnableMinorityReplay = true; // AI: replay minority bars through pass-2 oversampling (off = plain one-pass training)
input bool ConstrainReplay = true; // AI: cap replay aggression and use softer replay-only focal weighting
input bool FreezePriorCalibration = false; // AI: freeze class-prior updates once the first real prior is measured
input bool UseStaticPrior = false; // AI: never update priors mid-run; keep the first measured prior distribution fixed
input SWING_CONFIRMATION_PRESET SwingConfirmationBars = SC_100; // Swing confirm bars (label)
//--- Continual learning: after the model is deployed (converged/1000-era deploy) keep adapting it on a
//--- LIVE chart to newly-CONFIRMED market structure - the same supervised ZigZag task it was trained on,
//--- waiting the full SwingConfirmationBars delay so a still-repainting recent bar is never learned. The
//--- deployed model only moves toward the update while a rolling-accuracy guardrail holds. No effect in
//--- the Strategy Tester/optimizer (the model is held fixed there); forward-test it on a demo account.
//--- The class-imbalance gap that made this a single-class drift vector is FIXED as of 2026-07-28:
//--- OnlineLearnStep() previously backpropped the raw live distribution (~94% Neutral on SP500 H1) with
//--- an unweighted sampleWeight of 1.0, which actively pulled a balanced, converged model back toward
//--- Neutral. It now applies alpha-balanced focal loss (Lin et al. 2017) driven by the measured class
//--- priors and the same OversampleParity/ConstrainReplay/FocalLossGamma inputs training uses, and pins
//--- its own reduced learning rate. See the ONLINE_LEARN_* block in Expert\ExpertSignalAIBase.mqh.
//--- Still defaults OFF: the mechanism is complete but has never been forward-tested on a live feed, and
//--- this adapts a DEPLOYED model. Run it on demo first, watch the rolling-accuracy guardrail line in
//--- the journal, then flip this on.
input bool EnableOnlineLearning = false; // AI: keep learning live from confirmed bars (demo-test first)
input int SignalClusterWindow = 6; // Signal decluster window (bars, 0=off)
input MAX_ERAS_PRESET MaxErasPerRun = ME_1000; // Max eras before prompt
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