Warrior_EA/Variables/Inputs.mqh

616 lines
43 KiB
MQL5

//+------------------------------------------------------------------+
//| Inputs.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "AnimateDread"
#property link "https://www.mql5.com"
#include "..\Enumerations\InputEnums.mqh"
//--- Each `input string *_Settings` is a GUI-only section divider: MetaTrader renders an input string
//--- whose value equals its comment as a header. Never read by code.
//--- NN Optimizer / Performance must stay LAST - AI\Network.mqh's Adam/Sgd inputs render after it.
//==================================================================================================
// GENERAL
//==================================================================================================
input string Expert_Settings = "General"; // General
feat(magic): assign the magic number once, then remember it Expert_MagicNumber = 0 (the new default) means "draw one and write it down". On first attach the EA picks a random magic in a distinctive band, persists it to MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on every later start. Unique without anyone typing it, and STABLE. Stability is the whole point. The magic is how the EA recognises its own positions - a fresh one per start would leave every open position invisible to the scheduled close-all, the risk-budget flatten and the journal's MAE/MFE walk: trades still running that no code would ever manage again. So the value is persisted before it is ever used to trade. Stored TERMINAL-LOCAL rather than in Common\Files\Warrior_EA, on purpose: that folder is the one wiped for a retrain, and positions outlive retrains. It also gives two terminals on the same symbol different magics, which a chart-identity hash could not. Fallbacks, both of which stay stable without a file: * tester/optimizer/forward use a magic derived from chart identity, so two identical passes cannot differ. * an unwritable file falls back to that same derived value, and says so. Books occupy EVEN slots only, so one chart's short book (base+1) can never land on another chart's long book. WarriorOwnsMagic() now also recognises the legacy 2024/2025 pair permanently. Without it, switching an existing chart to 0 while a position was open would orphan that position. Every caller also matches the symbol, so claiming those values can only reach positions on this EA's own chart. Existing charts are untouched: MT5 stores inputs per chart, so the six live charts keep the 2024 they already have and keep managing what they hold. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:35:58 -04:00
//--- 0 = ASSIGN ONE AND REMEMBER IT. On first attach the EA draws a random magic, writes it to
//--- MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on every later start -
//--- so it is unique without anyone typing it, and STABLE, which is the part that matters: the magic
//--- is how the EA recognises its own positions. A magic that changed on restart would leave every
//--- open position invisible to the close-all, the risk-budget flatten and the journal - trades still
//--- running that no code would ever manage again.
//--- Set a non-zero value to pin one explicitly instead (back-compat; a chart already carrying 2024
//--- keeps it, because MT5 stores inputs per chart and an existing attach never sees this default).
input ulong Expert_MagicNumber = 0; // Magic number (0 = assign + remember automatically)
input bool Expert_EveryTick = false; // Calculate on every tick
//--- Also throttles the per-era training journal: false prints each diagnostic on the first eras and
//--- then every TRAIN_LOG_EVERY_ERAS-th (state CHANGES always print). true is the full firehose.
fix(vote): "0 fired" on USDJPY meant the threshold is above the highest vote the ensemble can cast USDJPY has taken no trades in 66 eras and its highest vote ever seen is 13 against a 25% threshold. Not a bug and not undertrained models - arithmetic. Direction() divides the summed contributions by the CAPABLE weight, so a unanimous vote returns the capability-weighted mean of the tier weights, which is roughly the pooled holdout win rate. USDJPY's members pool at 15.6-19.4% (its label base rate is 14.0% against SP500's 25.4%, because its derived geometry resolves far fewer bars directionally: Buy 10.3% Sell 11.2% Neutral 78.6%). So the ensemble's CEILING is ~19 and the threshold is 25. Coverage can never leave 0, and no amount of training moves it, because the ceiling IS the win rate. The report now computes that ceiling - every member voting at its best tier - and says so when the threshold sits above it, instead of printing "0 fired at vote>=25%" which reads as "the models are unsure". Same class as the excursion head's disjoint gate (ee4d459) and the reason ReportDetectability exists: a configuration that cannot reach its own bar has to say that, not report a number that looks like evidence. Also: VerboseMode and Run_Alglib_Baselines back to false. The per-era cadence was for reading the horizon break-even and the excursion sigmas; both are settled, and TrainLogDue still prints them every 25 eras. The baselines cost a 45 s single-threaded freeze at every attach and their forest row turned out to be one deterministic observation that does not survive overlap deflation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 08:16:11 -04:00
input bool VerboseMode = false; // Verbose journal + detailed panel (full per-era logs)
//--- Dev diagnostics to the Experts journal (plateau stage, deploy gate, selection internals).
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;
//--- Pins the dense-taper depth instead of deriving it (ComputeHiddenLayerCount). 0 = derived, the only
//--- value that should ship. Compile-time, so two forced depths cannot run from one .ex5.
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
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
feat(target): delete the barrier/geometry stack - the label is the verdict Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE target and the era verdict is precision + recall per class against the label's own base rate - no win rate, no break-even, no expectancy, no geometry anywhere in training. DELETED - Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep, FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives in Labeling/LabelOverlap.mqh), 3 test EAs. - TripleBarrierLabel + walk, fractal label, geometry derivation/scan/ adoption, exit-policy replay, excursion MI targets, the drift verdict (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry, the barrier defines, the .cfg geometry adopt (slots kept as zeros for the positional layout), the derived-geometry live-order override. - TRAINING_TARGET input/enum: direction models are always swing; META2 re-keys the meta head onto label agreement (descriptor loses its two geometry slots). REWORKED - Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is never cached, so it can never freeze as a false Neutral; training, calibration, OOS scoring and online learning all skip unresolved bars. - SDeployVerdict: significance-only; SOosTally chance = larger directional class share; pooled gate poolability = timeframe (record v2). - Purge/embargo/declustering gaps: the measured mean label resolution lag (LabelResolutionBars), not a barrier horizon. - Pool purge key + backfill DB rows: marked at the bar the label resolved on (m_labelResolveAge), not a fabricated barrier touch. - Online learning frontier: finality, not a horizon delay. - m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced -> m_erasSinceBest, ensemble vote outcome arrays -> label arrays. STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection are inputs again - trade management is the tester GA's search space. Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional, CUT token gone); META1 -> META2. Full retrain, as planned. Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs, 0 errors, 0 warnings each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
//--- TRADE MANAGEMENT IS THE TESTER GA'S SEARCH SPACE (2026-08-24). The NN's job is the swing label;
//--- none of these touch a fingerprint or a DB key, so the GA can sweep them without a retrain.
input TRADING_DIRECTION tradingdirection = BOTH; // Trade direction
feat(trade): two books per symbol, and delete the vote exit Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA an independent long book and short book on its symbol: at most one long and at most one short, each opened on its own side's vote and each held to its own barrier. On a netting account, or with the input off, the original single-position path runs bit-for-bit unchanged and init says which one is live. WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies P(label agrees | vote fired) and the label runs to the barrier, so closing early on a reversal makes the realised outcome stop being the labelled one - the certified precision no longer describes what is traded. Opening the other side acts on the new signal and leaves the old position's certification intact, and costs no more than reversing: both pay the new side's spread, the difference is only that the existing position runs on to a barrier already measured as positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned, along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an arithmetically unreachable 101 (the stock default of 100 is reachable by a weighted mean of values capped at 100). Note the two books can never both fill from one signal: CheckOpenLong and CheckOpenShort test opposite signs of the same m_direction, so at most one clears per tick. A hedge only forms when a LATER opposite vote fires - which is what keeps it from being a guaranteed-loss wash pair. The mechanism is a SelectPosition() override keyed on the active book's magic; every inherited close/trail path then operates on that book untouched. The long book keeps Expert_MagicNumber, so no existing position, journal row or risk-budget state file is re-addressed. Short book is +1. Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(), or the short book would have been invisible to the code that must reach it: the scheduled close-all (positions and orders), the risk budget's emergency flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT gated on Allow_Hedging - turning the input off while a short-book position is open would otherwise orphan it with nothing left to close it. Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(), which counts every position regardless of magic, so the second book is sized inside what the first one left. Conservative for a hedged pair, which cannot lose both stops - the safe direction. Retrain-neutral: neither input is in BuildModelFingerprint() or ComputeDbConfigFingerprint(). Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
//--- TWO BOOKS, ONE PER SIDE (2026-08-26). ON, and on a RETAIL_HEDGING account, the EA keeps an
//--- independent long book and short book on this symbol: at most one long and at most one short,
//--- each opened on its own side's vote and each held to its own barrier. OFF, or on a NETTING
//--- account, behaviour is exactly as before - one position per symbol, opposite votes ignored.
//---
//--- WHY THIS AND NOT A VOTE EXIT. The deploy gate certifies P(label agrees | the vote fired) and
//--- the label runs to the barrier. Closing early on a reversal vote makes the realised outcome stop
//--- being the labelled one, so the certified precision no longer describes what is traded. Opening
//--- the OTHER side instead acts on the new signal while leaving the old position's certification
//--- intact, and it costs no more than closing-then-reversing would: both pay the new side's spread,
//--- and the only difference is that the existing position runs on to the barrier its own gate
//--- already measured as positive-expectancy. That is why Signal_ThresholdClose could be deleted
//--- rather than tuned - see the ensemble deploy gate.
//---
//--- ALLOWED AT THE5ERS - single-account hedging is permitted; what their #12/#17 ban is hedging
//--- ACROSS accounts or firms (hedge arbitrage, inter-account, cross-firm). Correction recorded
//--- 2026-08-26 after an earlier reading of #17 wrongly caught this case.
//---
//--- NOT in BuildModelFingerprint(): no .nnw is re-keyed by turning this on or off.
input bool Allow_Hedging = true; // Hedging: independent long + short book (max 1 each)
feat(target): delete the barrier/geometry stack - the label is the verdict Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE target and the era verdict is precision + recall per class against the label's own base rate - no win rate, no break-even, no expectancy, no geometry anywhere in training. DELETED - Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep, FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives in Labeling/LabelOverlap.mqh), 3 test EAs. - TripleBarrierLabel + walk, fractal label, geometry derivation/scan/ adoption, exit-policy replay, excursion MI targets, the drift verdict (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry, the barrier defines, the .cfg geometry adopt (slots kept as zeros for the positional layout), the derived-geometry live-order override. - TRAINING_TARGET input/enum: direction models are always swing; META2 re-keys the meta head onto label agreement (descriptor loses its two geometry slots). REWORKED - Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is never cached, so it can never freeze as a false Neutral; training, calibration, OOS scoring and online learning all skip unresolved bars. - SDeployVerdict: significance-only; SOosTally chance = larger directional class share; pooled gate poolability = timeframe (record v2). - Purge/embargo/declustering gaps: the measured mean label resolution lag (LabelResolutionBars), not a barrier horizon. - Pool purge key + backfill DB rows: marked at the bar the label resolved on (m_labelResolveAge), not a fabricated barrier touch. - Online learning frontier: finality, not a horizon delay. - m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced -> m_erasSinceBest, ensemble vote outcome arrays -> label arrays. STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection are inputs again - trade management is the tester GA's search space. Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional, CUT token gone); META1 -> META2. Full retrain, as planned. Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs, 0 errors, 0 warnings each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
input ENTRY_MULTIPLIER Entry_Multiplier = MARKET; // Entry type/offset
input STOP_LOSS_MODE SL_Mode = SL_ATR_x2; // Stop-loss mode
input TAKE_PROFIT_MODE TP_Mode = TP_ATR_x6; // Take-profit mode
input TRAILING_STRATEGY TrailingStrategy = TRAILING_STRATEGY_NONE; // Trailing stop
input BARS_EXPIRATION Signal_Expiration = BARS_X3; // Pending order expiry (bars)
refactor(trade-mgmt): remove all confidence-scaled trade management Five modes went, all of them staking real risk on the model's confidence: Intelligent entry (ENTRY_INTELLIGENT), stop (SL_INTELLIGENT), target (TP_INTELLIGENT), trailing (CTrailingIntelligent) and lot size (CMoneyIntelligent's quarter-Kelly). With them, the Confidence_Source input and the CONFIDENCE_SOURCE enum, whose only job was choosing which number those five read. The reason is calibration, not correctness: the confidence magnitude is known to be miscalibrated against the label prior, so every one of these modes multiplied money by a quantity whose units were never established. The DB arm had a second, independent defect - since the tester DB guard (SignalDatabaseActive) it reads 0 in tester and optimizer but non-zero live, so any backtest of CONF_DB/CONF_BLENDED could not reproduce live trading. And what the DB produces is a filter-RANKING win rate, not a per-trade win probability. Both confidence numbers are still recorded per trade (aiConfidence / dbConfidence) and still bucketed against outcome in TradeJournalReport. Recording is what keeps the question answerable; acting on it was the part with no evidence behind it. ConfidenceBridge.mqh now carries an explicit telemetry-only rule at the top. ENUM ORDINALS PINNED. Removing a member vacated a value in four enums at once and MT5 does not validate an enum input replayed from a saved .set or a stored optimization pass. TRAILING_STRATEGY and MONEY_MANAGEMENT_STRATEGY now carry explicit values so the survivors keep the numbers they were saved as, and ValidateBarrierInputs is widened into ValidateTradeManagementInputs covering SL_Mode, TP_Mode, Entry_Multiplier, TrailingStrategy and MM_STRATEGY. Without that gate a chart saved with the Intelligent stop would feed SL_Mode = -1 into a multiplier now used verbatim, placing the stop on the wrong side of entry. RETRAIN-NEUTRAL: neither SL_Mode nor TP_Mode appears in BuildModelFingerprint() or ComputeDbConfigFingerprint() since the swing-pivot target replaced the barrier labels. No .nnw, .cfg or .db re-keys. Also drops the now-dead g_TradeRewardRiskRatio bridge, the CMoneyRiskBase::AdjustRiskAmount hook and the unsigned AIConfidence(). Compile-verified in _claude_stage: 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 10:10:20 -04:00
//--- Confidence_Source WAS REMOVED 2026-08-25 together with all five confidence-scaled trade-management
//--- modes it fed (Intelligent entry / SL / TP / trailing / lot size). See CONFIDENCE_SOURCE's removal
//--- note in Enumerations\InputEnums.mqh. Trade management is now entirely explicit: what the GA sets
//--- is what the trade gets, which is also what makes a GA sweep of it interpretable.
//--- CExpertSignal::m_threshold_open / m_threshold_close, on the library's own 0-100 scale: the
//--- vote is a WEIGHTED MEAN of the firing patterns' weights, which cannot exceed 100.
fix(pool): length-prefix the fingerprint - the cross-instrument pool was inert STrainPoolHeader wrote its fingerprint into a FILE_BIN stream as FileWriteString(h, fingerprint + "\n") and read it back with FileReadString(h) - no length argument. In binary mode FileWriteString emits the characters raw: no length prefix, no terminator, and "\n" is just another character rather than a delimiter anything honours. The reader had nothing to stop at, over-read into the float rows that follow, and returned the fingerprint plus a few bytes of binary garbage - so `fingerprint != wantFp` could never succeed between two genuinely identical models. Verified in the bytes rather than inferred: xxd on a v1 file shows three ints then the fingerprint starting immediately at offset 12 with no count in front of it, and EURUSD/USDJPY/USDCAD all stored width 624 with byte-identical fingerprints while each one's log rejected the other two as "different model fingerprint". The StringReplace on "\n" is the tell that a delimiter was intended. Cross-asset-class peers really are incompatible and always will be - FX majors carry XA:6, indices/metals/oil carry XA:6:IDX2, giving widths 600/612/624 - which is why the reject list looked plausible and this went unread. The three FX majors were always poolable and never pooled. Length-prefixes the string, bounds-checks the count before sizing a read from it, and bumps TRAINPOOL_RECORD_VERSION 1 -> 2 so existing files are refused by the version gate with a reason instead of being misread. Also documents, without changing, why Signal_ThresholdOpen is now a unanimity rule: the vote is a weighted mean of tier weights, those fell from ~70 to ~30 with the pivot-event label, so PCT_25 went from ~36% of the reachable ceiling to ~83%. Measured: all 6 symbols clear their precision bar, 4 of 6 fail only on coverage, and coverage decays 6.8% -> 2.2% over 35 eras as the models specialise - which shrinks effN and so RAISES the deploy bar at flat precision. PCT_20 (a 3-of-4 quorum) is the indicated change but is left unmade: MT5 stores input values per chart in profiles\Charts\*\chart*.chr, so an already-attached EA ignores this default entirely - confirmed by a full close/recompile/relaunch cycle after which the log still read "fired at vote>=25%". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:41:41 -04:00
//---
//--- THIS IS A CONSENSUS RULE IN DISGUISE, and its meaning moves with the win rate. The vote is
//--- sum(moduleWeight x tierWeight x sign) / sum(moduleWeight over every member that EVALUATED the
//--- bar - abstentions included, by design since 2026-08-19). So with N members of similar weight
//--- the reachable votes are quantised by how many agree, and the threshold picks the quorum:
//---
//--- 4 members, tier weight ~30 (the pivot-event label's win rate):
//--- 4 agree -> 30 PCT_25 => needs 4 of 4 (UNANIMITY)
//--- 3 agree, 1 abstain -> 22.5 PCT_20 => needs 3 of 4
//--- 2 agree, 2 abstain -> 15 PCT_15 => needs 2 of 4
//---
//--- PCT_25 was correct while the direction-to-next-pivot label produced ~70% win rates: the
//--- ceiling was ~70 and 25 asked for about a third of it. Under the pivot-event label the win
//--- rates are ~30%, so 25 sits at ~83% of the ceiling and has silently become a UNANIMITY rule.
//--- Measured 2026-08-26: every one of the 6 symbols cleared its precision bar and 4 of 6 failed
//--- ONLY on coverage, which fell 6.8% -> 2.2% over 35 eras as the four models specialised and
//--- unanimity got rarer. That also feeds a doom loop - fewer fires shrink effN, which RAISES the
//--- exact-binomial deploy bar (SP500: 24.1% -> 32.9% at FLAT precision).
//---
feat(vote): derive the threshold instead of configuring it Signal_ThresholdOpen becomes a seed. The era verdict now picks the HIGHEST sweep rung whose vote still clears the whole deploy gate - coverage floor, exact-binomial precision bar and two-sidedness together - computes the era's verdict AT that rung, and publishes it to the live signal's m_threshold_open so the bar the gate certifies is the bar the EA trades. Measured on 619 era verdicts across all six live charts: * every era on every symbol had at least one rung clearing the full gate. At the fixed 25% the fleet was actually running, four of six symbols had none, ever. The threshold, not the models, was the blocker. * walk-forward (rung derived on era N, scored on era N+1): 10.2% coverage / 31.8% precision, against an oracle re-picking on N+1 of 10.3% / 31.7%. Near-zero shrinkage - a measurement, not a fit. It holds because the binding constraint is COVERAGE, a near-deterministic step function of the vote distribution, not precision. * vs a fixed 15% (best global value): +0.6pp precision, 3.4pp less coverage. vs a fixed 20%: deployable on all six rather than four of six. Selection on the highest PASSING rung, never on the best-precision rung - that is a best-of-6 on a noisy statistic and this project has crowned noise that way four times. The multiplicity that remains is paid for: nTried in EnsembleSurvivesSelection is now eras x rungs. Costs nothing - all six charts clear it by 6.5-12 sigma even forming z on effective rather than raw calls. Also fixes, in the same path: the direction-policy gate is hoisted above the per-rung tally so every rung is scored on the population the gate certifies. Retrain-neutral: not in BuildModelFingerprint(), no .nnw re-keyed. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:02:58 -04:00
//--- THIS IS NOW ONLY A SEED (2026-08-26). The era verdict DERIVES the threshold - the highest rung
//--- whose vote still clears the whole deploy gate - and publishes it to the live signal every tick
//--- (THE DERIVED THRESHOLD in Expert\AIBase\Training.mqh; CExpertCustom::PublishVoteThreshold).
//--- The value below is read once, and only governs the bars traded BEFORE the first era has been
//--- scored. Setting it per chart is no longer necessary and no longer meaningful.
fix(vote): Signal_ThresholdOpen 25 -> 15, from the sweep's own numbers The quorum model that said 20 was wrong. It reasoned from the vote's quantisation - 4 members at tier weight ~30, so 3-of-4 agreeing gives 22.5 and PCT_20 admits it - and simultaneous agreement turns out to be rarer than a per-member coverage of ~27% implies. Measured on real rows by the threshold sweep added in the previous commit: symbol floor 15% cov/prec 20% cov/prec 25% cov/prec (active) EURUSD 6.7% 15.7 / 32.6 12.5 / 33.7 4.1 / 35.0 SP500 6.9% 9.8 / 32.7 4.0 / 34.4 X 1.0 / 34.7 X USDCAD 7.2% 18.9 / 32.2 12.8 / 32.6 4.0 / 34.7 X XAUUSD 6.7% 12.8 / 29.2 4.4 / 33.9 X 1.0 / 26.5 X XTIUSD 6.9% 11.4 / 34.3 7.8 / 35.7 1.7 / 38.4 X 15 clears the coverage floor on every symbol; 20 fails SP500 and XAUUSD; 25 fails all of them. The precision surrendered is about 2pp, because precision is nearly flat across these rungs while coverage moves 10-20x - the high thresholds were buying almost nothing for the coverage they cost. The rule this encodes is: take the cheapest rung whose coverage clears the floor, not the best precision. Precision above the bar earns nothing extra; coverage below the floor makes the era undeployable regardless. Not in BuildModelFingerprint(), so every trained .nnw survives. DOES NOT MOVE THE RUNNING FLEET. MT5 stores input values per chart in profiles\Charts\*\chart*.chr, so this only takes effect on a fresh attach; the live charts have to be changed in each one's EA properties. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 04:24:37 -04:00
//---
feat(vote): derive the threshold instead of configuring it Signal_ThresholdOpen becomes a seed. The era verdict now picks the HIGHEST sweep rung whose vote still clears the whole deploy gate - coverage floor, exact-binomial precision bar and two-sidedness together - computes the era's verdict AT that rung, and publishes it to the live signal's m_threshold_open so the bar the gate certifies is the bar the EA trades. Measured on 619 era verdicts across all six live charts: * every era on every symbol had at least one rung clearing the full gate. At the fixed 25% the fleet was actually running, four of six symbols had none, ever. The threshold, not the models, was the blocker. * walk-forward (rung derived on era N, scored on era N+1): 10.2% coverage / 31.8% precision, against an oracle re-picking on N+1 of 10.3% / 31.7%. Near-zero shrinkage - a measurement, not a fit. It holds because the binding constraint is COVERAGE, a near-deterministic step function of the vote distribution, not precision. * vs a fixed 15% (best global value): +0.6pp precision, 3.4pp less coverage. vs a fixed 20%: deployable on all six rather than four of six. Selection on the highest PASSING rung, never on the best-precision rung - that is a best-of-6 on a noisy statistic and this project has crowned noise that way four times. The multiplicity that remains is paid for: nTried in EnsembleSurvivesSelection is now eras x rungs. Costs nothing - all six charts clear it by 6.5-12 sigma even forming z on effective rather than raw calls. Also fixes, in the same path: the direction-policy gate is hoisted above the per-rung tally so every rung is scored on the population the gate certifies. Retrain-neutral: not in BuildModelFingerprint(), no .nnw re-keyed. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:02:58 -04:00
//--- WHY IT HAD TO STOP BEING AN INPUT. Two independent reasons, both measured:
//--- 1. THERE IS NO GOOD GLOBAL VALUE. The right rung differs per symbol AND drifts per era. On
//--- 619 era verdicts across the six live charts, EVERY era had at least one rung clearing the
//--- full gate - while at the fixed 25% the fleet was actually running, four of six symbols had
//--- none, ever. The models were deployable the whole time; the constant was the blocker.
//--- 2. AN INPUT CANNOT BE CORRECTED. MT5 stores an input's value PER CHART in
//--- profiles\Charts\*\chart*.chr. The live fleet kept running at 25 across a full
//--- close/recompile/relaunch cycle with the log still reading "fired at vote>=25%" - changing
//--- the default here moved nothing. A number that can only be fixed by hand-editing six charts
//--- is not a parameter, it is a liability.
fix(vote): Signal_ThresholdOpen 25 -> 15, from the sweep's own numbers The quorum model that said 20 was wrong. It reasoned from the vote's quantisation - 4 members at tier weight ~30, so 3-of-4 agreeing gives 22.5 and PCT_20 admits it - and simultaneous agreement turns out to be rarer than a per-member coverage of ~27% implies. Measured on real rows by the threshold sweep added in the previous commit: symbol floor 15% cov/prec 20% cov/prec 25% cov/prec (active) EURUSD 6.7% 15.7 / 32.6 12.5 / 33.7 4.1 / 35.0 SP500 6.9% 9.8 / 32.7 4.0 / 34.4 X 1.0 / 34.7 X USDCAD 7.2% 18.9 / 32.2 12.8 / 32.6 4.0 / 34.7 X XAUUSD 6.7% 12.8 / 29.2 4.4 / 33.9 X 1.0 / 26.5 X XTIUSD 6.9% 11.4 / 34.3 7.8 / 35.7 1.7 / 38.4 X 15 clears the coverage floor on every symbol; 20 fails SP500 and XAUUSD; 25 fails all of them. The precision surrendered is about 2pp, because precision is nearly flat across these rungs while coverage moves 10-20x - the high thresholds were buying almost nothing for the coverage they cost. The rule this encodes is: take the cheapest rung whose coverage clears the floor, not the best precision. Precision above the bar earns nothing extra; coverage below the floor makes the era undeployable regardless. Not in BuildModelFingerprint(), so every trained .nnw survives. DOES NOT MOVE THE RUNNING FLEET. MT5 stores input values per chart in profiles\Charts\*\chart*.chr, so this only takes effect on a fresh attach; the live charts have to be changed in each one's EA properties. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 04:24:37 -04:00
//---
feat(vote): derive the threshold instead of configuring it Signal_ThresholdOpen becomes a seed. The era verdict now picks the HIGHEST sweep rung whose vote still clears the whole deploy gate - coverage floor, exact-binomial precision bar and two-sidedness together - computes the era's verdict AT that rung, and publishes it to the live signal's m_threshold_open so the bar the gate certifies is the bar the EA trades. Measured on 619 era verdicts across all six live charts: * every era on every symbol had at least one rung clearing the full gate. At the fixed 25% the fleet was actually running, four of six symbols had none, ever. The threshold, not the models, was the blocker. * walk-forward (rung derived on era N, scored on era N+1): 10.2% coverage / 31.8% precision, against an oracle re-picking on N+1 of 10.3% / 31.7%. Near-zero shrinkage - a measurement, not a fit. It holds because the binding constraint is COVERAGE, a near-deterministic step function of the vote distribution, not precision. * vs a fixed 15% (best global value): +0.6pp precision, 3.4pp less coverage. vs a fixed 20%: deployable on all six rather than four of six. Selection on the highest PASSING rung, never on the best-precision rung - that is a best-of-6 on a noisy statistic and this project has crowned noise that way four times. The multiplicity that remains is paid for: nTried in EnsembleSurvivesSelection is now eras x rungs. Costs nothing - all six charts clear it by 6.5-12 sigma even forming z on effective rather than raw calls. Also fixes, in the same path: the direction-policy gate is hoisted above the per-rung tally so every rung is scored on the population the gate certifies. Retrain-neutral: not in BuildModelFingerprint(), no .nnw re-keyed. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:02:58 -04:00
//--- The knob that remains is MIN_COVERAGE_FRACTION_OF_BASE_RATE (ExpertSignalAIBase.mqh) - "how
//--- much of the market must I catch before I believe the measurement". That is the real policy
//--- question, and it is one question rather than six per-chart ones.
fix(vote): Signal_ThresholdOpen 25 -> 15, from the sweep's own numbers The quorum model that said 20 was wrong. It reasoned from the vote's quantisation - 4 members at tier weight ~30, so 3-of-4 agreeing gives 22.5 and PCT_20 admits it - and simultaneous agreement turns out to be rarer than a per-member coverage of ~27% implies. Measured on real rows by the threshold sweep added in the previous commit: symbol floor 15% cov/prec 20% cov/prec 25% cov/prec (active) EURUSD 6.7% 15.7 / 32.6 12.5 / 33.7 4.1 / 35.0 SP500 6.9% 9.8 / 32.7 4.0 / 34.4 X 1.0 / 34.7 X USDCAD 7.2% 18.9 / 32.2 12.8 / 32.6 4.0 / 34.7 X XAUUSD 6.7% 12.8 / 29.2 4.4 / 33.9 X 1.0 / 26.5 X XTIUSD 6.9% 11.4 / 34.3 7.8 / 35.7 1.7 / 38.4 X 15 clears the coverage floor on every symbol; 20 fails SP500 and XAUUSD; 25 fails all of them. The precision surrendered is about 2pp, because precision is nearly flat across these rungs while coverage moves 10-20x - the high thresholds were buying almost nothing for the coverage they cost. The rule this encodes is: take the cheapest rung whose coverage clears the floor, not the best precision. Precision above the bar earns nothing extra; coverage below the floor makes the era undeployable regardless. Not in BuildModelFingerprint(), so every trained .nnw survives. DOES NOT MOVE THE RUNNING FLEET. MT5 stores input values per chart in profiles\Charts\*\chart*.chr, so this only takes effect on a fresh attach; the live charts have to be changed in each one's EA properties. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 04:24:37 -04:00
//---
feat(vote): derive the threshold instead of configuring it Signal_ThresholdOpen becomes a seed. The era verdict now picks the HIGHEST sweep rung whose vote still clears the whole deploy gate - coverage floor, exact-binomial precision bar and two-sidedness together - computes the era's verdict AT that rung, and publishes it to the live signal's m_threshold_open so the bar the gate certifies is the bar the EA trades. Measured on 619 era verdicts across all six live charts: * every era on every symbol had at least one rung clearing the full gate. At the fixed 25% the fleet was actually running, four of six symbols had none, ever. The threshold, not the models, was the blocker. * walk-forward (rung derived on era N, scored on era N+1): 10.2% coverage / 31.8% precision, against an oracle re-picking on N+1 of 10.3% / 31.7%. Near-zero shrinkage - a measurement, not a fit. It holds because the binding constraint is COVERAGE, a near-deterministic step function of the vote distribution, not precision. * vs a fixed 15% (best global value): +0.6pp precision, 3.4pp less coverage. vs a fixed 20%: deployable on all six rather than four of six. Selection on the highest PASSING rung, never on the best-precision rung - that is a best-of-6 on a noisy statistic and this project has crowned noise that way four times. The multiplicity that remains is paid for: nTried in EnsembleSurvivesSelection is now eras x rungs. Costs nothing - all six charts clear it by 6.5-12 sigma even forming z on effective rather than raw calls. Also fixes, in the same path: the direction-policy gate is hoisted above the per-rung tally so every rung is scored on the population the gate certifies. Retrain-neutral: not in BuildModelFingerprint(), no .nnw re-keyed. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:02:58 -04:00
//--- NOT in BuildModelFingerprint(), so none of this re-keys a trained .nnw.
input PERCENTAGE_PRESETS Signal_ThresholdOpen = PCT_15; // Vote threshold SEED (derived after era 1)
feat(vote): exit-on-reversal boolean, pin the threshold, retry the atomic rename THE EXIT KNOB. Exit_On_Reversal_Vote (default false) replaces the deleted Signal_ThresholdClose with one boolean: false pins the close threshold to an arithmetically unreachable 101, true pins it to the SAME threshold the entry uses - the seed at first, then the derived value, republished together whenever it moves. A second threshold was always redundant; "the bot now says the other way" is one question. It also arms CExpertSignalCustom::m_holdToBarrier, which was DEAD CODE: HoldToBarrier(bool) had no caller anywhere in the build, so the flag had been permanently false and the disabled close threshold was carrying the whole hold-to-barrier policy alone. Both halves now move together. Default stays false because the reason is statistical: the gate certifies P(label agrees | vote fired) against a label that runs to the barrier, so an early close trades something never measured. Turning it on is a different strategy, not a tightening of this one. THE PIN. The live threshold now moves only when an era's weights become the checkpoint, and freezes once g_ensDeployApproved. Every era still derives its own rung - that is how the best one is found - but the rung that TRADES belongs to the checkpoint, exactly as the weights do. Two reasons, one measured and one structural: the per-era rung moves on 6-34% of steps (the live run flapped SP500 15 -> 10 -> 15 within a minute of starting), and without the pin a later era's rung could end up applied to an earlier era's deployed model. A ladder restart releases the pin, since clearing the checkpoint clears what it pinned. The era line now prints the rung its own numbers came from, so it stays honest when that differs from the pinned one. THE ATOMIC RENAME retried zero times. Six charts share the TrainPool and AltData directories, so a publish regularly lands while a peer chart holds the destination open and FileMove returns 5004 - 27 times in one day on the live fleet. Nothing was lost (the temp keeps the new content, the old file stays intact) but the row did not update until the next publish. Now four attempts at 25ms, on the FAILURE PATH ONLY - a successful rename never sleeps - and skipped in the tester, where the contention cannot happen and Sleep would distort a pass. A rescued retry is logged, so worsening contention is visible. Retrain-neutral. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:53:56 -04:00
//--- CLOSE ON THE OPPOSITE VOTE. Signal_ThresholdClose (and its SIGNAL_CLOSE_PRESETS enum) was
//--- replaced by this boolean 2026-08-26: a second THRESHOLD was always redundant, because "the bot
//--- now says the other way" is one question, not two. When this is ON the exit fires at the SAME
//--- derived threshold the entry uses, published to the signal by
//--- CExpertCustom::PublishVoteThreshold() - so there is still nothing to tune.
//---
//--- OFF IS THE DEFAULT, AND THE REASON IS STATISTICAL, NOT A PREFERENCE. The deploy gate certifies
//--- P(label agrees | the vote fired), and the label runs to the barrier. Close early and the
//--- realised outcome is no longer the labelled one, so the certified precision stops describing what
//--- is actually being traded. Turning this on is a DIFFERENT strategy from the one the gate
//--- measured, and it has not been measured. Treat any win rate on the panel with suspicion until it
//--- has been.
//---
//--- HOW IT COMPOSES WITH Allow_Hedging - they are alternative reversal policies, not independent:
//--- hedging ON, exit OFF (default) -> the opposite vote OPENS the other book; both positions run
//--- to their own barriers, both stay certified.
//--- hedging ON, exit ON -> the long book closes and the short book opens on the same
//--- pass, so this is classic reverse behaviour and a hedge
//--- never actually forms.
//--- hedging OFF, exit ON -> plain close-on-reversal on the single position.
//--- hedging OFF, exit OFF -> hold to the barrier, opposite votes ignored entirely.
//---
//--- It drives CExpertSignalCustom::m_holdToBarrier, which until now NOTHING EVER SET - the whole
//--- hold-to-barrier mechanism was dormant and the disabled close threshold was doing the work alone.
input bool Exit_On_Reversal_Vote = false; // Close on opposite vote (default: hold to the barrier)
//--- OFF = the filtered view, one arrow per position the EA would open (vote + ranking + threshold
//--- applied). ON = every model's raw opinion, per model - the diagnostic view that shows a collapsed
//--- member the filtered view cannot, because a collapsed member simply stops appearing in it.
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
input bool DrawUnfilteredSignals = false; // Draw raw per-model signals (bypass vote/ranking/threshold)
//==================================================================================================
ditch(signals): remove the four classic votes - all 26 patterns measured at chance research/classic.py transcribed all 26 shipped vote patterns (MA 4, RSI 4, MACD 6, Ichimoku 12) with their constructor weights and tested them as entries on 178k-bar histories, four instruments x three barrier geometries. Nothing separated from chance - not one pattern, not the averaged vote at any threshold 10-70, not a 2/3/4-module quorum, not event-plus-confirmation. Residual E[R] everywhere was -0.01 to -0.08 R, which is approximately the spread. The +4 sigma reading that had once justified the set was two bars of lookahead: closing it took MACD_p4 on EURUSD from +5.05pp to -0.02pp. All four inputs have shipped false ever since, so this deletes dormant code rather than changing behaviour. RETRAIN-NEUTRAL, deliberately. EnableMA and EnableRSI were hashed UNCONDITIONALLY into the DB config fingerprint, so they become literal 0 legacy slots - the same treatment the ind_Periods slot two lines above already uses, and every existing database keeps its key. EnableMACD/EnableIchimoku were appended only when enabled, so with both gone the segment simply never appears, which is byte-identical to today. No .nnw or .db is orphaned. WHAT THIS COSTS, STATED PLAINLY: these four were CSignalMETA's only wired candidate sources, so the on-chart ladder sweep (BuildCorpusBySweep) now has nothing to sweep and a META chart is no longer self-contained. That is survivable rather than fatal because MetaPrepareEra already falls back to CMetaCorpus::LoadLargestOnDisk, and its own comment names this exact case - "charts whose classic filters are disabled". Use_MetaLabeling ships false regardless. SignalMETA.mqh is otherwise UNTOUCHED, and its 26-slot one-hot stays at 26: a tester-built corpus on disk still encodes those pattern ids, and narrowing the descriptor would invalidate every stored corpus. Signals/SignalMA.mqh SignalRSI.mqh SignalMACD.mqh SignalIchimoku.mqh deleted Signals/OscillatorDivergence.mqh deleted - RSI and MACD were its only users Classic_Shift deleted - the four votes were its only readers Compile-verified in the stage copy: 0 errors, 0 warnings, against a 0/0 baseline taken before any edit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:08:43 -04:00
// INDICATOR SEEDS (the AI feature block's starting periods)
//==================================================================================================
ditch(signals): remove the four classic votes - all 26 patterns measured at chance research/classic.py transcribed all 26 shipped vote patterns (MA 4, RSI 4, MACD 6, Ichimoku 12) with their constructor weights and tested them as entries on 178k-bar histories, four instruments x three barrier geometries. Nothing separated from chance - not one pattern, not the averaged vote at any threshold 10-70, not a 2/3/4-module quorum, not event-plus-confirmation. Residual E[R] everywhere was -0.01 to -0.08 R, which is approximately the spread. The +4 sigma reading that had once justified the set was two bars of lookahead: closing it took MACD_p4 on EURUSD from +5.05pp to -0.02pp. All four inputs have shipped false ever since, so this deletes dormant code rather than changing behaviour. RETRAIN-NEUTRAL, deliberately. EnableMA and EnableRSI were hashed UNCONDITIONALLY into the DB config fingerprint, so they become literal 0 legacy slots - the same treatment the ind_Periods slot two lines above already uses, and every existing database keeps its key. EnableMACD/EnableIchimoku were appended only when enabled, so with both gone the segment simply never appears, which is byte-identical to today. No .nnw or .db is orphaned. WHAT THIS COSTS, STATED PLAINLY: these four were CSignalMETA's only wired candidate sources, so the on-chart ladder sweep (BuildCorpusBySweep) now has nothing to sweep and a META chart is no longer self-contained. That is survivable rather than fatal because MetaPrepareEra already falls back to CMetaCorpus::LoadLargestOnDisk, and its own comment names this exact case - "charts whose classic filters are disabled". Use_MetaLabeling ships false regardless. SignalMETA.mqh is otherwise UNTOUCHED, and its 26-slot one-hot stays at 26: a tester-built corpus on disk still encodes those pattern ids, and narrowing the descriptor would invalidate every stored corpus. Signals/SignalMA.mqh SignalRSI.mqh SignalMACD.mqh SignalIchimoku.mqh deleted Signals/OscillatorDivergence.mqh deleted - RSI and MACD were its only users Classic_Shift deleted - the four votes were its only readers Compile-verified in the stage copy: 0 errors, 0 warnings, against a 0/0 baseline taken before any edit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:08:43 -04:00
//--- THE FOUR CLASSIC VOTES WERE REMOVED 2026-08-24 (EnableMA / EnableRSI / EnableMACD /
//--- EnableIchimoku, and the Classic_Shift input that only they read). All 26 shipped patterns were
//--- measured as entries on 178k-bar histories across four instruments and three barrier geometries:
//--- nothing separated from chance individually, by vote threshold, by quorum, or as event plus
//--- confirmation, and the two bars of lookahead that had once produced a +4 sigma reading were the
//--- whole of it. All four inputs had shipped false ever since, so this removes dormant code, not
//--- behaviour. The seeds below stay because the AI FEATURE block still reads them.
//--- SEEDS ONLY. All indicator parameters are tuner-owned: the auto-tuner searches from these under a
ditch(signals): remove the four classic votes - all 26 patterns measured at chance research/classic.py transcribed all 26 shipped vote patterns (MA 4, RSI 4, MACD 6, Ichimoku 12) with their constructor weights and tested them as entries on 178k-bar histories, four instruments x three barrier geometries. Nothing separated from chance - not one pattern, not the averaged vote at any threshold 10-70, not a 2/3/4-module quorum, not event-plus-confirmation. Residual E[R] everywhere was -0.01 to -0.08 R, which is approximately the spread. The +4 sigma reading that had once justified the set was two bars of lookahead: closing it took MACD_p4 on EURUSD from +5.05pp to -0.02pp. All four inputs have shipped false ever since, so this deletes dormant code rather than changing behaviour. RETRAIN-NEUTRAL, deliberately. EnableMA and EnableRSI were hashed UNCONDITIONALLY into the DB config fingerprint, so they become literal 0 legacy slots - the same treatment the ind_Periods slot two lines above already uses, and every existing database keeps its key. EnableMACD/EnableIchimoku were appended only when enabled, so with both gone the segment simply never appears, which is byte-identical to today. No .nnw or .db is orphaned. WHAT THIS COSTS, STATED PLAINLY: these four were CSignalMETA's only wired candidate sources, so the on-chart ladder sweep (BuildCorpusBySweep) now has nothing to sweep and a META chart is no longer self-contained. That is survivable rather than fatal because MetaPrepareEra already falls back to CMetaCorpus::LoadLargestOnDisk, and its own comment names this exact case - "charts whose classic filters are disabled". Use_MetaLabeling ships false regardless. SignalMETA.mqh is otherwise UNTOUCHED, and its 26-slot one-hot stays at 26: a tester-built corpus on disk still encodes those pattern ids, and narrowing the descriptor would invalidate every stored corpus. Signals/SignalMA.mqh SignalRSI.mqh SignalMACD.mqh SignalIchimoku.mqh deleted Signals/OscillatorDivergence.mqh deleted - RSI and MACD were its only users Classic_Shift deleted - the four votes were its only readers Compile-verified in the stage copy: 0 errors, 0 warnings, against a 0/0 baseline taken before any edit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:08:43 -04:00
//--- family-wise gate and persists winners in TunedPeriods_{SYM}_{TF}.cfg, which the AI features read.
//--- Hand-setting means editing these constants, which deliberately bypasses that 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
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 Networks"; // Neural Networks
//--- Two or more enabled = an ensemble (|ENS1 fingerprint token + joint vote-level deploy gate);
//--- exactly one = solo, same fingerprint and files as the old preset; none = classic only. Every
//--- enabled NN trains a net per chart, so prefer fewer members on sub-daily timeframes.
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
input bool Use_MLP = true; // NN vote: MLP (dense)
input bool Use_CONV = true; // NN vote: CONV (convolutional)
input bool Use_LSTM = true; // NN vote: LSTM (recurrent)
input bool Use_CONVLSTM = true; // NN vote: CONVLSTM (conv front-end + LSTM)
//--- One-shot measurement: an Alglib forest, MLP and OLS fit on the net's OWN windows, labels, split and
//--- gate arithmetic. Answers whether a flat result is the architecture or the matrix. Nothing trades on
refactor(baselines): the first real module - a class, not an #included partial Baselines was 951 lines of CExpertSignalAIBase method bodies in a file that only looked like a module. It is now CBaselineComparator: a class the signal OWNS, which reads a CTrainingDataView and prints. It does not name the signal anywhere in its code. What the seam forced out into the open: - Thirty-odd ArraySize() bounds tests, each carried by its caller, are now one test per accessor next to the data. The two `hasValueN` and one `arrowN` locals are gone with them. - The -2.0 "never scored" sentinel on the arrow cache was tested at the call site. It is now inside DataDirectionalCall, where it cannot be read as a small confidence. - DoubleToSignal needs m_outputNeuronsCount, so a raw double could not be turned into a side by any reader. The view answers DirectionalCall(bar, isBuy, magnitude) instead - the conversion happens where the head width lives, and the module no longer needs ENUM_SIGNAL at all. - m_baselineDone was a latch on the signal for a decision only this module makes. It is m_done, private, where it belongs. Correction to my own earlier claim: I said Baselines had nine exclusive members "polluting the signal class". It had none. m_x, m_f, m_ngrad, m_AvgCE and the rest are FIELDS OF ALGLIB REPORT OBJECTS (state.m_x, mrep.m_AvgCE) that my `\bm_\w+` scan matched after the dot. The module needs no private state but its view pointer and that latch - which is why it came out this cleanly. The include sits below the g_ens* vote globals and the Alglib headers it reads, because unlike the AIBase\*.mqh partials this is a real class declaration compiled where it stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 12:00:18 -04:00
//--- it and no model is saved. See Expert\Training\BaselineComparator.mqh.
input bool Run_Alglib_Baselines = true; // Diagnostic: forest + linear on the NN's own matrix
feat(training): wire TrainingPool into pass 2 - peer rows contribute gradient only Peer rows join m_isTrainQueue as NEGATIVE sentinels before the shuffle, so they interleave with this chart's samples instead of training in a block at one end. A block would be a curriculum: whatever the optimizer saw last would decide where it landed. TrainPoolStep is a separate path on purpose. Everything in pass 2's local branch after the forward pass reaches for something indexed by a LOCAL bar - m_labelCache, m_winLongCache, the excursion target, the arrow cache, m_Time - and a peer row has none of those. Sharing the path would mean inventing values for all of them, which is how another instrument's outcomes end up inside m_cumIsCorrect and the operating point gets fitted to them. The IS-vs-OOS gap is read as THE overfitting signal, so polluting the IS side would not crash anything; it would just quietly stop meaning what it says. The purge key reuses the label walk's own two bounds - the horizon and NextScheduledCloseAll - rather than approximating with a bar offset. A second horizon model here would drift from the real one, and this project already measured that the close-all, not the nominal horizon, is what actually terminates labels. Cutoff is the OLDEST OOS BAR'S TIME, in wall clock, because bar indices cannot be compared across instruments that each have their own calendar. Contribution happens while the window is still in TempData and before the forward pass overwrites it, and is gated to direction models: the meta head trains a different target on a wider input, which the fingerprint gate alone would NOT catch, since a meta model's fingerprint matches its own peers perfectly well. Use_Training_Pool ships false and does nothing until a second chart runs a matching fingerprint. Compile-verified against a BASELINE of the same tree without the wiring: both produce 12 errors, all error 313 invalid-resource-path from #resource directives that cannot resolve in a headless staged build (stock Controls res\*.bmp, plus the pre-existing Network.cl). Code errors 0, warnings 0, identical to baseline. Staging copy and junctions removed; the live .ex5 was never touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 14:00:21 -04:00
//--- CROSS-INSTRUMENT TRAINING ROWS. Each chart publishes its own feature rows and adopts its peers',
//--- so one net trains on several instruments at once while every chart keeps its own model. Measured
//--- +2.02pp of paired skill at H4 (t_mkt 3.97, clearing its family-wise bar) and replicated at D1;
//--- the per-instrument arm was NEGATIVE on every feature set. Only peers whose MODEL FINGERPRINT
//--- matches contribute, so it does nothing until a second chart runs the same configuration.
//--- Deliberately NOT a fingerprint member: it changes what the model trains on, not what it is.
input bool Use_Training_Pool = true; // Train on peer charts' rows as well as this chart's
feat(target): delete the barrier/geometry stack - the label is the verdict Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE target and the era verdict is precision + recall per class against the label's own base rate - no win rate, no break-even, no expectancy, no geometry anywhere in training. DELETED - Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep, FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives in Labeling/LabelOverlap.mqh), 3 test EAs. - TripleBarrierLabel + walk, fractal label, geometry derivation/scan/ adoption, exit-policy replay, excursion MI targets, the drift verdict (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry, the barrier defines, the .cfg geometry adopt (slots kept as zeros for the positional layout), the derived-geometry live-order override. - TRAINING_TARGET input/enum: direction models are always swing; META2 re-keys the meta head onto label agreement (descriptor loses its two geometry slots). REWORKED - Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is never cached, so it can never freeze as a false Neutral; training, calibration, OOS scoring and online learning all skip unresolved bars. - SDeployVerdict: significance-only; SOosTally chance = larger directional class share; pooled gate poolability = timeframe (record v2). - Purge/embargo/declustering gaps: the measured mean label resolution lag (LabelResolutionBars), not a barrier horizon. - Pool purge key + backfill DB rows: marked at the bar the label resolved on (m_labelResolveAge), not a fabricated barrier touch. - Online learning frontier: finality, not a horizon delay. - m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced -> m_erasSinceBest, ensemble vote outcome arrays -> label arrays. STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection are inputs again - trade management is the tester GA's search space. Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional, CUT token gone); META1 -> META2. Full retrain, as planned. Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs, 0 errors, 0 warnings each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
//--- WHAT THE DIRECTION MODELS LEARN: the swing-pivot direction label, unconditionally. The label
//--- is geometry-free - it only says which way the next confirmed pivot lies - which is what leaves
//--- trade management to the tester GA instead of baking it into what the net learns.
//+------------------------------------------------------------------+
//| Roster string for logs and the journal's filterID column. Lives |
//| here because TradeJournalManager.mqh is included before |
//| Variables.mqh's globals and needs it too. |
//+------------------------------------------------------------------+
feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate User design (2026-08-19): 'remove the enum menu that selects neural networks... individual inputs for every NN just like classic signals... the META NN should be integrated into the voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.' - AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/ Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to any subset because the consensus divisor is the enabled capable weight. Two or more enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so existing weight files keep loading); one = the old solo preset; none = classic-only. - Use_MetaLabeling un-couples META from the direction NNs (the old selector made them mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR; pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly. - COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the meta target - solo-only until today, a trained META would otherwise sit in the consensus divisor as a permanent abstainer and shrink every vote by its module weight. - CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class, calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not model the veto - the standing solo-gate caveat, documented at the input. - DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType; DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member subsets get 100+bitmask, outside the legacy range) so no existing database re-keys. filterID becomes the enabled roster via one EnabledNNSummary(). - HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not only when an entry happens to be proposed. NOT COMPILED - user compiles in MetaEditor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00
string EnabledNNSummary()
{
string s = "";
if(Use_MLP)
s += (StringLen(s) > 0 ? "+MLP" : "MLP");
if(Use_CONV)
s += (StringLen(s) > 0 ? "+CONV" : "CONV");
if(Use_LSTM)
s += (StringLen(s) > 0 ? "+LSTM" : "LSTM");
if(Use_CONVLSTM)
s += (StringLen(s) > 0 ? "+CONVLSTM" : "CONVLSTM");
if(StringLen(s) <= 0)
s = "Classic";
return s;
}
input ENUM_OPTIMIZATION TrainingOptimizer = ADAM; // Weight optimizer
feat(target): delete the barrier/geometry stack - the label is the verdict Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE target and the era verdict is precision + recall per class against the label's own base rate - no win rate, no break-even, no expectancy, no geometry anywhere in training. DELETED - Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep, FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives in Labeling/LabelOverlap.mqh), 3 test EAs. - TripleBarrierLabel + walk, fractal label, geometry derivation/scan/ adoption, exit-policy replay, excursion MI targets, the drift verdict (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry, the barrier defines, the .cfg geometry adopt (slots kept as zeros for the positional layout), the derived-geometry live-order override. - TRAINING_TARGET input/enum: direction models are always swing; META2 re-keys the meta head onto label agreement (descriptor loses its two geometry slots). REWORKED - Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is never cached, so it can never freeze as a false Neutral; training, calibration, OOS scoring and online learning all skip unresolved bars. - SDeployVerdict: significance-only; SOosTally chance = larger directional class share; pooled gate poolability = timeframe (record v2). - Purge/embargo/declustering gaps: the measured mean label resolution lag (LabelResolutionBars), not a barrier horizon. - Pool purge key + backfill DB rows: marked at the bar the label resolved on (m_labelResolveAge), not a fabricated barrier touch. - Online learning frontier: finality, not a horizon delay. - m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced -> m_erasSinceBest, ensemble vote outcome arrays -> label arrays. STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection are inputs again - trade management is the tester GA's search space. Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional, CUT token gone); META1 -> META2. Full retrain, as planned. Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs, 0 errors, 0 warnings each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
//--- The target is 3-way (Buy/Sell/Neutral), so the head is a 3-class softmax. The regression path
//--- stays implemented but is no longer selectable.
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
const OUTPUT_NEURONS_COUNT OutputNeuronsCount = OUTPUT_CLASSIFICATION;
//--- First-layer width, LSTM hidden size, conv filter count, taper depth and reduction are all
//--- DERIVED from the post-selection input width and the in-sample bar count - see
//--- ComputeFirstLayerWidth(), ComputeConvFilterCount(), ComputeLstmHiddenSize().
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
const bool EnableBatchNorm = true; // AI: batch normalization
//--- EMA window for the running mean/variance, in training SAMPLES (there is no mini-batch to average
//--- over). <=1 silently disables the layer, which is the only other meaningful setting.
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
const int BatchNormWindow = 1000; // AI: batch-norm window (samples)
//--- Training starts at the earliest available bar (floored by MinTrainYear); the honest generalisation
//--- read comes from this holdout, not from withholding history.
input OOS_SPLIT_PRESET OOSSplit = OOS_30; // Out-of-sample holdout
//--- There is no "target accuracy" input: training runs until it stops improving and deploys its
//--- own best checkpoint (see the PLATEAU_* ladder).
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, NO KNOB
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
//==================================================================================================
//--- LOGIT-ADJUSTED LOSS (Menon et al. 2021): add tau*log(prior_c) to each class logit inside the
//--- TRAINING gradient only, so the raw argmax at inference is already balanced-error-optimal.
//--- tau is fixed at 1.0 - the full log-prior, the paper's consistent value; the priors come from the
//--- label prebuild's measured distribution, and the delivered strength is capped to the head's
//--- usable logit range (see ApplyLogitAdjustment). There is nothing left for a user to choose.
//--- Freeze the measured class priors after the first measurement. Letting them track is correct since
//--- the barrier relabel; freezing is a diagnostic for a genuinely shifting distribution.
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
const bool FreezePriorCalibration = false;
feat(target): delete the barrier/geometry stack - the label is the verdict Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE target and the era verdict is precision + recall per class against the label's own base rate - no win rate, no break-even, no expectancy, no geometry anywhere in training. DELETED - Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep, FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives in Labeling/LabelOverlap.mqh), 3 test EAs. - TripleBarrierLabel + walk, fractal label, geometry derivation/scan/ adoption, exit-policy replay, excursion MI targets, the drift verdict (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry, the barrier defines, the .cfg geometry adopt (slots kept as zeros for the positional layout), the derived-geometry live-order override. - TRAINING_TARGET input/enum: direction models are always swing; META2 re-keys the meta head onto label agreement (descriptor loses its two geometry slots). REWORKED - Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is never cached, so it can never freeze as a false Neutral; training, calibration, OOS scoring and online learning all skip unresolved bars. - SDeployVerdict: significance-only; SOosTally chance = larger directional class share; pooled gate poolability = timeframe (record v2). - Purge/embargo/declustering gaps: the measured mean label resolution lag (LabelResolutionBars), not a barrier horizon. - Pool purge key + backfill DB rows: marked at the bar the label resolved on (m_labelResolveAge), not a fabricated barrier touch. - Online learning frontier: finality, not a horizon delay. - m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced -> m_erasSinceBest, ensemble vote outcome arrays -> label arrays. STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection are inputs again - trade management is the tester GA's search space. Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional, CUT token gone); META1 -> META2. Full retrain, as planned. Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs, 0 errors, 0 warnings each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
//--- Repainting embargo for the swing-context FEATURES (not the labels - their lookahead control is
//--- the pivot-pair finality rule). ZigZag revises its recent legs, so a raw read would be straight
//--- lookahead.
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
const SWING_CONFIRMATION_PRESET SwingConfirmationBars = SC_100;
//--- Keep adapting a deployed model on a LIVE chart to newly-RESOLVED bars. The blend FREEZES if a
//--- rolling-accuracy guardrail decays, so drift cannot reach the account. No effect in the tester.
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
const bool EnableOnlineLearning = true;
feat(signal): make the signal cooldown tunable, and add a hard any-direction gate The declustering the charts needed already existed - NmsLiveAccept, per-direction run-collapse plus cross-direction resolution plus strict alternation - and it was already set to 10 bars. It could not be TUNED: SignalClusterWindow was a compile- time const, so finding the right value needed a rebuild. That is the actual gap. Now three inputs, as enum dropdowns: Signal_CooldownScope per-direction, or a hard any-direction gate on top Signal_CooldownBars SCB_OFF..SCB_50, default 10 Signal_CooldownMinutes SCM_OFF..SCM_1440, overrides bars when set Minutes resolve against the CHART period and round UP, so a cooldown asked for in wall-clock is never silently shorter than requested and survives a timeframe change. SCB_/SCM_ prefixes are deliberately unique. M15/M30/M60 are ALREADY members of NF_LOOKBACK_PRESETS, and MQL5 binds a duplicated enum member to the first-declared enum silently - the obvious names would have compiled straight into the news filter's values. THE ANY-DIRECTION GATE IS ADDITIVE, NOT A REPLACEMENT, and the first cut of this had it backwards. Measured on the live log: the current rules draw 222 arrows over 4999 bars, while a BARE 10-bar cooldown permits up to 454 - because ALTERNATION is what declutters today, not the window. Swapping the rules out would have roughly doubled the clutter it was asked to remove. Layered, it can only ever suppress more. Suppressed bars still advance the per-direction last-SEEN cursors, so a run straddling the boundary does not restart as if it were fresh. Applied at all THREE sites that must agree - live inference, OOS pass-3 scoring and the chart renderer. Their own comments say why: an arrow set that does not obey the same rule as the traded set shows calls the EA would never take. Also corrects a stale comment that called this window "display only". It is not: when it suppresses, the live path zeroes the signal outright - no arrow, no vote, no position. Training never sees it, so these cost no retrain and are correctly absent from the fingerprint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:48:28 -04:00
//--- SIGNAL COOLDOWN. NOT display-only - the previous comment here said so and was WRONG: when this
//--- suppresses a bar, CExpertSignalAIBase's live path zeroes the signal outright, so there is no
//--- arrow, no vote and NO POSITION. It gates trading, the tester and the drawn history alike. What it
//--- does NOT touch is training: labels, features and backprop never see it, so changing these costs
//--- no retrain and they are deliberately absent from the fingerprint.
//---
//--- INPUTS, not constants, because the right value is per-chart and per-timeframe and could not be
//--- tuned without a recompile before. The raw per-bar metrics are still never declustered.
input string CD_Settings = "Signal Cooldown"; // Signal Cooldown
input SIGNAL_COOLDOWN_SCOPE Signal_CooldownScope = SIGNAL_COOLDOWN_ANY_SIGNAL; // Cooldown: what a signal blocks
//--- Bars between signals. 0 disables the cooldown entirely.
//--- DEFAULT 30, and the floor is PRINCIPLED rather than cosmetic: a trade on this label is held for
//--- 5 + the median ZigZag leg = 18-19 bars, so any second signal inside that window is the SAME
//--- trade being re-announced. 20 is therefore the smallest defensible value; 30 is one comfortable
//--- step above it, and was chosen because the measured natural spacing between vote arrows is ~20
//--- bars - a window at or below that thins almost nothing (10 bars removed only 8-21%).
input SIGNAL_COOLDOWN_BARS Signal_CooldownBars = SCB_30; // Cooldown: bars between signals
feat(signal): make the signal cooldown tunable, and add a hard any-direction gate The declustering the charts needed already existed - NmsLiveAccept, per-direction run-collapse plus cross-direction resolution plus strict alternation - and it was already set to 10 bars. It could not be TUNED: SignalClusterWindow was a compile- time const, so finding the right value needed a rebuild. That is the actual gap. Now three inputs, as enum dropdowns: Signal_CooldownScope per-direction, or a hard any-direction gate on top Signal_CooldownBars SCB_OFF..SCB_50, default 10 Signal_CooldownMinutes SCM_OFF..SCM_1440, overrides bars when set Minutes resolve against the CHART period and round UP, so a cooldown asked for in wall-clock is never silently shorter than requested and survives a timeframe change. SCB_/SCM_ prefixes are deliberately unique. M15/M30/M60 are ALREADY members of NF_LOOKBACK_PRESETS, and MQL5 binds a duplicated enum member to the first-declared enum silently - the obvious names would have compiled straight into the news filter's values. THE ANY-DIRECTION GATE IS ADDITIVE, NOT A REPLACEMENT, and the first cut of this had it backwards. Measured on the live log: the current rules draw 222 arrows over 4999 bars, while a BARE 10-bar cooldown permits up to 454 - because ALTERNATION is what declutters today, not the window. Swapping the rules out would have roughly doubled the clutter it was asked to remove. Layered, it can only ever suppress more. Suppressed bars still advance the per-direction last-SEEN cursors, so a run straddling the boundary does not restart as if it were fresh. Applied at all THREE sites that must agree - live inference, OOS pass-3 scoring and the chart renderer. Their own comments say why: an arrow set that does not obey the same rule as the traded set shows calls the EA would never take. Also corrects a stale comment that called this window "display only". It is not: when it suppresses, the live path zeroes the signal outright - no arrow, no vote, no position. Training never sees it, so these cost no retrain and are correctly absent from the fingerprint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:48:28 -04:00
//--- OVERRIDES the bar count when > 0, so a cooldown can be expressed in wall-clock and stay put
//--- across a timeframe change. Converted to bars against the CHART's period, so it is exact on
//--- every timeframe rather than approximated.
input SIGNAL_COOLDOWN_MINUTES Signal_CooldownMinutes = SCM_OFF; // Cooldown: minutes instead
fix(signal): the cooldown belonged at the VOTE layer, as a filter - not per member cooldown-v1 extended NmsLiveAccept, which declusters each MEMBER's own signal. That is not what the charts show and not what trades. The combined vote in CExpertSignalCustom had NO spacing rule at all - grep found not one reference to the cluster window in that file - so four individually-declustered members were averaged into a vote that could fire on consecutive bars. Measured live: 2,970 voting bars becoming 299-328 vote arrows. Proof of the diagnosis, from the deployed fleet under cooldown-v1: SP500 273 and XAUUSD 212 arrows, unchanged from before the change. The member-level rule could not touch them. Gated where the vote becomes a trade - CheckOpenPosition, beside the open-prohibition and open-market-closed checks, tracing as "open-cooldown". That is the filter chain the request asked for from the start and it is where this should have gone first. Suppression there means no order AND no live arrow, honouring the same "no arrow, no vote, no position" contract the member rule already had. THE DRAWN HISTORY NEEDED A SECOND PASS, NOT AN INLINE TEST. The overlay sweep walks NEWEST->OLDEST and is chunked across ticks, so an inline cooldown would keep the NEWEST bar of a cluster while the live gate keeps the FIRST, and the drawn set would contradict the traded set - the exact defect the renderer's own comments warn about. The sweep now records what it drew and prunes it backwards over that record, which is forward in time. Direction() is a TRANSACTION that can run more than once on a bar, so the live accept is cached per bar time. Without that a second call flips the bar's verdict after it has already journaled one. One resolver, WarriorSignalCooldownBars(), now serves both layers so they can never disagree about the window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:29:12 -04:00
//--- ONE resolver, so the member layer and the VOTE layer can never disagree about the window. The
//--- minutes form overrides the bar count and is resolved against the CHART period, rounded UP so a
//--- cooldown asked for in wall-clock is never silently shorter than requested.
fix(signal): a changed input default cannot reach an already-attached EA Raising Signal_CooldownBars from 10 to 30 changed nothing. All six live charts kept reporting a 10-bar window, because MT5 stores an input PER CHART in profiles\Charts\*\chart*.chr and an already-attached EA ignores a changed default entirely. This codebase already documents that trap, in the derived- threshold comment in Training.mqh - and converting SignalClusterWindow from a const to an input reintroduced the exact problem the const existed to avoid. SignalCooldownOverrideBars (const, 30) now wins over the input; 0 hands control back to the panel. Tunability per chart is kept, source-correctability is back. Not applied when the input says OFF: an operator who switched the cooldown off meant it, and silently re-enabling it from source would be the same surprise pointed the other way. ALSO gates the per-model arrow restore on DrawUnfilteredSignals. DrawObject() returns early when the raw view is off, but AdvanceChartSignalRestore called WarriorPlotSignalLevel DIRECTLY and never checked - so every restart repainted up to MAX_PERSISTED_ARROWS per-model opinions per member, four members per chart, on top of the combined-vote arrows. Same shape as the vote-arrow restore bug in 322c052: a restore path that does not obey the rule its own draw path does. Stale arrows already on the chart are purged too, since MT5 persists objects in the profile and nothing else would ever remove them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 12:04:03 -04:00
//--- SOURCE OVERRIDE, and it exists because of a trap this codebase already documents: MT5 stores an
//--- input PER CHART in profiles\Charts\*\chart*.chr, so AN ALREADY-ATTACHED EA IGNORES A CHANGED
//--- DEFAULT ENTIRELY. Raising Signal_CooldownBars from 10 to 30 changed nothing on six live charts -
//--- they kept reporting a 10-bar window - because each had 10 saved in its own profile.
//---
//--- That is precisely why SignalClusterWindow was a const before this work. Making it an input
//--- restored per-chart tunability and lost source-correctability; this restores the second without
//--- giving up the first.
//---
//--- > 0 WINS over the input. 0 hands control back to the panel. Set it to 0 once the charts have
//--- been re-attached or their inputs set by hand.
const int SignalCooldownOverrideBars = 30;
fix(signal): the cooldown belonged at the VOTE layer, as a filter - not per member cooldown-v1 extended NmsLiveAccept, which declusters each MEMBER's own signal. That is not what the charts show and not what trades. The combined vote in CExpertSignalCustom had NO spacing rule at all - grep found not one reference to the cluster window in that file - so four individually-declustered members were averaged into a vote that could fire on consecutive bars. Measured live: 2,970 voting bars becoming 299-328 vote arrows. Proof of the diagnosis, from the deployed fleet under cooldown-v1: SP500 273 and XAUUSD 212 arrows, unchanged from before the change. The member-level rule could not touch them. Gated where the vote becomes a trade - CheckOpenPosition, beside the open-prohibition and open-market-closed checks, tracing as "open-cooldown". That is the filter chain the request asked for from the start and it is where this should have gone first. Suppression there means no order AND no live arrow, honouring the same "no arrow, no vote, no position" contract the member rule already had. THE DRAWN HISTORY NEEDED A SECOND PASS, NOT AN INLINE TEST. The overlay sweep walks NEWEST->OLDEST and is chunked across ticks, so an inline cooldown would keep the NEWEST bar of a cluster while the live gate keeps the FIRST, and the drawn set would contradict the traded set - the exact defect the renderer's own comments warn about. The sweep now records what it drew and prunes it backwards over that record, which is forward in time. Direction() is a TRANSACTION that can run more than once on a bar, so the live accept is cached per bar time. Without that a second call flips the bar's verdict after it has already journaled one. One resolver, WarriorSignalCooldownBars(), now serves both layers so they can never disagree about the window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:29:12 -04:00
int WarriorSignalCooldownBars(void)
{
int bars = (int)Signal_CooldownBars;
//--- MINUTES FIRST, so the source override below is genuinely LAST and wins over both. Applying it
//--- before this block left a latent copy of the very bug it exists to fix: Signal_CooldownMinutes
//--- is a per-chart input too, so a chart carrying a stored minutes value would have silently
//--- defeated the override. Harmless while every chart holds SCM_OFF, which is exactly the kind of
//--- "works today" that stops working the first time someone sets it.
fix(signal): the cooldown belonged at the VOTE layer, as a filter - not per member cooldown-v1 extended NmsLiveAccept, which declusters each MEMBER's own signal. That is not what the charts show and not what trades. The combined vote in CExpertSignalCustom had NO spacing rule at all - grep found not one reference to the cluster window in that file - so four individually-declustered members were averaged into a vote that could fire on consecutive bars. Measured live: 2,970 voting bars becoming 299-328 vote arrows. Proof of the diagnosis, from the deployed fleet under cooldown-v1: SP500 273 and XAUUSD 212 arrows, unchanged from before the change. The member-level rule could not touch them. Gated where the vote becomes a trade - CheckOpenPosition, beside the open-prohibition and open-market-closed checks, tracing as "open-cooldown". That is the filter chain the request asked for from the start and it is where this should have gone first. Suppression there means no order AND no live arrow, honouring the same "no arrow, no vote, no position" contract the member rule already had. THE DRAWN HISTORY NEEDED A SECOND PASS, NOT AN INLINE TEST. The overlay sweep walks NEWEST->OLDEST and is chunked across ticks, so an inline cooldown would keep the NEWEST bar of a cluster while the live gate keeps the FIRST, and the drawn set would contradict the traded set - the exact defect the renderer's own comments warn about. The sweep now records what it drew and prunes it backwards over that record, which is forward in time. Direction() is a TRANSACTION that can run more than once on a bar, so the live accept is cached per bar time. Without that a second call flips the bar's verdict after it has already journaled one. One resolver, WarriorSignalCooldownBars(), now serves both layers so they can never disagree about the window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:29:12 -04:00
if(Signal_CooldownMinutes > 0)
{
int secs = PeriodSeconds();
if(secs > 0)
bars = (int)MathCeil(((int)Signal_CooldownMinutes * 60.0) / secs);
}
//--- LAST WORD. Deliberately NOT applied when the resolved value is OFF: an operator who switched
//--- the cooldown off meant it, and silently re-enabling it from source would be the same class of
//--- surprise this override exists to fix, pointed the other way.
if(SignalCooldownOverrideBars > 0 && bars > 0)
bars = SignalCooldownOverrideBars;
fix(signal): the cooldown belonged at the VOTE layer, as a filter - not per member cooldown-v1 extended NmsLiveAccept, which declusters each MEMBER's own signal. That is not what the charts show and not what trades. The combined vote in CExpertSignalCustom had NO spacing rule at all - grep found not one reference to the cluster window in that file - so four individually-declustered members were averaged into a vote that could fire on consecutive bars. Measured live: 2,970 voting bars becoming 299-328 vote arrows. Proof of the diagnosis, from the deployed fleet under cooldown-v1: SP500 273 and XAUUSD 212 arrows, unchanged from before the change. The member-level rule could not touch them. Gated where the vote becomes a trade - CheckOpenPosition, beside the open-prohibition and open-market-closed checks, tracing as "open-cooldown". That is the filter chain the request asked for from the start and it is where this should have gone first. Suppression there means no order AND no live arrow, honouring the same "no arrow, no vote, no position" contract the member rule already had. THE DRAWN HISTORY NEEDED A SECOND PASS, NOT AN INLINE TEST. The overlay sweep walks NEWEST->OLDEST and is chunked across ticks, so an inline cooldown would keep the NEWEST bar of a cluster while the live gate keeps the FIRST, and the drawn set would contradict the traded set - the exact defect the renderer's own comments warn about. The sweep now records what it drew and prunes it backwards over that record, which is forward in time. Direction() is a TRANSACTION that can run more than once on a bar, so the live accept is cached per bar time. Without that a second call flips the bar's verdict after it has already journaled one. One resolver, WarriorSignalCooldownBars(), now serves both layers so they can never disagree about the window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:29:12 -04:00
return (bars > 0) ? bars : 0;
}
feat(train): ONE pass over the held-out slice at deploy, on the restored checkpoint The OOS slice is the newest history and the model never trains on it, while online learning adapts to every bar resolving AFTER deployment. That leaves a gap exactly at the handover, over the most regime-relevant data there is. This closes it: select on validation, then refit on everything, which is standard practice. Placed AFTER Net.RestoreWeights() and ResetOptimizerState() and BEFORE PersistDeployedModel(), so it refines the weights that were actually SELECTED rather than whatever the run happened to end on, and what it produces is what gets written down. THE COST IS REAL AND IS NOW STATED IN THE LOG. The deploy line promises "every model reverts to the weights it held at the era whose combined vote scored best, so the ensemble that trades is exactly the one that was measured". After this pass that is no longer literally true, so the pass prints that the certified numbers belong to the PRE-PASS weights and must be quoted that way. Set EnableOosFinalPass=false to keep certified == traded exactly. Guards: * ONE-SHOT PER RUN, and the flag is set BEFORE the loop so no early return inside it can leave the pass eligible to fire twice over bars it already trained on. Reset at m_trainRunActive=true, because a retrain is a fresh selection and earns a fresh pass. * THE CONVERGED RATE, never a plateau-boosted one: m_modelEta can still carry PLATEAU_RESTART_BOOST from an escape attempt, and this is a refinement of a selected model, not another warm restart. g_eta is what backProp reads, so that is what is capped and restored. * OLDEST -> NEWEST. Series indices count backwards, so decreasing i moves forward in time - the order the bars happened in. * A failed feedForward is never followed by backProp; the output layer would still hold the previous sample's activations and the update would be this bar's label against another bar's prediction. * m_oosFinalPassCutoff records the newest bar consumed and is deliberately NOT cleared on a new run, so a later run can say plainly that its out-of-sample window reaches back into bars this model has already seen. Expect the gain to come from CURRENCY rather than finer weights: OOS precision was measured flat from era 20 while in-sample error kept falling, so the data this model can already see is exhausted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 11:05:43 -04:00
//--- ONE PASS OVER THE HELD-OUT SLICE, at deploy, after the best checkpoint is restored.
//---
//--- WHAT IT BUYS: the OOS slice is the NEWEST history and the model never trains on it, while online
//--- learning adapts to every bar that resolves AFTER deployment. That leaves a gap exactly at the
//--- handover, over the most regime-relevant data there is. This is the standard
//--- select-on-validation-then-refit-on-everything move.
//---
//--- WHAT IT COSTS, and it is not nothing: the deployed weights are then NOT the weights that were
//--- measured. The deploy log's promise - "every model reverts to the weights it held at the era
//--- whose combined vote scored best, so the ensemble that trades is exactly the one that was
//--- measured" - stops being literally true. Every certified number belongs to the PRE-PASS weights
//--- and must be quoted that way.
//---
//--- Set false to keep certified == traded exactly.
const bool EnableOosFinalPass = true;
//--- A second small net predicting how FAR price travels within the horizon - never which way. Stage 1
//--- is a MEASUREMENT: it prints a Brier skill score and places no orders. Not in the fingerprint.
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
const bool UseExcursionHead = true;
//--- Runaway backstop, not a training control - the plateau ladder decides when a run ends.
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
//--- Bars per input sequence is DERIVED (DeriveHistoryBars) and pinned in the .cfg. The ATR feature
//--- period is deliberately decoupled and fixed: the indicator is created before the .cfg is adopted,
//--- so deriving it 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)
input bool EnableMAFeature = true; // Feature: Moving Average
ditch(features): remove the eight dead feature groups from the input matrix RSI, MACD, Ichimoku and the five AD/Wyckoff indicators (CumulativeDelta, ShorteningOfThrust, WyckoffEventStream, WyckoffFailedStructure, WyckoffSignificantBarInversion). All eight inputs shipped false and each carries a closed verdict: the three oscillators are the same patterns that measured at chance as entries, and the Wyckoff family returned zero out-of-sample on five independent instruments - which is what closed the context score. RETRAIN-NEUTRAL, and this one is worth stating precisely because the change looks larger than it is. Every removed group contributed `flag ? N : 0` to the input width, and every flag was false, so the width was ALREADY zero for all eight: no .nnw's input layer changes. On the fingerprints, UseRSI and the five AD flags were hashed unconditionally and become literal 0 legacy slots (the convention the m_focalGamma slot above them already uses); UseMACD/UseIchimoku were appended only when enabled, so their segments simply never appear - byte-identical to every fingerprint ever produced, since neither ever shipped on. CADIndicatorTuner IS DELIBERATELY NOT SHRUNK. Its flat parameter array is persisted inside every .nnw, and Unflatten() rejects a size mismatch by falling back to constructor defaults - so dropping the dead fields would silently revert the tuned MA period of every model on disk while keeping its trained weights. That is the feature/weight mismatch this project has already paid for twice, and it is not worth 200 lines. AD_TUNE_PARAM_COUNT stays 42, the dead slots are still written and read, and AutoTune's ParamOwner gate now matches only owner 5 (MA) so nothing searches them. The class comment says all of this at the declaration. Also renamed ReInitADIndicators -> ReInitTunableIndicators: it rebuilds exactly one indicator now, and a name saying "AD" for the MA handle is the kind of stale label that gets believed later. Its release-AFTER-recreate ordering is untouched - that is a documented fix, not bookkeeping. Compile-verified in the stage copy: 0 errors, 0 warnings, against the same 0/0 baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:21:03 -04:00
//--- Widths are per BAR, so each is multiplied by the sequence length - enable deliberately.
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 EnableSwingContext = true; // Feature: ZigZag swing context
ditch(features): remove the eight dead feature groups from the input matrix RSI, MACD, Ichimoku and the five AD/Wyckoff indicators (CumulativeDelta, ShorteningOfThrust, WyckoffEventStream, WyckoffFailedStructure, WyckoffSignificantBarInversion). All eight inputs shipped false and each carries a closed verdict: the three oscillators are the same patterns that measured at chance as entries, and the Wyckoff family returned zero out-of-sample on five independent instruments - which is what closed the context score. RETRAIN-NEUTRAL, and this one is worth stating precisely because the change looks larger than it is. Every removed group contributed `flag ? N : 0` to the input width, and every flag was false, so the width was ALREADY zero for all eight: no .nnw's input layer changes. On the fingerprints, UseRSI and the five AD flags were hashed unconditionally and become literal 0 legacy slots (the convention the m_focalGamma slot above them already uses); UseMACD/UseIchimoku were appended only when enabled, so their segments simply never appear - byte-identical to every fingerprint ever produced, since neither ever shipped on. CADIndicatorTuner IS DELIBERATELY NOT SHRUNK. Its flat parameter array is persisted inside every .nnw, and Unflatten() rejects a size mismatch by falling back to constructor defaults - so dropping the dead fields would silently revert the tuned MA period of every model on disk while keeping its trained weights. That is the feature/weight mismatch this project has already paid for twice, and it is not worth 200 lines. AD_TUNE_PARAM_COUNT stays 42, the dead slots are still written and read, and AutoTune's ParamOwner gate now matches only owner 5 (MA) so nothing searches them. The class comment says all of this at the declaration. Also renamed ReInitADIndicators -> ReInitTunableIndicators: it rebuilds exactly one indicator now, and a name saying "AD" for the MA handle is the kind of stale label that gets believed later. Its release-AFTER-recreate ordering is untouched - that is a documented fix, not bookkeeping. Compile-verified in the stage copy: 0 errors, 0 warnings, against the same 0/0 baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:21:03 -04:00
//--- THE RSI, MACD, ICHIMOKU AND FIVE AD/WYCKOFF FEATURE GROUPS WERE REMOVED 2026-08-24. Every one
//--- had shipped false, and each carried a closed verdict: the classic oscillators are the same 26
//--- patterns that measured at chance as entries, and the Wyckoff family returned zero out-of-sample
//--- on five independent instruments, which is what closed the context score. Their widths were
//--- already 0 in the input matrix, so removing them changes no fingerprint and orphans no model.
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
//==================================================================================================
//--- Tuner seeds, one per CONCEPT rather than per indicator (volClimax/volHigh/rangeClimax/... were
//--- restated verbatim across four indicators). The auto-tuner is the operator path to these values;
//--- editing a _DEF is a deliberate speed bump, because hand-set values bypass its family-wise gate.
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
#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
//--- Aliases keeping every consumer (CADIndicatorTuner seeds, ConfigFingerprint's ADP token) untouched.
#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
//--- Proximity/impact only, never actual-vs-forecast, which is not knowable ahead of the release.
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 EnableNews = false; // Feature: news proximity
input NF_LOOKBACK_PRESETS NewsFeatureWindowMinutes = M60; // News feature window
//--- Currency-strength panel built from the FX pairs in Market Watch. Needs >= 2 usable pairs; degrades
//--- to a neutral 0-fill with one logged line rather than blocking training.
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
input bool EnableCrossAsset = true; // Feature: cross-asset currency strength
//--- The only microstructure channel that is both FX-available and genuinely historical in the tester.
//--- Encodes a volatility REGIME; unsigned, like volume, so it can never pick a side.
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
input bool EnableSpreadFeature = true; // Feature: spread / volatility regime
//--- Gates CONSUMPTION only. With 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 shrinks the input width and correctly starts a fresh model.
input bool EnableAltData = true; // Feature: alternative data (COT / VIX / macro)
//--- Keys travel as input defaults so wiping Common\Files\Warrior_EA cannot silently kill a source.
//--- A keys.txt in the AltData folder is consulted only if an input is blanked. COT needs no key.
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)
//--- Searches the per-bar parameters of every ENABLED feature under a family-wise gate; the trial
//--- budget is derived, not configured (ComputeTuneTrialBudget).
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
//--- All three ON spans 00:00-22:00 GMT. The filter is evaluated once per BAR, so on D1 there is exactly
//--- one evaluation and a narrow default can starve the EA of entries entirely.
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 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
//--- Its own group because CExpertCustom::OnTick() evaluates this schedule unconditionally - it fires
//--- whether EnableSessionFilter is on or off. Set Close-all day = Disabled to switch it 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
input string CA_Settings = "Scheduled Close-All"; // Scheduled Close-All
input CLOSE_DAY_OF_WEEK targetDayOfWeek = CLOSE_FRIDAY; // Close-all day
//--- CH_MARKET_CLOSE resolves per day from the symbol's own session table and backs off by the
//--- minute setting, so it is right on every symbol and both sides of DST with no number to
//--- maintain.
input CLOSE_HOUR_OF_DAY targetHour = CH_MARKET_CLOSE; // Close-all hour
input CLOSE_MINUTE_OF_HOUR targetMinutes = CM_5; // Close-all minute
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 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 RiskGuard_Settings = "Risk Guard"; // Risk Guard
input bool EnableRiskGuard = true; // Signal: Risk Guard
//--- Free entry rather than a preset ladder, because prop limits are not always integers. Enter the
//--- limits from YOUR account agreement, slightly tighter if you want margin for slippage past a stop.
//--- 0 disables a rule. Enforced at quote frequency by Variables\RiskBudget.mqh, not once per bar.
input double MaxDailyLossPct = 4.0; // Daily loss limit % (0 = off)
input double MaxDrawdownPct = 8.0; // Max total drawdown % (0 = off)
//--- TRUE: measured down from the highest equity ever reached. FALSE: from the equity first seen. Use
//--- whichever your programme uses - a trailing rule on a static challenge halts far too early.
input bool MaxDrawdownIsTrailing = true; // Max DD trails the equity peak
//--- Broker-server hour, NOT local time. A misaligned window hands the allowance back early or late.
input int RiskDayResetHour = 0; // Risk day reset hour (broker time, 0-23)
//--- Share of the allowance genuinely LEFT after every open position's remaining loss-to-stop. Without
//--- it a trade at 3.2% into a 4% day still sized for a full risk unit and a routine stop-out breached.
input double RiskPerTradeOfBudget = 50.0; // Max % of remaining budget per trade
//--- Declining new entries cannot stop an ALREADY-OPEN position running through the limit, which is how
//--- a hard daily rule is actually breached. OFF means the limits above are advisory, not enforced.
input bool RiskGuardFlatten = false; // Close own positions on breach
//--- EXPECTANCY STOP. The limits above bound how FAST the account loses, never WHETHER. 0 = off.
//--- The halt is LATCHED and survives a restart; clearing it means deleting the risk state file.
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
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
//--- Scales each signal's vote by its historical win rate, records every trade, and powers the Export
//--- Trade Journal Report button.
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
input bool UseDatabaseRanking = true; // Weight filters by DB win-rate
refactor(meta): remove meta-labeling entirely - RETRAIN-NEUTRAL ~2,300 lines. META had real, repeatedly measured ranking skill and ZERO operating points that ever cleared break-even (0/350 H1 eras, 1/999 H4 pre-2-sigma, 0/8 pooled fitted points). The clinching arithmetic was edge x width = 0.095 ATR/trade against spread 0.099 ATR/trade, and the dose-response showed the high-conviction tail is temporally unstable - the precision-vs-threshold slope flips sign between calib and test on 3 of 4 symbols, so no ex-ante threshold rule exists. It shipped default-off and never gated a live entry. The self-measured tier weights are what actually rank the vote, and all six H4 instruments converged on them alone. RETRAIN-NEUTRAL, and that is the property that made this safe: - The weights fingerprint emitted "|TGT:META2" or "|TGT:SWG1" from an if/else. Every direction model already took the SWG1 arm, so collapsing it to an unconditional append is byte-identical. No .nnw or .cfg is orphaned or re-keyed. - NetInputWidth() lost its "+ MetaDescWidth()" term. MetaDescWidth() returned 0 for every direction model, so the input layer is unchanged. - DbLegacyAiSlot()'s slot 5 was reachable only with all four Use_* NNs off AND meta on - a config that never shipped. Every existing .db keeps its filename. Deleted outright: Signals/SignalMETA.mqh, Expert/Trading/MetaGate.mqh (the directory is now empty), Expert/Training/{MetaCorpus,MetaCandidateStore, MetaFamilies}.mqh, Tests/Test_MetaFamilies.mq5, Meta_Labeling_Design.md. Unwound in place, the delicate part: Training.mqh carried four IsMetaTarget() branches whose else-arm WRAPPED the direction body (pass 1 queueing, pass 2 backprop, pass 2.5 calibration, pass 3 OOS scoring). Each wrapper is removed and the direction body promoted back to its original nesting - the bodies were never re-indented when the wrappers were added, so the promoted code is byte-identical to what ran before META existed. Also gone: the ensemble verdict's meta-veto replay and its approved/vetoed/unscored counters, the per-family/per-side OOS decomposition arrays, the m_isTrainQueueCand parallel queue and its lockstep shuffle, and the S2 era report. Also removed: the CMetaGate abstraction and the live CheckOpenPosition veto; m_gates plus AddFilter's non-voter routing and IsVotingSignal() (META was the only non-voting child, so m_gates was always empty); m_parentSignal/SetParentSignal (existed only to reach the root's gate); SweepPrepare/SweepPrepareIndicator (only caller was the corpus sweep); IsMetaTarget() from all four view interfaces and their adapters; Use_MetaLabeling, EnableMETA, Meta_ExportDataset, m_trainTarget. EvalShift is KEPT - HistoricalNetVote() uses it for the filtered overlay, not just the corpus sweep; only its comment changed. The 2-output softmax arm in NetForward.mqh is kept too: it costs nothing and is the reusable binary-head path, now commented as unclaimed rather than as META's. Compile-verified in _claude_stage: 0 errors, 0 warnings, matching the pre-edit baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 09:44:52 -04:00
//--- Oldest row pruned past the cap. A high cap costs nothing until the rows exist.
input int DB_MaxRowsPerTable = 1000000; // Max rows kept per pattern table
//--- Header only - AI\Network.mqh's Adam*/Sgd* inputs render immediately after this divider.
input string NNPerf_Settings = "NN Optimizer / Performance"; // NN Optimizer / Performance
//--- Guarded, so the explicit include in Warrior_EA.mq5 stays harmless: every unit that sees the seed
//--- constants above also sees the g_Tuned* globals that supersede them.
#include "TunedPeriods.mqh"
feat(trade): two books per symbol, and delete the vote exit Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA an independent long book and short book on its symbol: at most one long and at most one short, each opened on its own side's vote and each held to its own barrier. On a netting account, or with the input off, the original single-position path runs bit-for-bit unchanged and init says which one is live. WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies P(label agrees | vote fired) and the label runs to the barrier, so closing early on a reversal makes the realised outcome stop being the labelled one - the certified precision no longer describes what is traded. Opening the other side acts on the new signal and leaves the old position's certification intact, and costs no more than reversing: both pay the new side's spread, the difference is only that the existing position runs on to a barrier already measured as positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned, along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an arithmetically unreachable 101 (the stock default of 100 is reachable by a weighted mean of values capped at 100). Note the two books can never both fill from one signal: CheckOpenLong and CheckOpenShort test opposite signs of the same m_direction, so at most one clears per tick. A hedge only forms when a LATER opposite vote fires - which is what keeps it from being a guaranteed-loss wash pair. The mechanism is a SelectPosition() override keyed on the active book's magic; every inherited close/trail path then operates on that book untouched. The long book keeps Expert_MagicNumber, so no existing position, journal row or risk-budget state file is re-addressed. Short book is +1. Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(), or the short book would have been invisible to the code that must reach it: the scheduled close-all (positions and orders), the risk budget's emergency flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT gated on Allow_Hedging - turning the input off while a short-book position is open would otherwise orphan it with nothing left to close it. Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(), which counts every position regardless of magic, so the second book is sized inside what the first one left. Conservative for a hedged pair, which cannot lose both stops - the safe direction. Retrain-neutral: neither input is in BuildModelFingerprint() or ComputeDbConfigFingerprint(). Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
//==================================================================================================
// THE TWO BOOKS - MAGIC NUMBERS
//==================================================================================================
//--- Allow_Hedging gives this EA a second position slot on the same symbol, and a position is
//--- addressed by (symbol, magic) everywhere in MT5 - so the short book needs a magic of its own.
//--- The LONG book keeps Expert_MagicNumber unchanged: every position, journal row and risk-budget
//--- state file this EA has ever written stays addressable exactly as before, and turning hedging on
//--- adopts no existing position into the wrong book.
//---
//--- The offset is +1, so magic N and N+1 are BOTH reserved by this EA. Every filter that uses them
//--- also matches the symbol, so the six charts sharing magic 2024 stay disjoint - but do not point a
//--- second EA at Expert_MagicNumber+1.
#define SHORT_BOOK_MAGIC_OFFSET 1
//--- The vote exit is pinned shut here rather than left at the stock CExpertSignal default of 100:
//--- the vote is a WEIGHTED MEAN of values that cannot exceed 100, so it CAN reach 100 exactly, and
//--- 100 would therefore arm an exit this strategy is not certified for. 101 is unreachable by
//--- arithmetic. See Signal_ThresholdClose's removal note above.
#define VOTE_EXIT_DISABLED_THRESHOLD 101
feat(magic): assign the magic number once, then remember it Expert_MagicNumber = 0 (the new default) means "draw one and write it down". On first attach the EA picks a random magic in a distinctive band, persists it to MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on every later start. Unique without anyone typing it, and STABLE. Stability is the whole point. The magic is how the EA recognises its own positions - a fresh one per start would leave every open position invisible to the scheduled close-all, the risk-budget flatten and the journal's MAE/MFE walk: trades still running that no code would ever manage again. So the value is persisted before it is ever used to trade. Stored TERMINAL-LOCAL rather than in Common\Files\Warrior_EA, on purpose: that folder is the one wiped for a retrain, and positions outlive retrains. It also gives two terminals on the same symbol different magics, which a chart-identity hash could not. Fallbacks, both of which stay stable without a file: * tester/optimizer/forward use a magic derived from chart identity, so two identical passes cannot differ. * an unwritable file falls back to that same derived value, and says so. Books occupy EVEN slots only, so one chart's short book (base+1) can never land on another chart's long book. WarriorOwnsMagic() now also recognises the legacy 2024/2025 pair permanently. Without it, switching an existing chart to 0 while a position was open would orphan that position. Every caller also matches the symbol, so claiming those values can only reach positions on this EA's own chart. Existing charts are untouched: MT5 stores inputs per chart, so the six live charts keep the 2024 they already have and keep managing what they hold. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:35:58 -04:00
//--- Band the assigned magics live in. Deliberately far from the values people type by hand (2024,
//--- 12345, 999) so an auto-assigned magic never collides with a hand-set one on the same account.
#define WARRIOR_MAGIC_BAND 1400000000
#define WARRIOR_MAGIC_SLOTS 100000
//--- Resolved once per run. 0 = not resolved yet; WarriorBookMagic() is called from per-tick filters,
//--- so the file must be touched exactly once and never again.
ulong g_warriorBaseMagic = 0;
//--- DETERMINISTIC FALLBACK, from chart identity. Used in the tester (where a random magic would make
//--- two identical passes differ) and whenever the file cannot be written - a magic that is stable
//--- WITHOUT a file is a far better failure mode than one that is fresh on every start.
ulong WarriorDerivedMagicBase(void)
{
string key = _Symbol + "|" + IntegerToString((int)_Period);
uint h = 2166136261;
int n = StringLen(key);
for(int i = 0; i < n; i++)
{
h ^= (uint)StringGetCharacter(key, i);
h *= 16777619;
}
//--- EVEN SLOTS ONLY. The short book is base+1, so odd bases would let one chart's short book land
//--- exactly on another chart's long book.
return (WARRIOR_MAGIC_BAND + 2 * (ulong)(h % WARRIOR_MAGIC_SLOTS));
}
string WarriorMagicFileName(void)
{
return StringFormat("Warrior_%s_%d.magic", _Symbol, (int)_Period);
}
//--- ASSIGN-ONCE-AND-REMEMBER. Terminal-local (MQL5\Files) ON PURPOSE, not Common: the Common
//--- Warrior_EA folder is the one that gets wiped for a retrain, and positions outlive retrains. It
//--- also keeps two terminals running the same symbol on separate magics, which a chart-identity hash
//--- could not do.
ulong WarriorResolveBaseMagic(void)
{
if(g_warriorBaseMagic != 0)
return g_warriorBaseMagic;
//--- Reproducibility beats uniqueness in the tester: two identical passes must not differ.
if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_FORWARD))
{
g_warriorBaseMagic = WarriorDerivedMagicBase();
return g_warriorBaseMagic;
}
string file = WarriorMagicFileName();
//--- SHARE flags on every open, always - see the tester optcache corruption this project already ate.
int h = FileOpen(file, FILE_READ | FILE_TXT | FILE_ANSI | FILE_SHARE_READ | FILE_SHARE_WRITE);
if(h != INVALID_HANDLE)
{
string line = FileReadString(h);
FileClose(h);
ulong stored = (ulong)StringToInteger(line);
if(stored != 0)
{
g_warriorBaseMagic = stored;
PrintFormat("Warrior magic: reusing %I64u for %s (from %s) - the positions this EA already"
" holds stay recognisable across this restart.", stored, _Symbol, file);
return g_warriorBaseMagic;
}
PrintFormat("Warrior magic: %s exists but holds no usable value (\"%s\") - assigning a new one.",
file, line);
}
//--- No usable file. Draw one, then PERSIST IT BEFORE RETURNING - a magic that was never written
//--- down is exactly the orphaned-positions failure this whole mechanism exists to prevent.
MathSrand((int)(GetTickCount() + (uint)TimeLocal() + (uint)StringLen(_Symbol) * 7919));
//--- MathRand() is 0..32767, so one draw cannot cover the slot range. Two do.
uint draw = ((uint)MathRand() << 15) ^ (uint)MathRand();
ulong candidate = WARRIOR_MAGIC_BAND + 2 * (ulong)(draw % WARRIOR_MAGIC_SLOTS);
int w = FileOpen(file, FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_SHARE_READ | FILE_SHARE_WRITE);
if(w == INVALID_HANDLE)
{
g_warriorBaseMagic = WarriorDerivedMagicBase();
PrintFormat("Warrior magic: CANNOT WRITE %s (error %d) - falling back to the chart-derived magic"
" %I64u, which is stable without a file. Trading continues; the assigned-random path"
" is what is unavailable, not position tracking.",
file, GetLastError(), g_warriorBaseMagic);
return g_warriorBaseMagic;
}
FileWriteString(w, IntegerToString((long)candidate));
FileClose(w);
g_warriorBaseMagic = candidate;
PrintFormat("Warrior magic: assigned %I64u to %s and wrote it to %s. Every later start reads it"
" back, so positions opened under it stay managed.", candidate, _Symbol, file);
return g_warriorBaseMagic;
}
feat(trade): two books per symbol, and delete the vote exit Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA an independent long book and short book on its symbol: at most one long and at most one short, each opened on its own side's vote and each held to its own barrier. On a netting account, or with the input off, the original single-position path runs bit-for-bit unchanged and init says which one is live. WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies P(label agrees | vote fired) and the label runs to the barrier, so closing early on a reversal makes the realised outcome stop being the labelled one - the certified precision no longer describes what is traded. Opening the other side acts on the new signal and leaves the old position's certification intact, and costs no more than reversing: both pay the new side's spread, the difference is only that the existing position runs on to a barrier already measured as positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned, along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an arithmetically unreachable 101 (the stock default of 100 is reachable by a weighted mean of values capped at 100). Note the two books can never both fill from one signal: CheckOpenLong and CheckOpenShort test opposite signs of the same m_direction, so at most one clears per tick. A hedge only forms when a LATER opposite vote fires - which is what keeps it from being a guaranteed-loss wash pair. The mechanism is a SelectPosition() override keyed on the active book's magic; every inherited close/trail path then operates on that book untouched. The long book keeps Expert_MagicNumber, so no existing position, journal row or risk-budget state file is re-addressed. Short book is +1. Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(), or the short book would have been invisible to the code that must reach it: the scheduled close-all (positions and orders), the risk budget's emergency flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT gated on Allow_Hedging - turning the input off while a short-book position is open would otherwise orphan it with nothing left to close it. Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(), which counts every position regardless of magic, so the second book is sized inside what the first one left. Conservative for a hedged pair, which cannot lose both stops - the safe direction. Retrain-neutral: neither input is in BuildModelFingerprint() or ComputeDbConfigFingerprint(). Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
ulong WarriorBookMagic(const bool longBook)
{
feat(magic): assign the magic number once, then remember it Expert_MagicNumber = 0 (the new default) means "draw one and write it down". On first attach the EA picks a random magic in a distinctive band, persists it to MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on every later start. Unique without anyone typing it, and STABLE. Stability is the whole point. The magic is how the EA recognises its own positions - a fresh one per start would leave every open position invisible to the scheduled close-all, the risk-budget flatten and the journal's MAE/MFE walk: trades still running that no code would ever manage again. So the value is persisted before it is ever used to trade. Stored TERMINAL-LOCAL rather than in Common\Files\Warrior_EA, on purpose: that folder is the one wiped for a retrain, and positions outlive retrains. It also gives two terminals on the same symbol different magics, which a chart-identity hash could not. Fallbacks, both of which stay stable without a file: * tester/optimizer/forward use a magic derived from chart identity, so two identical passes cannot differ. * an unwritable file falls back to that same derived value, and says so. Books occupy EVEN slots only, so one chart's short book (base+1) can never land on another chart's long book. WarriorOwnsMagic() now also recognises the legacy 2024/2025 pair permanently. Without it, switching an existing chart to 0 while a position was open would orphan that position. Every caller also matches the symbol, so claiming those values can only reach positions on this EA's own chart. Existing charts are untouched: MT5 stores inputs per chart, so the six live charts keep the 2024 they already have and keep managing what they hold. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:35:58 -04:00
//--- An explicitly pinned value wins and never touches the file: a chart already carrying 2024
//--- keeps addressing the positions it opened under 2024.
ulong base = (Expert_MagicNumber != 0) ? Expert_MagicNumber : WarriorResolveBaseMagic();
return (longBook ? base : base + SHORT_BOOK_MAGIC_OFFSET);
feat(trade): two books per symbol, and delete the vote exit Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA an independent long book and short book on its symbol: at most one long and at most one short, each opened on its own side's vote and each held to its own barrier. On a netting account, or with the input off, the original single-position path runs bit-for-bit unchanged and init says which one is live. WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies P(label agrees | vote fired) and the label runs to the barrier, so closing early on a reversal makes the realised outcome stop being the labelled one - the certified precision no longer describes what is traded. Opening the other side acts on the new signal and leaves the old position's certification intact, and costs no more than reversing: both pay the new side's spread, the difference is only that the existing position runs on to a barrier already measured as positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned, along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an arithmetically unreachable 101 (the stock default of 100 is reachable by a weighted mean of values capped at 100). Note the two books can never both fill from one signal: CheckOpenLong and CheckOpenShort test opposite signs of the same m_direction, so at most one clears per tick. A hedge only forms when a LATER opposite vote fires - which is what keeps it from being a guaranteed-loss wash pair. The mechanism is a SelectPosition() override keyed on the active book's magic; every inherited close/trail path then operates on that book untouched. The long book keeps Expert_MagicNumber, so no existing position, journal row or risk-budget state file is re-addressed. Short book is +1. Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(), or the short book would have been invisible to the code that must reach it: the scheduled close-all (positions and orders), the risk budget's emergency flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT gated on Allow_Hedging - turning the input off while a short-book position is open would otherwise orphan it with nothing left to close it. Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(), which counts every position regardless of magic, so the second book is sized inside what the first one left. Conservative for a hedged pair, which cannot lose both stops - the safe direction. Retrain-neutral: neither input is in BuildModelFingerprint() or ComputeDbConfigFingerprint(). Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
}
//--- IS THIS POSITION/ORDER OURS? Used by every sweep that acts on the EA's own trades - the
//--- scheduled close-all, the risk budget's emergency flatten, the journal's MAE/MFE walk.
//---
//--- DELIBERATELY NOT GATED ON Allow_Hedging. Ownership must not depend on a switch: turn hedging
//--- off while a short-book position is open and a gated predicate would stop recognising it, so the
//--- close-all would skip it and the flatten would leave it running - a position no code would ever
//--- close again. Only OPENING a short book is gated by the input; owning one is forever. This is the
//--- same asymmetry as ProtectOpenPosition(): an exit may act where an entry may not.
feat(magic): assign the magic number once, then remember it Expert_MagicNumber = 0 (the new default) means "draw one and write it down". On first attach the EA picks a random magic in a distinctive band, persists it to MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on every later start. Unique without anyone typing it, and STABLE. Stability is the whole point. The magic is how the EA recognises its own positions - a fresh one per start would leave every open position invisible to the scheduled close-all, the risk-budget flatten and the journal's MAE/MFE walk: trades still running that no code would ever manage again. So the value is persisted before it is ever used to trade. Stored TERMINAL-LOCAL rather than in Common\Files\Warrior_EA, on purpose: that folder is the one wiped for a retrain, and positions outlive retrains. It also gives two terminals on the same symbol different magics, which a chart-identity hash could not. Fallbacks, both of which stay stable without a file: * tester/optimizer/forward use a magic derived from chart identity, so two identical passes cannot differ. * an unwritable file falls back to that same derived value, and says so. Books occupy EVEN slots only, so one chart's short book (base+1) can never land on another chart's long book. WarriorOwnsMagic() now also recognises the legacy 2024/2025 pair permanently. Without it, switching an existing chart to 0 while a position was open would orphan that position. Every caller also matches the symbol, so claiming those values can only reach positions on this EA's own chart. Existing charts are untouched: MT5 stores inputs per chart, so the six live charts keep the 2024 they already have and keep managing what they hold. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:35:58 -04:00
//--- THE LEGACY PAIR. 2024 was this EA's shipped default magic for its whole life, so positions opened
//--- under it must stay recognisable even after a chart is switched to the assign-and-remember scheme
//--- (Expert_MagicNumber = 0). Without this, flipping a chart while a position was open would orphan
//--- it - still running, invisible to the close-all and the flatten, managed by nothing. Every filter
//--- that calls this ALSO matches the symbol, so claiming these two values can only ever reach
//--- positions on this EA's own chart. Safe to delete once no 2024-era position can still be open.
#define WARRIOR_LEGACY_MAGIC 2024
feat(trade): two books per symbol, and delete the vote exit Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA an independent long book and short book on its symbol: at most one long and at most one short, each opened on its own side's vote and each held to its own barrier. On a netting account, or with the input off, the original single-position path runs bit-for-bit unchanged and init says which one is live. WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies P(label agrees | vote fired) and the label runs to the barrier, so closing early on a reversal makes the realised outcome stop being the labelled one - the certified precision no longer describes what is traded. Opening the other side acts on the new signal and leaves the old position's certification intact, and costs no more than reversing: both pay the new side's spread, the difference is only that the existing position runs on to a barrier already measured as positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned, along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an arithmetically unreachable 101 (the stock default of 100 is reachable by a weighted mean of values capped at 100). Note the two books can never both fill from one signal: CheckOpenLong and CheckOpenShort test opposite signs of the same m_direction, so at most one clears per tick. A hedge only forms when a LATER opposite vote fires - which is what keeps it from being a guaranteed-loss wash pair. The mechanism is a SelectPosition() override keyed on the active book's magic; every inherited close/trail path then operates on that book untouched. The long book keeps Expert_MagicNumber, so no existing position, journal row or risk-budget state file is re-addressed. Short book is +1. Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(), or the short book would have been invisible to the code that must reach it: the scheduled close-all (positions and orders), the risk budget's emergency flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT gated on Allow_Hedging - turning the input off while a short-book position is open would otherwise orphan it with nothing left to close it. Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(), which counts every position regardless of magic, so the second book is sized inside what the first one left. Conservative for a hedged pair, which cannot lose both stops - the safe direction. Retrain-neutral: neither input is in BuildModelFingerprint() or ComputeDbConfigFingerprint(). Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
bool WarriorOwnsMagic(const long magic)
{
feat(magic): assign the magic number once, then remember it Expert_MagicNumber = 0 (the new default) means "draw one and write it down". On first attach the EA picks a random magic in a distinctive band, persists it to MQL5\Files\Warrior_<symbol>_<period>.magic, and reads that same value back on every later start. Unique without anyone typing it, and STABLE. Stability is the whole point. The magic is how the EA recognises its own positions - a fresh one per start would leave every open position invisible to the scheduled close-all, the risk-budget flatten and the journal's MAE/MFE walk: trades still running that no code would ever manage again. So the value is persisted before it is ever used to trade. Stored TERMINAL-LOCAL rather than in Common\Files\Warrior_EA, on purpose: that folder is the one wiped for a retrain, and positions outlive retrains. It also gives two terminals on the same symbol different magics, which a chart-identity hash could not. Fallbacks, both of which stay stable without a file: * tester/optimizer/forward use a magic derived from chart identity, so two identical passes cannot differ. * an unwritable file falls back to that same derived value, and says so. Books occupy EVEN slots only, so one chart's short book (base+1) can never land on another chart's long book. WarriorOwnsMagic() now also recognises the legacy 2024/2025 pair permanently. Without it, switching an existing chart to 0 while a position was open would orphan that position. Every caller also matches the symbol, so claiming those values can only reach positions on this EA's own chart. Existing charts are untouched: MT5 stores inputs per chart, so the six live charts keep the 2024 they already have and keep managing what they hold. Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:35:58 -04:00
if(magic == (long)WarriorBookMagic(true) || magic == (long)WarriorBookMagic(false))
return true;
return (magic == WARRIOR_LEGACY_MAGIC ||
magic == WARRIOR_LEGACY_MAGIC + SHORT_BOOK_MAGIC_OFFSET);
feat(trade): two books per symbol, and delete the vote exit Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA an independent long book and short book on its symbol: at most one long and at most one short, each opened on its own side's vote and each held to its own barrier. On a netting account, or with the input off, the original single-position path runs bit-for-bit unchanged and init says which one is live. WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies P(label agrees | vote fired) and the label runs to the barrier, so closing early on a reversal makes the realised outcome stop being the labelled one - the certified precision no longer describes what is traded. Opening the other side acts on the new signal and leaves the old position's certification intact, and costs no more than reversing: both pay the new side's spread, the difference is only that the existing position runs on to a barrier already measured as positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned, along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an arithmetically unreachable 101 (the stock default of 100 is reachable by a weighted mean of values capped at 100). Note the two books can never both fill from one signal: CheckOpenLong and CheckOpenShort test opposite signs of the same m_direction, so at most one clears per tick. A hedge only forms when a LATER opposite vote fires - which is what keeps it from being a guaranteed-loss wash pair. The mechanism is a SelectPosition() override keyed on the active book's magic; every inherited close/trail path then operates on that book untouched. The long book keeps Expert_MagicNumber, so no existing position, journal row or risk-budget state file is re-addressed. Short book is +1. Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(), or the short book would have been invisible to the code that must reach it: the scheduled close-all (positions and orders), the risk budget's emergency flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT gated on Allow_Hedging - turning the input off while a short-book position is open would otherwise orphan it with nothing left to close it. Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(), which counts every position regardless of magic, so the second book is sized inside what the first one left. Conservative for a hedged pair, which cannot lose both stops - the safe direction. Retrain-neutral: neither input is in BuildModelFingerprint() or ComputeDbConfigFingerprint(). Compiled clean; NOT yet run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00
}
//--- Is the two-book mode actually live? Needs BOTH the input and an account that can hold opposing
//--- positions - on NETTING the second book is arithmetically impossible, so the EA runs its original
//--- single-position path and says so once at init.
bool WarriorHedgingActive(void)
{
return (Allow_Hedging &&
(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
}