Commit graph Warrior_EA/Signals
Author SHA1 Message Date
AnimateDread
47a5ef338b Refactor Warrior EA: Integrate custom signal modules, enhance voting mechanism, and improve management features
- 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.
2026-09-13 14:32:40 -04:00
AnimateDread
5f8a2b1df8 Remove obsolete log and data files: deleted cpu_directml.log, opencl.log, and profiling.csv to clean up the repository. 2026-09-13 14:32:28 -04:00
AnimateDread
ac57a720e0 wip: snapshot before the KISS restructure
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>
2026-09-11 06:16:08 -04:00
AnimateDread
e80993f10b feat(book): daily and weekly charts are swing books - no session window, no weekly flat
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>
2026-09-09 13:17:00 -04:00
AnimateDread
649ca53fa9 fix(book): the plan line applies the management policy before it prints
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>
2026-09-08 21:14:04 -04:00
AnimateDread
6376720fe0 feat(book): Book_Management input - as the book states, or stop/target/time stop only
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>
2026-09-08 21:12:04 -04:00
AnimateDread
034301bbc0 fix(book): R is the original risk - breakeven was ending management on the first move
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>
2026-09-08 20:17:29 -04:00
AnimateDread
10463eba2f feat(meta): the meta-labeller - a random forest in pure MQL5, learned from the shadow journal
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>
2026-09-08 18:41:21 -04:00
AnimateDread
af3db90353 feat(journal): the shadow journal - every firing, independently, next to the trades taken
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>
2026-09-08 18:32:38 -04:00
AnimateDread
ad327735db feat(journal): the tester fills the database, and the journal can skip a categorical loser
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>
2026-09-08 17:36:02 -04:00
AnimateDread
8097ccd9a0 fix(book): a short on a netting account was invisible to its own time stop
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>
2026-09-08 12:11:15 -04:00
AnimateDread
3ee32784f3 fix(tester): the EA was long-only because every run inherited tradingdirection=LONG_ONLY
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>
2026-09-08 11:45:35 -04:00
AnimateDread
4da7bf322d feat(book): don't buy into resistance - the headroom rule, measured
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>
2026-09-08 10:15:44 -04:00
AnimateDread
19bef6dbd4 test(verdict): a decade says the spread is the whole gap
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>
2026-09-08 07:01:34 -04:00
AnimateDread
7abad65c80 feat(book): port the seven missing triggers, and the classic crowding veto
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>
2026-09-07 23:18:40 -04:00
AnimateDread
c961f5c8d7 feat(audit): the EA states its resolved trading plan at init
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>
2026-09-07 22:42:31 -04:00
AnimateDread
0c58e55173 feat(adapt): build the writer half of the live-learning loop
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>
2026-09-07 22:37:54 -04:00
AnimateDread
6c375ee462 fix(forex): drop the Tokyo window, and close the "open design question"
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>
2026-09-07 21:13:16 -04:00
AnimateDread
5418a5e326 feat(book): port the session findings research had and the EA did not
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>
2026-09-07 21:06:51 -04:00
AnimateDread
534d622ae5 feat(book): give crypto a measured clock, and say when a quorum is impossible
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>
2026-09-07 20:45:58 -04:00
AnimateDread
d685a3f952 fix(book): the entry price was wiped by the setup's own other side
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>
2026-09-07 19:57:56 -04:00
AnimateDread
f7b12413a4 feat(book): fourteen book setups as signal modules, each owning its own management
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>
2026-09-07 17:53:40 -04:00
AnimateDread
213ba83e97 feat(book): CBookSetupSignal - a setup owns its entry, its exit AND its management
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>
2026-09-07 17:46:40 -04:00
AnimateDread
4e5e6fcae3 refactor(ea): delete on-chart training - the EA stops learning and starts executing
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>
2026-09-07 17:23:36 -04:00
AnimateDread
84d0af6e91 fix(vote): the classic modules were the live book - inputs, not voters; certify with the live stop
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>
2026-09-06 23:07:53 -04:00
AnimateDread
a765e0f61f fix(news): weight by scheduled importance, not the post-release verdict
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>
2026-09-06 19:27:33 -04:00
AnimateDread
eb2a443582 fix(setup): the entry window is measured in BARS, as every book states - the 60 seconds came from an artefact
No book counts an entry window in seconds. The Forex book offers next-bar-only, next-2, next-3 or
unlimited and deliberately refuses to fix one (p199); the Bitcoin book fixes the immediately
following bar and says the setup is dead if it does not trigger there (p117).

The sixty seconds in this class was fitted to a measurement artefact, not read from a book: the
fast-fill population it selected for was 87% session-gap fills booked at the mother-bar high while
the market had opened far above it, which is where the whole apparent +0.398 R came from. Removing
the artefact removes the parameter.

m_entryWindowBars defaults to 1 - the tightest reading either book offers - and the expiry is
computed from the START of the bar the order is placed on. The per-tick expiry clock and the GTC
fallback in CExpertCustom stay: those were correct and are general to any setup with a window.

The class header still advertised the sixty seconds as the validated rule; corrected.

Compile-verified in _claude_stage: 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 15:59:08 -04:00
AnimateDread
c5da78dff3 feat(setup): the level is a breakout - if the market is already through it, we PASS and log it
Operator: "it's okay to miss certain entries as well, if we do not get filled, we log and wait
for the next setup. that's discipline."

This entry requires the market to trade UP THROUGH the mother bar's high. When the session has
already opened beyond it - on SP500 that is the 01:00 open after the daily break, and it is
most of them - a buy stop is impossible and the trade layer routed a buy LIMIT instead: a
pullback entry the books never described and the research never measured. LongCondition now
declares the setup VOID in that case, logs it with its full 48-feature context, and waits.

That is not a safety measure, it is the better book. Measured on SP500 M60 2019-2026:
  all signals   chased +0.049 (n=3,244)  ->  disciplined +0.060 (n=2,518, missed 22%)
  the rule      chased +0.063 (n=  185)  ->  disciplined +0.066 (n=  102, missed 45%)

Every occurrence of the pattern is now written to Adapt\features_{SYM}_{PERIOD}.csv with a
status - taken, void_gapped, void_network - so what we passed on is exactly as analysable as
what we took. The passes are data; that is the point of the journal.

The class header carried the old headline (+0.116 / +0.285 out of sample, "the order expiry is
the load-bearing part"). It has been rewritten to what is actually true: 87% of the rule's
minute-0 fills were gap-throughs booked at a price never offered, the SP500 92.6% win rate was
the tell, and honestly priced the book is +0.044 R at 52%. EnableInsideBarGap stays FALSE.

Compile-verified in _claude_stage: 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:58:41 -04:00
AnimateDread
92fa576224 feat(setup): the setup owns its trade - delegation, network confluence, feature port, expiry fallback
TESTER-FOUND, NOT YET TESTER-CLEAN. Compile-verified (0 errors) in _claude_stage; run 7
(SP500 H1, 2022-2026) shows buy stops shaped 1.25R/8R/60s, the 15-minute time stop
closing them, and the network gate active. Two defects remain open - see the handoff
memory project_handoff_20260906_opus.

WHAT WAS WRONG. The parent CExpertSignalCustom never called a child's OpenLongParams
or CheckCloseLong: once the inside-bar setup voted, the parent shaped a MARKET order
from its own ATR rule (2 ATR stop, no target) and closed it on the vote 9-35 hours
later, or on the very next tick through the protective path. And with the fifteen
classic voters on, the setup's 100 netted to ~11 against a threshold of 15.

Expert/ExpertSignalCustom.mqh
  OwnsTrade() / SetupArmed(isLong) virtuals; ArmedSetupOwner() scans m_filters.
  OpenParams() asks the armed owner for price/sl/tp/expiration first and records
  m_tradeOwner + m_shapedExpiration; the ATR path clears them.
  CheckClosePosition() with an owner returns the owner's CheckCloseLong/Short only.
  Direction(): an armed setup is decisive (+/-100), never diluted by the classics.
Expert/ExpertCustom.mqh
  OpenPosition(): on TRADE_RETCODE_INVALID_EXPIRATION resend GTC and set
  CExpert::m_expiration to the requested expiry. KNOWN DEFECT: that clock is only
  read on the new-bar path (Expert_EveryTick=false) so orders lived 30 min, not 60 s.
Signals/SignalInsideBarGap.mqh
  OwnsTrade/SetupArmed overrides, m_armedBar; CheckCloseLong no longer chains to the
  vote exit; CNNFilter + CInsideBarFeatures wired as the confluence gate (file
  Common\Files\Warrior_EA\Adapt\_ALL__inside_bar_u.nn, feature names checked one by
  one); nn_gate/nn_a/nn_b/nn_agree in the journal context; verbose-mode feature dump
  to Adapt\features_{SYMBOL}_{PERIOD}.csv for research/check_feature_parity.py.
Features/InsideBarFeatures.mqh (new)
  The 48 network inputs computed on a chart from closed bars, in the file's order.
  First parity pass on 13 bars: 30/48 agree, 18 drift - NOT trusted yet.
System/NNFilter.mqh (new)  two groups of dense nets, agreement = both >= threshold.
System/AltData*.mqh
  cot_am_idx3y / cot_lm_idx3y / vix_rank500 appended to the catalog (export v3),
  raw COT cache widened to asset-manager long/short (old caches refetch), HasColumn().

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 12:16:41 -04:00
AnimateDread
4004c48d1b feat(journal+signals): the journal becomes a research journal, the setup becomes a voter, and the exit adapts
THE VOTER FIX, found in the strategy tester and worth stating plainly because it
will bite the next standalone setup too. CExpertSignalCustom weights each child
by VoteCapableWeight(), which is m_weight only when GetPatternCount() > 0 and
ZERO otherwise: a child with no patterns is a news/session-style VETO with no say
in the consensus. CSignalInsideBarGap declared none, so its first tester run
fired 192 times over 2019-2022 and placed nothing - it voted 100 with a capable
weight of 0 and the accumulator divided by zero into 0.00 on every bar. It now
carries one pattern at weight 100, an ID, and its used-series mask, exactly as
every classic module's constructor does. Tester validation of the fixed build is
in progress; this commit is compile-verified only (0 errors, 0 warnings, in a
scratch copy inside the MQL5 tree - the repo path cannot resolve Network.cl).

THE JOURNAL. Two TEXT columns, added IN PLACE with ALTER TABLE (new
EnsureColumn on the database managers) rather than a dbVersion bump, which wipes
the directory:
  context   key=value;... the setup's view at signal time, the same terms the
            research conditioned on, named identically (PublishContext). Accepted
            by the journal only when published within 180 s of the position
            opening, so an expired order's line cannot be inherited by a later
            position from another signal.
  passages  first-touch MINUTE of every stop and target level on the research
            grid plus signed R at each time-stop horizon - the exact fields
            research/pricing.py prices an exit from. A synthetic round-trip
            proved the live format re-prices the traded exit identically to the
            research (max |diff| 4e-7 R) once the horizons carry six decimals.
One honest limit is written into the file header: a live trade is closed by its
own barriers, so from live rows an exit can only be re-selected TIGHTER.

THE ADAPTIVE EXIT. CSignalInsideBarGap reads
Common\Files\Warrior_EA\Adapt\{SYMBOL}_{PERIOD}_inside_bar_u.cfg once per server
day, written by research/journal_adapt.py with the research's discipline (choose
on the older trades, confirm on the newer, placeable stop, stable after dropping
the best 5%). No file means the validated defaults; a file outside the research
grid is refused and logged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:23:27 -04:00
AnimateDread
358c8b74e0 feat(signals): CSignalInsideBarGap - the first research-validated standalone book
M60 inside bar, buy stop at the MOTHER bar's high, risk down to the inside bar's
low, stop 1.25R, target 8.0R, 15-minute time stop, and a SIXTY-SECOND ORDER
EXPIRY that carries the edge.

Validated in Warrior_Research (RESULTS.md, tag promising-results-20260906) on
2003-2026 across eight instruments, selected pre-2019 and measured on 2019+:

  unfiltered            +0.116 R/trade (t 4.57, n 1,203)
  with the context rule +0.285 R (t 6.45, n 412), and 2022+ +0.309 - stronger
                        recently than in sample

The expiry is the finding, not the pattern. By time-to-fill, orders filling in
the first minute paid +0.398 R in sample and +0.116 out; every later fill paid
-0.004 and -0.015. Only ~4% fill that fast. Being an ORDER parameter rather than
a model feature, it is causal by construction and needs no run-time inference.

Maps onto the existing architecture without changing it: LongCondition() fires on
the closed bar, and OpenLongParams() returns a price ABOVE the market, which is
what makes CExpert place a pending buy stop - the order type the research
measured, and one that fills at its own level instead of crossing the spread.
The expiration is set absolutely to 60 s rather than the base class's whole
chart period, because that is the whole point.

Definitions match the research exactly and are pinned in the class comment, since
they are easy to "tidy up" into something that no longer matches what was
measured: entry is the MOTHER bar's high, and valid_test is Wyckoff p118 - volume
below EACH of the two preceding bars, a two-bar lookback and not a moving average.

EnableInsideBarGap DEFAULTS TO FALSE. Unlike the vote modules it sits beside, this
fires at weight 100 and names its own entry, stop and target, so enabling it
changes what the expert trades rather than how it weighs an opinion. Shorts return
0 deliberately: the mirror setup measured +0.054 R against the long side's +0.285.

Compiles clean - the expert built with exactly the same single pre-existing error
(Network.cl resource path, unrelated) and zero warnings, before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 02:22:13 -04:00
AnimateDread
095bd27a67 feat(signals): restore CCI, Stochastic, WPR, RVI and Parabolic SAR
Five more modules, 16 patterns, recovered from 1073262 where they had already been ported to
CExpertSignalCustom - so this is restoration rather than a fresh port. Same dead
SweepPrepareIndicator override stripped as before.

WEIGHTED ON THE SAME LADDER, and deliberately IDENTICALLY where the construct is identical: CCI,
Stochastic and WPR repeat the RSI shape - direction 10, overbought/oversold reversal 20-25,
divergence 60, double divergence 90 - because they measure similar things by similar means. Giving
one a higher number than another would invent a distinction the literature does not make, and would
be the first step back toward fitting the weights.

SAR is the exception worth noting: its pattern 0 is a persistent STATE (the dots are on one side and
stay there for many bars, 10) while pattern 1 is the SWITCH - a discrete, dated event and the only
thing SAR offers as an entry, so 50.

Running total: fifteen voting modules, 60 patterns, all on fixed priors. Ten are classic/stdlib
constructs and five are the Bill Williams suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 08:21:57 -04:00
AnimateDread
c99020abb7 feat(signals): complete the Bill Williams suite - Alligator, Fractals, Gator, BWMFI
Four new modules, 11 patterns, built on the stdlib CiAlligator/CiFractals/CiGator/CiBWMFI
indicator classes and ported to CExpertSignalCustom like the rest.

ALLIGATOR (4). Three smoothed averages of median price, displaced forward - 13/8, 8/5, 5/3, which
is Williams specification rather than a choice. Patterns encode his own reading: ordered lines are
context (10), a lips/teeth cross is the trigger (40), price clear of the lips with the mouth still
opening is the trend running (45), and AWAKENING - previous bar intertwined, this one ordered -
carries the most weight (55) because it times the transition instead of reporting a state that may
have been true for twenty bars. A SLEEPING alligator produces no vote at all rather than a weak one.

FRACTALS (3). A five-bar pattern, and the two-bar confirmation delay is the whole difficulty: a
fractal at bar i is only knowable at i+2, so reading it at i is exactly the lookahead that faked a
+4 sigma reading in bd076dd and had to be found twice. FRACTAL_CONFIRM_BARS is named rather than
inlined and every scan starts at from+2.

GATOR (2). The Alligator restated as two magnitude histograms, so it carries NO direction - it says
whether the market is trending or resting, never which way. Direction is borrowed crudely from the
bar close and the weights are low to match. Deliberately a regime filter, not an entry.

BWMFI (2). Williams four bar states from the movement/volume pair - green, fade, fake, squat. Only
green (20) and squat (50) vote; fade and fake are explicitly not trades. HONEST LIMIT: on FX and
CFDs MT5 reports TICK volume, not traded size, so green means many ticks rather than much money.
That weakens the construct and the weights are set below what an exchange-traded instrument would
justify. VOLUME_TICK is not a preference either - VOLUME_REAL is zero on these feeds and the index
would divide by it.

With the six restored modules this is 44 patterns across ten voting modules, all on fixed priors,
with the database recording outcomes and no longer writing weights back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 08:15:34 -04:00
AnimateDread
3ed053e3a8 feat(signals): restore the classic votes as a meta-labelling PRIMARY, with fixed weights
WHAT AND WHY. ed91919 removed the four classic votes because all 26 patterns measured AT CHANCE as
standalone entries - pre-registered, nothing fitted, and the +4 sigma that had once appeared was two
bars of lookahead. That result stands. It is also not the claim being made here.

That test measured UNCONDITIONAL edge: fired blind, does this pattern beat a coin on average. No -
after the lookahead fix MACD_p4 read -0.02pp at -0.02 sigma, flat rather than weak. A signal that is
zero on average can still be strongly positive on a SUBSET, and finding that subset is exactly what
meta-labelling is for. The primary supplies direction and an entry BAR and is judged on RECALL; the
net decides which firings to take.

THE MEASURED PROBLEM THIS ATTACKS. Under the leg-ride label the primary was "the ZigZag leg in
progress", so entry landed on an ARBITRARY bar inside a move already underway. Measured 2026-09-05
on the converged fleet: the ORACLE ride is +4.2 to +4.6 ATR per leg on every chart, and the models
captured +0.08 to +0.38 over a 14-17 bar hold - under 10%, because most of the leg was gone before
entry. Always-ride scores -0.14 to +0.10, i.e. an arbitrary entry inside a leg is worth nothing. A
pattern fires at a CHOSEN bar with the move ahead of it.

RESTORED: SignalMA (4 patterns), SignalRSI (4), SignalMACD (6), SignalIchimoku (12),
OscillatorDivergence, plus Bill Williams SignalAO (4) and SignalAC (3) recovered from 1073262.
33 patterns across six voting modules. The only edit needed was dropping SweepPrepareIndicator(),
whose base-class method no longer exists.

THE WEIGHTS ARE FIXED PRIORS (Variables\ClassicSignals.mqh), set from structural strength and rarity
in the spirit of the standard library's own ladder, NOT fitted to returns:
    10        confirming state ("price is on the right side") - every module's pattern 0
    15-30     simple state or weak-grade event (overbought reversal, cross in a poor location)
    30-50     a crossing or completed pull-back
    50-70     structural: divergence, cloud breakout, strong-grade cross
    80-100    rare confluence: double divergence (90), Sanyaku Kouten (95)
33 numbers tuned against measured returns would be 33 free parameters and would hand back the
family-wise problem this project keeps rediscovering. They are priors and must stay priors.

AND THE FEEDBACK LOOP IS CUT. DB_RankingFeedsWeights=false splits the signal database in two:
ProcessBufferedSignals() still RECORDS every firing, direction, entry, exit and outcome - that
corpus is the input to the meta-labelling work - while UpdateSignalsWeights() no longer writes win
rates back into the pattern weights. That loop is why the classic votes were never evaluable: the
same setup contributed a different amount at different times, so the vote drifted era to era under
the model and nothing could be measured against it. UseDatabaseRanking gates BOTH halves, so turning
the master switch off would have stopped the collection too.

RARITY CUTS BOTH WAYS, recorded rather than glossed: double divergence earns 90 BECAUSE it is rare,
which also means the net will see very few examples and can say little about it. The prior carries
those patterns; the model will learn mostly about the common, low-weight ones.

Compiles clean. Not yet deployed - the Bill Williams suite (Alligator, Fractals, Gator, BWMFI) and
the stdlib ports land next, and one restart should carry all of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 08:09:30 -04:00
AnimateDread
954020b261 feat(optimizers): RMSprop, Nesterov and AMSGrad across all four backends
THREE NEW OPTIMIZERS, APPENDED TO ENUM_OPTIMIZATION (never inserted - the ordinal is written into
every .nnw and hashed into the weights filename, so renumbering would silently reinterpret every
saved model). SGD=0 and ADAM=1 keep their values.

WHAT EACH ACTUALLY COSTS, because two of the three are nearly free and it is worth saying why:

  RMSPROP  = the EXISTING Adam kernel with beta1 = 0. With no first moment mt collapses to the raw
             gradient and the step becomes lr*g/sqrt(v), which is RMSprop exactly. Not one kernel
             was added in any backend - it is a scalar, not an algorithm. The bias correction had
             to move into WarriorOptLearnRate() because the literal (1 - pow(beta1,t)) denominator
             is (1 - 0^t) = 1 for t>=1 but ZERO at t==0, a division by zero on an unadvanced
             counter.
  NESTEROV = the momentum kernels' `optimizer` argument, which had been retained-but-unused since
             the DFA entry was deleted, finally selects something. Heavy-ball applies the velocity;
             Nesterov applies lr*g + mu*v_new (the Bengio/PyTorch reformulation, which expresses NAG
             without evaluating the gradient at a shifted point). No new state, no signature change
             on three of the four momentum entry points.
  AMSGRAD  = the only one needing extra state: a non-decreasing denominator needs the running max of
             the second moment (Reddi et al. 2018 - Adam can fail to converge because a rare large
             gradient inflates v and then DECAYS away, so the effective step GROWS again just after
             the event that should have shrunk it).
             IT REUSES DeltaWeights AS THE v-MAX. The Adam family already frees that buffer, it is
             the same element count, already zeroed by ZeroOptimizerBuffer, already saved and
             loaded. Net memory versus SGD: zero. That mattered: an extra per-tensor allocation is
             exactly what took this machine down twelve hours ago.

FOUR TIERS, IN LOCKSTEP, WHICH IS THIS FILE'S OWN STANDING RULE:
  * MQL5 host (NeuronCPU.mqh): all three, with CConnection gaining a vMax field (persisted
    unconditionally - CConnection cannot know which optimizer owns it, and a conditional would make
    the record length depend on state the reader does not have yet).
  * CPU DLL (WarriorCPU.cpp + .h): Nesterov in all four momentum kernels; a nullable vm pointer in
    all four Adam kernels (vmHandle < 0 leaves it null, so every non-amsgrad caller is
    byte-identical to before). Rebuilt and deployed.
  * MQL5 host FALLBACK inside ApplyAccumToBlock: the same amsgrad branch. This is the path the DLL
    drops to when the element-wise apply fails, and a model that trained differently depending on
    whether the DLL was healthy would be unreproducible.
  * OpenCL (Network.cl): Nesterov via the same `optimizer` argument; matrix_vm + an amsgrad flag on
    all four Adam kernels, bound to a harmless dummy buffer when off so there is still ONE kernel
    per operation, matching the DLL's shape rather than forking a second set.

THE OPENCL TIER IS UNVERIFIED AND SAYS SO AT RUNTIME. This machine has no OpenCL device ("cannot get
OpenCL platforms / opencl.dll not found"), so the CPU-DLL tier is what actually executed these. The
kernels are written and wired; they have never run. A throttled line names that on first use rather
than implying a parity that was never tested.

THE BINDINGS THAT WOULD HAVE BROKEN SILENTLY, and why the predicates exist:
`optimization == SGD` and `== ADAM` appeared at 34 sites and meant three different questions - which
buffers to allocate, which to persist, and which step to take. An unlisted optimizer would have
taken the ADAM branch for its buffers and the SGD branch for its persistence. They are now
WarriorOptUsesMoments / WarriorOptUsesDelta / WarriorOptIsMomentumStep, and the allocation and
persistence sites ask TWO INDEPENDENT questions instead of one if/else, because AMSGRAD is the first
optimizer that needs both families.

ASSIGNED PER ARCHITECTURE, so the ensemble's members now differ in their optimizer as well as their
shape - four members that fail in correlated ways average to nothing, and member correlation
(printed every era, r 0.19-0.36) is the measurement that says whether this bought diversity:
  Perceptron/MLP -> NESTEROV  (no structural prior, so a non-adaptive step is the implicit
                               regulariser that makes it the conservative ANCHOR vote; the lookahead
                               damps heavy-ball's overshoot at zero extra state)
  Conv           -> ADAM      (unchanged - AdamW already)
  LSTM           -> AMSGRAD   (the most heavy-tailed gradients here: shared weights across timesteps
                               mean one bad window contributes a burst of correlated updates, which
                               is the regime AMSGrad was derived for)
  ConvLSTM       -> RMSPROP   (two very differently-scaled gradient sources; per-weight
                               normalisation without a first moment stops the conv stage's momentum
                               dragging the recurrent one)

VERIFIED IN SITU, not by compiling: 5 charts, 18 eras, zero UpdateWeights failures, zero fallback
warnings, one fingerprint per architecture (PAI-0bd8 / CONV-5327 / LSTM-44d1 / HYB-452a). Commit
3,975 MB against 29,982 MB headroom.

COST: every model re-keys and retrains, the optimizer being a field of the weights filename.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 23:47:19 -04:00
AnimateDread
0405f1e8a4 feat(training): optimizer is per ARCHITECTURE, and the era cap goes 100 -> 10000
TWO OPERATOR REQUESTS.

1) THE OPTIMIZER IS NO LONGER AN INPUT. One input applied one optimizer to all four ensemble
   members, which is the opposite of what a voting ensemble wants - members that fail in correlated
   ways average to nothing. Each model class now names its own via the new virtual
   PreferredOptimizer(), so the choice lives with the architecture instead of in a global switch.

   It also had to stop being an input on this project's own rule: it is retrain-forcing (a field of
   the model filename), and retrain-forcing values are not inputs, because MT5 stores inputs PER
   CHART and an already-attached EA ignores a changed default - the exact trap that cost a deploy
   cycle in 2679b02.

   ASSIGNED WITHIN WHAT IS ACTUALLY IMPLEMENTED, and no further: only SGD (heavy-ball momentum) and
   ADAM exist here, and ADAM already carries DECOUPLED weight decay - it IS AdamW. RMSprop, Nesterov
   and amsgrad are NOT implemented and are not pretended to be.
     Perceptron / MLP        -> SGD, the deliberate odd one out. No structural prior, so it overfits
                                fastest and adaptive steps make that worse; SGD is an implicit
                                regulariser and makes it the conservative ANCHOR vote.
     Conv / LSTM / ConvLSTM  -> ADAM (AdamW).

   The learning rate follows the optimizer: InitialEtaForOptimizer() now TAKES it instead of reading
   the global, which would have handed every member the same LR and re-created exactly the
   uniformity this change removes.

   THIS IS ALSO THE CHEAPEST TEST OF THE DIVERSITY HYPOTHESIS. Member correlation is already printed
   every era - currently r 0.19-0.36 across the fleet, i.e. 4 models worth ~2.2 independent ones. If
   varying the optimizer moves that number, implementing RMSprop/Nesterov/amsgrad is justified by
   measurement; if it does not, the hypothesis is refuted for the price of one config line. I expect
   a small effect: the members already differ by ARCHITECTURE and still correlate at 0.29 on
   average, so the shared input vector and shared label look like the real cause.

2) ERA CAP 100 -> 10000. 100 was arbitrary and far too low - an era here is a full pass over ~179k
   bars, and networks of this shape are normally given thousands of them. It no longer terminates
   anything either (that was removed the same day), so all a low cap did was fire a redundant deploy
   and a log line every 100 eras while charts sat past era 260.

DB key note: the chart-level config fingerprint now hashes TrainingOptimizerDefault rather than any
per-member value, which is correct - per-member differences live in each model FILENAME - and leaves
every existing database key byte-identical.

COST: PAI re-keys to era 0 on every chart, and the ensemble lead cap will hold its siblings until it
catches up. Done now deliberately: the terminal is already restarting for an unrelated MT5 live
update, so nothing converged is being discarded that was not restarting anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 20:16:37 -04:00
AnimateDread
b512b3aa18 perf(signal): gate the VOTE on a new bar, keep the VETOES per tick; cache the news filter
Operator: "nothing should run every tick if expert_everytick is false." Almost
right, and the exception is why this is a split rather than one guard.

VOTE filters (GetPatternCount() > 0 - the AI members) read dPrevSignal, which
RefreshConvergedSignal() only refreshes when a bar closes. Re-running them per
tick recomputes the same number from the same inputs. Now gated on CNewBar, with
their contribution carried in the member scratch array across the bar's ticks.
Journaling and the raw draw are skipped with the call, deliberately: both read
state only a real Direction() call refreshes, so running them against a cached
value would re-journal one bar's pattern under many timestamps.

VETO filters (GetPatternCount() <= 0 - news, session, risk guard) signal by
returning EMPTY_VALUE from that same call and what they answer CHANGES WITHIN A
BAR. An entry can be placed on any tick, so freezing them to bar granularity
would let a trade through a news window that had already opened - and the risk
budget is required to enforce at quote frequency, not per bar. They stay live. A
cached veto still vetoes for the rest of the bar. Expert_EveryTick=true disables
the gate entirely, which is what that input means.

The abort rollback now revokes only filters actually entered this tick:
RevokeVote() restores a BeginVote() snapshot, and revoking one that never ran
would roll it back to a stale one. An aborted pass also drops the cache.

THE NEWS FILTER WAS THE EXPENSIVE VETO, and it had no cache at all: one
CalendarValueHistory() per relevant country on every call. Now cached to the
minute, which is EXACT rather than an approximation - the window is [now-N,
now+N] and calendar events carry minute resolution, so the set inside it cannot
change until TimeCurrent() crosses a minute. Country codes resolved once.

And its failure Print was UNCONDITIONAL. Error 4806 (no calendar data for the
window) is the normal case in a backtest, so that was a journal write per tick
per country - journal I/O being the most expensive thing in a tester pass, the
diagnostic cost more than the filter it was diagnosing. Once per run now, and it
says plainly that the filter is INERT for the run, so a config optimised there
is one that trades through news.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 10:04:32 -04:00
AnimateDread
1baa13c5b4 refactor(meta): remove meta-labeling entirely - RETRAIN-NEUTRAL
~2,300 lines. META had real, repeatedly measured ranking skill and ZERO
operating points that ever cleared break-even (0/350 H1 eras, 1/999 H4
pre-2-sigma, 0/8 pooled fitted points). The clinching arithmetic was edge x
width = 0.095 ATR/trade against spread 0.099 ATR/trade, and the
dose-response showed the high-conviction tail is temporally unstable -
the precision-vs-threshold slope flips sign between calib and test on 3 of
4 symbols, so no ex-ante threshold rule exists. It shipped default-off and
never gated a live entry. The self-measured tier weights are what actually
rank the vote, and all six H4 instruments converged on them alone.

RETRAIN-NEUTRAL, and that is the property that made this safe:

  - The weights fingerprint emitted "|TGT:META2" or "|TGT:SWG1" from an
    if/else. Every direction model already took the SWG1 arm, so
    collapsing it to an unconditional append is byte-identical. No .nnw or
    .cfg is orphaned or re-keyed.
  - NetInputWidth() lost its "+ MetaDescWidth()" term. MetaDescWidth()
    returned 0 for every direction model, so the input layer is unchanged.
  - DbLegacyAiSlot()'s slot 5 was reachable only with all four Use_* NNs
    off AND meta on - a config that never shipped. Every existing .db keeps
    its filename.

Deleted outright: Signals/SignalMETA.mqh, Expert/Trading/MetaGate.mqh (the
directory is now empty), Expert/Training/{MetaCorpus,MetaCandidateStore,
MetaFamilies}.mqh, Tests/Test_MetaFamilies.mq5, Meta_Labeling_Design.md.

Unwound in place, the delicate part: Training.mqh carried four
IsMetaTarget() branches whose else-arm WRAPPED the direction body (pass 1
queueing, pass 2 backprop, pass 2.5 calibration, pass 3 OOS scoring). Each
wrapper is removed and the direction body promoted back to its original
nesting - the bodies were never re-indented when the wrappers were added,
so the promoted code is byte-identical to what ran before META existed.
Also gone: the ensemble verdict's meta-veto replay and its
approved/vetoed/unscored counters, the per-family/per-side OOS
decomposition arrays, the m_isTrainQueueCand parallel queue and its
lockstep shuffle, and the S2 era report.

Also removed: the CMetaGate abstraction and the live CheckOpenPosition
veto; m_gates plus AddFilter's non-voter routing and IsVotingSignal()
(META was the only non-voting child, so m_gates was always empty);
m_parentSignal/SetParentSignal (existed only to reach the root's gate);
SweepPrepare/SweepPrepareIndicator (only caller was the corpus sweep);
IsMetaTarget() from all four view interfaces and their adapters;
Use_MetaLabeling, EnableMETA, Meta_ExportDataset, m_trainTarget.

EvalShift is KEPT - HistoricalNetVote() uses it for the filtered overlay,
not just the corpus sweep; only its comment changed. The 2-output softmax
arm in NetForward.mqh is kept too: it costs nothing and is the reusable
binary-head path, now commented as unclaimed rather than as META's.

Compile-verified in _claude_stage: 0 errors, 0 warnings, matching the
pre-edit baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 09:44:52 -04:00
AnimateDread
8f2164698b feat(target): delete the barrier/geometry stack - the label is the verdict
Step 3 of the swing-pivot plan, whole-hog. The swing label is now the ONE
target and the era verdict is precision + recall per class against the
label's own base rate - no win rate, no break-even, no expectancy, no
geometry anywhere in training.

DELETED
- Expert/Excursion/ (4), Expert/BarrierHorizon/ (4), GeometrySweep,
  FirstPassageLadder, Labeling/TripleBarrier.mqh (CLabelOverlap survives
  in Labeling/LabelOverlap.mqh), 3 test EAs.
- TripleBarrierLabel + walk, fractal label, geometry derivation/scan/
  adoption, exit-policy replay, excursion MI targets, the drift verdict
  (DIRECTION_INTELLIGENT), the recall floor, balanced-accuracy telemetry,
  the barrier defines, the .cfg geometry adopt (slots kept as zeros for
  the positional layout), the derived-geometry live-order override.
- TRAINING_TARGET input/enum: direction models are always swing; META2
  re-keys the meta head onto label agreement (descriptor loses its two
  geometry slots).

REWORKED
- Labels.mqh (1795 -> ~370 lines): AdvanceSwingLabelState with
  FINALITY-GATED CACHING - an unresolved bar (pivot pair uncommitted) is
  never cached, so it can never freeze as a false Neutral; training,
  calibration, OOS scoring and online learning all skip unresolved bars.
- SDeployVerdict: significance-only; SOosTally chance = larger
  directional class share; pooled gate poolability = timeframe (record v2).
- Purge/embargo/declustering gaps: the measured mean label resolution
  lag (LabelResolutionBars), not a barrier horizon.
- Pool purge key + backfill DB rows: marked at the bar the label
  resolved on (m_labelResolveAge), not a fabricated barrier touch.
- Online learning frontier: finality, not a horizon delay.
- m_bestBalancedOos -> m_bestSelectionScore, m_erasSinceBestBalanced ->
  m_erasSinceBest, ensemble vote outcome arrays -> label arrays.

STEP 4 folded in: Entry_Multiplier / SL_Mode / TP_Mode / tradingdirection
are inputs again - trade management is the tester GA's search space.

Fingerprints: every direction model re-keys (TGT:SWG1 now unconditional,
CUT token gone); META1 -> META2. Full retrain, as planned.

Compile-verified in _claude_stage: Warrior_EA + both surviving test EAs,
0 errors, 0 warnings each.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 20:42:31 -04:00
AnimateDread
f67e33a5f7 docs(signals): README described a folder that no longer existed
It listed SignalStoch/SignalPB/SignalITF/SignalRVI and six other files as though
present, marked SignalSessionFilter '(Removed)' while its 207 lines were being
instantiated on every init, and omitted SignalMETA (578 lines), OscillatorDivergence,
SignalRiskGuard and SignalHYBRID entirely - so the one file meant to orient a reader
was wrong in both directions at once.

Rewritten against the actual roster, with the removal reasons recorded here rather
than in commits nobody re-reads, and with the two conventions that are easy to get
wrong on first contact: the voting machinery is stock CExpertSignal (so 'simplify
back to stdlib' removes nothing), and Direction() is a transaction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:21:49 -04:00
AnimateDread
ed919194a4 ditch(signals): remove the four classic votes - all 26 patterns measured at chance
research/classic.py transcribed all 26 shipped vote patterns (MA 4, RSI 4, MACD 6,
Ichimoku 12) with their constructor weights and tested them as entries on 178k-bar
histories, four instruments x three barrier geometries. Nothing separated from
chance - not one pattern, not the averaged vote at any threshold 10-70, not a
2/3/4-module quorum, not event-plus-confirmation. Residual E[R] everywhere was
-0.01 to -0.08 R, which is approximately the spread. The +4 sigma reading that had
once justified the set was two bars of lookahead: closing it took MACD_p4 on EURUSD
from +5.05pp to -0.02pp.

All four inputs have shipped false ever since, so this deletes dormant code rather
than changing behaviour.

RETRAIN-NEUTRAL, deliberately. EnableMA and EnableRSI were hashed UNCONDITIONALLY
into the DB config fingerprint, so they become literal 0 legacy slots - the same
treatment the ind_Periods slot two lines above already uses, and every existing
database keeps its key. EnableMACD/EnableIchimoku were appended only when enabled,
so with both gone the segment simply never appears, which is byte-identical to
today. No .nnw or .db is orphaned.

WHAT THIS COSTS, STATED PLAINLY: these four were CSignalMETA's only wired candidate
sources, so the on-chart ladder sweep (BuildCorpusBySweep) now has nothing to sweep
and a META chart is no longer self-contained. That is survivable rather than fatal
because MetaPrepareEra already falls back to CMetaCorpus::LoadLargestOnDisk, and its
own comment names this exact case - "charts whose classic filters are disabled".
Use_MetaLabeling ships false regardless. SignalMETA.mqh is otherwise UNTOUCHED, and
its 26-slot one-hot stays at 26: a tester-built corpus on disk still encodes those
pattern ids, and narrowing the descriptor would invalidate every stored corpus.

  Signals/SignalMA.mqh SignalRSI.mqh SignalMACD.mqh SignalIchimoku.mqh   deleted
  Signals/OscillatorDivergence.mqh   deleted - RSI and MACD were its only users
  Classic_Shift                      deleted - the four votes were its only readers

Compile-verified in the stage copy: 0 errors, 0 warnings, against a 0/0 baseline
taken before any edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:08:43 -04:00
AnimateDread
d226d26c15 refactor(signals): factor CSignalRSI/CSignalMACD's divergence detector into CDivergenceDetector
StateRSI/StateMain, ExtStateRSI/ExtState, and CompareMaps were duplicated
verbatim (~110 lines each, even the comments) between SignalRSI.mqh and
SignalMACD.mqh - the only real difference was the oscillator value source
(RSI(pos) vs Main(pos)). Neither method touches the pattern-weight fields
LongCondition/ShortCondition read, so unlike the previously-declined
ApplyPatternWeight dedup, this extraction needed no change to the live
voting logic itself - only the ExtState(idx)/CompareMaps(...) call sites
now go through the shared collaborator.

New Signals/OscillatorDivergence.mqh: IOscillatorDivergenceSource
(abstract - oscillator value + price low/high extremum lookup) and
CDivergenceDetector (owns m_extr_osc/pr/pos/map as real members, STATEFUL,
fully exclusive - grep-confirmed no reader outside the old per-class
copies). MQL5 has no multiple inheritance and both signals already extend
CExpertSignalCustom, so each gets a thin CSignalXxxDivergenceSource
adapter (owner pointer + 3 forwards) rather than implementing the view
directly, matching the Expert/* view+adapter pattern. Added 3 small
public DivergenceOscillatorValue/DivergencePriceLow/DivergencePriceHigh
wrappers per class since the adapter is a separate object, not a
subclass, and can't reach RSI()/Main()/m_low/m_high (protected) directly.

Every moved statement verified against the pre-edit body; the only
non-mechanical change is a stale comment ("ExtStateRSI's scan") updated
to the new method name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 03:45:47 -04:00
AnimateDread
de9b7c278c refactor(meta): factor the shared training/live descriptor tail into one method
CSignalMETA::AppendCandidateFeatures (training corpus rows) and
AppendLiveDescriptor (ScoreProposal's live gate descriptor) duplicated the
identical one-hot/side/tanh-squash-netVote/SL-TP-multiple/spread-ATR sequence
byte-for-byte, with a comment admitting they had to be kept in sync by hand -
the exact "copies disagreeing" failure mode this campaign exists to close,
and here it feeds both the training corpus and a live trade-gating decision.

Added private AppendDescriptorTail(slot, side, netVote, idx): slot -1 zeroes
every one-hot column (AppendLiveDescriptor's case, since the vote is not a
classic-ladder fire), any other slot sets exactly that column to 1.0 (the
training case via OneHotSlot()). Both callers now delegate to it; each
statement verified equivalent to the original inline copy before the edit
(int/char side both narrow to the same (double)side write, m_metaCands.Bar()
vs the barIdx parameter both feed the same idx argument).

Self-compiled 0 errors, 0 warnings via the _claude_stage mirror.
2026-08-24 02:32:06 -04:00
AnimateDread
a16ed41677 refactor(signals): dedupe SignalSessionFilter's per-session window computation
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>
2026-08-24 02:18:46 -04:00
AnimateDread
4dede6f6db refactor(features): FeatureBuilder is a real collaborator, not a raw-include partial
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.
2026-08-24 00:00:31 -04:00
AnimateDread
9120826ec2 refactor(signals): SweepPrepare is a template method, not 4 copies of the same override
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.
2026-08-23 21:05:06 -04:00
AnimateDread
624bde5b6a fix(meta): two m_metaGate members in one inheritance chain
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>
2026-08-23 16:23:47 -04:00
AnimateDread
0254342424 docs(meta): say why the corpus filter reads m_period, not _Period
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>
2026-08-23 15:42:39 -04:00
AnimateDread
e886139c81 refactor(meta): one corpus, three sources - not two corpora, two schemas
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>
2026-08-23 15:41:47 -04:00
AnimateDread
303c9bf412 refactor(meta): the era's candidates are an object, not eight base members
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>
2026-08-23 15:36:31 -04:00