Warrior_EA/Expert/Persistence/IPersistenceView.mqh

99 lines
5.8 KiB
MQL5
Raw Permalink Normal View History

refactor(persistence): ModelPersistence is a real collaborator, not a raw-include partial (S3) Expert/AIBase/Persistence.mqh (598 lines, 8 methods) -> Expert/Persistence/: IPersistenceView.mqh (abstract, 68 read+write accessors) + AIBasePersistenceView.mqh/ AIBasePersistenceViewImpl.mqh (the adapter) + ModelPersistence.mqh (CModelPersistence, the real collaborator - signal owns m_modelPersistence and binds it to m_persistenceView, same shape as ChartUI's S2). Grep-verified before starting: every field these 8 methods touch is ALSO touched elsewhere in the class (Training/Lifecycle/OnlineLearning/Topology/FeatureScreen/ Labels.mqh) or already exposed via ChartView. Zero exclusive state, unlike ChartUI's arrow-restore/rescan queues - CModelPersistence is stateless, holding only the borrowed view pointer, operating entirely through 68 Persist*/PersistSet*() accessors on the signal. ValidateCpuInference's Net-pointer/throwaway-clone core is ONE consolidated view call (PersistRunCpuInferenceSelfCheck) rather than field-by-field - irreducible pointer/ object work, not signal state, same doctrine as ChartScoreBarForRescan. LoadNetWithRetry keeps its original CheckPointer(Net)-free Net.Load() call unchanged (no guard added - would change failure behaviour on what must be a pure relocation). This code writes the actual on-disk .cfg/.stats binary layouts every deployed model depends on (explicit "DO NOT REORDER" comment in the original), so beyond compiling clean (0 errors, 0 warnings) this was verified with a positional field-order diff: every FileWrite*/FileRead* call's target field, extracted and normalized from both the original and the new file, matches 1:1 in the same order (43/43 on the write side covering SaveModelStats+SaveTopologyConfiguration, 17/17 on LoadModelStats' read side; LoadAndCompareTopologyConfiguration's local-variable read block was copied verbatim, untouched, so nothing to diff there). The magic-version conditionals (WST2-6, haveDerivedStages/haveBarrierGeometry/etc.) moved unchanged. All 8 methods keep their exact original signatures as one-line forwards - zero external call sites changed.
2026-08-23 21:45:09 -04:00
//+------------------------------------------------------------------+
//| Warrior_EA |
//| AnimateDread |
//| |
//| The read/write surface CModelPersistence needs from the signal. |
//| Same shape as Chart\IChartView.mqh - abstract, pure `= 0`, no |
//| signal include. This view is READ+WRITE (unlike CChartView, |
//| which is read-only) because Persistence's whole job is loading |
//| saved state BACK into the signal. |
//+------------------------------------------------------------------+
class CPersistenceView
{
public:
virtual ~CPersistenceView(void) { }
//--- IDENTITY (reused via the signal's existing Data*()/Chart*() getters)
virtual string Id(void) = 0;
virtual string FileName(void) = 0;
virtual int NeuronsCount(void) = 0;
virtual long CumIsCorrect(void) = 0;
virtual long CumIsTotal(void) = 0;
virtual long CumOosCorrect(void) = 0;
virtual long CumOosTotal(void) = 0;
//--- EnforceTopologyContract()
virtual bool NetLoaded(void) = 0;
virtual bool UsesConvStage(void) = 0;
virtual uint NetFirstConvWindow(void) = 0;
virtual int ConvReceptiveFieldBars(void) = 0;
virtual ENUM_ACTIVATION OutputLayerActivation(void) = 0;
virtual bool NetEnforceOutputActivation(const ENUM_ACTIVATION intended, ENUM_ACTIVATION &stale) = 0;
virtual void SetTopologySuperseded(const bool v) = 0;
//--- SaveModelStats()/LoadModelStats() - one scalar getter+setter per on-disk field, in WST6 order
virtual double PriorBuy(void) = 0;
virtual void SetPriorBuy(const double v) = 0;
virtual double PriorSell(void) = 0;
virtual void SetPriorSell(const double v) = 0;
virtual double PriorNeutral(void) = 0;
virtual void SetPriorNeutral(const double v) = 0;
virtual double ConfidenceCalScale(void) = 0;
virtual void SetConfidenceCalScale(const double v) = 0;
virtual bool MqlInferenceValidated(void) = 0;
virtual void SetMqlInferenceValidated(const bool v) = 0;
virtual datetime OnlineLearnedUpToTime(void) = 0;
virtual void SetOnlineLearnedUpToTime(const datetime v) = 0;
virtual double OnlineRollingAcc(void) = 0;
virtual void SetOnlineRollingAcc(const double v) = 0;
virtual long OnlineSamples(void) = 0;
virtual void SetOnlineSamples(const long v) = 0;
virtual int LastBuyFiredPrecPct(void) = 0;
virtual void SetLastBuyFiredPrecPct(const int v) = 0;
virtual int LastSellFiredPrecPct(void) = 0;
virtual void SetLastSellFiredPrecPct(const int v) = 0;
virtual int LastBuyRecallPct(void) = 0;
virtual void SetLastBuyRecallPct(const int v) = 0;
virtual int LastSellRecallPct(void) = 0;
virtual void SetLastSellRecallPct(const int v) = 0;
virtual int LastBuyFired(void) = 0;
virtual void SetLastBuyFired(const int v) = 0;
virtual int LastSellFired(void) = 0;
virtual void SetLastSellFired(const int v) = 0;
virtual void SetCumIsCorrect(const long v) = 0;
virtual void SetCumIsTotal(const long v) = 0;
virtual void SetCumOosCorrect(const long v) = 0;
virtual void SetCumOosTotal(const long v) = 0;
fix(vote): persist the tier ladder - a converged model was mute after every restart THIS IS NOT A DISPLAY BUG. A deployed model could not vote, or trade, at any point after a terminal restart, and never would have. LiveVoteContribution() returns 0 for every call until m_tiersSelfRanked is set - deliberately, and correctly: before RankTiersFromOos() runs, m_pattern_0..3 hold the constructor's stock 25/50/75/100, which since the 2026-08-18 currency change is the WRONG UNIT rather than a weak opinion, and one unranked member would drag the whole ensemble over any threshold. But that ladder is produced ONLY by a completed pass 3, and it was never persisted - the code comment at LiveVoteContribution says so outright. A converged model runs no further passes. So on every restart it lost its entire vote permanently: LiveVoteContribution -> 0 => no live vote ("0 vote/4 flat") ReconstructionWeight -> 0 => overlay divisor 0 ("0 had a snapshot") => no arrows => no fired bars, so g_ensCumOosTotal stays 0 => "measuring..." forever Every symptom reported over the last three exchanges is that one cause. The log is unambiguous: six H4 charts resumed at era 70/71, all 24 rescans completed with ~2700 Buy / ~2200 Sell per model, and the overlay then swept 4999 bars finding "0 had a snapshot". The calls were there; nothing was permitted to count them. WST7 now stores the four tier weights, the module trust weight and the self-ranked flag beside the model. Restored only when the stored flag says the ladder was MEASURED - a .stats written before a model's first pass 3 holds the stock ladder, and adopting that as if measured is the exact error the flag exists to prevent. A .stats predating WST7 has no ladder, so existing converged models stay silent until their next scoring pass mints one. That case now prints a warning naming all three of its symptoms, because each one independently looks like a different bug. Compile-verified in _claude_stage: 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:26:33 -04:00
//--- THE TIER LADDER (WST7). The four confidence-tier win rates, the module trust weight, and the
//--- flag saying they were measured rather than assumed. Produced ONLY by a completed pass 3, so
//--- without persistence a converged model loses its entire vote on restart - see
//--- CExpertSignalAIBase::LiveVoteContribution for why an unranked member returns 0.
virtual int TierWeight(const int tier) = 0;
virtual void SetTierWeight(const int tier, const int v) = 0;
virtual double ModuleTrustWeight(void) = 0;
virtual void SetModuleTrustWeight(const double v) = 0;
virtual bool TiersSelfRanked(void) = 0;
virtual void SetTiersSelfRanked(const bool v) = 0;
fix(vote): persist the member's skill verdict - a converged model was ruled no-skill on every restart SP500 resumed converged at era 136 with its tier ladder correctly restored and still swept 4999 bars reporting "0 had a snapshot, drew 0 arrow(s)" while the other five charts drew 221-312. HasDemonstratedEdge() - added with the no-skill exclusion - compares m_eraStatPrecPct against m_eraStatChancePct. Both are written once per era by EnsembleStashEraStats. A converged model runs no eras, so after a restart both sat at their -1 ctor defaults, every member was ruled no-skill, ReconstructionWeight() returned 0 for all four, and the overlay divisor was zero on every bar. Exactly the failure the WST7 ladder persistence fixed one level down: the ladder says how much a member votes, this says whether it may. RankTiersFromOos already computes the pair (pooled holdout precision and the zero-skill reference rate) and now records it as the CERTIFIED edge. That path is reached by the era end AND by the deployed replay, which is the only measurement a converged model will ever make. Persisted as WST8; HasDemonstratedEdge() prefers the era pair and falls back to it. The census line also had to be fixed: it reported "NOT ONE of those bars had a single member snapshot ... no enrolled member has published m_overlaySigSnap" for a condition that was purely a skill verdict. The snapshots were there. It now counts the two causes separately and names the one that fired. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 16:53:16 -04:00
//--- THE MEMBER'S CERTIFIED PRECISION AND ITS CHANCE RATE (WST8). Exactly the same failure mode as
//--- the ladder above, one level up: HasDemonstratedEdge() compares these two, VoteCapableWeight()
//--- and ReconstructionWeight() return 0 when it says no, and both numbers were era-only state.
//--- A converged model runs no eras, so on restart they stayed at their -1 ctor defaults, every
//--- member reported no skill, and the overlay divisor was zero on every bar.
virtual double CertifiedPrecPct(void) = 0;
virtual double CertifiedChancePct(void) = 0;
virtual void SetCertifiedEdge(const double precPct, const double chancePct) = 0;
refactor(persistence): ModelPersistence is a real collaborator, not a raw-include partial (S3) Expert/AIBase/Persistence.mqh (598 lines, 8 methods) -> Expert/Persistence/: IPersistenceView.mqh (abstract, 68 read+write accessors) + AIBasePersistenceView.mqh/ AIBasePersistenceViewImpl.mqh (the adapter) + ModelPersistence.mqh (CModelPersistence, the real collaborator - signal owns m_modelPersistence and binds it to m_persistenceView, same shape as ChartUI's S2). Grep-verified before starting: every field these 8 methods touch is ALSO touched elsewhere in the class (Training/Lifecycle/OnlineLearning/Topology/FeatureScreen/ Labels.mqh) or already exposed via ChartView. Zero exclusive state, unlike ChartUI's arrow-restore/rescan queues - CModelPersistence is stateless, holding only the borrowed view pointer, operating entirely through 68 Persist*/PersistSet*() accessors on the signal. ValidateCpuInference's Net-pointer/throwaway-clone core is ONE consolidated view call (PersistRunCpuInferenceSelfCheck) rather than field-by-field - irreducible pointer/ object work, not signal state, same doctrine as ChartScoreBarForRescan. LoadNetWithRetry keeps its original CheckPointer(Net)-free Net.Load() call unchanged (no guard added - would change failure behaviour on what must be a pure relocation). This code writes the actual on-disk .cfg/.stats binary layouts every deployed model depends on (explicit "DO NOT REORDER" comment in the original), so beyond compiling clean (0 errors, 0 warnings) this was verified with a positional field-order diff: every FileWrite*/FileRead* call's target field, extracted and normalized from both the original and the new file, matches 1:1 in the same order (43/43 on the write side covering SaveModelStats+SaveTopologyConfiguration, 17/17 on LoadModelStats' read side; LoadAndCompareTopologyConfiguration's local-variable read block was copied verbatim, untouched, so nothing to diff there). The magic-version conditionals (WST2-6, haveDerivedStages/haveBarrierGeometry/etc.) moved unchanged. All 8 methods keep their exact original signatures as one-line forwards - zero external call sites changed.
2026-08-23 21:45:09 -04:00
//--- ValidateCpuInference() - the whole Net-pointer/throwaway-clone core, consolidated (see
//--- ChartScoreBarForRescan for the same "irreducible pointer work, not signal state" precedent)
virtual bool RunCpuInferenceSelfCheck(double &maxDiff) = 0;
//--- SaveTopologyConfiguration()/LoadAndCompareTopologyConfiguration() - fields beyond the params
//--- every caller already passes
virtual void SetDirConfThreshold(const double v) = 0;
virtual double BestDirConfThreshold(void) = 0;
virtual void SetBestDirConfThreshold(const double v) = 0;
virtual string CrossAssetPairsPinned(void) = 0;
virtual void SetCrossAssetPairsPinned(const string v) = 0;
virtual void SetCrossAssetCfgSaved(const bool v) = 0;
virtual string AltDataNamesPinned(void) = 0;
virtual void SetAltDataNamesPinned(const string v) = 0;
virtual void ApplyAltDataPinnedNames(const string v) = 0;
//--- LoadNetWithRetry()
virtual bool LoadNetOnce(double &indicatorParams[]) = 0;
};
//+------------------------------------------------------------------+