2026-08-20 09:49:33 -04:00 | | | //+------------------------------------------------------------------+
|
2026-07-22 13:33:56 -04:00 | | | //| Inputs.mqh |
|
| | | //| AnimateDread |
|
| | | //| https://www.mql5.com |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "AnimateDread"
|
| | | #property link "https://www.mql5.com"
|
| | | #include "..\Enumerations\InputEnums.mqh"
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-07-22 13:33:56 -04:00 | | | //==================================================================================================
|
| | | // 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)
|
2026-07-22 13:33:56 -04:00 | | | input bool Expert_EveryTick = false; // Calculate on every tick
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-22 08:16:11 -04:00 | | | input bool VerboseMode = false; // Verbose journal + detailed panel (full per-era logs)
|
2026-08-20 09:49:33 -04:00 | | | //--- 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;
|
2026-08-20 09:49:33 -04:00 | | | //--- 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;
|
2026-07-22 13:33:56 -04:00 | | | //==================================================================================================
|
| | | // 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
|
2026-07-22 13:33:56 -04:00 | | | 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.
|
2026-08-22 00:25:52 -04:00 | | | //--- 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.
|
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.
|
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.
|
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)
|
2026-08-20 09:49:33 -04:00 | | | //--- 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)
|
2026-07-22 13:33:56 -04:00 | | | //==================================================================================================
|
 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)
|
2026-07-22 17:17:23 -04:00 | | | //==================================================================================================
|
 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.
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-20 09:49:33 -04:00 | | | //--- Hand-setting means editing these constants, which deliberately bypasses that gate.
|
2026-08-16 15:12:54 -04:00 | | | 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;
|
2026-07-22 13:33:56 -04:00 | | | //==================================================================================================
|
| | | // NEURAL NETWORK (training)
|
| | | //==================================================================================================
|
2026-08-19 14:23:37 -04:00 | | | input string NNetworks_Settings = "Neural Networks"; // Neural Networks
|
2026-08-20 09:49:33 -04:00 | | | //--- 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)
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
 diag(baselines): turn the forest/linear comparison on, and deflate its SE
Run_Alglib_Baselines = true. It answers the question the campaign is
actually stuck on - is a flat result the architecture or the matrix - by
fitting an Alglib forest, MLP and OLS on the net's OWN windows, labels,
split and gate arithmetic. Nothing trades on it and no model is saved.
Not in BuildModelFingerprint, so this does NOT re-key anything: no
retrain, it can go out with the next compile. One-shot per chart
(m_baselineDone latches first, and g_ensembleChartBaselinesDone stops
the other three members re-measuring the identical fit), bounded by
BASELINE_BUDGET_MS = 45 s, which it spends frozen because the EA is
single threaded - it says so when it stops early.
FIXED WHILE TURNING IT ON: ReportBaselineModel sized its SE on RAW call
count while the NN's own DEPLOY BAR deflates by the mean label lifespan.
The whole point of this line is a like-for-like comparison against the
net on the same windows, and it was holding the baseline to the more
permissive standard - which is how the last forest run's +8.4pp read as
significant when it was +1.0 SE after deflation. Now uses
EffectiveSampleSize and prints how many independent calls that leaves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 10:47:25 -04:00 | | | 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.
|
2026-08-24 22:38:38 -04:00 | | | 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.
|
2026-08-20 09:49:33 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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;
|
| | | }
|
2026-07-29 00:03:54 -04:00 | | | 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;
|
2026-08-22 00:25:52 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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)
|
2026-08-20 09:49:33 -04:00 | | | //--- Training starts at the earliest available bar (floored by MinTrainYear); the honest generalisation
|
| | | //--- read comes from this holdout, not from withholding history.
|
2026-07-22 13:33:56 -04:00 | | | input OOS_SPLIT_PRESET OOSSplit = OOS_30; // Out-of-sample holdout
|
2026-08-22 00:25:52 -04:00 | | | //--- 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 | | | //==================================================================================================
|
2026-08-24 19:33:37 -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 | | | //==================================================================================================
|
2026-08-20 09:49:33 -04:00 | | | //--- LOGIT-ADJUSTED LOSS (Menon et al. 2021): add tau*log(prior_c) to each class logit inside the
|
2026-08-24 19:33:37 -04:00 | | | //--- 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.
|
2026-08-20 09:49:33 -04:00 | | | //--- 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;
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-27 11:59:17 -04:00 | | | //--- 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.
|
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;
|
2026-08-27 12:06:10 -04:00 | | | //--- 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);
|
| | | }
|
2026-08-27 12:06:10 -04:00 | | | //--- 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;
|
2026-08-20 09:49:33 -04:00 | | | //--- 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;
|
2026-08-20 09:49:33 -04:00 | | | //--- Runaway backstop, not a training control - the plateau ladder decides when a run ends.
|
2026-08-16 23:52:22 -04:00 | | | 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-11 21:53:37 -04:00 | | | #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
|
| | | //==================================================================================================
|
2026-08-20 09:49:33 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- Aliases keeping every consumer (CADIndicatorTuner seeds, ConfigFingerprint's ADP token) untouched.
|
2026-08-16 14:51:24 -04:00 | | | #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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-16 15:12:54 -04:00 | | | input bool EnableAltData = true; // Feature: alternative data (COT / VIX / macro)
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-16 15:48:05 -04:00 | | | 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)
|
2026-08-22 00:25:52 -04:00 | | | //--- Searches the per-bar parameters of every ENABLED feature under a family-wise gate; the trial
|
| | | //--- budget is derived, not configured (ComputeTuneTrialBudget).
|
2026-08-16 14:51:24 -04:00 | | | 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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
|
2026-08-22 00:25:52 -04:00 | | | //--- 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.
|
2026-08-20 08:39:26 -04:00 | | | 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-02 12:25:20 -04:00 | | | input double MaxDailyLossPct = 4.0; // Daily loss limit % (0 = off)
|
| | | input double MaxDrawdownPct = 8.0; // Max total drawdown % (0 = off)
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-02 12:25:20 -04:00 | | | input bool MaxDrawdownIsTrailing = true; // Max DD trails the equity peak
|
2026-08-20 09:49:33 -04:00 | | | //--- Broker-server hour, NOT local time. A misaligned window hands the allowance back early or late.
|
2026-08-02 12:25:20 -04:00 | | | input int RiskDayResetHour = 0; // Risk day reset hour (broker time, 0-23)
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-02 12:25:20 -04:00 | | | input double RiskPerTradeOfBudget = 50.0; // Max % of remaining budget per trade
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-02 12:25:20 -04:00 | | | input bool RiskGuardFlatten = false; // Close own positions on breach
|
2026-08-22 00:25:52 -04:00 | | | //--- 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
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-13 19:25:02 -04:00 | | | input int DB_MaxRowsPerTable = 1000000; // Max rows kept per pattern table
|
2026-08-20 09:49:33 -04:00 | | | //--- Header only - AI\Network.mqh's Adam*/Sgd* inputs render immediately after this divider.
|
2026-07-22 17:17:23 -04:00 | | | input string NNPerf_Settings = "NN Optimizer / Performance"; // NN Optimizer / Performance
|
2026-08-20 09:49:33 -04:00 | | | //--- 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.
|
2026-08-16 15:12:54 -04:00 | | | #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);
|
| | | }
|