2026-07-13 03:23:39 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Warrior_EA |
|
|
|
|
|
//| AnimateDread |
|
|
|
|
|
//| |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
// Money management classes (CExpertMoney) are invoked by the standard library's
|
|
|
|
|
// CExpert with a fixed (price, sl) signature - they have no pointer back to the
|
|
|
|
|
// signal filter that computed those levels. CExpertSignalCustom::OpenParams()
|
|
|
|
|
// refreshes these globals right before Money.CheckOpenLong/Short() is called for
|
|
|
|
|
// the same trade, so Money classes can read a same-tick confidence value without
|
|
|
|
|
// requiring an intrusive change to the wizard framework's call chain.
|
|
|
|
|
double g_AISignedConfidence = 0.0; // -1..1, sign = direction, magnitude = AI confidence; 0 if no AI filter
|
|
|
|
|
double g_DBConfidence = 0.0; // 0..1, historical time-based win rate of the active pattern set
|
|
|
|
|
// source takes CONFIDENCE_SOURCE's underlying int values (0=CONF_AI, 1=CONF_DB, 2=CONF_BLENDED).
|
|
|
|
|
// Declared as int rather than the enum type so this header has no dependency on the include
|
|
|
|
|
// order of Enumerations\InputEnums.mqh (this file is pulled in from class headers that are
|
|
|
|
|
// included before Inputs.mqh in Warrior_EA.mq5).
|
2026-07-18 17:39:58 -04:00
|
|
|
// reward:risk ratio of the specific trade OpenParams() just sized (b in the Kelly-criterion
|
|
|
|
|
// formula CMoneyIntelligent::AdjustRiskAmount() uses) - refreshed on the same same-tick
|
|
|
|
|
// contract as the two confidence globals above; always > 0 when populated, since OpenParams()
|
2026-08-09 14:51:59 -04:00
|
|
|
// no longer rejects on reward:risk at all (the filter was removed 2026-08-09), so g_TradeRewardRiskRatio
|
|
|
|
|
// reaches Money as a SIZING input rather than as the survivor of a veto.
|
2026-07-18 17:39:58 -04:00
|
|
|
double g_TradeRewardRiskRatio = 0.0;
|
2026-07-22 13:33:56 -04:00
|
|
|
// Live per-tick signed AI confidence (-1..1, sign = predicted direction, magnitude = confidence),
|
|
|
|
|
// refreshed every tick/timer from the active AI signal's SignedAIConfidence() in
|
|
|
|
|
// CExpertSignalAIBase::ScheduleTrainingIfNeeded() - independent of the OpenParams() same-tick
|
|
|
|
|
// contract above, because an ALREADY-OPEN position generates no OpenParams() calls yet the
|
|
|
|
|
// intelligent trailing (Trailing\TrailingIntelligent.mqh) still needs a current read while holding.
|
2026-07-27 22:08:55 -04:00
|
|
|
// This is written by the active AI signal once per tick; 0.0 means no AI filter is active/converged yet.
|
2026-07-22 13:33:56 -04:00
|
|
|
double g_LiveAISignedConfidence = 0.0;
|
feat(gate): grade OOS calls on the exit policy actually in force, and move vote combining out of the members and into the orchestrator
Option (a) from the exit-policy question: the certified number must be the traded
number. Plus the modularity correction the user called for on 778b6c0.
1. VOTE COMBINING BELONGS TO THE ORCHESTRATOR, NOT TO A MEMBER.
778b6c0 fixed the last-writer-wins bug on g_LiveAISignedConfidence by having a
member average its siblings through g_warriorEnsemble. That trades a scheduling bug
for a coupling bug, and it is the wrong shape for this EA: every signal runs in its
own instance, minds its own state, and VOTES to the orchestrator, which is the only
thing allowed to combine opinions.
Replaced with a publish/aggregate pair in Variables\ConfidenceBridge.mqh whose split
is enforced by shape rather than by convention:
- PublishAIVote(slot, conf) - a member writes ONLY its own slot, reads nobody's;
- AggregateAIVotes() - called by CExpertSignalCustom::LiveSignedConfidence.
CExpertSignalAIBase::EnsembleLiveSignedConfidence is gone. The orchestrator also
republishes the aggregate into g_LiveAISignedConfidence, because the intelligent
trailing reads that global directly and must act on the same number the exit route
does rather than on a leftover from whichever member ticked last. A solo AI signal
owns slot 0, so the non-ensemble path is unchanged.
2. THE GATE NOW REPLAYS THE REAL EXIT RULE.
SimulateTradeOutcome() walks the same price series with the same fill/barrier/spread
convention as ComputeLabelForBar - deliberately by copy, so a disagreement between
the two can only be a policy effect and never a discrepancy between two pieces of our
own arithmetic - and terminates at the FIRST of stop / target / vote reversal /
horizon. Barriers are tested before the vote on the same bar: intrabar we cannot know
which came first, and the barrier is what the broker executes automatically, so
checking the vote first would credit the exit policy with escapes a real stop would
have taken out of its hands.
It runs AFTER pass 3, not inside it. A vote-flip exit for a trade entered at bar r is
decided by the model's output at bars r-1, r-2, ... - NEWER bars - and pass 3 walks
oldest-to-newest, so at the moment r is graded its own exit does not exist yet. Only
once m_oosDecisionSeries is complete over the whole OOS window can the replay run.
In ensemble mode that series carries the member's adjusted decision and the live exit
reads the ensemble aggregate, which is the coupling the user identified: an LSTM entry
really can be closed by the ensemble turning against it.
3. THE STATISTIC HAS TO CHANGE WITH THE POLICY, AND THAT IS THE REAL FINDING.
A barrier exit pays a fixed R. A vote exit pays whatever the close happens to be. So
the moment vote exits are enabled the payoff is CONTINUOUS, and "win rate vs
break-even" stops being a meaningful test - there is no fixed break-even for a
variable payoff. SimulateTradeOutcome therefore returns R rather than a bool, and the
replay reports expectancy in R with its SE taken from the R distribution (overlap-
deflated on the same EffectiveSampleSize doctrine as every other SE here), not from a
binomial.
This is the same class of error as win-based scoring in 2026-08-09: measuring a
variable-payoff process with a fixed-payoff statistic. Naming it now, while vote exits
are still off, is much cheaper than discovering it after they go on.
4. WHY THIS IS SAFE TO SHIP TODAY.
Min_Vote_Close ships Disabled (101, unreachable on both scales it drives) and reaches
the AI signal through the new ExitPolicy() setter as 1.01, which the setter turns into
0.0 = no vote exit. Under that policy every replayed trade resolves at a barrier and
the simulation is arithmetically the same trade the deploy gate already certifies -
they cannot drift. The report says so explicitly, and prints ONCE per run in that
state; when vote exits are on it prints every era, because then the divergence is the
thing to watch. Nothing about today's numbers moves.
The gate switchover is wired but dormant by construction: it becomes exit-aware the
moment the input is enabled, which is exactly what "the certified number is the traded
number" has to mean.
KNOWN LIMIT, stated rather than hidden: only the AI early-exit route is replayed. The
rule-based averaged-vote close (m_threshold_close) depends on every other filter's
live vote, which pass 3 does not reproduce, so a position the classic filters would
have closed is held to its barrier here. The replay therefore holds LONGER than live
and overstates barrier-reached outcomes. Faithful only while the AI is the dominant
vote - which is the configuration this is being built for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:25:57 -04:00
|
|
|
//--- THE LIVE AI VOTE BOARD, and it exists to keep one signal out of another signal's business.
|
|
|
|
|
//---
|
refactor(kiss): drop the AI sub-vote early-exit route; certified == traded
First of the AI vote layers to go. CheckClosePosition had two exit routes:
the stock blended vote, and an AI-only one reading the AI members' sub-vote
undiluted. The second existed because an AI reversal averaged in with the
classic filters could be diluted below the threshold before it could close
a position.
It is gone, and with it m_lastAiVote and the aiResult/aiWeightSum pair
Direction() carried to feed it.
This CLOSES the certified-vs-traded gap rather than widening it. The
deploy gate certifies a win rate measured on hold-to-resolution outcomes,
and CheckClosePosition already gated the blended route off whenever an AI
model's derived geometry was on the order - so the AI route was the only
vote exit an AI-certified trade could take, and the exit replay existed to
reproduce it. With it removed, an AI-certified position holds to its
barrier by construction instead of by reconstruction, so Warrior_EA.mq5
now pushes ExitPolicy(0.0, true) unconditionally. Previously it forwarded
Min_Vote_Close and relied on Disabled arriving as 1.01 to switch the
simulated exit off by arithmetic - correct at the shipped default, and one
input change away from the simulation and the live path describing
different games.
Min_Vote_Close keeps its meaning for the classic route and is now
documented as inert wherever an AI certificate governs, rather than
appearing to drive an exit it can no longer reach.
Comment debt cleared while here: a tombstone block for m_ai_exit_threshold
(a member deleted 2026-08-18) still sat in the header, and four sites still
named LiveSignedConfidence's "two consumers" - it had one, the intelligent
trailing stop, since that same date.
NOT touched, and deliberately: NMS declustering is NOT a quality layer. It
gates the live signal at Inference.mqh:226 (NmsLiveAccept), and the
undeclustered population is ~8x what the EA trades. Removing it would
multiply live position count, not simplify a scoring path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:27:28 -04:00
|
|
|
//--- g_LiveAISignedConfidence above is written once per tick and read by the intelligent trailing stop.
|
|
|
|
|
//--- With ONE AI signal that is exactly right. With several (an ensemble) every member wrote it
|
|
|
|
|
//--- unconditionally, last writer won, and an LSTM entry's stop was moved by whichever member's OnTick
|
|
|
|
|
//--- happened to run last (user-identified 2026-08-17).
|
feat(gate): grade OOS calls on the exit policy actually in force, and move vote combining out of the members and into the orchestrator
Option (a) from the exit-policy question: the certified number must be the traded
number. Plus the modularity correction the user called for on 778b6c0.
1. VOTE COMBINING BELONGS TO THE ORCHESTRATOR, NOT TO A MEMBER.
778b6c0 fixed the last-writer-wins bug on g_LiveAISignedConfidence by having a
member average its siblings through g_warriorEnsemble. That trades a scheduling bug
for a coupling bug, and it is the wrong shape for this EA: every signal runs in its
own instance, minds its own state, and VOTES to the orchestrator, which is the only
thing allowed to combine opinions.
Replaced with a publish/aggregate pair in Variables\ConfidenceBridge.mqh whose split
is enforced by shape rather than by convention:
- PublishAIVote(slot, conf) - a member writes ONLY its own slot, reads nobody's;
- AggregateAIVotes() - called by CExpertSignalCustom::LiveSignedConfidence.
CExpertSignalAIBase::EnsembleLiveSignedConfidence is gone. The orchestrator also
republishes the aggregate into g_LiveAISignedConfidence, because the intelligent
trailing reads that global directly and must act on the same number the exit route
does rather than on a leftover from whichever member ticked last. A solo AI signal
owns slot 0, so the non-ensemble path is unchanged.
2. THE GATE NOW REPLAYS THE REAL EXIT RULE.
SimulateTradeOutcome() walks the same price series with the same fill/barrier/spread
convention as ComputeLabelForBar - deliberately by copy, so a disagreement between
the two can only be a policy effect and never a discrepancy between two pieces of our
own arithmetic - and terminates at the FIRST of stop / target / vote reversal /
horizon. Barriers are tested before the vote on the same bar: intrabar we cannot know
which came first, and the barrier is what the broker executes automatically, so
checking the vote first would credit the exit policy with escapes a real stop would
have taken out of its hands.
It runs AFTER pass 3, not inside it. A vote-flip exit for a trade entered at bar r is
decided by the model's output at bars r-1, r-2, ... - NEWER bars - and pass 3 walks
oldest-to-newest, so at the moment r is graded its own exit does not exist yet. Only
once m_oosDecisionSeries is complete over the whole OOS window can the replay run.
In ensemble mode that series carries the member's adjusted decision and the live exit
reads the ensemble aggregate, which is the coupling the user identified: an LSTM entry
really can be closed by the ensemble turning against it.
3. THE STATISTIC HAS TO CHANGE WITH THE POLICY, AND THAT IS THE REAL FINDING.
A barrier exit pays a fixed R. A vote exit pays whatever the close happens to be. So
the moment vote exits are enabled the payoff is CONTINUOUS, and "win rate vs
break-even" stops being a meaningful test - there is no fixed break-even for a
variable payoff. SimulateTradeOutcome therefore returns R rather than a bool, and the
replay reports expectancy in R with its SE taken from the R distribution (overlap-
deflated on the same EffectiveSampleSize doctrine as every other SE here), not from a
binomial.
This is the same class of error as win-based scoring in 2026-08-09: measuring a
variable-payoff process with a fixed-payoff statistic. Naming it now, while vote exits
are still off, is much cheaper than discovering it after they go on.
4. WHY THIS IS SAFE TO SHIP TODAY.
Min_Vote_Close ships Disabled (101, unreachable on both scales it drives) and reaches
the AI signal through the new ExitPolicy() setter as 1.01, which the setter turns into
0.0 = no vote exit. Under that policy every replayed trade resolves at a barrier and
the simulation is arithmetically the same trade the deploy gate already certifies -
they cannot drift. The report says so explicitly, and prints ONCE per run in that
state; when vote exits are on it prints every era, because then the divergence is the
thing to watch. Nothing about today's numbers moves.
The gate switchover is wired but dormant by construction: it becomes exit-aware the
moment the input is enabled, which is exactly what "the certified number is the traded
number" has to mean.
KNOWN LIMIT, stated rather than hidden: only the AI early-exit route is replayed. The
rule-based averaged-vote close (m_threshold_close) depends on every other filter's
live vote, which pass 3 does not reproduce, so a position the classic filters would
have closed is held to its barrier here. The replay therefore holds LONGER than live
and overstates barrier-reached outcomes. Faithful only while the AI is the dominant
vote - which is the configuration this is being built for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:25:57 -04:00
|
|
|
//---
|
|
|
|
|
//--- The obvious patch - have a member average its siblings - trades a scheduling bug for a coupling bug,
|
|
|
|
|
//--- and this EA is deliberately built the other way: every signal runs in its own instance, minds its own
|
|
|
|
|
//--- state, and VOTES to the orchestrator, which is the only thing allowed to combine opinions. So the
|
|
|
|
|
//--- board below is a publish/aggregate pair with that split enforced by shape:
|
|
|
|
|
//--- - a member writes ONLY its own slot (PublishAIVote), never reads another's;
|
|
|
|
|
//--- - the orchestrator (CExpertSignalCustom::LiveSignedConfidence) calls AggregateAIVotes() to combine.
|
|
|
|
|
//--- Same contract as every other global here: one-way, same-tick, no back-pointer into the wizard chain.
|
|
|
|
|
//---
|
|
|
|
|
//--- The mean, matching the live open decision (a weighted-average vote, not unanimity) and the ensemble
|
|
|
|
|
//--- gate's combined-vote score. A member that abstains publishes 0 and dilutes, exactly as it does there;
|
|
|
|
|
//--- a member still training publishes 0, so a half-trained ensemble reads WEAKER rather than louder,
|
|
|
|
|
//--- which is the safe direction for something that can close a position.
|
|
|
|
|
#define AI_VOTE_BOARD_MAX 8
|
|
|
|
|
double g_AIVoteBoard[AI_VOTE_BOARD_MAX];
|
|
|
|
|
bool g_AIVoteBoardUsed[AI_VOTE_BOARD_MAX];
|
|
|
|
|
void PublishAIVote(int slot, double signedConfidence)
|
|
|
|
|
{
|
|
|
|
|
if(slot < 0 || slot >= AI_VOTE_BOARD_MAX)
|
|
|
|
|
return;
|
|
|
|
|
g_AIVoteBoard[slot] = signedConfidence;
|
|
|
|
|
g_AIVoteBoardUsed[slot] = true;
|
|
|
|
|
}
|
|
|
|
|
double AggregateAIVotes(void)
|
|
|
|
|
{
|
|
|
|
|
double sum = 0.0;
|
|
|
|
|
int n = 0;
|
|
|
|
|
for(int i = 0; i < AI_VOTE_BOARD_MAX; i++)
|
|
|
|
|
if(g_AIVoteBoardUsed[i])
|
|
|
|
|
{
|
|
|
|
|
sum += g_AIVoteBoard[i];
|
|
|
|
|
n++;
|
|
|
|
|
}
|
|
|
|
|
return (n > 0) ? sum / n : 0.0;
|
|
|
|
|
}
|
fix: live trades now use the geometry the gate certifies; perf: BN kernels
Three changes, one theme: the trade placed, the trade graded, and the trade
computed are now the same trade.
1) GEOMETRY WIRE (correctness, the ranked #1 open issue). The measured barrier
pair reached the LABELS only - OpenParams still placed orders at the enum
geometry (2*ATR/6*ATR), so the deploy gate certified "reaches 1.62*ATR before
3.33*ATR above break-even" about trades the EA never placed. Published via
g_DerivedSlAtrMult/g_DerivedTpAtrMult (ConfidenceBridge, same same-tick
contract as the confidence globals, because OpenParams runs on the root signal
which has no pointer to the AI filter). Two writers: DeriveBarrierGeometry at
era 0, and the .cfg adoption a deployed model takes. Overrides both legs and
both Intelligent modes - the certificate is exact or it is nothing. TP is
ATR-anchored like the label, NOT risk-relative, so a floor-widened stop cannot
reshape the certified target.
2) BATCH NORM RUNS DEVICE-SIDE ON OPENCL. Four kernels in Network.cl -
forward, hidden gradient, gamma/beta accumulate, gamma/beta apply - each a
line-for-line transcription of the host implementation (NormalizeHost /
HiddenGradHost / StepGammaBeta) including every NaN guard, clamp, and the
exact moment-write ordering. The host copies remain the runtime for the DLL
and pure-MQL5 tiers and the reference the kernels must match.
Because this box has no OpenCL platform, the safety story is layered:
- shim validation: kernels compiled as C and driven against a fp64 host
transcription over NaN-poisoned stats, NaN gamma, over-clamp inputs, the
frozen path, both optimizers, 3 batches - ALL PASS, worst normalized diff
0.132 vs tolerance 1.0
- in-situ self-check: each kernel is compared against its host twin ON FIRST
USE on the real device (SelfCheckBn*), covering what the shim cannot - arg
indices and buffer bindings. Any disagreement resyncs from the good copy,
latches all BN kernels off process-wide, and training continues host-side.
A transcription bug costs a warning and some speed, never a poisoned .nnw.
- sync discipline: BatchOptions is now a CBufferDouble with explicit
authority tracking (m_bnDeviceAuthoritative). Checkpoints/saves pull
read-only; restores/loads/resets push; a mid-batch handover drains the
device gamma/beta accumulator into the host arrays so no sample is lost.
3) SMALL FIXES. Apply-kernel build failure now latches the dispatch path at
init (one warning instead of warning + failed Execute). Build tag bumped to
win-scoring-gpu-v1 - first tag change since expectancy-stop-v1 despite five
binary-changing commits.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:51:40 -04:00
|
|
|
// MEASURED barrier geometry, in ATR multiples, published by the AI signal for the LIVE order path.
|
|
|
|
|
// Written from exactly two places: DeriveBarrierGeometry() when the geometry is measured at era 0, and
|
|
|
|
|
// the .cfg adoption in Persistence.mqh when a trained model is loaded with its pinned pair. 0.0 = not
|
|
|
|
|
// derived (fresh start before era 0, or no AI filter) - OpenParams() then falls back to the SL_Mode/
|
|
|
|
|
// TP_Mode enum multiples exactly as before.
|
|
|
|
|
//
|
|
|
|
|
// This bridge exists because of a real incident, not tidiness: the deploy gate certifies "this model's
|
|
|
|
|
// trades reach the MEASURED target before the MEASURED stop at a win rate beating break-even" - and
|
|
|
|
|
// until 2026-08-09 the live EA then placed trades with the ENUM geometry (2*ATR stop, 6*ATR target on
|
|
|
|
|
// the shipped SP500 config) that the certificate says nothing about. The model was graded on one game
|
|
|
|
|
// and paid on another. Same one-way, same-tick contract as the confidence globals above; OpenParams()
|
|
|
|
|
// runs on the aggregate/root signal, which has no pointer to the AI filter that measured these.
|
|
|
|
|
double g_DerivedSlAtrMult = 0.0;
|
|
|
|
|
double g_DerivedTpAtrMult = 0.0;
|
2026-07-13 03:23:39 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Combine AI/DB confidence into a single 0..1 magnitude |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
double CombinedConfidence(int source)
|
|
|
|
|
{
|
2026-07-27 15:52:39 -04:00
|
|
|
double aiMag = MathIsValidNumber(g_AISignedConfidence) ? MathAbs(g_AISignedConfidence) : 0.0;
|
|
|
|
|
double dbMag = MathIsValidNumber(g_DBConfidence) ? g_DBConfidence : 0.0;
|
|
|
|
|
aiMag = MathMax(0.0, MathMin(aiMag, 1.0));
|
|
|
|
|
dbMag = MathMax(0.0, MathMin(dbMag, 1.0));
|
2026-07-13 03:23:39 -04:00
|
|
|
switch(source)
|
|
|
|
|
{
|
|
|
|
|
case 1: // CONF_DB
|
|
|
|
|
return dbMag;
|
|
|
|
|
case 2: // CONF_BLENDED
|
|
|
|
|
return (aiMag + dbMag) / 2.0;
|
|
|
|
|
default: // CONF_AI
|
|
|
|
|
return aiMag;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|