 fix: the recall gate was unsatisfiable and the LR decay was a spiral
Both made the run structurally unable to succeed, independently of any
signal in the data. Found by reading the 13:01 log.
RECALL GATE. m_objectiveMet required Buy, Sell AND Neutral OOS recall
each >= 40%. First-touch resolution (ce52654) collapsed Neutral from
the ~94% majority it was under exact-pivot labels to a same-bar-tie
residue - 250 of 38,261 bars, 0.65% - so the floor was asking the model
to identify 40% of coin-flip ties before it could converge. Measured:
CONV, LSTM and HYBRID all logged "Neutral:0% (need >=40% each)" on
every era. No model could ever satisfy it; every run was destined for
the plateau ladder or the era cap.
Only the DIRECTIONAL floors are load-bearing for the anti-collapse job
the gate exists to do: an all-Neutral model shows Buy and Sell recall
at 0% and is blocked by them. Neutral's own floor guarded the mirror
bias (over-calling Buy/Sell at Neutral's expense), which was real at
94% prevalence and is not at 0.65% - there, almost never calling
Neutral is correct rather than biased.
Prevalence-guarded rather than hardcoded off, so it returns by itself
if a future label rule makes Neutral substantial again. Deliberately
NOT extended to Buy/Sell: exempting a thin directional class reopens
the era-44-46 hole, which directionalRecallMeasured only half-covers -
it checks those classes were MEASURED, not that they passed.
ETA DECAY. A regressing era restored the checkpoint, reset the
optimizer and cut eta - all on the FIRST regression. The next era then
started from an identical state with a smaller step, regressed again,
and got the same treatment. The loop is self-sustaining and cannot
discover anything, because rolling the weights back is exactly what
removes the exploration that would end it.
Measured on PAI: eras 2-11 every one a regression against era 1, eta
0.000594 -> 0.000024, dW/W 0.000%/0.000% from era 2 onward. Ten eras,
~45s each, reproducing era 1 exactly and unable to do anything else.
Now requires ETA_DECAY_PATIENCE_ERAS consecutive regressions - the
standard ReduceLROnPlateau formulation. A single bad era is noise, and
an improving era clears the counter so alternating runs never
accumulate into a decay.
Build tag -> gate-patience-v3. It had not moved in six commits, which
is why the running binary could not be identified from its own log.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 13:28:58 -04:00 | | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | //| Warrior_EA.mq5 |
|
2026-07-14 22:36:27 -04:00 | | | //| AnimateDread |
|
| | | //| |
|
2026-09-13 14:32:28 -04:00 | | | //| THE WHOLE EA, in the shape MetaEditor's wizard generates. |
|
 refactor(trade-mgmt): remove all confidence-scaled trade management
Five modes went, all of them staking real risk on the model's confidence:
Intelligent entry (ENTRY_INTELLIGENT), stop (SL_INTELLIGENT), target
(TP_INTELLIGENT), trailing (CTrailingIntelligent) and lot size
(CMoneyIntelligent's quarter-Kelly). With them, the Confidence_Source
input and the CONFIDENCE_SOURCE enum, whose only job was choosing which
number those five read.
The reason is calibration, not correctness: the confidence magnitude is
known to be miscalibrated against the label prior, so every one of these
modes multiplied money by a quantity whose units were never established.
The DB arm had a second, independent defect - since the tester DB guard
(SignalDatabaseActive) it reads 0 in tester and optimizer but non-zero
live, so any backtest of CONF_DB/CONF_BLENDED could not reproduce live
trading. And what the DB produces is a filter-RANKING win rate, not a
per-trade win probability.
Both confidence numbers are still recorded per trade (aiConfidence /
dbConfidence) and still bucketed against outcome in TradeJournalReport.
Recording is what keeps the question answerable; acting on it was the
part with no evidence behind it. ConfidenceBridge.mqh now carries an
explicit telemetry-only rule at the top.
ENUM ORDINALS PINNED. Removing a member vacated a value in four enums at
once and MT5 does not validate an enum input replayed from a saved .set
or a stored optimization pass. TRAILING_STRATEGY and
MONEY_MANAGEMENT_STRATEGY now carry explicit values so the survivors keep
the numbers they were saved as, and ValidateBarrierInputs is widened into
ValidateTradeManagementInputs covering SL_Mode, TP_Mode,
Entry_Multiplier, TrailingStrategy and MM_STRATEGY. Without that gate a
chart saved with the Intelligent stop would feed SL_Mode = -1 into a
multiplier now used verbatim, placing the stop on the wrong side of entry.
RETRAIN-NEUTRAL: neither SL_Mode nor TP_Mode appears in
BuildModelFingerprint() or ComputeDbConfigFingerprint() since the
swing-pivot target replaced the barrier labels. No .nnw, .cfg or .db
re-keys. Also drops the now-dead g_TradeRewardRiskRatio bridge, the
CMoneyRiskBase::AdjustRiskAmount hook and the unsigned AIConfidence().
Compile-verified in _claude_stage: 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 10:10:20 -04:00 | | | //| |
|
2026-09-13 14:32:28 -04:00 | | | //| CExpert |
|
| | | //| CWarriorVote the root signal - holds the filters |
|
| | | //| classic modules MA, RSI, MACD, Ichimoku, CCI, ... |
|
| | | //| Wyckoff modules event stream, failed structure |
|
| | | //| NN modules trained in-terminal, vote like the rest|
|
| | | //| CWarriorMoney fixed lot or fixed risk % |
|
| | | //| CTrailingATR ATR trailing stop |
|
| | | //| |
|
| | | //| Every filter answers LongCondition()/ShortCondition() with 0..100. |
|
| | | //| The root sums them, divides by how many answered, and CExpert |
|
| | | //| opens a trade when the result clears the threshold. That is the |
|
| | | //| entire decision. There is no override, no quorum counter, no |
|
| | | //| per-setup order shaping and no special case for the networks - a |
|
| | | //| net that votes is indistinguishable from an RSI that votes, which |
|
| | | //| is the property that makes the whole thing rankable. |
|
| | | //| |
|
| | | //| WHAT THE DATABASE IS FOR. Every filter evaluation is queued during |
|
| | | //| the vote and written on the timer, so each module accumulates its |
|
| | | //| own record. The ranking layer reads those win rates and sets each |
|
| | | //| module's Weight(). A module that carries nothing earns a small |
|
| | | //| weight and stops mattering - which is the alternative to deleting |
|
| | | //| it, and the reason the classic modules can be left switched on. |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "AnimateDread"
|
| | | #property version "1.00"
|
| | | #property description "Warrior EA - standard-library vote, NN signals, self-ranking database."
|
 refactor(dry): one retry-and-report for OnInit's five init steps
Trailing, money management, settings validation, indicator setup and
timer registration each carried their own copy of the same 18-line
retry loop - a bool, a counted loop, a RandomSleep backoff, and two
Print lines - differing only in which call they made and what they
called it. Five places for the retry count, the backoff and the failure
wording to drift apart, and OnInit was 635 lines partly because of it.
RetryInitStep() is now the only copy. `what` completes both sentences
the loop printed, so the journal reads exactly as it did; `caller` is
passed in rather than read from __FUNCTION__ so the line still names
OnInit and not the helper.
The five steps become one-line wrappers because MQL5 function pointers
bind neither a method call nor an argument, and these are two of each
(Expert.ValidationSettings/InitIndicators, and the timer's interval).
That interval moves to WARRIOR_TIMER_INTERVAL_MS beside its wrapper,
taking its full rationale with it instead of leaving it stranded in the
middle of OnInit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 23:53:12 -04:00 | | |
|
2026-09-13 14:32:28 -04:00 | | | #include <Expert\Expert.mqh>
|
| | | //--- The database layer logs through System\PrintVerbose.mqh, which reads these two globals. They
|
| | | //--- are consts, not inputs: this EA reports through the threshold ladder and the journal summary,
|
| | | //--- both of which print unconditionally. The old per-tick trace was 35% of a 1.5M-line tester log
|
| | | //--- and told nobody anything - if something here needs explaining, it should print a sentence, not
|
| | | //--- a number per tick.
|
| | | const bool VerboseMode = false;
|
| | | const bool TraceMode = false;
|
 refactor(dry): one retry-and-report for OnInit's five init steps
Trailing, money management, settings validation, indicator setup and
timer registration each carried their own copy of the same 18-line
retry loop - a bool, a counted loop, a RandomSleep backoff, and two
Print lines - differing only in which call they made and what they
called it. Five places for the retry count, the backoff and the failure
wording to drift apart, and OnInit was 635 lines partly because of it.
RetryInitStep() is now the only copy. `what` completes both sentences
the loop printed, so the journal reads exactly as it did; `caller` is
passed in rather than read from __FUNCTION__ so the line still names
OnInit and not the helper.
The five steps become one-line wrappers because MQL5 function pointers
bind neither a method call nor an argument, and these are two of each
(Expert.ValidationSettings/InitIndicators, and the timer's interval).
That interval moves to WARRIOR_TIMER_INTERVAL_MS beside its wrapper,
taking its full rationale with it instead of leaving it stranded in the
middle of OnInit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 23:53:12 -04:00 | | |
|
2026-09-13 14:32:28 -04:00 | | | #include "Enumerations\WarriorEnums.mqh"
|
2026-09-13 14:32:40 -04:00 | | | //--- CExpert with a session-aware bar clock: a daily bar that opens during the CFD maintenance hour
|
| | | //--- is acted on at the first tick inside a session, not discarded. See the header for the count.
|
| | | #include "Expert\WarriorExpert.mqh"
|
2026-09-13 14:32:28 -04:00 | | | #include "Expert\WarriorSignal.mqh"
|
| | | #include "Database\WarriorJournal.mqh"
|
| | | #include "Expert\WarriorVote.mqh"
|
| | | #include "Money\WarriorMoney.mqh"
|
2026-09-13 14:32:40 -04:00 | | | //--- OURS, NOT THE WIZARD'S. These are the repo's own classic modules, restored from 3ed053e /
|
| | | //--- 095bd27. They are the MetaQuotes indicators with the one thing the stock modules lack: each
|
| | | //--- NAMES the pattern that matched, so the journal can key a row to it and the ranking layer can
|
| | | //--- give that pattern its own weight. 49 named patterns across the eleven, against 0 from the
|
| | | //--- stdlib versions - a CExpertSignal cannot be dynamic_cast to CWarriorSignal, so every stock
|
| | | //--- module voted invisibly and none of them could ever be ranked.
|
| | | #include "Signals\SignalMA.mqh"
|
| | | #include "Signals\SignalRSI.mqh"
|
| | | #include "Signals\SignalMACD.mqh"
|
| | | #include "Signals\SignalCCI.mqh"
|
| | | #include "Signals\SignalStoch.mqh"
|
| | | #include "Signals\SignalWPR.mqh"
|
| | | #include "Signals\SignalRVI.mqh"
|
| | | #include "Signals\SignalSAR.mqh"
|
| | | #include "Signals\SignalAO.mqh"
|
| | | #include "Signals\SignalAC.mqh"
|
| | | #include "Signals\SignalIchimoku.mqh"
|
| | | //--- Regime context: trending / consolidating / mean reverting, from closes alone.
|
| | | #include "Signals\SignalRegime.mqh"
|
| | | //--- The measured US-index dip-buy. Run ALONE on SP500/NAS100/US30 D1, Direction = DIR_LONG.
|
| | | #include "Signals\SignalDipBuy.mqh"
|
2026-09-13 14:32:28 -04:00 | | | //--- OURS. Wyckoff derives from CWarriorSignal, so it votes exactly like the modules above and is
|
| | | //--- additionally JOURNALLED - the vote cannot tell them apart, the database can.
|
| | | #include "Signals\Wyckoff\SignalWyckoffEvent.mqh"
|
| | | #include "Signals\Wyckoff\SignalWyckoffStructure.mqh"
|
2026-09-13 14:32:40 -04:00 | | | //--- The network. An ordinary signal module: it builds its own inputs, votes 0..100, and
|
| | | //--- trains itself from this chart's history the first time it runs.
|
| | | #include "Signals\SignalNeural.mqh"
|
2026-09-13 14:32:28 -04:00 | | | //--- ATR trailing stop - a plain CExpertTrailing, self-contained.
|
| | | #include "Trailing\TrailingATR.mqh"
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | |
|
 perf(deinit): I/O-free chart cleanup, dead-panel purge, skip clean weight saves
The 18:23 terminal close (20260825.log) killed two of six charts inside
OnDeinit: they printed "shutting down" then nothing for 5.9 s until
"Abnormal termination", stranding ~700 objects each - including the one
family no prefix sweep can reach, the control panel (CAppDialog names
its 15 objects <numeric instance id><control>, and a re-attach mints a
new id, so a killed panel is a permanent ghost; XTIUSD carried one
across sessions). The stall sat in the two file writes that preceded
all visible cleanup while the four sibling charts flooded the same
2013-era disk - the ~4x18MB-per-chart shutdown weight saves.
Three changes:
1. OnDeinit touches no file until the chart is clean. CVoteArrowStore
splits Save() into Snapshot() (the chart scan, in memory) and
WriteSnapshot() (the disk half, consuming). New order: status label,
vote-arrow snapshot, prefix sweep, panel destroy - all object ops -
then member sidecars, final sweep, timings, and only then the
visibility file, the vote-arrow write and the weight saves.
2. PurgeOrphanedPanelObjects() at OnInit: deletes numeric-prefix
CAppDialog ghosts by name (6 chrome + 9 buttons), qualifying a
prefix only when >=4 of OUR button names carry it, so a foreign
dialog sharing stock chrome names is never touched.
3. m_netDirty: set by every net mutation (both backProp sites, both
RestoreWeights sites, online learning conservatively, panel reset),
cleared only on a successful Net.Save. Shutdown AND the per-bar
autosave now skip the ~18MB write when the net is provably unchanged
- for converged ensembles that is every save - which removes the
very flood that starved the sibling charts. .stats still writes
every time (small; carries the vote record and calibration). A
skipped save leaves the .nnw header dtStudied stale, which is the
already-handled attach-after-offline-gap case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 18:46:51 -04:00 | | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | //| Inputs - every one an enum, every value its own payload. |
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | input group "=== General ==="
|
| | | input ulong Magic = 20260911; // Magic number
|
| | | input bool EveryTick = false; // Evaluate on every tick (else bar open)
|
| | |
|
| | | input group "=== Vote ==="
|
| | | input WARRIOR_THRESHOLD ThresholdOpen = THR_15; // Vote needed to open (% of ensemble)
|
2026-09-13 14:32:40 -04:00 | | | //--- 101 = never close on a vote (the default: exits are the stop, the target or the trail). The
|
| | | //--- dip-buy module's exit IS a vote - its ShortCondition fires when the dip has reverted - so on an
|
| | | //--- index chart this is set to 50 and, with the module alone, its 100 clears it.
|
| | | input int ThresholdClose = 101; // Vote needed to close (101 = never)
|
2026-09-13 14:32:28 -04:00 | | | input WARRIOR_DIRECTION Direction = DIR_BOTH; // Allowed direction
|
2026-09-13 14:32:40 -04:00 | | | input WARRIOR_FADE FadeCrowd = FADE_OFF; // Fade near-unanimous votes, else stand aside
|
2026-09-13 14:32:28 -04:00 | | |
|
| | | input group "=== Risk ==="
|
| | | input WARRIOR_MONEY_MODE MoneyMode = MONEY_FIXED_LOT; // Position sizing
|
| | | input WARRIOR_RISK_PCT RiskPercent = RISK_1; // Risk per trade (when sizing by risk)
|
| | | input double FixedLots = 0.01; // Lot size (when sizing fixed)
|
| | | input WARRIOR_SL_MODE StopMode = SL_ATR_x2; // Stop loss
|
| | | input WARRIOR_TP_MODE TargetMode = TP_ATR_x4; // Take profit
|
| | | input WARRIOR_TRAIL_MODE TrailMode = TRAIL_NONE; // Trailing stop
|
2026-09-13 14:32:40 -04:00 | | | input WARRIOR_BREAKEVEN BreakevenAt = BE_OFF; // Move stop to entry at this profit
|
| | | input WARRIOR_MGMT_CUT ManagementCut = MGMT_OFF; // Exit at +0.5R when the model says retrace
|
| | |
|
| | | //--- MODULE WEIGHTS - the optimiser's half of the ranking. The journal sets pattern weights from
|
| | | //--- measured expectancy; these say how loudly each module's patterns are heard. WEIGHT_0 switches
|
| | | //--- a module off entirely, which is the setting worth sweeping: it lets a pass answer "does this
|
| | | //--- indicator contribute anything", a question the EA could never ask while every module voted at
|
| | | //--- 1.0 by default.
|
| | | input group "=== Module weights (optimise these) ==="
|
| | | input WARRIOR_WEIGHT W_MA = WEIGHT_100; // MA
|
| | | input WARRIOR_WEIGHT W_RSI = WEIGHT_100; // RSI
|
| | | input WARRIOR_WEIGHT W_MACD = WEIGHT_100; // MACD
|
| | | input WARRIOR_WEIGHT W_CCI = WEIGHT_100; // CCI
|
| | | input WARRIOR_WEIGHT W_Stoch = WEIGHT_100; // Stochastic
|
| | | input WARRIOR_WEIGHT W_WPR = WEIGHT_100; // Williams %R
|
| | | input WARRIOR_WEIGHT W_RVI = WEIGHT_100; // RVI
|
| | | input WARRIOR_WEIGHT W_SAR = WEIGHT_100; // Parabolic SAR
|
| | | input WARRIOR_WEIGHT W_AO = WEIGHT_100; // Awesome Oscillator
|
| | | input WARRIOR_WEIGHT W_AC = WEIGHT_100; // Accelerator
|
| | | input WARRIOR_WEIGHT W_Ichimoku = WEIGHT_100; // Ichimoku
|
| | | input WARRIOR_WEIGHT W_WyckoffEv = WEIGHT_100; // Wyckoff events
|
| | | input WARRIOR_WEIGHT W_WyckoffSt = WEIGHT_100; // Wyckoff structure
|
| | | input WARRIOR_WEIGHT W_Neural = WEIGHT_100; // Neural
|
| | | input WARRIOR_WEIGHT W_Regime = WEIGHT_100; // Regime
|
| | | input WARRIOR_WEIGHT W_DipBuy = WEIGHT_100; // Dip-buy
|
2026-09-13 14:32:28 -04:00 | | |
|
| | | input group "=== Modules ==="
|
| | | input bool UseClassics = true; // Classic indicator modules
|
| | | input bool UseWyckoff = true; // Wyckoff modules
|
2026-09-13 14:32:40 -04:00 | | | //--- OFF BY DEFAULT so every comparison made before it still holds. Registering a fourteenth voter
|
| | | //--- changes the divisor in CExpertSignal::Direction(), which changes what EVERY threshold
|
| | | //--- percentage means - a roster change and a threshold change cannot be measured in one run.
|
| | | input bool UseRegime = false; // Regime module (trend/range/revert)
|
| | | //--- THE DIP-BUY IS NOT A VOTE MEMBER. One filter at 100 scores 7.7% on a thirteen-voter roster and
|
| | | //--- clears nothing; it must run ALONE (every other module off) with Direction = DIR_LONG, on a US
|
| | | //--- index D1 chart, with a wide stop and no target (StopMode SL_ATR_x3, TargetMode TP_NONE). See the
|
| | | //--- module header for what was measured and where it does NOT work (Europe, forex, gold).
|
| | | input bool UseDipBuy = false; // US-index dip-buy (run alone, long only, D1)
|
| | | //--- The dip's two measured forms (see the module header): RSI(2) on D1 with the 200-bar filter and
|
| | | //--- the SMA5 exit, or the z-score on H4 with no filter and the SMA20 exit. All four are optimiser
|
| | | //--- inputs; the module refuses values outside the measured neighbourhood.
|
| | | input WARRIOR_DIP_ENTRY DipEntry = DIP_RSI2; // Dip-buy: entry form
|
| | | input double DipZ = 1.5; // Dip-buy: z-score depth (DIP_ZSCORE)
|
| | | input int DipExitMA = 5; // Dip-buy: exit when close crosses above this SMA
|
| | | input int DipTrendMA = 200; // Dip-buy: only above this SMA (0 = no filter)
|
| | | input int DipMaxBars = 10; // Dip-buy: time stop in bars
|
| | | //--- THE META-LABEL (System\DipMeta.mqh): an ALGLIB forest + MLP, trained in the terminal on every
|
| | | //--- past dip and what the EA's own exit made of it, says take/skip. CONF_50 = off. Walk-forward:
|
| | | //--- refitted every DipMetaRefit bars from history-so-far, never saved.
|
| | | input WARRIOR_CONFIDENCE DipMetaCut = CONF_50; // Dip-buy: skip when P(pays) is below this
|
| | | input int DipMetaRefit = 500; // Dip-buy: refit the meta-label every N bars
|
| | | input bool UseNeural = false; // Neural module (trains on first run)
|
| | | input WARRIOR_CONFIDENCE NeuralCut = CONF_50; // Neural: probability cut
|
2026-09-13 14:32:28 -04:00 | | | input bool UseDatabase = true; // Record every evaluation to the database
|
2026-09-13 14:32:40 -04:00 | | | //--- 🛑 THE RANKING FEEDS THE WEIGHTS - AND MEASURED, IT LOSES MONEY. Default OFF.
|
| | | //---
|
| | | //--- EURUSD D1 2015-2026, identical settings, the ONLY difference being how much of the journal the
|
| | | //--- ranker had to act on: with a cold database (patterns mostly keeping their AUTHORED weights,
|
| | | //--- 1 module ranked at the start, 12 modules / 25 patterns by the end) the run made +150.67 at
|
| | | //--- PF 1.33. With the same window but a journal pre-filled from 2000-2014 - fully ranked from the
|
| | | //--- first bar, 12 modules / 27 patterns immediately - it made +27.53 at PF 1.05.
|
| | | //---
|
| | | //--- More evidence made it WORSE, which is the signature of a measurement that does not persist:
|
| | | //--- a pattern's edge is real on the window it was measured on and gone on the next. Recording
|
| | | //--- stays ON regardless - the journal found the give-back and the 46/54 continue-vs-retrace split,
|
| | | //--- and neither of those was visible any other way. It is the CONTROL LOOP that is switched off,
|
| | | //--- not the LOGGING. Left as an input so the optimiser can re-test it rather than trusting this.
|
| | | input bool DB_RankingFeedsWeights = false; // Let measured expectancy set the weights
|
2026-09-13 14:32:28 -04:00 | | |
|
| | | //--- ATR drives the stop, the target and the trail, so it is read once here and shared.
|
| | | input int AtrPeriod = 14; // ATR period for stop/target/trail
|
| | |
|
2026-07-16 00:56:33 -04:00 | | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:40 -04:00 | | | CWarriorExpert ExtExpert;
|
2026-09-13 14:32:28 -04:00 | | | CWarriorVote *ExtSignal = NULL;
|
| | | CWarriorJournal ExtJournal;
|
| | | CDatabaseManager ExtDbm;
|
| | | bool ExtDbReady = false;
|
| | |
|
| | | //--- Registers one filter and says so if it cannot. A filter that fails to attach silently is a
|
| | | //--- module missing from the vote, which changes what the threshold means without changing the log.
|
| | | //--- Takes the STDLIB type, so a MetaQuotes module and one of ours register identically.
|
2026-09-13 14:32:40 -04:00 | | | bool AddFilter(CExpertSignal *filter, const string name, const WARRIOR_WEIGHT weight)
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | if(filter == NULL)
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | PrintFormat("OnInit: could not create %s", name);
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | | return false;
|
2026-07-14 22:36:27 -04:00 | | | }
|
2026-09-13 14:32:40 -04:00 | | | //--- A module at WEIGHT_0 is REGISTERED AND SILENT, not skipped. That matters: the vote divides
|
| | | //--- by the number of filters that answered, so removing a module from the roster changes what
|
| | | //--- every threshold percentage means, and a sweep over weights would then be comparing runs
|
| | | //--- whose thresholds are not the same quantity. Registered-at-zero keeps the divisor fixed.
|
| | | filter.Weight((double)weight / 100.0);
|
2026-09-13 14:32:28 -04:00 | | | if(!ExtSignal.AddFilter(filter))
|
 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 | | | {
|
2026-09-13 14:32:28 -04:00 | | | PrintFormat("OnInit: could not add %s to the vote", name);
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | | return false;
|
2026-07-14 22:36:27 -04:00 | | | }
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | | return true;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | int OnInit()
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | if(!ExtExpert.Init(Symbol(), Period(), EveryTick, Magic))
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | Print("OnInit: ExtExpert.Init failed");
|
| | | return INIT_FAILED;
|
2026-07-14 22:36:27 -04:00 | | | }
|
2026-09-13 14:32:28 -04:00 | | | ExtSignal = new CWarriorVote;
|
| | | if(ExtSignal == NULL)
|
| | | return INIT_FAILED;
|
| | | ExtExpert.InitSignal(ExtSignal);
|
| | | ExtSignal.ThresholdOpen((int)ThresholdOpen);
|
| | | //--- CLOSED ONLY BY THE STOP, THE TARGET OR THE TRAIL. A vote-driven exit is a different strategy
|
| | | //--- from the one the entry threshold was chosen for, and mixing them makes neither measurable.
|
2026-09-13 14:32:40 -04:00 | | | ExtSignal.ThresholdClose(ThresholdClose);
|
| | | //--- WHICH SIDE MAY OPEN. Declared as an input since the rebuild and applied nowhere until now:
|
| | | //--- the gate lives on the vote's CheckOpenLong/Short, which the stdlib's reversal path also uses.
|
| | | ExtSignal.Allowed(Direction);
|
2026-09-13 14:32:28 -04:00 | | | //--- THE BARRIERS. StopMode and TargetMode are ATR MULTIPLES (the enum value IS the multiple), so
|
| | | //--- the stop is the same size in volatility terms on every symbol and in every regime - which a
|
| | | //--- fixed point distance is not. Set BEFORE InitIndicators, which is where the ATR is created.
|
| | | ExtSignal.Barriers(AtrPeriod, (double)StopMode, (double)TargetMode);
|
2026-09-13 14:32:40 -04:00 | | | ExtSignal.ManagementCut((double)ManagementCut / 100.0);
|
| | | ExtSignal.FadeAt((int)FadeCrowd);
|
 refactor(init): split OnInit's ~430-line boot sequence into named phases
OnInit() orchestrated a dozen unrelated boot concerns (chart-object
purge/reporting, risk-budget config, alt-data/cross-asset blocking
warm-up, DB+journal init, creation/wiring of eleven signal objects,
filter registration, a DB-transaction retry loop, control-panel
setup) inline in one function. Extracted each into a free function
(PurgeStaleChartObjectsAndReport/ConfigureRiskBudget/WarmExternalData/
InitDatabaseAndJournal/CreateAndConfigureSignals/
VerifyDatabaseTransactionCycle/FinalizeStartupUI), called from OnInit
in the exact original order - the load-bearing ordering comments
("alt-data MUST be on disk before any model is built", "filters added
exactly once, before the DB retry loop") stay next to the calls they
govern. OnInit: ~430 -> ~100 lines.
Pure relocation, no logic changes: every INIT_FAILED return became a
plain false/true return at the new function boundary; __FUNCTION__/
functionName usages became an explicit `caller` parameter so logged
messages still read "OnInit: ..." rather than the helper's own name.
Verified via a diff script - quoted-string set identical (64/64), and
the only structural deltas (if(): +3, return: +6) are fully explained
by the 3 new call-site guards and the 3 new function-end `return
true;` lines a void-context call chain didn't need before.
Self-compiled 0 errors, 0 warnings.
2026-08-24 00:54:15 -04:00 | | |
|
2026-09-13 14:32:28 -04:00 | | | if(UseClassics)
|
 fix(init): a warm re-init kept the H4 run's globals; the training pool adopted other timeframes
Operator report 2026-09-02: the six charts were switched from H4 to H1 and the
status label kept showing the H4 run. MetaTrader does not unload the program on a
timeframe, symbol or input change - it runs OnDeinit and OnInit inside the same
instance and every file-scope global survives the pair. The members' converged
flags, the live vote line, the panel rows, the shared best-era/plateau state and
the once-per-chart report flags all belonged to the models just torn down.
- Warrior_EA.mq5: WarriorResetWarmReinitState() runs first in OnInit and puts
every such global back to its cold-start default. Kept on purpose: the chart's
book magic (positions opened before the switch stay owned), the alt-data fetch
throttle (rate-limited APIs), the tester profile, the RNG, the OpenCL flags.
- TrainingPool.mqh: peers must be the caller's own timeframe. The fingerprint
does not carry the period, so the first H1 census adopted 39,754 H4 rows from
three peer files and credited them to the capacity budget. Files are named
SYMBOL_PERIOD.bin; the suffix decides, and the reader names the rejection.
Build tag warm-reinit-1. Compiled 0 errors / 0 warnings. RETRAIN-NEUTRAL.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 14:25:20 -04:00 | | | {
|
2026-09-13 14:32:40 -04:00 | | | if(!AddFilter(new CSignalMA, "MA", W_MA)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalRSI, "RSI", W_RSI)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalMACD, "MACD", W_MACD)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalCCI, "CCI", W_CCI)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalStoch, "Stoch", W_Stoch)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalWPR, "WPR", W_WPR)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalRVI, "RVI", W_RVI)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalSAR, "SAR", W_SAR)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalAO, "AO", W_AO)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalAC, "AC", W_AC)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalIchimoku, "Ichimoku", W_Ichimoku)) return INIT_FAILED;
|
| | | }
|
| | | if(UseRegime && !AddFilter(new CSignalRegime, "Regime", W_Regime))
|
| | | return INIT_FAILED;
|
| | | if(UseDipBuy)
|
| | | {
|
| | | CSignalDipBuy *dip = new CSignalDipBuy;
|
| | | if(dip == NULL)
|
| | | return INIT_FAILED;
|
| | | dip.Entry(DipEntry);
|
| | | dip.ZEntry(DipZ);
|
| | | dip.ExitPeriod(DipExitMA);
|
| | | dip.TrendPeriod(DipTrendMA);
|
| | | dip.MaxBars(DipMaxBars);
|
| | | dip.StopAtr((double)StopMode); // the label simulates the stop the EA places
|
| | | dip.MetaCut((double)DipMetaCut / 100.0);
|
| | | dip.MetaRefit(DipMetaRefit);
|
| | | if(!AddFilter(dip, "DipBuy", W_DipBuy))
|
| | | return INIT_FAILED;
|
 fix(init): a warm re-init kept the H4 run's globals; the training pool adopted other timeframes
Operator report 2026-09-02: the six charts were switched from H4 to H1 and the
status label kept showing the H4 run. MetaTrader does not unload the program on a
timeframe, symbol or input change - it runs OnDeinit and OnInit inside the same
instance and every file-scope global survives the pair. The members' converged
flags, the live vote line, the panel rows, the shared best-era/plateau state and
the once-per-chart report flags all belonged to the models just torn down.
- Warrior_EA.mq5: WarriorResetWarmReinitState() runs first in OnInit and puts
every such global back to its cold-start default. Kept on purpose: the chart's
book magic (positions opened before the switch stay owned), the alt-data fetch
throttle (rate-limited APIs), the tester profile, the RNG, the OpenCL flags.
- TrainingPool.mqh: peers must be the caller's own timeframe. The fingerprint
does not carry the period, so the first H1 census adopted 39,754 H4 rows from
three peer files and credited them to the capacity budget. Files are named
SYMBOL_PERIOD.bin; the suffix decides, and the reader names the rejection.
Build tag warm-reinit-1. Compiled 0 errors / 0 warnings. RETRAIN-NEUTRAL.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 14:25:20 -04:00 | | | }
|
2026-09-13 14:32:28 -04:00 | | | if(UseWyckoff)
|
 feat(cost): the broker charges by ASSET CLASS - commission schedule, detected not guessed
Tag commission-1. Cost_CommissionPerLotPerSide (one number, shipped at 0.0, never set)
is replaced by the operator's actual contract, applied per class:
indices none forex 4 USD per lot
crypto 0.03% notional metals 0.001% notional energy 0.03% notional
CLASS IS DETECTED FROM SYMBOL_PATH, which is what the broker itself organises its tree
by, with symbol-name and SYMBOL_TRADE_CALC_MODE as fallbacks for a flat Market Watch.
Verified in situ on all ten live charts rather than asserted - every one resolved
correctly from its own path (Indices\, Forex\, Crypto\, Energy\, Precious_Metals\).
A PERCENTAGE OF NOTIONAL NEEDS NO CONTRACT SIZE AND NO FX RATE. Commission in money is
pct x contractSize x price x (quote->account rate); the price-equivalent divides by
money-per-price-unit, which is tickValue/tickSize - and tickValue already carries the
same contractSize and the same rate. They cancel exactly, leaving
price_equiv = pct x price for ANY quote currency
so nothing stale or missing can be read. Worth stating because it looks too easy.
THE FACTOR-OF-TWO, and the two branches need it OPPOSITE ways round. The percentage
branch builds the round turn itself, so a per-side quote is MULTIPLIED by 2. The flat
branch hands a per-side figure to WarriorCommissionRoundTurnPrice, which does its own
doubling, so a round-turn quote is HALVED on the way in. Writing them the same way round
would have been a factor-of-four error between asset classes. The operator's figures are
read as the FULL ROUND TURN (Cost_CommissionIsRoundTurn, default true), which is how a
prop contract quotes it; false doubles every figure without editing any of them.
MEASURED EFFECT - COMMISSION DOMINATES SPREAD ON HALF THE FLEET, and the gate has been
charging spread alone until now:
BTCUSD comm 24.42 + spread 3.58 = 28.00 cost was UNDERSTATED 7.8x
USDJPY comm 0.006 + spread 0.003 = 0.009 3.0x
EURUSD comm 0.00004 + 0.00002 = 0.00006 3.0x
GBPUSD comm 0.00004 + 0.00003 = 0.00007 2.3x
XAUUSD comm 0.04 + spread 0.55 = 0.60 1.1x
indices comm 0.00 unchanged
In ATR terms BTCUSD goes 0.008 -> ~0.063 and still clears on a +0.52 book, but GBPUSD
goes ~0.014 -> ~0.033 against a +0.03 book and should now FAIL. That is the correct
outcome: it was the thinnest book on the fleet and it was being charged a third of its
true cost.
The resolved class, both cost halves and the spread sample count are PRINTED at init, so
a symbol filed in an unexpected folder shows up as a wrong class rather than as a
silently wrong number.
Not retrain-forcing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 16:03:46 -04:00 | | | {
|
2026-09-13 14:32:40 -04:00 | | | if(!AddFilter(new CSignalWyckoffEvent, "WyckoffEvent", W_WyckoffEv)) return INIT_FAILED;
|
| | | if(!AddFilter(new CSignalWyckoffStructure, "WyckoffStructure", W_WyckoffSt)) return INIT_FAILED;
|
| | | }
|
| | | if(UseNeural)
|
| | | {
|
| | | CSignalNeural *net = new CSignalNeural;
|
| | | if(net == NULL)
|
| | | return INIT_FAILED;
|
| | | net.AtrPeriod(AtrPeriod);
|
| | | net.Confidence((double)NeuralCut / 100.0);
|
| | | if(!AddFilter(net, "Neural", W_Neural))
|
| | | return INIT_FAILED;
|
 feat(cost): the broker charges by ASSET CLASS - commission schedule, detected not guessed
Tag commission-1. Cost_CommissionPerLotPerSide (one number, shipped at 0.0, never set)
is replaced by the operator's actual contract, applied per class:
indices none forex 4 USD per lot
crypto 0.03% notional metals 0.001% notional energy 0.03% notional
CLASS IS DETECTED FROM SYMBOL_PATH, which is what the broker itself organises its tree
by, with symbol-name and SYMBOL_TRADE_CALC_MODE as fallbacks for a flat Market Watch.
Verified in situ on all ten live charts rather than asserted - every one resolved
correctly from its own path (Indices\, Forex\, Crypto\, Energy\, Precious_Metals\).
A PERCENTAGE OF NOTIONAL NEEDS NO CONTRACT SIZE AND NO FX RATE. Commission in money is
pct x contractSize x price x (quote->account rate); the price-equivalent divides by
money-per-price-unit, which is tickValue/tickSize - and tickValue already carries the
same contractSize and the same rate. They cancel exactly, leaving
price_equiv = pct x price for ANY quote currency
so nothing stale or missing can be read. Worth stating because it looks too easy.
THE FACTOR-OF-TWO, and the two branches need it OPPOSITE ways round. The percentage
branch builds the round turn itself, so a per-side quote is MULTIPLIED by 2. The flat
branch hands a per-side figure to WarriorCommissionRoundTurnPrice, which does its own
doubling, so a round-turn quote is HALVED on the way in. Writing them the same way round
would have been a factor-of-four error between asset classes. The operator's figures are
read as the FULL ROUND TURN (Cost_CommissionIsRoundTurn, default true), which is how a
prop contract quotes it; false doubles every figure without editing any of them.
MEASURED EFFECT - COMMISSION DOMINATES SPREAD ON HALF THE FLEET, and the gate has been
charging spread alone until now:
BTCUSD comm 24.42 + spread 3.58 = 28.00 cost was UNDERSTATED 7.8x
USDJPY comm 0.006 + spread 0.003 = 0.009 3.0x
EURUSD comm 0.00004 + 0.00002 = 0.00006 3.0x
GBPUSD comm 0.00004 + 0.00003 = 0.00007 2.3x
XAUUSD comm 0.04 + spread 0.55 = 0.60 1.1x
indices comm 0.00 unchanged
In ATR terms BTCUSD goes 0.008 -> ~0.063 and still clears on a +0.52 book, but GBPUSD
goes ~0.014 -> ~0.033 against a +0.03 book and should now FAIL. That is the correct
outcome: it was the thinnest book on the fleet and it was being charged a third of its
true cost.
The resolved class, both cost halves and the spread sample count are PRINTED at init, so
a symbol filed in an unexpected folder shows up as a wrong class rather than as a
silently wrong number.
Not retrain-forcing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 16:03:46 -04:00 | | | }
|
2026-09-13 14:32:28 -04:00 | | |
|
| | | CWarriorMoney *money = new CWarriorMoney;
|
| | | if(money == NULL)
|
2026-07-14 22:36:27 -04:00 | | | return INIT_FAILED;
|
2026-09-13 14:32:28 -04:00 | | | if(MoneyMode == MONEY_FIXED_LOT)
|
| | | money.FixedLot(FixedLots);
|
 feat(trade): two books per symbol, and delete the vote exit
Allow_Hedging (default ON, live only on a RETAIL_HEDGING account) gives the EA
an independent long book and short book on its symbol: at most one long and at
most one short, each opened on its own side's vote and each held to its own
barrier. On a netting account, or with the input off, the original
single-position path runs bit-for-bit unchanged and init says which one is live.
WHY THIS INSTEAD OF A VOTE EXIT. The deploy gate certifies
P(label agrees | vote fired) and the label runs to the barrier, so closing early
on a reversal makes the realised outcome stop being the labelled one - the
certified precision no longer describes what is traded. Opening the other side
acts on the new signal and leaves the old position's certification intact, and
costs no more than reversing: both pay the new side's spread, the difference is
only that the existing position runs on to a barrier already measured as
positive-expectancy. So Signal_ThresholdClose is DELETED rather than tuned,
along with its SIGNAL_CLOSE_PRESETS enum; the threshold is pinned to an
arithmetically unreachable 101 (the stock default of 100 is reachable by a
weighted mean of values capped at 100).
Note the two books can never both fill from one signal: CheckOpenLong and
CheckOpenShort test opposite signs of the same m_direction, so at most one clears
per tick. A hedge only forms when a LATER opposite vote fires - which is what
keeps it from being a guaranteed-loss wash pair.
The mechanism is a SelectPosition() override keyed on the active book's magic;
every inherited close/trail path then operates on that book untouched. The long
book keeps Expert_MagicNumber, so no existing position, journal row or
risk-budget state file is re-addressed. Short book is +1.
Four ownership filters had to widen from "== m_magic" to WarriorOwnsMagic(),
or the short book would have been invisible to the code that must reach it:
the scheduled close-all (positions and orders), the risk budget's emergency
flatten, and the journal's MAE/MFE walk. WarriorOwnsMagic() is deliberately NOT
gated on Allow_Hedging - turning the input off while a short-book position is
open would otherwise orphan it with nothing left to close it.
Risk sizing needed no change: CapRiskAmount already subtracts OpenRiskAtStops(),
which counts every position regardless of magic, so the second book is sized
inside what the first one left. Conservative for a hedged pair, which cannot
lose both stops - the safe direction.
Retrain-neutral: neither input is in BuildModelFingerprint() or
ComputeDbConfigFingerprint(). Compiled clean; NOT yet run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:20:35 -04:00 | | | else
|
2026-09-13 14:32:28 -04:00 | | | money.RiskPercent((double)RiskPercent);
|
| | | if(!ExtExpert.InitMoney(money))
|
| | | return INIT_FAILED;
|
2026-07-27 15:52:39 -04:00 | | |
|
2026-09-13 14:32:40 -04:00 | | | //--- ONE OBJECT, TWO INDEPENDENT FEATURES. CTrailingATR carries both the ATR trail and the
|
| | | //--- breakeven stop, and either may be off: the stdlib's plain CExpertTrailing has neither, so
|
| | | //--- installing it whenever TrailMode is NONE would silently discard BreakevenAt.
|
| | | if(TrailMode == TRAIL_NONE && BreakevenAt == BE_OFF)
|
 feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols
EIA (user directive: "the NN might find patterns in it for both oil and regular
symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR,
field production, refinery utilization - three features (1y percentile, 4w
change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on
WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not
the screen, decides whether a model trained on them trades. Publication stamp
observed+6d mirrors research/altdata/eia.py.
Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24
instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC
Markets naming, with prefix matching for the broker suffix zoo (US500.cash,
XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are
named by CANONICAL so two brokers' names for one contract share a download.
Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog +
dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and
"No alternative data" is a recorded choice, not a nag. Non-blocking by design:
an unmapped symbol contributes 0 features and must never hold up a chart.
Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the
wildcard and an unescaped one corrupts the query; docs/ gains the whitelist
URLs, an API-key backup, and the catalog reference.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:18:29 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | CExpertTrailing *none = new CExpertTrailing;
|
| | | if(none == NULL || !ExtExpert.InitTrailing(none))
|
| | | return INIT_FAILED;
|
 feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols
EIA (user directive: "the NN might find patterns in it for both oil and regular
symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR,
field production, refinery utilization - three features (1y percentile, 4w
change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on
WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not
the screen, decides whether a model trained on them trades. Publication stamp
observed+6d mirrors research/altdata/eia.py.
Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24
instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC
Markets naming, with prefix matching for the broker suffix zoo (US500.cash,
XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are
named by CANONICAL so two brokers' names for one contract share a download.
Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog +
dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and
"No alternative data" is a recorded choice, not a nag. Non-blocking by design:
an unmapped symbol contributes 0 features and must never hold up a chart.
Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the
wildcard and an unescaped one corrupts the query; docs/ gains the whitelist
URLs, an API-key backup, and the catalog reference.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:18:29 -04:00 | | | }
|
| | | else
|
| | | {
|
2026-09-13 14:32:28 -04:00 | | | CTrailingATR *trail = new CTrailingATR;
|
| | | if(trail == NULL)
|
| | | return INIT_FAILED;
|
2026-09-13 14:32:40 -04:00 | | | trail.Multiplier((double)TrailMode); // 0 = no ATR trail, breakeven only
|
| | | trail.BreakevenAt((double)BreakevenAt / 10.0); // the enum is TENTHS of R
|
2026-09-13 14:32:28 -04:00 | | | if(!ExtExpert.InitTrailing(trail))
|
| | | return INIT_FAILED;
|
2026-09-13 14:32:40 -04:00 | | | PrintFormat("OnInit: management - ATR trail %s, breakeven %s.",
|
| | | (TrailMode == TRAIL_NONE ? "off" : "x" + IntegerToString((int)TrailMode)),
|
| | | (BreakevenAt == BE_OFF ? "off" : "at +" +
|
| | | DoubleToString((double)BreakevenAt / 10.0, 1) + "R"));
|
 feat(altdata): EIA wired, 24-instrument symbol catalog, mapping dialog for unknown symbols
EIA (user directive: "the NN might find patterns in it for both oil and regular
symbols"). Weekly Petroleum Status Report via the v2 API - crude stocks ex-SPR,
field production, refinery utilization - three features (1y percentile, 4w
change, utilization) on EVERY catalog symbol, not just oil. EIA screened NULL on
WTI's short 7y sample, so these ship as EXPLORATORY inputs: the deploy gate, not
the screen, decides whether a model trained on them trades. Publication stamp
observed+6d mirrors research/altdata/eia.py.
Symbol handling was hardcoded to three if-blocks; it is now a catalog of 24
instruments x alias lists covering The5ers/FTMO/AvaTrade/Dukascopy/OANDA/IC
Markets naming, with prefix matching for the broker suffix zoo (US500.cash,
XAUUSDm, EURUSD.r). Adding an instrument is one AddSpec row. COT caches are
named by CANONICAL so two brokers' names for one contract share a download.
Unrecognised symbol -> a chart dialog (Panel\AltDataMapDialog.mqh, CAppDialog +
dropdown) asks which instrument it is; the answer persists in symbol_map.cfg and
"No alternative data" is a recorded choice, not a nag. Non-blocking by design:
an unmapped symbol contributes 0 features and must never hold up a chart.
Also: UrlEncodePart now escapes '%' - SoQL like-predicates use it as the
wildcard and an unescaped one corrupts the query; docs/ gains the whitelist
URLs, an API-key backup, and the catalog reference.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 16:18:29 -04:00 | | | }
|
2026-09-13 14:32:28 -04:00 | | |
|
| | | if(!ExtExpert.ValidationSettings() || !ExtExpert.InitIndicators())
|
2026-07-25 16:39:11 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | Print("OnInit: validation or indicator init failed");
|
| | | return INIT_FAILED;
|
2026-07-25 16:39:11 -04:00 | | | }
|
2026-09-13 14:32:28 -04:00 | | |
|
| | |
|
| | | //--- THE DATABASE. Off during optimisation and forward passes on purpose: those run many agents
|
| | | //--- in parallel against one file, which is write contention, not a throttle problem.
|
| | | if(UseDatabase && !MQLInfoInteger(MQL_OPTIMIZATION) && !MQLInfoInteger(MQL_FORWARD))
|
| | | {
|
| | | //--- One database per symbol AND per timeframe. Mixing timeframes in one file would pool a
|
| | | //--- module's D1 record with its M5 record under the same table name, and they are not the
|
| | | //--- same claim - the whole point of ranking is that a module can be good on one and not the
|
| | | //--- other. FILE_COMMON, so the tester and the live terminal read the same corpus.
|
2026-09-13 14:32:40 -04:00 | | | string folders[] = {"Warrior_EA", "Journal"};
|
2026-09-13 14:32:28 -04:00 | | | const string dbName = Symbol() + "_" + IntegerToString(Period()) + ".db";
|
| | | if(ExtDbm.Init("1.0", folders, dbName) && ExtDbm.OpenDatabase())
|
2026-08-16 13:59:03 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | ExtDbReady = true;
|
| | | ExtJournal.Bind(GetPointer(ExtDbm), (bool)MQLInfoInteger(MQL_TESTER));
|
| | | ExtSignal.Journal(GetPointer(ExtJournal));
|
2026-08-16 13:59:03 -04:00 | | | }
|
2026-07-14 22:36:27 -04:00 | | | else
|
2026-09-13 14:32:28 -04:00 | | | Print("OnInit: database unavailable - running without a record.");
|
2026-07-14 22:36:27 -04:00 | | | }
|
2026-09-13 14:32:40 -04:00 | | | //--- SAY OUT LOUD WHETHER THIS EA CAN TRADE AT ALL. Five switches can stop it dead and none of
|
| | | //--- them raise an error - the EA simply places nothing, which is indistinguishable from a
|
| | | //--- strategy that found no setups. This repo lost a session to exactly that (AlgoTrading off).
|
| | | //--- Not fatal in the tester, where some of these read differently, so it reports rather than
|
| | | //--- refuses - but it always reports.
|
| | | string tradeWhy = "";
|
| | | if(!TCTradingPermitted(tradeWhy))
|
| | | PrintFormat("OnInit: ⚠ TRADING IS NOT CURRENTLY POSSIBLE - %s", tradeWhy);
|
| | | ENUM_ORDER_TYPE_FILLING fill = ORDER_FILLING_FOK;
|
| | | string fillWhy = "";
|
| | | TCPickFilling(Symbol(), fill, fillWhy);
|
| | | PrintFormat("OnInit: filling mode %s, %s account.", EnumToString(fill),
|
| | | (TCIsHedging() ? "hedging" : "netting"));
|
| | | //--- THE ACCOUNT'S KILL SWITCH, PRINTED. This broker is a prop firm and its account carries a
|
| | | //--- money-mode stop-out at 95% of deposit; the tester inherits it and ENDS THE RUN the moment
|
| | | //--- balance crosses it - "stop out occurred on 33% of testing interval", final balance 4747.48.
|
| | | //--- Four symbols across three sweeps were written off as broken history or margin faults on
|
| | | //--- that evidence. They had simply lost 5%. Every run that ended early sat just past -250 on a
|
| | | //--- 5,000 deposit and every run that did not, did not.
|
| | | const ENUM_ACCOUNT_STOPOUT_MODE soMode = (ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
|
| | | PrintFormat("OnInit: stop-out mode %s, margin call at %.2f, stop out at %.2f, balance %.2f - "
|
| | | "%s",
|
| | | EnumToString(soMode),
|
| | | AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL), AccountInfoDouble(ACCOUNT_MARGIN_SO_SO),
|
| | | AccountInfoDouble(ACCOUNT_BALANCE),
|
| | | (soMode == ACCOUNT_STOPOUT_MODE_MONEY
|
| | | ? "a MONEY stop-out ends a backtest at that equity, whatever the margin level"
|
| | | : "percent mode - a stop-out needs the margin level to fall this low"));
|
2026-09-13 14:32:28 -04:00 | | | EventSetTimer(60);
|
| | |
|
| | | //--- THE RESOLVED LADDER. The threshold is a percentage of the ensemble's weight, so what it
|
| | | //--- demands depends on how many filters are registered - state it rather than let it be assumed.
|
| | | const int members = ExtSignal.FilterCount();
|
| | | if(members > 0)
|
| | | PrintFormat("OnInit: %d filter(s) registered, so one unopposed voter at full weight scores"
|
2026-09-13 14:32:40 -04:00 | | | " %.2f%%. ThresholdOpen = %d%% therefore needs about %d of them agreeing."
|
| | | " ThresholdClose = %d%%, direction %s.",
|
2026-09-13 14:32:28 -04:00 | | | members, 100.0 / members, (int)ThresholdOpen,
|
2026-09-13 14:32:40 -04:00 | | | (int)MathCeil((double)ThresholdOpen * members / 100.0),
|
| | | (int)ThresholdClose, EnumToString(Direction));
|
2026-09-13 14:32:28 -04:00 | | | return INIT_SUCCEEDED;
|
2026-07-14 22:36:27 -04:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | void OnDeinit(const int reason)
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | EventKillTimer();
|
| | | if(ExtSignal != NULL)
|
| | | ExtSignal.ReportLadder();
|
| | | if(ExtDbReady)
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | ExtJournal.Flush();
|
| | | ExtJournal.Report();
|
| | | ExtDbm.CloseDatabase();
|
2026-07-14 22:36:27 -04:00 | | | }
|
2026-09-13 14:32:28 -04:00 | | | ExtExpert.Deinit();
|
2026-07-14 22:36:27 -04:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | void OnTick()
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | ExtExpert.OnTick();
|
2026-07-14 22:36:27 -04:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | //| The queue is drained here and nowhere else. Writing per |
|
| | | //| evaluation would put SQLite in the tick path; the connection is |
|
| | | //| opened once at init and held for the whole run. |
|
2026-07-14 22:36:27 -04:00 | | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:40 -04:00 | | | //--- HOW OFTEN THE WEIGHTS ARE RE-DERIVED, in days of market time. The ranking pass is ~100 SQL
|
| | | //--- aggregates; run every timer tick it would dominate a decade-long pass, and run once at init
|
| | | //--- it would freeze the weights on whatever the first days happened to show. Daily is the natural
|
| | | //--- cadence for a swing EA: often enough to track a module going bad, rare enough to be free.
|
| | | #define WARRIOR_RERANK_DAYS 1
|
| | | datetime ExtLastRerank = 0;
|
| | |
|
2026-09-13 14:32:28 -04:00 | | | void OnTimer()
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | if(ExtDbReady)
|
2026-09-13 14:32:40 -04:00 | | | {
|
| | | //--- FLUSH BEFORE RANKING, always. The ranking reads the database, not the queue, so a
|
| | | //--- rerank on an unflushed buffer silently scores the module on everything EXCEPT its most
|
| | | //--- recent evidence - which is the evidence most likely to have changed the answer.
|
2026-09-13 14:32:28 -04:00 | | | ExtJournal.Flush();
|
2026-09-13 14:32:40 -04:00 | | | const datetime now = TimeCurrent();
|
| | | if(now - ExtLastRerank >= WARRIOR_RERANK_DAYS * 86400)
|
| | | {
|
| | | ExtLastRerank = now;
|
| | | if(DB_RankingFeedsWeights)
|
| | | ExtSignal.Rerank(GetPointer(ExtDbm));
|
| | | }
|
| | | }
|
2026-09-13 14:32:28 -04:00 | | | ExtExpert.OnTimer();
|
2026-07-14 22:36:27 -04:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:28 -04:00 | | | void OnTrade()
|
2026-07-14 22:36:27 -04:00 | | | {
|
2026-09-13 14:32:28 -04:00 | | | ExtExpert.OnTrade();
|
2026-07-14 22:36:27 -04:00 | | | }
|
| | | //+------------------------------------------------------------------+
|