- Replaced standard library signal modules with custom implementations to allow for named patterns and improved voting.
- Added new input parameters for module weights, allowing for optimization of individual signal contributions.
- Enhanced the management of trades with new options for breakeven and management cut.
- Introduced a mechanism for dynamic ranking of signal weights based on historical performance.
- Improved initialization logic to ensure proper registration of filters and handling of trading conditions.
- Added detailed logging for trading permissions and account status during initialization.
Operator, 2026-09-07: "we don't want to train on the legs anymore." That removes the reason
the whole training stack existed, so it goes.
77 files deleted, 68,078 -> 29,113 lines: 57% of the codebase. A full build drops from 66s to
24s. Compiles 0 errors / 0 warnings against a 0/0 baseline taken before the first cut.
THE SEAM. The four AI signal modules (PAI/CONV/LSTM/HYBRID) were the only consumers of
ExpertSignalAIBase -> AI/Network -> AI/Impl/*, AIBase/*, Training/*, Persistence/*, Topology/*,
Labeling/*, Features/*, OnlineLearning/*, ConfigLock/* and the training half of Chart/*. Cutting
those four dropped all of it. Nothing else reached in.
WHAT SURVIVES, and it is the part that matters: System\NNFilter.mqh - 225 self-contained lines
with their own forward pass, reading a plain ASCII model written by research/export_nn_filter.py,
with the feature-name contract that REFUSES a file whose feature list does not match rather than
approximating it. The offline meta-label net's entire runtime already existed; it never needed
any of what was deleted.
THE LEG-RIDE LABEL AND ITS EXIT. LiveLegDirection() replicated the stock ZigZag so the exit could
fire on the same event the label's ride ended on. No label, nothing to agree with - the replica,
the exit and Expert\Labeling\LegState.mqh are gone, and the take-profit is unconditional again
(it was suppressed only to avoid capping the tail the leg label selected for).
ONE THING THIS NEARLY DID SILENTLY. ClassicVotesMoveMoney() returned "no while an AI member is
present, yes when none is registered". Deleting the networks made the second clause true
everywhere - which would have reinstated the worst defect this codebase has had: fifteen classic
modules at weight 1.0 as the live money vote, which is what "the EA is not profitable" turned out
to mean. It now returns false unconditionally. The only thing that can open a trade is an ARMED
BOOK SETUP through the +/-100 override in Direction(). The classics stay wired because they are
silent and free, and because they are the raw material for the agreement count.
Also extracted System\ChartObjects.mqh - the chart-object namespace list and its sweep, which had
lived inside ExpertSignalAIBase.mqh and were never about training.
NOTE THE CONSEQUENCE, PLAINLY: the only book setup that exists in MQL5 is CSignalInsideBarGap and
it is still disabled, so this EA now trades nothing until the book triggers are wired. That is
slice 3 in REFACTOR_PLAN.md and it is a deliberate state, not an oversight.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator checked the Toolbox: the Calendar tab is FULL. Calendar data comes
from MetaQuotes' servers; the News tab is the broker's own feed and is separately
empty. So my conclusion this morning - "this broker delivers no calendar" - was
wrong, and it was built on proxies: an empty news.dat, which is the unrelated
news-headline subsystem, and the absence of any file named *calendar* on disk,
which only means the store is not named what I guessed. One glance at the tab
settled what three filesystem checks could not.
The real fact is narrower and stranger: CalendarValueHistory over 2010..now with
NULL country and NULL currency returns zero rows and error 0 - success, no data -
while the terminal plainly holds the data. So the script now probes before it
exports, reporting CalendarCountries() and then trying the call four ways with n
and error printed for each: (NULL,NULL), ("",""), a narrow recent range, and a
per-country loop over CalendarCountries(). It exports the first variant that
returns rows, preferring the widest, and if all four are empty it says so rather
than offering a cause.
Compile-verified in _claude_stage: 0 errors, 0 warnings. Deployed to the fleet
terminal's MQL5\Scripts so it can be dragged onto a chart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured on a live attach today: the fleet terminal returns ZERO calendar
values with error 0, news.dat is a 428-byte empty header, and no calendar base
exists on disk. NewsEnable=1, so it is the broker and not a local setting -
Five Percent Online does not deliver the calendar. NewsExport.mqh is correct
and has nothing to export there, which no amount of EA work changes.
FILE_COMMON resolves to the same Common\Files folder for every standard MT5
install of one Windows user, so a terminal that DOES carry a calendar can write
the file the research reads without anything about the fleet terminal changing.
A script rather than the EA because that terminal is a borrowed one: it should
not have to host a trading system, its symbols and its models to hand over a
CSV. Column-for-column identical to NewsExport.mqh, since newsdata.py reads
whichever of the two wrote the file.
Does not replace Scripts/CalendarRecorder.mq5 and says so in its header. That
one exists because actual_value is post-revision, so a surprise from history is
leaky; this dumps history, which is honest for the columns that are never
restated - the release TIME and importance - and those are what the T-5/T+15
blackout and the two news setups need.
Compile-verified in _claude_stage: 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DumpSymbolSpecs exports symbol specifications and deal history so the research
cost model uses the account's real commission and swap rather than assumptions.
ExportIndicatorBuffers dumps every AD/Wyckoff indicator buffer over full history,
so the research conditions on the PRODUCTION detectors rather than a Python
re-implementation of them - which is what made the Wyckoff verdict a verdict on
the indicators rather than on my approximation of them.
Both are read-only: handles, CopyBuffer, and writes under Common\Files. No orders,
no chart changes, no writes to any model or AltData file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DiagnoseADBuffers.mq5 reports, per indicator per buffer per chart, the share of
EMPTY_VALUE, the share of exact zeros, the single most common value share, and the
standard deviation - flagging NEVER-FILLED / ALL-ZERO / CONSTANT / NEAR-CONSTANT.
It answers the question that has to be settled BEFORE any "this feature carries no
signal" verdict: is the buffer computing anything at all. A constant column is a code
fault, not a weak feature - a real but weak signal still varies.
FIRST RUN, 40,000 H1 bars x 5 charts: ADWyckoffEventStream buffers 12 and 13
(Reaccumulation, Redistribution) are identically zero with sd exactly 0 on ALL FIVE
charts. Tracing it gives a proof rather than a suspicion:
line 465 accumulation ranges open only under tr<=0, calling OpenRange(+1, bi, tr)
line 392 continuation needs (dir>0 && trendAtOpen>0), and trendAtOpen IS that tr
so the reaccumulation branch is unreachable by construction, and redistribution is
unreachable symmetrically. gRIsContinuation can never be true.
The conceptual error is one Trend(bi) reading serving two different questions - the
climax gate asks about the trend the climax TERMINATES, reaccumulation asks about a
larger trend the range sits INSIDE. Different horizons; collapsing them makes the
conditions mutually exclusive.
This corrects my own earlier reading. I reported the block as "dead at every parameter
set" from a 41-candidate sweep on two bands; no parameter can make an unreachable
branch fire, so that search was never a test of the concept. The feature set is
unchanged - the operator is having the suite reviewed for correctness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
REMOVED, NOT DISABLED. TuneIndicatorsByFilter() and ScoreCurrentParamsByMI() are gone, along with
the block in TuneIndicatorsAndTrain() that invoked them. AutoTune.mqh 701 -> 473 lines.
WHY REMOVAL AND NOT A FIX. The leak was the cheap half of the problem. The expensive half is that
the search CANNOT WIN:
* It scored candidates by per-column MUTUAL INFORMATION against an overlapping label. That
statistic sits at the noise floor on this data, so 324 candidates per model moved 0.00236 ->
0.00236 on all three AD configs and 0.00370 -> 0.00370 on PAI, and the family-wise gate
correctly refused every winner it ever produced.
* It searched PER CHART. A per-chart winner is a per-chart overfit, and this project ships one
parameter set for every instrument.
* It leaked one terminal indicator instance per candidate (CIndicator::Create overwrites the
handle, it does not release). Measured 2026-09-04: 20,076 live full-history buffers, 27.4 GB
committed and never touched, 202,125 "VirtualAlloc failed in large allocator" lines in a day.
Fixing only the leak would have bought a cheaper way to lose.
KEPT DELIBERATELY, and this is the part a wholesale delete would have broken:
* FeatureColumnMI / BuildMiSample / ScoreMiSample stay. They are the sampling layer under the MI
DIAGNOSTIC, and FeatureScreen.mqh calls all three - that screen produced the per-column table
showing cumdelta clears 4/6 columns on all five charts while wyckoffEvent clears 0/16. The
sampling was never the problem; using it to SELECT was.
* CADIndicatorTuner stays. It still holds the ACTIVE indicator settings ReInitADIndicators builds
handles from. It is now a settings holder, not a search.
* The .nnw indicator-parameter field stays. Dropping it would change the persistence format and
force a second retrain on top of the one the const AutoTuneIndicators already triggered.
WHAT REPLACES IT: Scripts\ResearchADParams.mq5, run once, offline, output edited into the _DEF
constants by hand. It changes the three things that were actually wrong:
1. BLOCK-LEVEL, NOT PER-COLUMN. Fits a block's k buffers jointly against the label, which is what
the network does with them and has far more power than k univariate tests.
2. ATR PER CALL, NOT NATS. The metric is the mean forward RIDE RETURN of the bars the fit ranks
highest - the same currency as the book the deploy gate judges. MI was never convertible to it.
3. SELECT IN-SAMPLE, REPORT OUT-OF-SAMPLE, on a PURGED split. Max-of-N is biased upward by
construction, so the IS number is printed but explicitly labelled as not evidence.
Ranking is MAXIMIN ACROSS CHARTS - a candidate is only as good as its WORST instrument - because a
mean lets one strong chart carry a set that is useless on the other four, which is precisely how
this project has previously promoted results that then failed to replicate.
Every iCustom handle is released in the iteration that created it.
A FLAW IN ITS OWN VERDICT LINE, recorded rather than quietly patched: the script prints "clears
every chart OOS" when maximin OOS > 0. That test is nearly free, because every chart's no-model
baseline is already POSITIVE (+0.09 to +0.74 ATR - the forward ride pays a drift). The meaningful
quantity is the EDGE over that baseline, which the per-chart lines do print. Read the edges, not
the verdict.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First live run exposed both problems at once. It logged "+83 release(s)
recorded", and every one of those rows shared a single observed_time up to 30
hours after its event_time: they were the startup backfill, not release-time
observations. Their actual figures are whatever the terminal holds NOW - the
post-revision values this recorder exists to avoid - and the very first batch
proved that is not hypothetical: a Retail Sales row came back previous 3.5 /
revised_prev 3.4, and a Core CPI row already carried revision=1.
Backfill is still worth keeping (a fine snapshot of the revised series, and it
carries the event metadata) but must never be silently mixed with release-time
observations. Every row now records lag_sec and a capture class, so the
distinction cannot be lost by whoever loads the CSV later:
LIVE observed within InpLiveLagSeconds (default 600s) of release
BACKFILL seen long after the fact - MUST NOT be used for surprise research
The log now reports the split per poll and says so explicitly when a poll is
entirely backfill.
Second and worse, in LoadSeen: the FILE_CSV field walk was off by one and keyed
the seen-set on event_id instead of value_id. event_id identifies the event TYPE,
not the release, so after any restart every future release of every event already
in the file would have been skipped - permanently, and silently, exactly for the
recurring high-importance events (NFP, CPI) that matter most. Now reads whole
lines and indexes a split array by a NAME-CHECKED column position, which cannot
drift when the schema changes. Refuses to guess if value_id is absent.
Schema change is handled by rotating any file with a non-matching header to
<name>.<timestamp>.old rather than appending, since mixing layouts mis-parses
every old row.
Compiles 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The calendar is the only non-price source MQL5 carries with real content, and it
is the one this project cannot research from history:
MqlCalendarValue.actual_value returns the CURRENT figure, i.e. after every later
revision. Reading 2019's NFP today returns a number nobody could have known in
2019, so any surprise = actual - forecast feature built from history carries
lookahead - and the flattering kind, since it makes a model look most prescient
exactly on the events that were revised most.
The only sound fix is to write down what the terminal reported at the moment of
release and never touch that row again. Write-once is the entire contract here: a
row is appended the first time a value_id is seen carrying an actual figure, and
is never rewritten, because re-recording on a later poll would silently import
the revision this file exists to avoid. The seen-set is rebuilt from the file on
init so a restart cannot duplicate or re-import either.
Deliberately standalone - no includes from the EA tree, and not wired into
Warrior_EA. It may run for months on a spare chart, and coupling it to the
trading system would mean a refactor there can stop the recorder; a gap in a
write-once series cannot be backfilled by definition. It also keeps a
data-collection task from adding any failure mode to a system about to trade a
prop account.
Records actual/forecast/previous/revised_previous, revision number, impact,
importance, units, plus observation time and the quote at observation. Every
FileOpen carries FILE_SHARE_READ|FILE_SHARE_WRITE per the rule this codebase
learned the hard way (exclusive opens fail 5004 and look like "no data").
Compiles 0 errors, 0 warnings. Value starts at zero and accrues with time, which
is the argument for starting it now rather than when it is wanted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dumps CopyTicksRange output to CSV in the COMMON files folder, where research/ already
reads its rate exports from. Chunked by hour range because a single unbounded request over
years is both slow and liable to ERR_HISTORY_SMALL_BUFFER; boundaries are half-open on
purpose since CopyTicksRange is inclusive at both ends and adjacent chunks would otherwise
duplicate any tick landing exactly on a split.
Keeps MqlTick.flags RAW rather than decoding to a direction. On FX/CFD only
TICK_FLAG_BID/ASK are ever set - TICK_FLAG_BUY/SELL and volume/volume_real are empty for
Forex - so signed trade direction does not exist in this feed and has to be synthesised
offline from quote dynamics. Exporting a decoded 'side' column would be inventing data.
Written as the reliable alternative to decoding StrategyQuant's .dat: that format's base
record parses cleanly (32 bytes, ms timestamp + bid + ask + one volume, prices x1e6,
verified against a known SP500 level) but the delta stream is a custom bit-aligned
dictionary scheme, and it carries only ONE volume field - so it offers nothing MT5 does not
already provide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>