Warrior_EA/Variables/ConfidenceBridge.mqh
AnimateDread f64e0f8b67 feat(ensemble): per-NN inputs replace the preset selector - the meta head becomes the vote's gate
User design (2026-08-19): 'remove the enum menu that selects neural networks... individual
inputs for every NN just like classic signals... the META NN should be integrated into the
voting decision pipeline when enabled... as a bonus meta labelling is applied to enabled NNs.'

- AI_CHOICE is GONE (tombstoned per the stale-.set doctrine). Use_MLP/Use_CONV/Use_LSTM/
  Use_CONVLSTM are ordinary bools like the classic votes; the ensemble arithmetic adapts to
  any subset because the consensus divisor is the enabled capable weight. Two or more
  enabled = ensemble (|ENS1 token + joint gate, exactly the old AI_HYBRID fingerprints, so
  existing weight files keep loading); one = the old solo preset; none = classic-only.
- Use_MetaLabeling un-couples META from the direction NNs (the old selector made them
  mutually exclusive). S3 ships: CSignalMETA::LiveMetaGate scores each vote-cleared entry
  (shared window at bar 1 + proposal descriptor: side, net vote, live geometry, spread/ATR;
  pattern one-hot ZEROED - ranking, not calibrated probability, documented in the body) and
  vetoes below the cost-adjusted break-even. Entries only; fail-open everywhere, loudly.
- COEXISTENCE HAZARDS closed: VoteCapableWeight()=0 and ProspectiveVote()=false for the
  meta target - solo-only until today, a trained META would otherwise sit in the consensus
  divisor as a permanent abstainer and shrink every vote by its module weight.
- CERTIFIED == TRADED: the ensemble era verdict replays the identical veto through the same
  g_warriorMetaGate pointer over its OOS fired bars (bar re-resolved from the row's own
  time; fail-open counted as fires and reported: 'metaGate: N approved, M vetoed, K
  unscored'). The overlay deliberately does NOT replay it (veto-filter-in-replay class,
  calendar-cliff precedent) - documented at the sweep site. Solo charts' own gate does not
  model the veto - the standing solo-gate caveat, documented at the input.
- DB continuity: the pattern/journal DB fingerprint's first slot was (int)AIType;
  DbLegacyAiSlot() maps every legacy-expressible config to its OLD value (new 2-3 member
  subsets get 100+bitmask, outside the legacy range) so no existing database re-keys.
  filterID becomes the enabled roster via one EnabledNNSummary().
- HUD: the meta line shows the gate (armed/(trn), last P vs BE, ok/veto tally); the
  armed/disarmed announcement fires on state change via one latch (MetaGateArmedNow), not
  only when an entry happens to be proposed.

NOT COMPILED - user compiles in MetaEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:01:02 -04:00

105 lines
6.4 KiB
MQL5

//+------------------------------------------------------------------+
//| 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()
// 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;
//--- 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 several (an ensemble) 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;
}
// 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;
}
}
//+------------------------------------------------------------------+