Warrior_EA/Expert/Chart/IChartView.mqh
AnimateDread 6308a19f27 feat(signal): make the signal cooldown tunable, and add a hard any-direction gate
The declustering the charts needed already existed - NmsLiveAccept, per-direction
run-collapse plus cross-direction resolution plus strict alternation - and it was
already set to 10 bars. It could not be TUNED: SignalClusterWindow was a compile-
time const, so finding the right value needed a rebuild. That is the actual gap.

Now three inputs, as enum dropdowns:
  Signal_CooldownScope    per-direction, or a hard any-direction gate on top
  Signal_CooldownBars     SCB_OFF..SCB_50, default 10
  Signal_CooldownMinutes  SCM_OFF..SCM_1440, overrides bars when set

Minutes resolve against the CHART period and round UP, so a cooldown asked for in
wall-clock is never silently shorter than requested and survives a timeframe
change.

SCB_/SCM_ prefixes are deliberately unique. M15/M30/M60 are ALREADY members of
NF_LOOKBACK_PRESETS, and MQL5 binds a duplicated enum member to the first-declared
enum silently - the obvious names would have compiled straight into the news
filter's values.

THE ANY-DIRECTION GATE IS ADDITIVE, NOT A REPLACEMENT, and the first cut of this
had it backwards. Measured on the live log: the current rules draw 222 arrows over
4999 bars, while a BARE 10-bar cooldown permits up to 454 - because ALTERNATION is
what declutters today, not the window. Swapping the rules out would have roughly
doubled the clutter it was asked to remove. Layered, it can only ever suppress
more. Suppressed bars still advance the per-direction last-SEEN cursors, so a run
straddling the boundary does not restart as if it were fresh.

Applied at all THREE sites that must agree - live inference, OOS pass-3 scoring
and the chart renderer. Their own comments say why: an arrow set that does not
obey the same rule as the traded set shows calls the EA would never take.

Also corrects a stale comment that called this window "display only". It is not:
when it suppresses, the live path zeroes the signal outright - no arrow, no vote,
no position. Training never sees it, so these cost no retrain and are correctly
absent from the fingerprint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:48:28 -04:00

105 lines
6.5 KiB
MQL5

//+------------------------------------------------------------------+
//| Warrior_EA |
//| AnimateDread |
//| |
//| A READ-ONLY VIEW of one model's identity, bars, model and |
//| training/vote state - everything the chart-rendering side needs. |
//+------------------------------------------------------------------+
#ifndef WARRIOR_CHART_ICHARTVIEW_MQH
#define WARRIOR_CHART_ICHARTVIEW_MQH
//+------------------------------------------------------------------+
//| WHAT A CHART-SIDE COLLABORATOR IS ALLOWED TO SEE. |
//| |
//| Arrows, the status panel and the HUD line all need the same |
//| handful of things: who this model is, what a bar's time/price |
//| are, what the deployed net currently says, and the training/vote |
//| numbers the panel text summarises. Before this, CChartUI's |
//| predecessor (Expert\AIBase\ChartUI.mqh) was a raw-include body of |
//| CExpertSignalAIBase and reached straight into ~500 members. |
//| |
//| MQL5 has interfaces, but a class may implement one only if it |
//| inherits nothing else, and CExpertSignalAIBase is already a |
//| CExpertSignalCustom. So this is an abstract class and the signal |
//| exposes itself through a small ADAPTER that does inherit it - see |
//| CAIBaseChartView. Same shape as Training\ITrainingData.mqh. |
//+------------------------------------------------------------------+
class CChartView
{
public:
~CChartView(void) { }
//--- IDENTITY / CONFIG.
virtual string Id(void) = 0;
virtual string FileName(void) = 0;
virtual string ArrowPrefix(void) = 0;
virtual ENUM_TIMEFRAMES Period(void) = 0;
virtual int Digits(void) = 0;
virtual string DisplayNameForChart(void) = 0;
virtual bool ModelLoadedFromDisk(void) = 0;
virtual bool TrainingComplete(void) = 0;
virtual bool BothDirectionsTradeable(void) = 0;
virtual int SignalClusterWindow(void) = 0;
//--- The cooldown SCOPE must reach the renderer too: the drawn set and the traded set obey one
//--- rule or the chart shows calls the EA would never take.
virtual SIGNAL_COOLDOWN_SCOPE SignalCooldownScope(void) = 0;
//--- Stop requested, or the program is unloading - same wrap as CTrainingDataView::Stopping().
virtual bool Stopping(void) = 0;
//--- BARS / MODEL. m_Time/m_Close are protected stdlib series the adapter cannot expose directly.
virtual datetime BarTime(const int idx) = 0;
virtual double BarClose(const int idx) = 0;
virtual int HistoryBars(void) = 0;
virtual int OutputNeuronsCount(void) = 0;
virtual bool NetReady(void) = 0;
//--- Bars() on the chart's OWN period (deliberately PERIOD_CURRENT, not this model's Period() -
//--- same as the code this replaces), for sizing a manual rescan's lookback.
virtual int AvailableBars(void) = 0;
virtual bool ResizeBuffers(const int barIndex) = 0;
virtual bool RefreshData(void) = 0;
virtual int ServableBars(const int want, const string context) = 0;
virtual void EnsureShadowNet(void) = 0;
//--- ONE bar through the deployed (shadow-preferred) net: builds the feature window, forwards,
//--- and hands back both the raw argmax-basis signal and the prior-corrected one - the exact pair
//--- AdvanceChartSignalRescan needs, without handing the net itself to a chart-rendering object.
//--- False = the window could not be built (bar skipped, not scored).
virtual bool ScoreBarForRescan(const int idx, double &rawSignal, double &adjustedSignal) = 0;
virtual ENUM_SIGNAL ToSignal(const double value) = 0;
//--- PREDICTION CACHE (m_arrowSignalCache). Stays signal-owned - Training.mqh writes it directly
//--- every era and the training-data view already reads it for the baseline comparator - so this
//--- is a bounds-checked window onto shared state, not a copy.
virtual int PredictionCacheSize(void) = 0;
virtual double PredictionAt(const int idx) = 0;
virtual void SetPredictionAt(const int idx, const double value) = 0;
virtual void ResizePredictionCache(const int size, const double fillValue) = 0;
//--- Hand the cache the rescan just rebuilt to the overlay as this member's snapshot, and mark the
//--- member ready for a sweep. The rescan is the only way a DEPLOYED model can produce one - it
//--- runs no further eras, and the era end is the only other publisher.
virtual void PublishOverlaySnapshot(void) = 0;
//--- PANEL / HUD SCALARS. Read-only summaries of training, vote and meta-gate state.
virtual long EraCount(void) = 0;
virtual long CumIsTotal(void) = 0;
virtual long CumIsCorrect(void) = 0;
virtual long CumOosTotal(void) = 0;
virtual long CumOosCorrect(void) = 0;
virtual void OosTally(int &buyPredicted, int &sellPredicted, int &buyPredictedHits, int &sellPredictedHits) = 0;
virtual string PassLabel(void) = 0;
virtual int PassProgressPct(void) = 0;
virtual int OosSplitPct(void) = 0;
virtual int OosSamples(void) = 0;
virtual void ClassCounts(int &predBuy, int &predSell, int &predNeutral,
int &trueBuy, int &trueSell, int &trueNeutral) = 0;
virtual void OosRecallPct(int &buyRecallPct, int &sellRecallPct) = 0;
virtual void OosLivePrecision(int &buyPrecPct, int &buyFired, int &sellPrecPct, int &sellFired) = 0;
virtual double Forecast(void) = 0; // dForecast
virtual double ErrorPct(void) = 0; // dError
virtual double OosForecast(void) = 0; // dOosForecast
virtual double OosErrorPct(void) = 0; // dOosError
virtual double NetRecentAverageError(void) = 0;
//--- Throttled, side-effect-free forward of the CURRENT decision bar. See the signal's own
//--- declaration comment (AIBase\Inference.mqh).
virtual bool DisplayInference(void) = 0;
virtual double LiveVoteContribution(const double signal) = 0;
virtual double VoteCapableWeight(void) = 0;
//--- STATUS.
virtual void PublishStatus(const string text, const bool force) = 0;
};
#endif // WARRIOR_CHART_ICHARTVIEW_MQH
//+------------------------------------------------------------------+