- 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.
Everything from tonight, committed so the restructure that follows is
recoverable: the graded stdlib vote, the Wyckoff modules and feed, the
ALGLIB serializer workaround, the restored DB queue, and the Simple/
prototype that is about to be folded into the real filetree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A session window (10:00-12:00) on a D1 chart would silence a setup for good, since every daily bar
opens at the same minute; and the Friday flat is a day-trading rule that would close every swing
before its first weekend. Both are skipped at PERIOD_D1 and above so the same patterns can be tested
on closed daily bars, carrying the swap (operator's request, 2026-09-09).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every reading touches all ~64 market-watch symbols, and under a tick model the tester answers
SymbolInfoTick for a symbol by synchronising that symbol's whole tick history first. One EURUSD
backtest on 2026-09-09 downloaded 4.7 GB of other symbols' ticks, filled the disk, and the tester
ended the test on its first tick without reaching OnDeinit. The census is a report on the live feed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The shadow journal filled a stop entry AT its level, once a minute. Real
stop orders under the tester's 1-minute OHLC ticks filled 0.7-1.2 bp
past it. On the EURUSD decade the same agreement-3 firings priced +0.9
bp virtually and realised -1.8 bp traded, with identical exit rules;
the pairing of real trades with their own virtual twins put the whole
difference in the fill and in a label cost 0.8 bp too low.
- Unfilled firings are checked on every tick; a stop fills at the price
that crossed it, a limit at its own price; risk is measured from the
fill. The ladder still advances once per minute.
- fill_slip_bp and fill_spread_bp are appended to the row's context.
- The meta-label cost per row is the spread at that fill plus the class
commission; the chart's spread now is only the fallback.
- A real trade's notebook line is the OWNER's (the setup that shaped
the order) with the bar's agreement annotation, not whichever setup
published last - 343 of 632 rows named another setup's entry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The audit printed before the validate step, so it showed the book's
trail for a run that traded with none. One idempotent ApplyManagementPolicy,
called from both.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A tester sweep, not a choice the EA makes. Measured on the EURUSD decade:
the agreement-3 firings run on stop + time stop alone paid +0.91 bp net
(shadow journal, 2,031 rows) while the same cell traded with breakeven
and the 1R trail realised -1.5 bp, 561 of 631 stopped. Whether the trail
gives the move back is what this switch measures.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ManageTrailing derived risk from |open - CURRENT SL|. At +1R the stop
moved to breakeven, risk became zero, and the function returned false
for the rest of the trade: no trail ever ran, and a trade that had
reached +1R waited at breakeven to be stopped at 0.
Measured on the clean EURUSD decade (shadow journal): the agreement-3
firings re-priced under their own setup's exit pay +0.91 bp net
(n 2,031); the same cell traded realised -1.54 bp on 632 - 529 stopped,
94 time-stopped, 9 targets, mean MFE 1.06R, realised R piled at -1.0 and
0.0. Slippage against the setup's level: 0.00 bp. Same shape as the
m_entry defect: a quantity redefined by the code's own side effect.
Risk now comes from the journal's record of the stop the position was
opened with (TrackedRisk by POSITION_IDENTIFIER); the current stop is
the fallback only on a position the tracker has not seen yet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two consecutive fits on the same 53,745 firings gave +0.05 and +0.38 bp
at the same threshold: the replace-on-key rewrites rows in place, SQL
returned them in a different order, and the bootstrap followed. Sorted,
then three seeds summed into the same accumulators.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
First fill run: the uncompressed forest was 56 MB and RefreshIfDue
re-read it every server day; the threshold rule picked the highest mean
and kept 2% of firings on 474 rows. Now: 60 trees, DFBinaryCompression
before serialising, reload only on a changed modify time, and the
threshold (and take-all) scored as mean minus one standard error.
Also found: the abandoned fill runs had written Adapt files daily from
2015 samples, and the next run adopted their skip flags from day one -
sos_bar long fired 102 times in 2015 and never again. Artefacts wiped;
the tester now writes only at deinit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The daily adaptive pass reads the whole table; with the shadow journal
that table holds every firing so far, and a decade of daily full reads
of a growing table is quadratic - 2% per five minutes on the first fill
run. Live keeps the daily cadence; the tester learns once, at the end,
and the next run adopts it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator: "could we use mql5's alglib random forest and mlp instead of
relying on python? very quick training could reopen the door to selling
the bot."
Database\MetaLabel.mqh. Trained from the Virtual:<setup> rows (every
firing, not the few the gate traded): 14 features parsed from the
notebook line by ONE parser shared with the live gate (setup ordinal,
side, agreement count, opposed, armed count, headroom, minutes to the
forced close, risk in bp, day, hour, order type, valid test, window,
target); label = the firing paid after cost under its own setup's exit,
re-priced from the ladder as research/pricing.py prices it; cost = the
symbol's spread plus the class's commission, printed with every run.
The honest number is walk-forward: for every year from the third, a
forest trained on the years before scores that year, and the take
threshold is the one whose out-of-sample rows paid best - adopted only
if it beats taking everything, else no gate is written and a stale one
is deleted. Final model on every row, ALGLIB CDecisionForest via the
builder (100 trees, 0.66 subsample, Gini importance), serialised to
Adapt\{SYM}_{PERIOD}_meta.rf with the feature contract in the header; a
file whose contract differs is refused.
Gate: in the agreement block, last, as the mean P(pay) over the setups
armed on that side; a refusal is counted as its own entry gate. Trained
at deinit in the tester (fill, then learn, then trade gated - the 2024
loop) and daily when live. Pure MQL5, no DLL, no Python.
Also: virtual firings advance once per minute, not per tick - the
per-tick walk made a decade run four times slower.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator: "is there not a meta labeling neural network in the EA? why
did it keep taking a losing pattern? The journal is there for that."
There is no meta-label net (CNNFilter is a loader nobody instantiates;
the offline net was never built), the adaptive layer learns management
only by design, and it could not read anyway: SignalDatabaseActive()
switched the DB off for every tester run on the argument that "a tester
run's DB is written and never read" - a premise AdaptiveExitWriter had
already deleted. Every run ended with 93 verbose-level "journal read
failed - database not initialized" lines and learned nothing.
- The DB is ON in a single tester pass; optimisation and forward stay
off (12 agents on one FILE_COMMON SQLite file finished zero passes).
- A closed trade REPLACES its own row on the natural key (symbol, open
minute, side, entry price): tester tickets restart at 1 on every run,
so a re-run over the same history no longer double-counts.
- AdaptiveExitWriter writes skip_long / skip_short when a setup-side is
a categorical loser on this chart: n >= 60 and t <= -2.5 on realised
R x risk in bp, gross of commission. Not ranking - the measurement
that made selection anti-predictive ranked marginal cells against
each other; this is the shape Second Wind showed on forex (t -4.0).
A skipped side never arms, so it never counts toward agreement.
- The reader adopts the flags with the exit, once per server day, and
prints the verdict when it changes.
- "Could not read" is printed once at normal level with the reason.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The owner alone named whichever setup sits first in the roster, so an
agreement-4 table read as "PIN" 24 times. The combination is the thing
the research measured; the comment now carries it (31 chars, cut not dropped).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The stock CExpert::OpenLong/OpenShort send no comment, and the tester
keeps no SQLite journal, so a backtest report could say how the EA did
and never which setup did it. One send site now, lot sizing unchanged,
comment = the owning setup's id (or "vote" for a vote-driven entry).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
WarriorHedgingActive() false leaves every trade stamped with the long
book's magic, and SelectOwnPosition(false) asked for the short book's.
The two-magic premise was deleted by the account type and the test did
not notice - the same shape as ClassicVotesMoveMoney() after the nets
were removed. The POSITION_TYPE check already tells the sides apart.
Also names the remainder in the ENTRY GATES line: bars that armed while
the book already held a position or an order.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Root cause of "13,209 SP500 trades, every one a buy": not the arming code.
Every tester run since 2026-09-06 echoed `tradingdirection=1` in its
"started with inputs" block - inherited from a stale tester preset
(Profiles\Tester\Warrior_EA.SP500.H4.20190101_20260824.120.ini, 09-01)
that no launch ini overrode. The source default is BOTH, so reading the
source said the EA traded both sides; the trace that names the gate sat
behind TraceMode=false, so its absence was not evidence either.
A one-month BTCUSD H1 trace run said it in one line once the per-side
counters existed: 39 bars armed SHORT, 0 shaped, 39 blocked by the
direction policy. The SP500 decade re-run with tradingdirection=0 pinned:
22,694 trades, 12,383 of them short, net -1,812.74 against -7,871.58
long-only. Still negative at agreement 1, but now the whole strategy.
Hardening so this class of failure announces itself:
- PlanAudit prints "side policy: BOTH" or a !! line at init.
- ENTRY GATES LONG/SHORT at deinit: armed bars per side and the gate each
stopped at (direction, veto, market closed, threshold, cooldown, not
shaped, SHAPED), counted once per armed bar and ungated by any input.
Sweep findings fixed in the same pass, each verified against the source:
- CBookSrRetest could never fire: the level was the extreme of bars 2..61
and the break test asked whether a bar in 2..21 exceeded it. The level
of a candidate break bar is now the extreme of the 60 bars BEFORE it,
as the research reads it (rolling max shifted by one).
- TimeStopReached evaluated the bar-denominated stop in seconds, so a
weekend or session break closed trades with most of their bars unlived.
Now counted with WarriorBarsBetween like every other spacing rule.
- LoadAdaptiveExit installed the file's MINUTES into m_timeStopMin, which
on H1 made every grid value shorter than the 24-bar rule and on H4 made
"15" a quarter of a bar. Converted to whole bars, refused under three.
- PublishSetupContext wrote time_stop_min=0 for every book setup, so the
adaptive writer's never-wider clamp compared against nothing. The
effective minutes are published now.
- WarnIfAgreementUnreachable swept all 1440 minutes; InWindow only ever
sees bar OPENS, so an H4 chart with a 09:30-12:00 window was reported
reachable while dead. It samples bar-open minutes now.
- Order deviation was 3 adjusted points: 3 pips on EURUSD, three cents on
BTCUSD. Floored at 1 bp of price.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator: "look at the data without scripts... you can mimick a human
trader much better than mechanical rules, which will allow us to find what
is wrong." Reading the trade list instead of aggregating it, two minutes in:
SP500 decade, agreement 1 : 13,209 round turns, opening side {buy: 13209}
EURUSD decade, agreement 4 : 47 round turns, opening side {buy: 47}
OPPOSED events in 346,188 SP500 bars : 0
THE EA HAS NEVER OPENED A SHORT POSITION. Half the strategy has never run,
and every negative result of this campaign was measured on a long-only EA
while research measured both sides.
It is NOT the direction gate: WarriorEffectiveDirection() returns
tradingdirection, which ships BOTH, and the "blocks short entries" trace
fired zero times in a decade - that gate is never even reached. So
ArmedSetupCount(false) is permanently 0 and no setup sets m_armedShort. The
stdlib calls ShortCondition() as well as LongCondition(), so the failure is
inside ShortCondition or inside every DetectShort. NOT YET DIAGNOSED.
ReportArmingHistogram now counts the sides separately. The combined peak
histogram reports max(long, short) and therefore CANNOT show a one-sided
EA - which is exactly why this survived a full night of screens, sweeps and
significance tests. The aggregate hid a categorical failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: "follow every guidelines from the book, so if we are close to a
resistance level we need to be careful on going long."
Every book says it - the Forex book builds three setups on levels (p89-95),
the Stocks book warns off buying into the previous day's high, and Wyckoff's
whole method is about where supply sits - and the EA checked it NOWHERE.
The journal already carried the measurement. `range_pos` is where price sits
in its own range, so headroom is (1 - range_pos) for a long and range_pos
for a short. On the agreement 4+ population, five majors, spread charged:
headroom 0.00-0.25 n=27,787 IS -0.481 OOS +1.151 2022+ +1.004 all +0.019
headroom 0.25-0.50 n=17,288 IS +0.112 OOS +2.079 2022+ +2.712 all +0.673
headroom 0.50-0.75 n=14,809 IS +0.766 OOS +2.874 2022+ +3.466 all +1.439
headroom 0.75-1.01 n= 7,202 IS +2.677 OOS +3.061 2022+ +3.343 all +2.821
MONOTONE IN ALL THREE PERIODS. A 2.8 bp spread from worst bucket to best,
and the crowded-into-the-level bucket is the only one that fails outright.
WHY THIS IS TRUSTED WHERE MOST CUTS ARE NOT, and it matters: the agreement
4+ cell is ALREADY positive in all three periods, so slicing it mostly
yields positive slices - 111 of 155 cuts "survived" against a noise
expectation of 19, which means the survivor count is worthless as evidence.
What noise does NOT produce is a monotone gradient across an ordered
variable in three independent periods. That ordering is the whole argument,
and it is why st_ctx_expanded at +5.649 bp on 4,969 rows is NOT being
shipped alongside it.
0.25 is where the only failing bucket ends - the boundary the measurement
draws, not a tuned value. The check sits at the one place a side arms, so it
applies to all 21 setups, and it runs AFTER detection so a setup declined
for want of room is still journalled as having fired: that population is
exactly what the meta-label net has to learn from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator pushed back on last night's negative verdict - "spread is just
a fraction of one candle... we should target 1:2rr... we can try taking
partial profits on 0.5r" - and pushing on the cost question is what found
this. Three of the four ideas measured wrong, and asking them properly
found the cell that works.
WHAT THE SPREAD ACTUALLY DOES. research/pricing.py has no spread term and
spreadmodel.py ships SPREAD_MULTIPLIER = 0.0 by deliberate decision
("forget about spread. just charge commission"). That is why every research
number looked better than the tester: the EA pays what the model was told
to ignore. Charged honestly - commission always, spread only where it is
crossed, which spread_paid() puts at zero for a long pending entry - on
11.1M rows across the five majors:
agreement 2 -1.618 IS -0.355 OOS -0.318 2022+ -1.215 all
agreement 3+ -0.791 +0.743 +0.855 -0.316
agreement 4+ +0.257 +2.008 +2.252 +0.802 SURVIVES
Positive in all three periods, monotone, 67,086 rows. THREE WAS RESEARCH'S
NUMBER AND IT WAS TUNED AT ZERO COST; paying the spread moves the bar up
one. So the default is 4.
TWO PROPOSALS MEASURED AND REFUSED, both for the same reason:
agreement 4+, 2R target -0.084 all (vs +0.802 running it)
half off at +0.5R halves the edge before the extra crossing
+0.5R is touched before the stop on 62.0% of trades, so the intuition was
right about the path. But at a 42-45% win rate the expectancy is entirely
in the right tail, and both a 2R cap and a 0.5R partial sell exactly the
tail that pays for the losers.
AND THE MINIMUM-DISTANCE FILTER DOES NOT WORK EITHER. The cost toll really
does fall with the risk unit - 0.289 R on trades risking under 6 bp against
0.138 R over 20 bp - but the tight-stop trades win anyway at agreement 4+
(+1.002), because their gross edge is larger by more than the toll. The
filter is agreement, not distance.
THE REAL CONSTRAINT IS RARITY. The EA reaches agreement 4 on 0.03% of bars:
227 in a decade of EURUSD M5, 47 round turns, which settles nothing
(t = -0.37, CI [-4.6, +3.2]). Research's 67,086 rows are rows-per-trigger
across five majors over twenty years, and its forex triggers carried no
session window where the EA's fire only in the book's London and New York
openings - 4 hours of 24. One chart yields ~5 trades a year; five majors
yield ~115. That is what the FILE_COMMON journal exists to pool, and the
arming histogram now says so at the end of every run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: "take the needed decisions to bring the EA to positive
expectancy. make backtests driven decisions." The backtests are in and the
answer is negative. SP500 M5, 2015-2025, 21 setups, fixed lot, guard off:
agreement 1, books' management 13,209 RT -1.197 bp t = -6.30
agreement 1, RESEARCH exit 12,682 RT -1.305 bp t = -6.32
agreement 2, books' management 3,312 RT -1.284 bp t = -2.88
agreement 2 + crowd veto 6 212 RT -0.598 bp t = -0.45
One favourable year (2024: +0.354 bp, t=1.02) had suggested otherwise.
THREE LEVERS ELIMINATED AT PROPER POWER
Confluence does not transfer: agreement 2 is no better than agreement 1,
where research had it monotone. The books' management is not the cause:
same entries, both exits, a decade, and the two are statistically identical
- so the give-back measured on EURUSD (83.7% of trades green, median
capture 0.0%) is real but is not what loses the money. And the crowd veto's
calibration does not hold - threshold 6 kept 19% of 2024's firings and 6%
of the decade's, because the crowding distribution is not stationary.
THE ANSWER, AND IT IS NOT A PARAMETER
research/pricing.py has no spread term. It charges commission only, and:
SP500_d commission 0.000 bp RT spread 1.026 bp
EURUSD_d commission 0.800 bp RT spread 0.291 bp
BTCUSD_d commission 6.000 bp RT spread 6.000 -> see costs.py
EVERY RESEARCH NUMBER ON INDICES WAS MEASURED AT ZERO COST. Commission is
nil on an index and the spread was deliberately never charged. The EA pays
it on every fill. Net -1.197 plus the 1.026 spread puts the gross at about
-0.17 bp: the entries carry essentially no gross edge on SP500 M5, and the
spread turns nothing into a reliable loss.
The EA is not broken. It is measuring what research measured, with the cost
research omitted - the third confirmation of this project's own standing
verdict, now on the instrument that looked most promising BECAUSE it was
the one where cost was invisible.
WHAT WOULD HAVE TO BE TRUE: more than 1.03 bp of gross edge. Research's
best cell - 3+ agreeing AND sparse classics - measured +3.922 bp and would
clear it. The EA reaches that cell 212 times in a decade. So the gap is not
the threshold or the exit; it is that the cell with the edge is too rare on
one instrument to trade. Research pooled 942,918 trades over 8 instruments
to find it, which is what the FILE_COMMON journal and AdaptiveExitWriter
exist for. Do not tune further on one symbol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: "port everything from research... there cannot be too much
analysis." Two things, and the first is the cause of the second.
SEVEN SETUPS PORTED - 14 to 21
The journal carries 25 distinct triggers where the EA had 14, and the gap
was not random: EVERYTHING the EA implemented was a BAR SHAPE. An inside bar
is by definition not an engulfing bar, a pin bar is a third shape, and only
ma_cross was shape-independent. Four mutually exclusive patterns cannot
agree, so the agreement count - the only measured edge this project has -
had almost nothing to count. Measured on EURUSD M5 over 74,678 bars, peak
simultaneous arming was 0:68761 1:5333 2:517 3:63 4:4, which is agreement 2
on 0.78% of bars and 3+ on 0.09%.
sr_retest forex p89-91 broken resistance becomes support
tl_bounce forex p92-95 a line through two KNOWN pivots
tl_break forex p92-95 the same line, broken
cci_div forex p237-243 price/oscillator divergence
pin_bar_c forex p206-209 the book's actual 3-bar pin, option 2
cons1234 stocks p185-186 four small bars at the extreme
fvg SMC a three-bar imbalance
NONE OF THESE IS A BAR SHAPE - levels, lines, an oscillator, a gap - so each
can fire on the same bar as an inside bar. On SP500 M5 that took agreement 2
from 2.40% to 4.70% of bars and 3+ from 0.23% to 0.64%, and the plan audit
now reports 19 setups eligible together where it reported 4.
Trend lines read pivots only k bars after they print, which is where that
study usually leaks the future. cons1234 carries no window because the
stocks book explicitly calls it session-agnostic - which also makes it one
of the few that can agree across hours. fvg is labelled book="smc" and is
reachable ONLY through BOOK_ROSTER_ALL: AUTO must never hand a chart a setup
no book endorses, and no result it contributes may be called a book result.
THE CLASSIC MODULES EARN THEIR PLACE - AS A VETO, INVERTED
Their fate, asked properly: given a book setup has fired, does a classic
module agreeing change what it is worth? Measured on ~1.3M journal rows
across 8 instruments, sparse (0-2 of 26 patterns) against crowded (3+):
agreement 1 +1.074 vs +0.407
agreement 2 +2.283 vs +0.696
agreement 3+ +3.922 vs +2.016
Positive in all three periods at every level, and sparse beats crowded on
7 of 7 instruments. ONE binary comparison, not the best of a grid. The
classics are not confirmation - they measure how OBVIOUS the move already
is, and the book setups are largely reversals and breakouts, which do worse
when everything already agrees. That mechanism predicts the sign, which is
why this is trusted where their standalone edge (at chance) is not.
Scale does not transfer: research counted 26 PATTERNS, each module here
reports ONE direction, so the EA counts modules and tops out near 15.
Calibrated on the EA's own distribution instead - 14,338 armed firings, and
vetoing at 6 keeps 19% of them against the journal's 20.75% sparse cell.
AND THE EA DOES NOT REPRODUCE THE SIZE. SP500 M5 2024, agreement 1:
no veto 1780 round turns +0.354 bp t = 1.02
veto at 6 465 round turns +0.414 bp t = 0.63
Right direction, 1.17x where the journal measured 2.6x, on samples too small
to tell either from zero. Two decisive sources disagreeing about magnitude
is what an input is for, so Signal_CrowdVeto is one, defaulting to 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The init audit says whether a quorum COULD form from the declared windows.
EURUSD M5 proved that is not enough: the audit correctly reported "agreement
2 of 4 setup(s) is reachable" and the year traded NOTHING.
Both were right. The four forex setups share sessions and are MUTUALLY
EXCLUSIVE BAR SHAPES - an inside bar is by definition not an engulfing bar,
a pin bar is a third shape, and only ma_cross is shape-independent. They can
be eligible together and never arm together, and no init-time check can see
that. It is the second structural reason within-book agreement cannot work,
after the Stocks book's disjoint windows.
So the run now reports what actually happened. ReportArmingHistogram()
samples the peak simultaneous arming ONCE PER BAR, taken where the counts
already exist so "armed" keeps a single definition, and prints at deinit:
ARMING over 74678 bar(s) - peak simultaneous setups per bar:
0:68761 1:5333 2:517 3:63 4:4 | the threshold of 2 was reached on
584 bar(s) (0.78%).
...with an explicit "NOTHING COULD HAVE TRADED" when the threshold was never
reached. Init asks could it; deinit asks did it; the pair is the diagnosis.
That measurement is also a finding in its own right. Agreement 2+ occurs on
0.78% of bars and 3+ on 0.09% with ALL FOURTEEN setups enabled. Research's
3+ cell held 416,366 rows because its journal carries 36 triggers where the
EA implements 14, so co-firing is far rarer here and the threshold optimal
there is structurally too strict. The default of 2 is right for this EA.
CExpertCustom exposes the report as a BEHAVIOUR rather than widening
GetCustomSignal to public: OnDeinit needs this one line, not the signal
pointer, and handing out the pointer to get it would widen the class for a
single caller.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: "the EA should be dynamic and work on any timeframes and symbols.
it needs its own internal research framework."
Every failure this EA had today was a SCALE failure - a rule meaning one
thing where it was measured and something else on the chart it ran on:
* the opening reversal is a 20-minute construct; on H1 that is ZERO bars
* a 120-minute time stop is 24 bars at M5 and TWO at H1 - a different rule
* the Stocks book's three windows are disjoint, so a quorum of 2 is
unreachable and the run trades nothing
* m_entry was shared by both sides, so an armed setup shaped from a zero
None of those raise anything. The EA trades nothing, or trades a different
strategy, and looks exactly like a quiet market doing it. Three cost a full
test run to find; the fourth had been live since the setups were written.
The fix is disclosure, not cleverness. At init every setup now states what
it will actually do on this symbol and period, and what cannot be true here:
===== TRADING PLAN, resolved for EURUSD PERIOD_M5 =====
IB inside_bar forex win 10:00-12:00,15:00-17:00 entry 1 bar
stop 1.00R target none trail 1.00/1.00R be 1.00R timestop 24 bars
...
agreement 2 of 4 setup(s) is reachable - up to 4 eligible together
===== 4 setup(s), no scale problems =====
DESIGN, and it is the reason this cannot rot. Each filter writes its OWN
line (PlanLine) and answers for its OWN coherence (ScaleIssues); PlanAudit
only collects and orders them. Adding a setup, or a rule to one, cannot
leave the audit behind because the audit knows nothing about either.
ValidationSettings now asks ScaleIssues rather than carrying its own copy of
the timeframe test, so a rule is checked in exactly one place.
ScaleIssues catches, among others, the case that cost today's run: a
wall-clock time stop that resolves to under three bars on this chart is a
value measured on a faster one, and says so.
It warns, never refuses - one roster can hold setups from books of different
granularity, and aborting because one cannot fire here is a worse answer
than running the rest and saying so. WarnIfAgreementUnreachable is called
from the audit so there is a single init entry point.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: "I just want it to keep learning during live trade. similar to
2024 versions of the ea where I would run a backtest to fill the database
and then it would keep filling in real time, averaging on the whole sample."
Most of this already existed. CBookSetupSignal::LoadAdaptiveExit() has
always been the READER - once per server day it re-reads
Common\Files\Warrior_EA\Adapt\{SYM}_{PERIOD}_{setup}.cfg and adopts the exit
in it, refusing anything outside the research grid. FILE_COMMON is the whole
point: the tester and the live chart share one file and one journal table,
so a backtest fills the sample and live trading carries on filling it.
Nothing ever WROTE that file.
And the journal already records what is needed to re-price an exit without
re-running anything: `passages` carries the first-touch minute of 9 stop
levels and 10 target levels plus the signed R at 6 horizons, in exactly the
field names research/pricing.py reads.
So this is the estimator: read every recorded trade for this symbol, group
by setup, re-price each one across the (target x horizon) grid with the same
first-passage rule pricing.py applies, and publish the winning cell.
WHY THIS IS NOT THE FEEDBACK LOOP THAT WAS DELETED. Two differences, both
measured rather than asserted:
1. IT LEARNS MANAGEMENT, NEVER SELECTION. Selection by a cell's own past
P&L measured ANTI-predictive on this journal - a cell gets WORSE as its
evidence accumulates - while management measured positive. So it may move
a target or a time stop. It may never decide which setups fire, what the
agreement threshold is, or what any vote weighs. DB_RankingFeedsWeights
stays false.
2. IT IS A CUMULATIVE MEAN OVER THE WHOLE SAMPLE, NOT A ROLLING RE-FIT. The
harm in the old loop was a MOVING RULER - a pattern's contribution changed
as the DB re-scored it, so the same setup voted differently at different
times and nothing could be evaluated against anything. An average over
everything ever recorded converges instead of chasing.
THE ASYMMETRY THAT WOULD OTHERWISE POISON IT, and TradeJournalManager's own
header stated it before this was written: a live trade is closed by its own
exit, so levels beyond the one it took are never touched and record as -1.
From live rows an exit can honestly be re-selected TIGHTER, never WIDER, and
a layer ignoring that would "learn" that wide targets never pay because it
never saw one reached. That is not hypothetical - today's journal sweep found
the best exit is the WIDEST (6R on a 1R stop is never touched, so it is
really "no target", with a long horizon: +2.311 bp at 3+ agreement, positive
in all three periods). So the writer is hard-capped at "never wider than what
produced the evidence".
Other guards: 120 trades before a setup is considered at all, 60 re-priced
rows before a cell is, a positive mean, and at least one neighbouring cell
also populated and positive - a plateau rather than a spike, because picking
the argmax off a grid is what this journal punishes. The stop is never tuned:
a book setup's stop is its structure and the books state it.
FetchClosedTrades is now public - it is the read side of this loop and the
writer only reads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator delegated the call on when a forex trade should be closed. The
memory recorded it as an open question - "the book's own answer is closest
to 'be flat by the end of the session you entered in'". THAT WAS A
MISREADING. Rule 11 is verbatim: "every position closed before the end of
the trading day", for gap risk and overnight swap (p16, p156). The book's
session table closes New York at 16:00 ET and its explicit dead zone starts
there, so the trading day ends at 16:00 ET. There is no open question, and
CH_MARKET_CLOSE already implements exactly that rule.
What the re-read DID expose is that the book's three entry windows and its
own overnight rule cannot both stand: a Tokyo entry at 19:00-21:00 ET is on
the far side of a 16:00 ET flat.
Measured on the five majors, 2015+, raw H1 bars - no patterns, no trades,
no cost model, so none of it can be an artefact of our own setups:
London 03:00-05:00 ET 15.73 bp 1.38x the 24h median
New York 08:00-10:00 ET 19.17 bp 1.68x
Tokyo 19:00-21:00 ET 10.40 bp 0.91x <- BELOW median
dead zone 16:00-18:00 ET 7.85 bp 0.69x <- the book is right
Tokyo is the only named window quieter than an average hour on this
universe, and per pair it is alive on USDJPY alone (1.22x) while EURUSD,
GBPUSD and USDCAD read 0.78-0.87x. That is the book agreeing with itself:
"trade pairs local to the session... a US daytime trader should not be
trading AUD/JPY." Our universe is US and European majors.
So Tokyo is dropped rather than kept and quietly overridden by the flat-by.
Restoring it means restoring it for JPY pairs ONLY, with its own later
flat-by - stated in the file so the next reader does not have to re-derive
the conflict.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator asked whether everything from research was ported. It was not,
and the gap was systematic: every TIME-BASED finding was missing.
THE FOREX BOOK'S THREE ENTRY WINDOWS
BookForex.mqh contained no SessionWindow call at all, so all four forex
setups fired around the clock - while every research number that measured
them ran only inside the book's own windows. The book names them (p145):
London 03:00-05:00 ET, New York 08:00-10:00 ET, Tokyo 19:00-21:00 ET, "the
first one or two hours after a session open".
This is the same mismatch that made research/add_session.py apply the
STOCKS cash session to forex. Correcting it there TRIPLED gross edge on M5;
every pair went positive gross on both timeframes, EURUSD M60 net
-0.856 -> -0.271 bp against a 0.80 round turn. Raw hourly range confirms it
independently of any trade definition: a 3.5x spread across the day, and
the dead hours are exactly the ones an all-day EA was trading.
The three windows are DISJOINT, so CBookSetupSignal now holds a SET of
windows rather than one pair. That is also why the forex windows could not
simply have been added before.
NO NEW ENTRY INSIDE THE LAST 60 MINUTES
The most robust result of the campaign, and it was absent entirely - nothing
in the EA knew how much session was left when it placed a trade. On 951,919
session trades from 2005: enter with 30 minutes left and 87.4% never resolve
at all, so the forced close decides them, and that close is worth +2.8 bp
early against -1.0 bp late. Late setups are not worse patterns; they are
never given room.
cutoff kept net bp vs all years improved
15 96.2% -0.961 +0.039 22/22
30 92.1% -0.936 +0.064 22/22
60 83.4% -0.895 +0.105 22/22
120 67.7% -0.867 +0.134 22/22
Monotone, saturating near 90-120, improving EVERY one of 22 years. Trusted
where mined rules are not because the mechanism predicted it before it was
measured. 60 rather than the 120 that measured best: 60 carries 78% of the
improvement while keeping 83.4% of trades against 67.7%, and trade COUNT is
the binding constraint right now. One #define to raise it.
Measured against WarriorScheduledFlatMinuteOfDay - the SAME forced close the
expert actually applies, now extracted so both callers share one owner
rather than each deriving a schedule that can drift from the other.
Context - BTCUSD H1 2024, equal weight, risk guard off, 0.10 lots:
agreement 1, no clock 391 RT -5.65 bp
agreement 1, clock 113 RT -3.15 bp
agreement 2, no clock 116 RT -2.11 bp
agreement 2, clock 31 RT +18.93 bp <- first positive, but n=31
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two fixes for the same class of failure: a configuration that trades
nothing while looking exactly like a quiet market.
THE CRYPTO CLOCK - OURS, AGAINST THE BOOK, AND MEASURED
The Bitcoin book says crypto has no session and no hour effect: "as active
at 3AM on a Sunday morning as it is at 9AM on Monday" (p38, p77). The
second half is FALSE, on 868,560 BTCUSD M5 bars from 2017-05 to 2026-09:
quiet 05:00-14:00 server 51.0 - 60.7 bp
middle 00:00-04:00 63.4 - 71.8 bp
ACTIVE 15:00-23:00 66.3 - 91.8 bp peak 17:00 = 91.8
quietest 07:00 = 51.0, busiest 17:00 = 91.8, ratio 1.80x
Sat 0.59x, Sun 0.63x against weekdays 0.99-1.02x
WHY IT IS A COST RULE AND NOT A SESSION CLAIM. The book is right that there
is no open, no close and no meaningful "day", and nothing is gated on one.
But cost in R is cost_bp / dist_bp, so at 51 bp an hour instead of 92 the
same pattern gives a stop about half as wide and pays close to TWICE the
relative cost for the identical trade - on the class that already pays the
highest commission we trade.
HOW THE WINDOW WAS CHOSEN, so it is not a fitted parameter: the median of
the 24 hourly medians is 64.25 bp, and 15:00-23:00 is the ONE CONTIGUOUS
BLOCK entirely above it. It lands on the US cash session plus the hour
into it. One statistic, one threshold, one contiguous run, no search.
The weekend skip is cruder and needs no threshold at all: 0.59x and 0.63x.
AN UNREACHABLE AGREEMENT THRESHOLD NOW SAYS SO AT INIT
SP500 M10 under AUTO ran a full year and traded nothing at agreement 2.
Nothing was broken. The Stocks book's three setups are SESSION-DISJOINT BY
DESIGN - opening reversal 16:30-19:00 server, ERBO 17:00-19:00, PDH/PDL
21:00-23:00 - so PDH/PDL can never be armed on the same bar as either
morning setup, at most two of three are ever eligible at once, and a quorum
of two needs a gap fill and a range break on one bar and one side.
WarnIfAgreementUnreachable() walks all 1,440 minutes, counts how many book
setups are ELIGIBLE at each, and takes the maximum. Firing is rarer than
eligibility and can only be rarer, so that maximum is a hard ceiling: a
threshold above it is unreachable with certainty, not merely unlikely. It
warns rather than refuses, so a sweep across rosters still scores the pass.
Measured today on BTCUSD H1 2024, equal weight, risk guard off:
AUTO agreement 1 391 RT -5.65 bp (research: -6.47 for one alone)
AUTO agreement 2 116 RT -2.11 bp
ALL agreement 2 269 RT -2.18 bp
ALL agreement 3 64 RT -5.13 bp
The 1 -> 2 improvement is the confluence mechanism reaching the EA for the
first time. It does not continue to 3, and nothing is positive yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three tester runs returned exactly the opening balance. The setups fired,
the agreement count counted them, the opposed veto fired 98 times over six
months of BTCUSD H1 - and not one order reached the broker.
m_entry and m_stop are ONE PAIR shared by both sides. LongCondition() and
ShortCondition() both run on every tick, and whichever runs second zeroes
the pair before running its own detection. So a setup that armed LONG had
its entry wiped by its own SHORT test microseconds later, and
OpenLongParams then shaped an order from a zero: RiskDistance() read 0.00,
and the 5 bp noise floor read 0.00 with it, because that floor is computed
from the entry.
Only the last step failed, which is exactly why it was invisible.
Latch (entry, stop, target multiple) per side at the moment the side arms.
The target multiple is latched too - the triangle's mouth, second wind's
leg projection and the gap fill all set it PER FIRING from a measured
distance, so it is per-side state for the same reason. The scratch pair is
now documented as detection-only.
Same BTCUSD window after the fix: 242 orders shaped, 90 deals, final
balance 99,212.96. It trades. Negative at agreement 1, which is the policy
the research measured at -6.47 bp on this instrument.
Two things the runs established on their own account:
- The opening reversal is a 20-minute construct and ERBO a 30-minute one,
computed as (minutes * 60) / PeriodSeconds. On H1 that is ZERO bars, so
both are permanently inert and SP500 M60 could never have worked. Setups
now declare the minutes they are built on, and ValidationSettings() says
so at init when the chart period does not divide them. M10 is the only
period that divides both 20 and 30, and its bars are twice M5's - which
is also what the risk-distance floor wants.
- DistanceIsTradeable() refused silently. It now prints the distance, the
broker floor and the 5 bp floor. Three months of nothing with no line in
the journal to explain it is not an acceptable failure mode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: "commission is automatically charged on mt5 during backtest, so
make sure to not include anything related to commissions in the EA. have as
little inputs variables as possible for a clean menu."
COMMISSION. Expert\CostModel.mqh deleted, the six Cost_Comm* inputs with
it, along with the per-bar spread sampler in OnTick and the init COST MODEL
printout. This removes a DUPLICATE, not the cost: MetaTrader applies the
broker's own schedule to every deal in the tester and on the account, so a
hand-typed second copy inside the EA could only ever disagree with it - and
a schedule that drifts from the broker's is worse than none, because it
looks authoritative. Nothing consumed the numbers any more in any case; the
gate that read them went with the era verdict.
Only the asset-class detector survived, moved to Expert\AssetClass.mqh. It
answers a question MT5 does not: which book applies.
THE ROSTER IS ONE INPUT NOW. Fourteen per-setup constants stood in
ClassicSignals.mqh, all false, none reachable without a recompile. They are
replaced by Book_Roster, chosen a BOOK at a time - which is the only
grouping that respects the accountability rule (one book per asset class, a
setup is only accountable on the class its own book covers) and the only
one the agreement count can use, since a roster of one cannot express it.
AUTO reads the class off the instrument. Eight tester values instead of
16,384 mostly meaningless combinations.
INPUTS 63 -> 22 (17 parameters and 5 section headers). Cut or made const:
everything dead after the training deletion, the two API keys (a credential
is not a strategy parameter and must not travel in a .set), and knobs that
cannot change an outcome - Signal_ThresholdOpen above all, since Direction()
now returns exactly 0 or +/-100, so every threshold in (0,100] behaves
identically.
TWO OF THOSE ARE FIXES, NOT TIDYING.
- The 30-bar signal cooldown was still live. Its floor came from the
leg-ride label ("a trade is held 5 + the median ZigZag leg = 18-19 bars").
That label is deleted, and the thing being spaced now is a book setup that
owns its trade end to end. Worse, it would have silently thinned the very
population the agreement count counts. Off, and const.
- Signal_MinAgreement defaults to 2 rather than 1. One trigger alone is the
policy measured NEGATIVE on every asset class.
The session filter is const off for the same class of reason: every setup
carries its own window from its own book, and a global one layered on top
applies one book's clock to another book's market.
SweepGuard's table was rebuilt - it was almost entirely names from the
deleted training layer. It now refuses Asset_Class (a statement about the
instrument, not a strategy choice) and pins the log-volume switches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fourteen setups on CBookSetupSignal with per-signal management, and the agreement count with the
opposed-veto. Records that the EA no longer chooses anything: the roster, the threshold, the
timeframe and the instrument are all Strategy Tester sweeps.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An armed setup used to be decisive on its own. That is exactly the policy the research measured,
and it is negative: across 942,918 journal trades, one trigger alone is -2.15 bp out of sample
and up on 2 of 8 instruments, two agreeing +5.65, three or more +7.59 at 240 trades a year and
8 of 8, monotone in all three periods. Gross moves as much as net, so it is not a cost artefact.
Direction() now counts DISTINCT setups armed on the same bar and side, over the same population
ArmedSetupOwner() picks the order-shaper from - so the count and the shaper can never disagree
about who fired - and takes the trade only at Signal_MinAgreement or more.
OPPOSED IS TESTED FIRST, because it is a veto and not a tie. Setups firing both ways on one bar
is the worst cell measured anywhere in this project, -5.80 bp pooled and -21.31 on BTCUSD,
negative gross as well, and worth more than the agreement bonus is. It stands aside and says so
once per bar.
Firings that arm but fall short of the threshold are traced rather than dropped: those declined
signals are the population the offline meta-label net has to learn take-or-skip from, so they
belong in the journal.
Signal_MinAgreement is an INPUT because choosing it is a Strategy Tester question - 2 buys far
more trades, 3 a better rate, and the walk-forward decides. It defaults to 1, which is the
measured-negative policy, only because that is also what a chart with a single setup enabled must
do; a silent no-trade would be worse than an honest bad default.
All three Walsh books state the principle in words, so this is the books' own rule rather than an
overlay on them.
Compiles 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The books are now implemented in the EA rather than only in the research repo. Fourteen setups
across four books, one class each, all on CBookSetupSignal:
FOREX inside bar (mother-bar entry), pin bar, engulfing, 5/30 MA cross
BITCOIN inside bar (own-bar entry, next bar only), pin-after-inside-bar, EMA10/21/100 + MFI(2),
second wind, triangle on rising volume
STOCKS opening reversal / gap fill, early range breakout, previous day's high-low
WYCKOFF spring, sign-of-strength bar
EACH BOOK'S OWN RULES, NOT AN AVERAGE OF THREE. The forex and bitcoin inside bars are separate
classes because they are separate setups: one enters at the MOTHER bar's extreme and leaves the
entry window unfixed, the other at the INSIDE BAR's own extreme on the next bar only. Collapsing
them measured the average of two books nobody wrote. The forex book's qualifiers are applied
rather than emitted raw - angled-and-not-flat average, inside bar smaller than its mother and at
the correct extreme, the engulfing bar's one-or-two-bar pullback taken with the trend.
MANAGEMENT IS PER SETUP because the books disagree: stocks breakeven at 1.25R (the corrected
constant), bitcoin's ratcheted trail, forex's ~1R breakeven and trail. Two setups carry MEASURED
targets set per firing rather than a fixed multiple - the triangle's mouth and second wind's leg
projection - which is what those books actually specify.
The session window moved from the stocks class into the base: a setup several books carry has a
window in one and none in another (stocks confine triangles to the afternoon, the Bitcoin book
states crypto has no session at all), so whoever constructs it says which reading is traded. The
base gates LongCondition/ShortCondition on it so no subclass can forget.
TWO THINGS DELIBERATELY NOT BUILT, and named rather than faked: zero bouncing, because the
round-number premise under it measured null against matched controls; and ERBO's "clear bias"
precondition, which is a discretionary read of the pre-market with no honest mechanical stand-in.
Second wind's book-stated stop is a round number for the same reason and is ours instead.
All default OFF and individually switchable - which roster to run is a Strategy Tester question,
not an EA one. Note they are useless alone: one trigger measured NEGATIVE on every class, and it
is three or more agreeing that pays, so the agreement count needs candidates to count.
Compiles 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The base class the operator asked for. A book setup now writes only its pattern; arming, the
entry window, order shaping, the time stop, breakeven, the ratcheted trail, the journal context
line and the per-setup adaptive-exit file are all shared.
PER-SIGNAL MANAGEMENT IS THE NEW PART, and it is why one global trailing object was never
enough: the books state management per setup and they disagree. Walsh's stocks book moves to
breakeven at 10-15 cents on a 5-10 cent stop (1.0-3.0R, centred 1.25R - the constant that was
inverted in research for a whole campaign); the Bitcoin book uses a RATCHETED trail and says so
in those words; the forex book trails at ten pips on a 5-10 pip risk. Added
CExpertSignalCustom::ManageTrailing() and wired CExpertCustom::CheckTrailingStop() to ask the
OWNING setup first, falling through to the global object only for positions no setup owns.
A setup returning false from ManageTrailing is an ANSWER, not an abstention - it wants the stop
left where the book put it - so the position is not then handed to CExpertTrailing to have a
different book's rule applied to it.
The ratchet is enforced by only ever returning a stop better than the one the position already
carries, rather than by remembering a peak in the EA. Same rule, and it cannot drift out of step
with the broker's copy across a restart or a re-init. The risk unit is recovered from the
position's own open price and stop, not recomputed from the pattern, because the bars that
produced the pattern may be long gone.
FIRST FOUR SETUPS, all from the forex book with its own qualifiers applied rather than emitted
raw: the inside bar (angled-and-not-flat 10-SMA, inside bar smaller than its mother, at the
correct extreme - only 14.2% of inside bars meet all of it), the pin bar, the engulfing bar (with
the one-or-two-bar pullback and the with-trend requirement that this project computed for months
and applied nowhere), and the MA cross.
ShouldTraceTradeRejections() moved private -> protected so a setup can log its own VOID.
Compiles 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
They read in the present tense about machinery that no longer exists, which is exactly the stale
comment this codebase keeps getting bitten by. Marked as the starting inventory, with the deleted
entries struck through and the survivors counted: the direction column is down to the armed-setup
override, the exit column from ten paths to eight, and the whole may-this-model-trade column is
gone because there are no on-chart models to gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Slice 2 was listed last and blocked on the wiring; the operator reordered it by retiring leg
training, and it is done. Records the seam, the 57% cut, the ClassicVotesMoveMoney trap, and the
fact that the next tester run should trade nothing until the book triggers are wired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to the training deletion. The whole "Neural Networks" input section survived the file
deletions and every one of its knobs was inert: Use_MLP / Use_CONV / Use_LSTM / Use_CONVLSTM,
Run_Alglib_Baselines, Use_Training_Pool and OOSSplit are inputs the operator sees in the dialog,
and after the cut none of them reached anything. Exit_On_Leg_Flip was the same - a switch for a
label that no longer exists. 132 lines, and Inputs.mqh drops 1,022 -> 891.
An inert switch in the Inputs tab is worse than a deleted one: it invites the operator to change
a setting and conclude the EA ignores them, which is close to what the complaint that started
this refactor actually was.
TWO SLOTS DELIBERATELY KEPT AS LITERALS so no existing database is re-keyed. DbLegacyAiSlot()
encoded which architectures were enabled and now returns the legacy AI_NONE value; the optimizer
slot is written as 1, ADAM's ordinal, which is what TrainingOptimizerDefault always carried
(ENUM_OPTIMIZATION pins SGD/ADAM at 0/1 precisely so this cannot drift). I first wrote 0 there,
which would have silently changed the fingerprint on every database this EA has written.
The trade journal's run label was the enabled NN roster ("MLP+LSTM"); it is now "Book".
Compiles 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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's complaint is three complaints with three causes, so this separates them and
orders the work. Written after reading the code and a 1,570,535-line tester log rather than
from the shape of the codebase.
The framing fact, verified in situ: the EA wires ZERO enabled book setups
(ClassicSignals.mqh:100, EnableInsideBarGap = false, and it is the only one that exists in
MQL5), while the research repo has 36 book triggers built and measured. What the EA trades is
the leg-ride NN vote. Almost every mechanism in the "whether to trade at all" column exists to
serve on-chart training and its deploy decision, so the deletions are downstream of moving the
net offline - and the wiring has to come first.
Records the confluence specification the research just produced, because it is what the EA
should be executing and what the meta-label net has to beat: one setup alone is negative
everywhere, three or more agreeing is +1.39 bp pooled OOS and +7.80 bp on BTCUSD at M60 after a
full 6 bp round turn, both sides firing is a veto, and the timeframe sets the base.
Also records, as the worked example of "mechanisms fighting", the 2026-09-06 sequence where a
setup asked for a buy stop at 2930.49 with a 14 bp stop and got a market buy at 2929.46 with a
2 ATR stop, closed twenty seconds later by the protective path. That log predates the three
fixes of 09-06/07 and the document says so - re-running it is slice 2, and slice 2 is left for
the operator because starting the fleet terminal unattended is not mine to do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator's complaint that "the logs get filled" is measurable, so it was measured. The
2026-09-06 tester log is 1,570,535 lines and FOUR print statements are 86.6% of it:
556,732 35.4% "Starting direction calculation with total filters: N"
556,731 35.5% "Final directional result: N"
247,146 15.7% the two "open rejected" traces
None of the four is a decision. Direction() runs on every tick and recurses into every filter -
about eight calls a tick on this fleet - and emits two lines carrying a filter count and a
number. The rejection traces fire on every tick a side is blocked, which on a one-sided chart
is forever.
They were all on the same switch as everything else, so turning VerboseMode on to diagnose one
thing produced a journal too large to search. Added a second level, TraceMode, off by default,
and moved exactly those four sites to it. PrintVerbose() keeps its meaning and none of its ~40
call sites changed.
TCLog's throttle was near-inert where it mattered most. It measured its window with
TimeCurrent(), which in the Strategy Tester is SIMULATED time: a pass over years of history
crosses sixty simulated seconds many times a second, so the throttle admitted nearly every call.
That is why 247,146 lines got through a function whose whole purpose is collapsing them. Now
GetTickCount64(), which is real elapsed milliseconds and behaves identically in both worlds -
unchanged in live trading, genuinely one line per key per minute of run time in a tester pass.
Suppressed calls are counted and reported on the next line through, so nothing is hidden.
Compiled clean in the stage copy: 0 errors, 0 warnings, against a 0/0 baseline taken first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The single most frequent line in a 180 MB agent log this session was
WarriorExpandFleetIfDue: ChartSaveTemplate failed (error 4003) - cannot expand the fleet
printed once per bar, for the whole pass. A tester run has one chart and cannot open another, so
ChartSaveTemplate fails every time and the sweep had nothing to do even when it succeeded. It now
returns immediately under the tester, optimiser and forward pass.
The print is also latched to once per run for the live case. The condition behind it does not
change between sweeps, so repeating it says nothing new and buries the lines that do - the same
one-shot latch the calendar probe and the pooled-gate writer already use.
This is the measured half of the operator's "the logs get filled"; the rest of that is the
per-era training diagnostics, which go with the training layer in the planned refactor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The second half of the sweep. Everything the first pass reported and left.
THE ENSEMBLE GATE JUDGED A POPULATION THE ACCOUNT NEVER SEES. It scored every OOS bar whose
vote cleared the rung; live, a vote that clears the threshold still has to pass
VoteCooldownAccept, and a suppressed bar produces no arrow, no order and no position. On this
fleet that window is 30 bars against a mean ride of ~34, so the certificate counted roughly an
order of magnitude more trades than the account could take, each overlapping its neighbours.
The member gate was fixed for exactly this on 2026-09-03 and both sites carried a comment
saying the ensemble still had the defect. The sweep now replays the vote cooldown per rung:
rows arrive in time order, so one kept-timestamp per rung reproduces it exactly. sweepTraded[]
carries precision, the book, the cost and the by-side counts; sweepFired[] stays the signal
population and only coverage reads it, because a cooldown caps traded coverage by construction.
The effN uses the declustered helper - the cooldown has already spaced that stream.
BOTH FAMILY-WISE SELECTION GATES FORMED THEIR SE ON RAW CALL COUNTS, the last SEs in the
project still undeflated for label overlap, which made the correction guarding the deploy
decision the most permissive test here. Now effective. This TIGHTENS both bars, which is why
it was left standing until asked for; the ensemble gate's own note already recorded that every
chart clears it by 6.5-12 sigma on effective calls, so the measured cost is nothing.
THE RIDE WAS PRICED AT BAR CLOSES NO ORDER CAN FILL AT. Entry was the close of the bar the
vote was formed on. Direction() runs on the first tick of the NEXT bar and the market order
fills there, which is that bar's open; the exit is read from closed bars and acted on one bar
after the flip. So the book credited every ride with two bar gaps and called it the trade the
EA places. Entry is now the next bar's open and the exit the open after the flip - the same
event, at the price the account gets.
RIDES LONGER THAN THE 200-BAR CAP WERE DROPPED, not capped: one-way survivorship against the
longest winners a trend-riding label has. A ride whose bars existed and simply had not flipped
is now marked to market at the cap. Only one that ran off the leading edge of loaded history
stays unresolved, which is the one case genuinely not knowable.
Both label changes re-key: TGT:LEG1 -> TGT:LEG2. Every model retrains from era 0.
Also: the minimum-stop floor wrote an un-normalised price (TCAdjustStops normalises only what
it widens, so a legal floored stop reached the trade layer off-tick); and the journal's context
window was 180 seconds, sized for the dead sixty-second order expiry, while an entry window is
counted in BARS - so any fill later than three minutes silently lost the context column the
per-pattern adaptation is built from.
Compiles 0 errors, 0 warnings. Smoke-tested on EURUSD H1 over 2026-08-24..09-01: runs clean,
no runtime errors, fingerprint reads TGT:LEG2:10:D12 in situ, and nothing trades - which is
the re-keyed label refusing the stale models, as intended. Not deployed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sweep after "still not profitable" found the EA was not trading the strategy it certifies.
THE VOTE. Direction() aggregated all children in one accumulator: 15 classic modules at the
stdlib weight of 1.0 each, the four AI members at trust weights of 0.17-0.22, and the derived
threshold (1% on most charts) had been certified on the AI members' vote alone. Four unanimous
networks netted 1.01; two classic modules confirming a state (10 + 10) netted 1.27 and opened
the trade with the networks silent. Proven in the tester on the old build: EURUSD H1 from
2025-09-01, threshold 1% published at 00:05, market buy 3.5 lots on the first bar, then vote
magnitudes of 4-6 that the networks cannot produce. The book that traded was the classic
consensus this project measured at chance; the certified book could barely open.
- ClassicVotesMoveMoney(): classic modules stay in pass 1 (journal, raw arrows, the vote vector
the networks read) and leave the money sum and the overlay whenever an AI member exists. With
no AI member they remain the book. Announced once.
- CExpertSignalAIBase::LiveVote(): the parent sums the AI member's certified contribution
(module weight x (tier - chance), clamped) instead of its raw tier weight.
- VoteCapableWeight() uses LongCondition's readiness test, m_deployedLive included: a deployed
member had numerator and no divisor share, which with the classics gone would have been a
division by zero live while the inference-only tester looked fine.
THE BOOK. The ride book the gate judged carried no stop; every live position carries one at the
published mean adverse excursion. The verdict now gates the ride under that stop (sideG*: a ride
whose adverse excursion reached it pays -stop) and prints both. Sweep line: [book|stopped|cost].
THE REST OF THE SWEEP.
- Scheduled close-all: a +-1 minute window with no catch-up, and Processing() ran on the same
tick with the cached vote, so it could re-open five minutes before the weekend. Now a per-day
latch from target-1 min, retried every tick and timer, and OpenPosition refuses while latched.
- News filter: an empty CalendarCountries() answer (the base still synchronising) was cached for
the session, leaving the filter inert with EnableNewsFilter true. Not cached any more.
- NF_MinImpact default HOLIDAYS vetoed 50% of EURUSD weekday hours (36% GBPUSD, 38% USDJPY,
27% USD-only), measured on the terminal's own calendar export; HIGH vetoes 14/12/11/9%.
Default -> HIGH. Charts attached before keep their stored value.
- One m_tradeOwner for both books delegated the short book's exit to a long-only setup, whose
default CheckCloseShort fell into the base vote exit on the child (m_direction EMPTY_VALUE =
DBL_MAX >= any threshold): every short died the tick after the inside bar armed. Per-side
owners, and the stdlib's EMPTY_VALUE guard restored in CheckClosePosition.
- One expiry clock for two books -> per-book m_bookExpiration[2].
- The inside bar's time stop selected the lowest-ticket position on the symbol -> by book magic.
Build tag inputs-not-voters-1. Compiles 0 errors, 0 warnings. Not deployed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator's report, and it is correct: ImpactWeightedProximity weighted by
MqlCalendarValue.impact_type, which is MT5's ACTUAL-versus-FORECAST verdict
(ENUM_CALENDAR_EVENT_IMPACT: 0 NA, 1 POSITIVE, 2 NEGATIVE) and is only
knowable after the release. With searchForward=true on a historical bar it
read the post-hoc outcome of an event that had not yet happened - the exact
thing the comment above it claimed was "deliberately not exposed anywhere
here". The comment stated the right principle and the code contradicted it.
Verified against the terminal's own 200,510-release export rather than
argued from the docs:
- impact_type is 0/1/2 and NEVER 3, so the /3.0 divisor capped the feature
at 0.667 and 1.0 was unreachable. Exactly as reported.
- of 16,103 rows where nothing had been released (no actual value), 16,079
- 99.85% - carry impact_type 0. It is a function of the outcome.
- it is near-orthogonal to importance: 8,783 of the 13,662 HIGH-importance
releases (64%) carry impact_type 0. The feature scored ZERO on two thirds
of the biggest events on the calendar, while scoring its maximum on a
trivial event that happened to surprise.
THE SAME FIELD WAS ALSO DRIVING THE LIVE NEWS FILTER, and there it is worse.
NF_IMPACT_PRESETS is plainly the importance ladder - HOLIDAYS=0, LOW=1,
MEDIUM=2, HIGH=3, which is ENUM_CALENDAR_EVENT_IMPORTANCE exactly - and it
was being compared against impact_type. Since impact_type never reaches 3,
selecting "High Impact News", the obvious choice for anyone wanting to avoid
major news, made the test unsatisfiable and SILENTLY DISABLED THE FILTER: the
EA would trade straight through NFP with the news filter on and set to its
strictest setting.
Both now read the event's scheduled importance via CalendarEventById, which
is published in advance, takes the full 0..3 the divisor was written for, and
makes the forward-looking half honest - a training bar may know NFP is due in
twenty minutes, because everyone did. Lookups are cached in a sorted array;
a failed lookup is NOT cached, because an unsynchronised calendar base fails
transiently and would otherwise pin an event to 0 for the session.
Topology gains |NEWSV:2 so a model trained on the old leaky feature can never
load against the new one - UseNews and NewsFeatureWindowMinutes could not
catch it, because neither of them changed. Conditional append, per the rule
the MACD/Ichimoku block states, so a fingerprint with news OFF stays
byte-identical.
BEHAVIOUR UNDER THE SHIPPED DEFAULTS IS UNCHANGED, which is why no retrain is
triggered: EnableNews is false, so the feature is off and NEWSV appends
nothing; and NF_MinImpact defaults to HOLIDAYS=0, where ">= 0" vetoed every
relevant event before and does so now. What changes is that every other
preset now means what its label says.
Compiled clean in the _claude_s2build scratch copy: Result: 0 errors, 0
warnings. NOT DEPLOYED - feedback_no_compiling authorises compiling in a
scratch copy and forbids touching the deployed build; _claude_stage's .ex5 is
untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The export now works: 200,507 releases of 1,494 distinct events, 2010.01.01 to
2026.09.06, written on all eight charts and loading clean through
research/newsdata.py.
What it actually was: none of the four query variants. The identical
(NULL, NULL) call that returned zero rows at 16:12 returned everything at
16:22 - the calendar base had simply not finished synchronising, which the
original failure message offered as a possibility and which I then talked
myself out of, twice: first into "this broker has no calendar feed" (wrong -
the Calendar tab comes from MetaQuotes, only the News tab is the broker's, and
the empty news.dat I reasoned from was the wrong subsystem), then into "the
query is being refused" (also wrong).
So the variants stay - a terminal that genuinely refuses the query is still
something this can meet, and a silent zero is precisely the failure that cost
hours - but the normal path must not pay for them in log noise. The plain call
now runs SILENTLY, and only when it comes back empty does anything print, at
which point it prints everything: CalendarCountries(), then each variant with
its n and error. The failure message leads with synchronisation, since that is
what it actually was.
Compile-verified in _claude_stage: 0 errors, 0 warnings.
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>