Warrior_EA/Variables/ConfidenceBridge.mqh

105 lines
6.4 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| 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).
// 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()
feat: remove Min_Risk_Reward_Ratio - a guess was overriding a measurement The barrier geometry is derived from the instrument's own excursion distribution (stop at q75 of adverse travel, target at q50 of favourable), and then a 1:2 floor was applied on top, raising the target to twice whatever the stop happened to be. On SP500 H1 that pushed the target to 6.66*ATR, reached on 3.3% of bars inside the horizon - so the label became "almost never a win" and every topology was trained to predict an event that essentially does not occur. A measured target has to stay measured. The ratio never bought what it was believed to buy. A reward:risk floor does not create expectancy; it trades hit rate against payoff at a break-even the geometry already fixes - which this project has separately MEASURED (payoff 0.92 -> 5.72 with expectancy flat). What it did buy was two outages: four consecutive Market validation rejections for "no trading operations" when it rejected 100% of setups, and the label corruption above. Removed: - the input and the RISK_REWARD_RATIO enum (deleted, not left dangling - a live enum with no input behind it is the shape of the stale-.set incident that trained ~250 eras on the wrong target) - the forced target raise in the label geometry - the rrOK eligibility gate in the barrier-geometry scan, so every unclamped pairing now competes on the measurement alone. Clamping stays disqualifying for its own unrelated reason. - the reward < minRR*risk veto in OpenParams Kept: g_TradeRewardRiskRatio still computed and still bridged to Kelly sizing in MoneyIntelligent - the ratio as a SIZING input was always the sound use. Risk stays bounded where it actually is - account risk % and CRiskBudget. The low-reachability warning survives but is re-aimed: with nothing inflating the target, a target the market rarely reaches can only mean the horizon is truncating the excursions the geometry is derived from. Both build variants compile 0 errors / 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
double g_TradeRewardRiskRatio = 0.0;
// 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.
// This is written by the active AI signal once per tick; 0.0 means no AI filter is active/converged yet.
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.
//---
//--- g_LiveAISignedConfidence above is written once per tick and read by the AI early-exit route and the
//--- intelligent trailing. With ONE AI signal that is exactly right. With four (AIType == AI_HYBRID) every
//--- member wrote it unconditionally, last writer won, and the exit that closed an LSTM entry was decided
//--- by whichever member's OnTick happened to run last (user-identified 2026-08-17).
//---
//--- 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;
//+------------------------------------------------------------------+
//| Combine AI/DB confidence into a single 0..1 magnitude |
//+------------------------------------------------------------------+
double CombinedConfidence(int source)
{
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));
switch(source)
{
case 1: // CONF_DB
return dbMag;
case 2: // CONF_BLENDED
return (aiMag + dbMag) / 2.0;
default: // CONF_AI
return aiMag;
}
}
//+------------------------------------------------------------------+