inTradingSession() had three near-identical if(session==...) branches
(London/NewYork/Tokyo) that each set an openUtcMin from a DST test and
called the same inTimeInterval() shape, differing only in the trade
toggle, DST function, winter-UTC open hour, and window length. Replaced
with one dispatch that fills 4 locals (winterOpenUtcHour, lengthHours,
tradeToggle, isSummer) per session name, then one shared computation +
inTimeInterval() call.
Verified branch-by-branch equivalence before compiling: London
(8,8,EuSummer), NewYork (13,9,UsSummer), Tokyo (0,9,none) reproduce the
exact original arithmetic per session, including Tokyo's already-UTC-
converted 0 (not 9, since Tokyo has no DST and JST is UTC+9). Unknown
session still returns false. This is live trade-veto logic, so no
structural reordering beyond the branch consolidation itself.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SetStatusLabel's trailing-line trim and ClearStatusLabel's full wipe
had byte-identical bg/txt ObjectFind+ObjectDelete loop bodies, differing
only in start bound. Extract DeleteStatusLines(fromIdx, toIdx); both
call sites keep their own counter reset/redraw. Pure UI, no behavior
change.
All three MM_STRATEGY branches repeated new+null-check+Expert.InitMoney()+
null-check verbatim. Added CreateAndInitMoney<TMoney>(functionName), the
same template-helper shape as the existing CreateSignalWithRetry<TSignal>
a few hundred lines up. Pure relocation - same error text, same control
flow, only the branch-specific setter calls (Percent/Lots/UseAIConfidence...)
stay inline.
CExpertCustom's three Long/Short pairs (OpenLong/OpenShort,
TrailingStopLong/Short, TrailingOrderLong/Short) each duplicated the
same pre-send gate logic, differing only in the order-type constant
and which base CExpert::Xxx method to delegate to - the same shape
CExpertSignalCustom already fixed for CheckOpenPosition/CheckClosePosition.
Added OpenPosition/TrailingStopCommon/TrailingOrderCommon, each
isLong-parameterized with a caller string threaded through (passed as
__FUNCTION__ from each 1-line wrapper) so every TCLog message keeps
its original per-direction function name and topic tag. Pure
relocation - every log string, arithmetic expression and branch order
verified unchanged against the pre-edit file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both methods shared ~90% identical bodies differing only in ORDER_TYPE_BUY
vs ORDER_TYPE_SELL. Extracted a private CheckOpen(type, price, sl), mirroring
the CheckTrailingStop(pos, sl, tp, isLong) unification already used in
Trailing/TrailingATR.mqh. Pure relocation, no arithmetic/ordering change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ParamCandidates() repeated an identical resize+copy+return-count block
9x, once per discrete MA/RSI/MACD/Ichimoku preset list. Added a private
FillCandidatesFromPresets(src[], out[]) doing that once; all 9 sites
now a one-line call. Pure mechanical dedup, no arithmetic/ordering
change.
CTradeJournalManager::AddSuggestion() does the resize+assign+increment
once; the hour/dow/near-miss/sl-tight/confidence-tier suggestion sites
now each call it with their already-built StringFormat text. Pure
textual relocation, no arithmetic or ordering change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CExpertCustom and CExpertSignalCustom each defined their own
stops-level order-type decision against the same TCStopsLevel()
helper, identical except CExpertSignalCustom's guard skipped the
EMPTY_VALUE check (a huge finite double would fall through into the
ask/bid comparison and misclassify as a pending order instead of a
market order). Moved the logic into TCResolveOrderType() in
System/TradeChecks.mqh, keeping the more defensive guard; both
classes now delegate. ask/bid are still passed in from each class's
own m_symbol so routing keeps using whatever RefreshRates() snapshot
that class already had - only the duplicated arithmetic moved.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TunedPeriodsFileName() inlined the same 9-line StringReplace sanitizer
AltData.mqh already defines once as AltDataFileSymbol(), whose own
comment said it was meant for exactly this. Gave AltData.mqh a proper
include guard (it had none) and included it directly from
TunedPeriods.mqh so the call is safe regardless of include order.
Compile-verified 0 errors/0 warnings.
CDatabaseFileSystemManager's CreateDirectory/CleanDirectory/DeleteFile had
three identical retry loops differing only in which FolderCreate/FolderClean/
FileDelete ran and the noun in the log line - collapsed into one
RetryFileSystemOp(enum, target, verb, caller) private helper, dispatched by
enum rather than a function pointer (MQL5 function pointers to a built-in
with default params is untested territory, not worth it for 3 one-liners).
CDatabaseVersionManager's ReadStoredDbVersion/UpdateStoredDbVersion had the
same retry shape around FileOpen, differing only in the open flags and the
reading/writing noun - collapsed into OpenVersionFileWithRetry(path, flags,
verb).
CDatabaseConnectionManager::OpenDatabase (the 6th instance the finding named)
is the only retry loop in its file - no in-file duplication to fix there, and
sharing it with the other two would need a cross-class free function bound to
DatabaseOpen/FileOpen as function pointers, an untested construct for a
20-line win. Left as-is.
Every Print() message text verified identical at each call site; public
method signatures unchanged, no external caller needed a rewrite.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CCrossAssetPanel::Warm() and WarmBlocking() opened with the identical
pinned-vs-discovered pair-set resolution block. Extracted a private
ResolvePairSet(pairs) returning whether a usable set was resolved; each
caller keeps its own early-return semantics (void vs bool-true) around
the call. Pure mechanical relocation, no arithmetic/behavior change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Added private CAltDataFetch::ShouldAttemptFetch(s, staleDays, throttleSlot, now)
combining the staleness check (parameterized by staleDays) and the byte-for-byte
identical throttle-check-and-stamp that opened UpdateFred/UpdateCot/UpdateEia.
Each call site drops from 5-6 lines to one guard call; UpdateGex is untouched
(its day/hour gate is unrelated). Same dedup doctrine as c214d3e (FredKey/EiaKey)
and defab3b (identifier-validation guard), just one level up the call chain.
FredKey() and EiaKey() implemented the same contract (input, then cache,
then a keys.txt "<prefix>=" line, cache + warn once) differing only in
which input/cache/warned-flag/prefix/messages they used. Both are now
one-line callers of a shared private helper taking those as parameters
(cache and warned-flag by reference). Pure relocation, no behavior
change - verified the warning text is character-identical at both call
sites and that the one flag-timing difference (LoadCommonKey only sets
warnedFlag inside the branch that actually prints, matching EiaKey's
original shape, vs FredKey's unconditional set) is unobservable since
a found key always short-circuits on the cache check before the flag
is ever read again.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CreateTable/DeleteTable/InsertTradeRecord/FetchRecordCount/FetchOpenTradeEntry/
FetchNewestTimeKey/FetchWinLossCounts/FetchTradeRecords/UpdateTradeRecord/
DeleteOldestEntry each repeated the same three-line "if(!IsValidIdentifier)
{ Print(...); return false; }" block, differing only in the verb printed.
Replaced with one private RequireValidIdentifier(tableName, verb) helper that
does the check + Print + returns the bool; each call site is now a single
guard line. Pure mechanical dedup - every Print message text and control-flow
path is unchanged, no call-site signature changes since it's all internal to
this one class.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
The ~185-line switch mixed chart-UI toggling, AI training-lifecycle
dispatch, weight save/load/reset and DB/report admin in one function
with subtly different guard conditions per branch. Each CP_ACTION_*
case is now its own HandleCp*() free function (matching this file's
existing procedural style - ConfirmDestructiveAction, RefreshControlPanelLabels,
etc. are already standalone functions over the same globals); the
switch is now a one-line-per-case dispatch table. Every guard/confirm/
Alert/Print sequence is preserved verbatim (break -> return only
change; verified via quoted-string-set diff = empty and if/Alert/
Print/DispatchSignalCommand counts identical against the original).
Compiled 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expert/AIBase/Labels.mqh (1807 lines) exclusivity-grepped almost entirely
SHARED: the label/win/excursion/ladder caches and the geometry-derivation/
prebuild state are touched with real per-bar array logic by Training.mqh's
hot era loop (m_labelCacheBuy/Sell/HasValue at 22+ sites), by FeatureScreen.mqh's
geometry scan (direct writes to m_barrierScanSlMult/TpMult, m_geometryAdopted,
m_geometryCfgSaved), by AutoTune.mqh and by SignalMETA.mqh - moving that state
into a collaborator would mean wrapping dense hot-loop array indexing behind
method calls across 5 files for no coupling reduction (same judgment already
recorded for AutoTune.mqh's remainder / Inference.mqh).
One genuinely closed sub-cluster survived the grep: the scheduled close-all
budget and the horizon ladder snap (NextScheduledCloseAll, MeasureCloseAllBudget,
EffectiveHorizonMax, RequiredHorizonBars, SnapHorizonToLadder, GrantedHorizonBars).
Only 2 fields are exclusive (m_closeAllCycleBars/m_closeAllMeanBudget - grep-
verified, Lifecycle.mqh's touch was constructor-init-list only) and NONE of the
6 methods has any external caller outside Labels.mqh (grep-verified whole-repo),
so nothing needed rewiring. New Expert/BarrierHorizon/: IBarrierHorizonView.mqh
(abstract, 4 accessors, 3 reused from the signal's existing Chart* getters, 1
new HorizonSwingMedianBars() wrapper) + AIBaseBarrierHorizonView.mqh/
AIBaseBarrierHorizonViewImpl.mqh (the adapter) + BarrierHorizon.mqh (CBarrierHorizon,
STATEFUL - owns the 2 exclusive fields as real members). Every method body is a
verbatim relocation (diffed programmatically against git HEAD modulo the field->
view substitutions - identical except one comment-wording update). The original
6 declarations on CExpertSignalAIBase became one-line forwards at their existing
position; Labels.mqh's own callers of these six needed zero changes since they
call them unqualified, which now resolves through the forwards.
Labels.mqh: 1807 -> 1643 lines. The rest of the file (label-cache population,
TripleBarrierLabel, DeriveBarrierGeometry, StartLabelCachePrebuild/
AdvanceLabelCachePrebuild, exit-policy simulation) is deliberately left as a
raw-include partial - not separable without relocating Training.mqh's era-loop
coupling, not reducing it.
Self-compiled 0 errors, 0 warnings (_claude_stage, ~94s).
Expert/AIBase/Features.mqh (2017 lines, 38 methods) split by exclusivity grep
(whole-repo, not just Expert/): 30 methods -> Expert/Features/FeatureBuilder.mqh
(CFeatureBuilder + CFeaturesView/CAIBaseFeaturesView), 8 stay behind as a much
smaller raw partial.
CFeatureBuilder is STATEFUL, same shape as Excursion/OnlineLearning: owns the
10 feature-only indicator handles (m_Volumes/m_MA/m_RSI/m_MACDFeature/
m_Ichimoku/5 AD* CiCustom indicators - grep-verified touched nowhere else in
the repo, only their bare declarations) plus the depth-probe/handle-repair/
spread-series/detectability-latch scalars (exclusive, Lifecycle.mqh ctor-init
only elsewhere). m_Open/m_Close/m_High/m_Low/m_Time/m_ATR/m_ADZigZag stay
signal-owned - Labels.mqh/AutoTune.mqh/Training.mqh read them directly - and
are reached read-only through the view (FeatureOpenAt/FeatureHighAt/
FeatureLowAt/ChartBarClose/ChartBarTime/OnlineAtrMain, all reused where a
forward already existed).
Deliberately did NOT move InitOpen/InitClose/InitHigh/InitLow/InitTime/
InitADZigZag/ResizeBuffers/RefreshData: they manage the 7 shared indicators'
Create/BufferResize/Refresh lifecycle, which would need a pure-relay wrapper
per operation per indicator for zero coupling benefit - same judgment as
Topology's boot sequence. They stay in Expert/AIBase/Features.mqh and reach
CFeatureBuilder's 10 owned indicators through 20 new Feature*BufferResize()/
Feature*Refresh() forwards (signal calling into its own owned collaborator
directly, no view needed in that direction).
Whole-repo grep (not just Expert/) caught a real external miss the campaign's
own doctrine warns about: Signals/SignalMETA.mqh read m_spreadSeries/
m_spreadSeriesBars directly as an inherited protected field (a subclass, not
an AIBase/*.mqh partial) - fixed with two new FeatureSpreadSeriesBars()/
FeatureSpreadSeriesAt() forwards.
Verified: if(/for(/while( counts identical between the original file and the
new split (269/20/1); return-count delta (+12) fully accounted for by the 12
new trivial one-line forwards added (10 indicator BufferResize + 2 spread-
series getters); quoted-string-literal diff empty except two doc-comment
paraphrases. Self-compiled 0 errors, 0 warnings.
Expert/AIBase/Topology.mqh (1191 lines) held two genuinely different jobs: the
fingerprint/derived-shape/BuildFreshTopology math, and InitNeuralNetwork/
InitFeatureIndicators - the network boot sequence (config-lock, tester-cache
seeding, load/save the .cfg, net-load backend fallback, chart/persistence/
online-learning orchestration).
Extracted the first job to Expert/Topology/ as CTopology + CTopologyView/
CAIBaseTopologyView (20 methods: BuildModelFingerprint, the Estimated*/Compute*
budget math, the Conv*/Lstm* shape helpers, Add*Stage, BuildFreshTopology).
STATELESS, like ModelPersistence - grep-verified zero exclusive fields, every
member these methods touch is shared elsewhere in the signal. Reused ~15
existing Data*/Chart*/Persist*/Exc* getters per the established convention;
added ~20 new getter overloads next to their existing setters (UseVolumes(),
MinDirectionalRecall(), etc. - same pattern as SignalClusterWindow) and ~16
new Topology*() wrappers for fields with no prior accessor. The Net-pointer
swap in BuildFreshTopology is one consolidated view call
(TopologyReplaceNetFromTopology), same doctrine as Persistence's
RunCpuInferenceSelfCheck - irreducible pointer work, not signal state.
Deliberately did NOT extract InitNeuralNetwork/InitFeatureIndicators: they
orchestrate nearly every other collaborator (chart, persistence, online-
learning, cross-asset, config-lock) rather than deriving a shape, so moving
them would just relocate a hub, not reduce coupling - same judgment call as
Inference.mqh (assessed, not extracted). They stay in the AIBase/Topology.mqh
partial, byte-identical to before (diffed against git HEAD to confirm), and
now call the extracted math through the same public forwards every other
caller already used.
Verified: string- and numeric-literal diff of the old file's 20 method bodies
against the new CTopology methods (0 differences), InitNeuralNetwork/
InitFeatureIndicators byte-identical, self-compiled 0 errors/0 warnings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expert\AIBase\OnlineLearning.mqh (595 lines) -> Expert\OnlineLearning\:
IOnlineLearningView.mqh (abstract, ~50 accessors) + AIBaseOnlineLearningView.mqh/
AIBaseOnlineLearningViewImpl.mqh (the adapter) + OnlineLearning.mqh (COnlineLearning).
STATEFUL, unlike CModelPersistence: grep-verified the shadow net, the OOS
continual-learning simulation state and the pattern-database backfill state are
genuinely exclusive to this file's own methods - Training.mqh/Topology.mqh/
Lifecycle.mqh/the signal's own header only ever CHECKED or RESET this state at
era/lifecycle boundaries, never owned it, so it moved onto the collaborator as
real members (same doctrine as Excursion). Those external touch points became
consolidated view/forward calls instead of raw field pokes - AbortSimIfActive()
replaces THREE separate copies of the same delete/null/false triple (Training.mqh's
stop path, FlushTrainRun, ResetWeights), matching the geometry-scan duplicate-reset
precedent in project memory. ResetForFreshTopology() replaces Topology.mqh's five-
field reset block, DeployNet() replaces the shadow-preferred net selection duplicated
in Inference.mqh and ChartScoreBarForRescan, and BlendTowardNet() replaces the era-end
blend Training.mqh used to poke m_shadowNet for directly.
Reused the signal's existing Data*()/Chart*()/Persist*() getters wherever one already
answered the question (labels/outcome/history/horizon/priors/servable-bars/etc.);
added ~30 new Online*() wrappers only for what nothing else exposed yet. The three
PersistOnline*() get/set pairs (WST3 .stats fields) now forward through the owning
member instead of touching the field directly - CModelPersistence is unaffected.
Every method body is a pure relocation of the original's statements in original
order; verified against `git show HEAD~1:Expert/AIBase/Excursion.mqh`-style diff
against the pre-extraction file kept in the working tree until this commit.
Compiled 0 errors, 0 warnings (stage mirror + MetaEditor64 /compile, ~91s).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expert/AIBase/Excursion.mqh was 13 method bodies of CExpertSignalAIBase,
#include'd after its declaration - same "not a module" problem already
fixed for ChartUI (S2) and Persistence (S3). Extracted to
Expert/Excursion/CExcursionHead behind CExcursionHeadView/
CAIBaseExcursionHeadView, same view+adapter shape.
STATEFUL, unlike Persistence (0 exclusive fields): grep-verified 26
fields (m_excNet and every accumulator/trailing-ring field) touched
nowhere else in Expert\ except Lifecycle.mqh's old ctor-init-list
defaults and destructor deletes (now moved onto CExcursionHead's own
ctor/dtor). m_geo (SGeometryScan) and m_ladder (CFirstPassageLadder)
stay on the signal - both are genuinely shared with Labels.mqh/
Training.mqh at era boundaries - and are reached only through 15 new
Exc*() view wrappers, including one consolidated
ExcGeometryScanAccumulate() call (same doctrine as Persistence's
RunCpuInferenceSelfCheck) rather than field-by-field pokes.
All 13 original public methods stay at their same declaration point as
one-line forwards to m_excursionHead. Training.mqh's 2 raw m_excUs
reads now go through the new ExcursionMicroseconds() forward. Every
method body is a pure relocation, verified statement-by-statement
against the original (git show HEAD~1:Expert/AIBase/Excursion.mqh).
Compile-verified: 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expert/AIBase/Persistence.mqh (598 lines, 8 methods) -> Expert/Persistence/:
IPersistenceView.mqh (abstract, 68 read+write accessors) + AIBasePersistenceView.mqh/
AIBasePersistenceViewImpl.mqh (the adapter) + ModelPersistence.mqh (CModelPersistence,
the real collaborator - signal owns m_modelPersistence and binds it to m_persistenceView,
same shape as ChartUI's S2).
Grep-verified before starting: every field these 8 methods touch is ALSO touched
elsewhere in the class (Training/Lifecycle/OnlineLearning/Topology/FeatureScreen/
Labels.mqh) or already exposed via ChartView. Zero exclusive state, unlike ChartUI's
arrow-restore/rescan queues - CModelPersistence is stateless, holding only the
borrowed view pointer, operating entirely through 68 Persist*/PersistSet*() accessors
on the signal.
ValidateCpuInference's Net-pointer/throwaway-clone core is ONE consolidated view call
(PersistRunCpuInferenceSelfCheck) rather than field-by-field - irreducible pointer/
object work, not signal state, same doctrine as ChartScoreBarForRescan.
LoadNetWithRetry keeps its original CheckPointer(Net)-free Net.Load() call unchanged
(no guard added - would change failure behaviour on what must be a pure relocation).
This code writes the actual on-disk .cfg/.stats binary layouts every deployed model
depends on (explicit "DO NOT REORDER" comment in the original), so beyond compiling
clean (0 errors, 0 warnings) this was verified with a positional field-order diff:
every FileWrite*/FileRead* call's target field, extracted and normalized from both
the original and the new file, matches 1:1 in the same order (43/43 on the write
side covering SaveModelStats+SaveTopologyConfiguration, 17/17 on LoadModelStats'
read side; LoadAndCompareTopologyConfiguration's local-variable read block was
copied verbatim, untouched, so nothing to diff there). The magic-version
conditionals (WST2-6, haveDerivedStages/haveBarrierGeometry/etc.) moved unchanged.
All 8 methods keep their exact original signatures as one-line forwards - zero
external call sites changed.
Neither ever touched a CExpertSignalAIBase member - both take everything as
parameters. Moved to System\SharedFileCopy.mqh (free functions, same doctrine as
TradeChecks.mqh's TC*), matching what they actually are instead of carrying them as
methods on a class they don't depend on. Topology.mqh's call sites are unchanged -
unqualified calls from within a class method resolve to the free function exactly
the same way. Compiled clean.
Correction to project_oop_module_pattern's Persistence(696) note: LoadNetWithRetry
is NOT a third pure utility alongside these two - it touches Net/dError/dUndefine/
dForecast/dtStudied/m_activeFileName/m_activeFileCommon/m_eraCount/m_trainingComplete.
The rest of Persistence.mqh (EnforceTopologyContract, Save/LoadModelStats,
ValidateCpuInference, Save/LoadAndCompareTopologyConfiguration, ReadAltDataPinFromCfg)
is heavily coupled to signal state - a real view+adapter extraction on the scale of
ChartUI's, not attempted here.
Expert.Init() and the main signal's `new CExpertSignalCustom` each had their own
5-retry loop, hand-rolled, while CreateSignalWithRetry<T>() and RetryInitStep()
already exist and are used for every OTHER signal/init step in this same function.
Expert.Init() -> a StepExpertInit() shim through RetryInitStep (same pattern as
StepInitTrailing/StepInitIndicators/etc. - and picks up RetryInitStep's fatal-reason
fast-fail, which the hand-rolled loop didn't have: a permanent AcquireConfigLock
refusal now fails immediately instead of blindly retrying 5 times).
`new CExpertSignalCustom` -> CreateSignalWithRetry<CExpertSignalCustom>(maxRetryOnError,
true), the exact template already used for PAI/CONV/LSTM/HYBRID/META/MA/RSI/MACD/
Ichimoku/NewsFilter/SessionFilter/RiskGuard. The dbm.OpenDatabase/BeginTransaction/
CommitTransaction/CloseDatabase loop stays hand-rolled - it's a multi-step
transactional retry with different per-step cleanup, not a single-op retry, so it
does not fit either existing helper's shape. Compiled clean (0 errors, 0 warnings).
A bad pathspec in a multi-file `git add` made the whole invocation a no-op except for
the 4 deletions already staged from an earlier `git rm` - Network.mqh's new inline
declarations, AI_NETWORK.md's table update, and the 4 new AI/Impl/*.mqh bodies never
landed in 46523dc. Same content already compiled clean; this just gets it into the
index. Tree is correct as of this commit; 46523dc alone is not.
CSignalMA/RSI/MACD/Ichimoku each re-overrode SweepPrepare() with an identical body -
call the base, resize/refresh one indicator buffer, return - differing only by the
buffer's field name. Base class now does the shared price-series prep once and calls
a new SweepPrepareIndicator() hook; each signal overrides only the hook. Compiled clean.
NeuronPrimitives/NeuronCPU/NeuronOCLConvPool/NeuronBatchNorm.mqh were the only neuron
files still holding declaration+body together at AI/ root; every other neuron class
already split declaration-in-Network.mqh / body-in-AI/Impl/. Same split applied here,
compiled clean (0 errors, 0 warnings).
Expert/AIBase/ChartUI.mqh was 869 lines of method bodies of
CExpertSignalAIBase, #included after the class declaration - free to touch
any of its ~500 members. First of the eleven AIBase/*.mqh partials to come
out (fewest inbound edges - see the SOLID campaign session order), using
the same view+adapter shape already proven for CTrainingDataView.
CChartView (Expert/Chart/IChartView.mqh) is the abstract read/behaviour
surface a chart-rendering collaborator needs - identity, bar/model access,
the prediction cache, and the training/vote/meta scalars the panel and HUD
line summarise. CAIBaseChartView is the adapter the signal owns and binds
to itself (MQL5 gives a class exactly one base, so CExpertSignalAIBase
cannot implement the view directly). CChartUI is the real collaborator: it
owns the arrow-restore queue, the rescan queue/tally, the last-arrows-saved
count and the purge-mismatch latch as its own fields (verified via grep to
be touched nowhere else in Expert/), and reaches everything else - including
StartChartSignalRescan, moved in from its old inline home in the header
since it drives the exact same rescan state machine AdvanceChartSignalRescan
drains - through the view.
m_arrowSignalCache and m_signalClusterWindow stay on the signal: Training.mqh
writes the cache directly every era and the training-data view already reads
it, so moving it would mean rewriting Training.mqh's write sites too - out of
scope here. CChartUI reaches it through four bounds-checked accessors instead
of a raw member poke. All 10 public methods keep their exact signatures and
become one-line forwards on the signal, so no other file's call sites change
except Training.mqh's one era-end status refresh, which now reads
RefreshStatusLabel() rather than reaching into CChartUI's now-private
last-displayed-neuron cache directly.
Verified structurally, not compiled (never compile - the operator does, in
MetaEditor): brace balance checked on every touched/new file against HEAD,
and the view/adapter/impl method lists cross-diffed to confirm all 59
accessors match 1:1 across the interface, the adapter declaration and the
adapter body.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three backends left, as the operator specified: OpenCL, the CPU DLL,
and pure MQL5. CDirectMLMy was a two-tier wrapper (GPU via
WarriorDML.dll, CPU via WarriorCPU.dll) whose name only ever named the
tier being removed here; the CPU DLL tier - the one actually used on
the training machine (no OpenCL, no DirectML) - is untouched.
AI/NeuronDirectML.mqh -> AI/ComputeDll.mqh: dropped the DML_* #import
block and COMPUTE_TIER_GPU (checked first that nothing persists the
enum value and only one external site reads .Tier() - safe), collapsed
every tier==CPU?CPU_x():DML_x() ternary to a straight CPU_x() call.
Renamed CDirectMLMy->CComputeDll, InitDirectML()->InitComputeDll(),
member directml/DirectML->computeDll/ComputeDll across every AI/ file
that touched a neuron/net backend plus Topology.mqh/OnlineLearning.mqh.
NetBuild.mqh's InitComputeDll also lost the dead D3D12 error-code
switch and the now-impossible GPU-tier log branch.
Verified via per-file brace-balance diff against HEAD and a whole-repo
grep for every removed symbol (CDirectMLMy/InitDirectML/
COMPUTE_TIER_GPU/DML_*) - the only surviving hit is an intentional
historical-note comment in the new file's header.
DirectML\WarriorDML.cpp/.h and its build scripts are now orphaned C++
source, left in place pending an operator decision. Architecture docs
(AI_NETWORK.md, Warrior_EA_System_Overview.md, etc.) still describe the
4-backend/GPU-tier shape and are not updated in this pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Last surviving compile-time feature switch in the codebase - the same pattern
already killed for the MARKET build and DirectML tier (02766b5): one build,
configured at runtime like every other module (inputs + getters/setters, set
in ConfigureAISignal during OnInit), not a second code path that only existed
if someone remembered to define a macro before compiling.
Replaced with `input bool ExportFeaturesOnly = false` (Variables/Inputs.mqh)
and a plain m_exportFeaturesOnly member + setter, matching AutoTuneIndicators'
exact shape. Four call sites converted from #ifdef to a runtime read of the
same variable:
- Warrior_EA.mq5 OnTick() - reads the input directly (this check has to
stand before any per-signal object exists)
- Topology.mqh's config-lock skip and ExportFeatureMatrix() call - read
m_exportFeaturesOnly, now set by ConfigureAISignal before InitIndicators()
runs (same init-order guarantee AutoTuneIndicators already relies on)
- ExportFeatureMatrix()/ExportRawRates() declarations - always compiled now,
called conditionally instead of not existing as symbols
No change to what the flag does when off (the state of every build that
exists today, since the macro was never defined anywhere in-repo) or when on;
only how it's set. Verified: WARRIOR_EXPORT_FEATURES fully gone from every
#ifdef/#endif in the tree; brace and ifdef/endif counts balance in every
touched file; ConfigureAISignal runs before StepInitIndicators in OnInit's
linear init chain, so the flag reaches InitNeuralNetwork() in time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Session C of the feature-selection/labeling refactor track. AutoTune.mqh was
two responsibilities in one 1,584-line file: SEARCH (TuneIndicatorsByFilter,
coordinate-descent over indicator settings) and MEASUREMENT (the "does this
feature vector predict this label at all" evidence screen and its three
sub-reports). Split along that seam into a new Expert/AIBase/FeatureScreen.mqh.
Moved, verbatim (diffed byte-for-byte against the pre-split content - zero
lines differ beyond the file-boundary comment headers): ReportFeatureLabel-
Information, ReportExcursionInformation, ReportFeatureLagProfile, Report-
BarrierGeometryScan, ApplyAdoptedGeometry.
Stayed in AutoTune.mqh: the MI engine (FeatureColumnMI/BuildMiSample/
ScoreMiSample) both files call - a shared dependency used by two consumers is
not itself a reason to split further; TuneIndicatorsByFilter; the export
utilities; and TuneIndicatorsAndTrain, the entry point that decides which of
the two branches a given model runs - it is the coordinator, not a member of
either side.
Still body-only method definitions of CExpertSignalAIBase, same as every
other Expert\AIBase\*.mqh file - MQL5 has no partial classes, so this is a
file-organisation move (legibility, SRP-per-file), not a coupling reduction.
The include site says order between AIBase\*.mqh files is irrelevant, so the
new include was added next to AutoTune.mqh's; the file-scope g_ensembleChart*
globals both files reference stay declared in AutoTune.mqh's header, ahead of
the new include either way.
Verified: brace counts split exactly 105 -> 57+48; every one of the 14
function definitions HEAD had in AutoTune.mqh accounted for in exactly one of
the two files, no duplicates; the WARRIOR_EXPORT_FEATURES ifdef/endif pair
(unrelated, lines 35/147) untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Session B of the feature-selection/labeling refactor track. Extracts the two
pieces of triple-barrier arithmetic that were genuinely duplicated or
scattered, taking price/ATR/geometry as plain arguments - no chart, no
indicator handle - so it is testable with synthetic numbers.
CTripleBarrier::ComputeLevels() replaces the fill/barrier level arithmetic
that TripleBarrierLabel() and SimulateTradeOutcome() each spelled out by
hand; their own comments already called it "IDENTICAL... deliberately and by
copy." One caller resolves both sides at once (the both-won tie-break needs
both); the other selects the side its isLong argument names. Same for
ApplyMinStopWidening(), the broker-minimum-stop floor both walks applied.
Fuzzed 200k random (entry, spread, risk, reward, minStop, isLong) tuples
against both original hand-written forms: 0 mismatches.
CLabelOverlap replaces m_labelLifespanSum/m_labelLifespanCount - two members
reset from three separate call sites (constructor, label-cache rebuild), the
exact "N loose members cleared in more than one place" shape a candidate-
geometry incident (7452bd1) turned into a live bug. One object, one Reset(),
default-constructed like every other object member. MeanLabelLifespan() and
EffectiveSampleSize() on the signal become thin forwarders with an unchanged
signature - every one of their ~15 existing callers, direct and through the
CAIBaseTrainingData adapter, is unaffected.
SnapHorizonToLadder() forwards to CTripleBarrier::SnapToLadder(), the ladder
array's one remaining copy; EffectiveHorizonMax() (the close-all cache) stays
on the signal since that state has no clean argument form.
NOT extracted: TripleBarrierLabel()'s ~200-line walk itself. It resolves both
sides simultaneously, tracks the first-passage ladder, and feeds the label
every live order is sized from; a rewrite of it cannot be checked without a
compiler, so only the two pieces provably identical to their originals moved.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operator, 2026-08-23: retraining is a cost they absorb routinely and is never
to gate work. Two comments claimed the mask stays report-only because pruning
re-keys BuildModelFingerprint() and invalidates every .nnw. That is a real
consequence and worth stating, but it was never the reason.
The actual reason is that no report has been read yet, and selecting features
on a screen nobody has looked at is how a measurement becomes a mistake - a
hold that lifts after one compile and one attach, not one that needs a policy
decision. Comment only; no behaviour changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CFeatureSelector keeps the per-column MI vector ScoreMiSample has always
computed and thrown away. It is fed from inside the 200 draws
ReportFeatureLabelInformation already performs, so the screen costs an array
copy per draw and not one extra mutual-information computation.
The keep-mask is cut on the single-step maxT (Westfall-Young) statistic - a
column must beat the MAXIMUM of a null draw over all columns, which is strong
family-wise control needing no Bonferroni factor, and is the same null of the
maximum the headline verdict already trusts. The uncorrected per-comparison
p is reported alongside it; the gap between the two counts IS the multiplicity
correction, shown rather than described. Checked offline at 40 columns: 0/40
noise runs keep anything, where the uncorrected rule hands back ~2 columns per
run, and a planted column is recovered 40/40.
REPORT-ONLY. Nothing reads the mask. Pruning changes m_neuronsCount, which is
in BuildModelFingerprint(), which invalidates every .nnw - that is a retrain
across every chart and an operator's call to make after reading the report.
Also fixes the block permutation, found while moving it. When blockRows did
not divide n the short last block, drawn to a non-final slot, read past the
end of the array; the read was clamped to labels[n-1], duplicating one label
and truncating whichever block landed last. 18 of the 24 possible block orders
on n=10/blockRows=3 altered the class counts. A duplicated label concentrates
the class distribution, lowering H(Y) and so the null MI those draws can reach,
so p-values leaned toward significance - the permissive direction, and
m_dirEvidence is a deploy gate. Each block now contributes exactly its own
length. The invariance the old comment asserted ("a permutation preserves the
class counts - that invariance is itself a check on the shuffle") was never
actually compared anywhere; BlockPermute now checks it and returns false, and
all six shuffled call sites already guard on a negative return.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Market rule IV forbids DLL calls, and the DLL compute tier plus the
WebRequest alt-data fetch are what make this bot work. If it is ever sold
it goes through its own channel with the DLLs intact, so a no-DLL build has
nothing left to be for (operator, 2026-08-23).
Removed:
- Warrior_EA.mq5 the //#define toggle and the #resource block behind it
- Variables/Inputs.mqh two #ifdef pairs whose market arms forced every
Use_* NN input and Meta_ExportDataset to false
- AI/NeuronDirectML.mqh the 62-line market stand-in CDirectMLMy whose every
method returned false so the chain fell through to
plain MQL5
- IndicatorResources.mqh WARRIOR_CI(name), which had already collapsed to
(name) - a switch with one position is not a switch.
Its five call sites in Features.mqh name the
indicators directly now.
Behaviour is the private build's, unchanged: bare indicator names loaded
from <MQL5>\Indicators\, all four NN votes defaulting on, dataset export on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate-ownership commit put CMetaGate *m_metaGate on CExpertSignalCustom
while SMetaGateTelemetry m_metaGate already sat on CExpertSignalAIBase,
which derives from it - so the derived name hid the base one.
The telemetry is m_metaTelemetry now, and the declaration says why:
m_metaGate is the gate the root signal OWNS; this is the RECORD of what a
gate did. Different things, and they read differently at a glance.
Checked the rest of the chain for the same shape - no other member name is
declared in more than one of CExpertSignalCustom / CExpertSignalAIBase /
CSignalMETA.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
g_warriorMetaGate was a file-scope mutable pointer, and it did not need to
be. The root CExpertSignalCustom - the one CExpert actually calls
CheckOpenLong/Short on - now holds the gate as a member, and children reach
it through a parent back-pointer AddFilter sets on adoption.
That was the last piece of the meta veto that behaved like ambient state:
- CheckOpenPosition reads MetaGate() instead of a global.
- EnsembleEraVerdict's replay reads the same MetaGate(). It sits deep in
the training code inside an AI filter and had no route up the tree; a
global WAS that route. m_parentSignal is now, and a back-pointer is safe
for the same reason the gate adapter's owner pointer is - m_filters and
m_gates free their children, so a parent always outlives them.
- The stale-pointer hazard is gone by construction. The global had to be
hand-cleared at every re-init because an input change re-enters OnInit in
the same program instance and frees the old head; the root signal is
new'd fresh each time, so nothing survives one. That reset line is
deleted, not moved.
Note what did NOT need doing: the tree already owned the meta head itself.
AddFilter routes non-voters into m_gates, so it has been a gate child of
the root since the S3 wiring - it was only the VETO that lived outside.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same value in this EA (CExpert::Init is given Period()), but the filter
exists so the rows resolve onto the grid MetaPrepareEra resolves them onto,
and that grid is m_period.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CMetaCorpus already existed (S1, report-only) with a proper SMetaCandidate
row. CSignalMETA kept a SECOND corpus of the same journaled candidates
right next to it: six parallel arrays, a second walk of the same 52 pattern
tables, a second row-filling loop, and THREE six-line ArrayResize blocks
keeping the six arrays the same length by hand. Same rows, same tables,
same meaning - and neither copy was reviewable without the other.
Now one class with one row schema and three sources:
LoadFromConfigDb() was Load(). This chart's fingerprinted DB via dbm;
what the S1 report reads.
LoadLargestOnDisk() moved in from CSignalMETA, header and all - the
symbol+period filter and the read-only open are the
point of it, and so is NOT going through the config
fingerprint (the trap that burned four corpus-build
runs). CountDbPatternRows moved with it as the one
"how big is this corpus" table walk.
Add() the on-chart ladder sweep, which needs the EA's live
filters and so stays in CSignalMETA - but stores here.
Storage is encapsulated: Count() is the truth, Reserve()/ShrinkToFit() are
hints, and every read is bounds-checked with an out-of-range answer that
cannot pass for a real candidate. That retires the sweep's hand-rolled
capacity block, which had already failed both ways - silently truncating
the corpus at a bar boundary on SP500, and running off the end mid-bar on
USDJPY/XAUUSD/XTIUSD because it only reserved room for two appends. The
lesson stays in the comment; the arithmetic does not.
SignalMETA.mqh 713 -> 573 lines. Also moved MetaPrepareEra's header
comment back above MetaPrepareEra - it had drifted two functions away.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five parallel arrays, a count and a two-array intrusive chain sat on
CExpertSignalAIBase - inherited by every direction model, filled and read
by exactly one subclass. CMetaCandidateStore takes all eight.
What that fixes beyond the clutter:
- THE CHAIN WAS LINKED BY HAND. MetaPrepareEra wrote next[id] = head[bar]
then head[bar] = id itself, after six ArrayResize calls it also wrote out
itself. Add() does the linking, Reset() does the sizing, and a bar off
the grid now cannot be stored at all rather than stored unreachable.
- THE BOUNDS TEST HAD FOUR SITES AND THREE IMPLEMENTATIONS.
MetaCandidateWon indexed side[] with no test at all and answered
"short" for any id out of range - the same shape as the ladder's
negative-index read (2c351a0). Side() is three-state here, IsLong() and
SideIndex() are the safe ways to ask, and the per-side era tally in
RunOosPass is now guarded exactly like the per-family one beside it,
which always was.
Like the ladder and the OOS tally, none of it needs a chart, a net or a
broker: hand it bars and rows and every answer is a function of those.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Since S3 (f64e0f8) the meta head casts no vote - it scores an entry the
consensus already cleared and vetoes the ones under the cost-adjusted
break-even. The code still said otherwise. LiveMetaGate() was a virtual on
CExpertSignalCustom, so MA, RSI, MACD, Ichimoku, the four direction nets,
the session and news filters and the risk guard each carried a meta-gate
method they had no business having; one class implemented it and a dozen
inherited it. The trading pipeline held the gate as a CExpertSignalCustom*
- a signal pointer, with a signal's two hundred other methods reachable
from the entry path.
Expert\Trading\MetaGate.mqh now owns the abstraction:
CMetaGate one pure virtual, Evaluate(), and the two static
readings of a verdict (Blocks / Scored)
META_GATE_* names for the four codes the three call sites used
to spell as bare 0/1/2 and test three different ways
(`< 0` here, `== 2` there, `else` for the rest).
Codes unchanged; only ONE of them blocks, and that
asymmetry is now stated where it lives.
SMetaGateTelemetry the five m_metaGate* members that were on the AI
signal base - inherited by every direction model,
meaningful for none of them. One lifetime, one
writer, one object; the arm latch and the two
counters are a set that clears together.
g_warriorMetaGate is a CMetaGate*. MQL5's single inheritance means the head
cannot also BE one (it already extends the AI base for the net, the era
loop, the feature windows, the label caches and persistence), so it owns a
bound CMetaGateAdapter and hands that out - the same shape CTrainingDataView
uses for the same reason. LiveMetaGate() is gone from the signal base.
Behaviour unchanged: same codes, same thresholds, same fail-open doctrine,
same live-only telemetry rule. The adapter fails open when unbound, on that
same doctrine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The run-start block calls TrainWindowStart(StartTrainBar), and StartTrainBar
is Train()'s parameter. Moving the block into its own method left the read
behind. Now passed explicitly.
THIRD TIME THIS FAMILY HAS BILLED THIS SESSION, and the third distinct
sub-shape:
1d7ebbd a DELETED loop's variable still read by its body
d7469c6 a RENAMED field still read by its call site
here a MOVED block still reading its old enclosing scope
Same root cause each time: I verify the side I edited. What I had been
checking - statement multisets, brace balance, field-name resolution - all
passed, because none of them models SCOPE. The move was faithful; the
scope was not.
So scope is now checked too. For every CExpertSignalAIBase::Method, collect
the identifiers its body reads and subtract what can actually resolve:
names declared in the body (any type, and every name in a multi-declarator),
the method's own parameters, class members, file-scope globals and #defines.
Parameter names from OTHER declarations must NOT count as resolvable - that
is the bug in the first version of this check, which let StartTrainBar
through because Train() declares it in the header.
Validated against the broken commit before being trusted: it reports
StartTrainBar there and not here. The only residual output is MQL5 enum
members and EA inputs declared outside the scanned headers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Train() was 1,273 lines. It is now 79, of which about 35 are statements, and
they read as what the function is: preempt, begin run, begin era, four
passes, advance, complete, report, finalize.
Seven methods carry what left it:
TrainCallPreempted 107 six ways this call is not a training call at all
BeginTrainRun 130 once per run - history sync, window, one-shot walks
BeginEra 232 once per era, or resume a chunk that yielded
ReportPass1Outcome 105 what pass 1 found, said out loud
AdvanceEra 68 count the era, decide whether the RUN ends
CompleteEra 590 calibrate, gate, rank, checkpoint, ladders, persist
ReportBarrierHold 62 why this member is idle at the era barrier
ClaimCallForWalk 15 the preamble the three exclusive walks shared
TWO DRY FIXES fell out rather than being looked for. The three exclusive
walks each had to tell TWO watchdogs the same thing - the stall reporter
which branch is running, the era-barrier watchdog that this member is BUSY
rather than stuck - written out three times, so a fourth walk was three
chances to be added with only one of them. And the barrier-hold reporting
was 44 lines inline in a branch whose only other statement was resetting a
tick.
CompleteEra is lifted WHOLE and stays that way for now. Its parts share
thirty-odd locals - the recalls, the gate verdict, the better/worse flags -
and threading those through three signatures would recreate exactly the
eight-locals-across-four-passes problem STrainEra was built to end.
Splitting it needs an era-outcome object first, not more parameters.
VERIFIED AS A PURE MOVE: statement multisets, old file vs new, differ only
by the 14 `return;` that became 17 `return true;` plus 3 new returns at the
call sites, the 3 collapsed walk preambles, the 8 new signatures and their
braces. Nothing else moved, and every function closes at depth 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SDeployVerdict's bothSidesLive became twoSided when the member and ensemble
gates were unified, and the member call site kept reading gate.bothSidesLive.
SECOND TIME THIS EXACT SHAPE HAS BILLED THIS SESSION - the first was `s == 0`
surviving the deletion of the loop that declared `s` (1d7ebbd). Renaming a
declaration does not find its readers, and the compiler only finds them when
no other binding happens to fit.
So this is now checked rather than reviewed: every `instance.field` read
against the six value objects is resolved against what the struct actually
declares. Six structs, zero unresolved reads.
Also renames the ensemble local `vote` to `voteGate`. SVoteAccumulator is
already called `vote` in the base class, and two different `vote`s one
inheritance step apart is a reader trap even where the scopes do not clash.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SDeployVerdict::EvaluateRates() is now the deploy arithmetic - coverage
floor, chance + EDGE_MIN_SIGMAS x SE, tradeability, and the coverage-
discounted ranking score - and both gates call it.
The duplicate was self-documenting. The ensemble copy carried three comments
asking a reader to keep it in step with the member copy by hand: "same
intent as the member gate's coverage floor + bothSidesLive", "the two gates
have to apply the identical correction or the ensemble becomes the easier
one to clear", "same lexicographic ordering as isBetterEra". They had
already fallen out of step once - 2c443ba found the ensemble certifying a
vote the EA never casts, in the wrong currency and against the wrong
denominator.
THE TWO REAL DIFFERENCES ARE NOW ARGUMENTS, not branches:
chancePct - the ensemble filters its zero-skill reference by the
direction policy, because with shorts blocked "always short"
is not a book anyone could run.
twoSided - a member reads per-side RECALL against a floor; the vote
reads whether it actually fired both ways.
Everything else was identical and is now literally identical.
effN stays an argument so the label-overlap deflation lives where it is
measured - and so the remaining inconsistency stays visible rather than
buried: the two FAMILY-WISE selection gates still take their SE from RAW n.
Recorded in the header, deliberately not changed; tightening them is a
policy call, not a refactor.
The decision now reads no chart, holds no net, prints nothing and opens no
file, so it can be exercised against a made-up tally.
BEHAVIOUR UNCHANGED: every expression keeps its formula, its guard and its
-1 sentinel; the ensemble's chance-reference and two-sidedness rules are
passed through untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SOosTally holds this era's OOS confusion counts and the rates they imply.
The signal keeps one member where it kept twenty-one, and the era-reset
block loses twenty of its twenty-one clearing lines.
THE SHAPE THIS ENDS is the one that produced 7452bd1: a group of tallies
read together but cleared one-per-line, so a second reset path could clear
a subset and leave stale numerators over restarted denominators. Reset()
is now the only way to clear them and it clears all of them.
The pair had already started to drift. m_oosBuyFired/m_oosBuyFiredHits sat
at line 1085 and their Sell twins at line 1140 - 55 lines and an unrelated
member apart, with the Buy comment still claiming to describe both.
DERIVED RATES MOVE WITH THE DATA. `(bars > 0) ? (int)MathRound(100.0 * x /
bars) : -1` was written out twelve times, and the "-1 means not measurable,
never 0" convention re-spelled at each - a convention the deploy gate
depends on, since every caller tests `< 0` to mean "this does not block".
One rounding rule and one sentinel now.
GROUPED BY LIFETIME, NOT BY NAME. m_oosSamples looks like it belongs here
and does not: it is RUN-level, reset only with the weights, and the status
panel prints it beside dOosError which is also a run-level EMA. That pairing
is correct and stays. But the confidence-calibration block divided per-era
numerators by it, naming the results `empiricalAccuracy` and
`avgClaimedConfidence` when neither is that - the run-level denominator
cancels in their ratio, so eraScale was right and the two named
intermediates were not. Now written as the ratio it actually is, with the
cancellation stated, so nobody logs or gates on a half that decays with era
count.
BEHAVIOUR UNCHANGED: every moved expression preserves its formula, its
denominator and its sentinel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CFirstPassageLadder owns the three caches (per-rung up/down first-touch ages
plus the terminal travel) and every question asked of them. The signal keeps
one member where it kept three arrays and a lifespan scalar.
WHAT THIS ENDS. The log-space rung snap existed THREE times: once as
LadderRungFor, twice written out inline inside LadderWinShare - and
LadderRungFor's own header said "Same rule LadderWinShare snaps with, so a
rung chosen here and a rung chosen there are the same rung". A comment asking
a reader to keep three copies equal by hand is the arrangement CMetaFamilies
was built to end. It is now one static RungFor(), so the two rungs agree by
construction.
The bounds test was spelled out at four sites and the "0 means never, tie
goes to the stop" comparison at three. Now Has() and FirstTouch(), once.
The four-site bounds test was also subtly weak: it computed
`idx * COUNT` and tested only the upper end, so a negative index slipped
through into a negative array read. Row() rejects it.
Spread and horizon are ARGUMENTS, not state. The ladder is pure travel in ATR
multiples; what a spread costs and how long the walk ran are facts the caller
supplies. Every answer is now a function of its inputs alone - which is the
point, because this is the barrier arithmetic that failed its own acceptance
test in b5e22a1 and it has never been runnable without a chart, a net and a
broker attached.
BEHAVIOUR UNCHANGED. Each moved body was checked statement-multiset against
its predecessor with the rename map reversed; the only differences are the
substitutions named above.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The flat walk replaced `for(f) for(p) for(s)` with one TableAt() index, but
LoadMetaCorpus's row body still read `s == 0` to stamp m_corpusSide. The
compiler caught it - `undeclared identifier 's'` - which is the good case.
Worth naming the near-miss anyway: had an outer `s` been in scope, this
would have compiled and stamped every candidate with one side. The side is
now taken from TableAt's own isBuy, so the name that opens the table is the
name that labels its rows - one source, not two.
Also restores stdlib indentation at three sites where the removed nesting
left braces at the old depth.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by grouping, not by looking for it.
The candidate-geometry scan kept ten accumulators as ten separate
members. Era start cleared all ten in a ten-line block. The
shutdown-abort path inside the exit-policy simulation cleared
m_geoTrades and nothing else, so nine partial sums - diffSum,
diffSumSq, incSum, candSum, candSl, candTp, incOpen, candOpen,
startTick - survived the abort with the aborted era's values.
The next era then accumulated onto those sums while counting from
zero, so the paired mean is sum/trades with a numerator carrying an
extra era's worth of difference. The paired sigma is worse: diffSumSq
inherits the same contamination, so the scan reports a tighter or wider
spread than it measured depending on what the abort happened to be
holding.
That is the SAME arithmetic that failed its own acceptance test in
b5e22a1, where the reported gain turned out to be monotone in timeout
share. This is not that bug - it needs a shutdown mid-era to fire - but
it lands on the same number, and any geometry reading taken from a
session that was stopped and restarted is suspect.
SGeometryScan now owns all ten with one Reset(). Both sites call it.
A partial reset is no longer something that can be written: there is
one door, and it clears everything behind it.
The struct initialises itself, so the ten constructor-initialiser
entries in Lifecycle.mqh are gone too - MQL5 cannot list struct fields
there, which is a second reason ten loose members was the wrong shape.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>