forked from animatedread/Warrior_EA
The //| box blocks were excluded from0b06f8eand5efdb48and were what remained: 160 of them ran to 10+ lines, the longest to 88. Compressed to their leading topic sentences - 5 lines for a function header, 8 for a file header - keeping the box format and the standard MQL5 name/author lines verbatim. Verified at the BYTE level this time, across every in-scope file: the list of non-comment lines is byte-identical to HEAD and braces balance. The first check compared a locale-decoded 'git show' against a UTF-8 read and flagged 25 files that had not changed at all - every BOM and every non-ASCII line mismatched. 47,696 -> 40,665 lines in scope; comment share 38% -> 26%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2017 lines
97 KiB
MQL5
2017 lines
97 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Warrior_EA |
|
|
//| AnimateDread |
|
|
//| |
|
|
//| Indicator creation and the per-bar input feature vector. |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef WARRIOR_AIBASE_FEATURES_MQH
|
|
#define WARRIOR_AIBASE_FEATURES_MQH
|
|
//--- Plausibility ceiling for any single input value, enforced once over the whole bar at the end
|
|
//--- of BufferTempDataCompute(). See the sanitize loop at the end of BufferTempDataCompute() for
|
|
//--- what it protects.
|
|
#define FEATURE_ABS_MAX 1.0e4
|
|
//+------------------------------------------------------------------+
|
|
//| Rebuilds only the enabled AD* CiCustom handles in place, so a |
|
|
//| new trial's member-struct param values take effect. |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalAIBase::TunableBarsCalculated(int &enabled)
|
|
{
|
|
enabled = 0;
|
|
int worst = INT_MAX;
|
|
if(m_useMA)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_MA.BarsCalculated());
|
|
}
|
|
if(m_useRSI)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_RSI.BarsCalculated());
|
|
}
|
|
if(m_useMACD)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_MACDFeature.BarsCalculated());
|
|
}
|
|
if(m_useIchimoku)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_Ichimoku.BarsCalculated());
|
|
}
|
|
if(m_useADCumulativeDelta)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_ADCumulativeDelta.BarsCalculated());
|
|
}
|
|
if(m_useADShorteningOfThrust)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_ADShorteningOfThrust.BarsCalculated());
|
|
}
|
|
if(m_useADWyckoffEventStream)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_ADWyckoffEventStream.BarsCalculated());
|
|
}
|
|
if(m_useADWyckoffFailedStructure)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_ADWyckoffFailedStructure.BarsCalculated());
|
|
}
|
|
if(m_useADWyckoffSignificantBarInversion)
|
|
{
|
|
enabled++;
|
|
worst = (int)MathMin(worst, m_ADWyckoffSignificantBarInversion.BarsCalculated());
|
|
}
|
|
return (worst == INT_MAX) ? -1 : worst;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Back-compatible form for the callers that only want the number. |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalAIBase::TunableBarsCalculated(void)
|
|
{
|
|
int enabled = 0;
|
|
return TunableBarsCalculated(enabled);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See the declaration. THE one place that decides how much history |
|
|
//| may be asked of the indicators; every ResizeBuffers() call site |
|
|
//| goes through it. |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalAIBase::ServableBars(int want, string context)
|
|
{
|
|
if(want <= 0)
|
|
return want;
|
|
int enabled = 0;
|
|
int servable = TunableBarsCalculated(enabled);
|
|
//--- THE BLIND SPOT THAT COST 2026-08-17 (fixed the same day, after the fact). Fine. enabled >
|
|
//--- 0, servable == -1 -> a handle answered INVALID. The dead case is now REPORTED and REPAIRED.
|
|
if(enabled == 0 || servable >= want)
|
|
{
|
|
//--- Cleared on the healthy path too, not only on the clamp path below: a handle that recovers
|
|
//--- all the way to full depth would otherwise leave the latch set and a LATER outage would be
|
|
//--- swallowed - which is the failure mode this whole function is being fixed for.
|
|
m_indicatorDepthDeadWarned = false;
|
|
return want;
|
|
}
|
|
if(servable <= 0)
|
|
{
|
|
//--- REPORT FIRST, THEN REPAIR - in that order, so the depths in this line are the ones that
|
|
//--- caused it.
|
|
if(!m_indicatorDepthDeadWarned)
|
|
{
|
|
m_indicatorDepthDeadWarned = true;
|
|
PrintFormat("%s: TUNABLE INDICATOR REPORTS NO CALCULATED BARS - %d tunable indicator(s) enabled"
|
|
" and the least-ready answers BarsCalculated()=%d while %s asked for %d. Either way"
|
|
" CopyBuffer fails at EVERY index, the buffer holds nothing, and every feature block"
|
|
" that reads it rejects every bar. -1 means the terminal would not answer for this"
|
|
" handle, which covers BOTH a handle freed out from under this member AND one just"
|
|
" created that has not calculated yet - the handle numbers below separate them, the"
|
|
" depth cannot. This is NOT the depth cap below (that one clamps and trains on what"
|
|
" is servable) - there is nothing to clamp to. Per-indicator depth:%s",
|
|
ID, enabled, servable, context, want, IndicatorDepthReport());
|
|
}
|
|
//--- A dead handle answers EMPTY_VALUE at every index, so a 50k-bar pass over it is 50k
|
|
//--- guaranteed rejections followed by a discarded era, forever - the exact loop that froze
|
|
//--- USDJPY and XAUUSD. Rate-limited inside.
|
|
RepairDeadIndicatorHandles();
|
|
return want;
|
|
}
|
|
m_indicatorDepthDeadWarned = false;
|
|
//--- -(m_historyBars + 2): the deepest window slot reads (r + m_historyBars - 1), and the MA
|
|
//--- block one further back again for its bar-over-bar change, so the last usable anchor sits
|
|
//--- that far inside the buffer.
|
|
int capped = servable - ((int)m_historyBars + 2);
|
|
if(capped < 0)
|
|
capped = 0;
|
|
//--- Depends only on `servable` and m_historyBars, never on `want`, so it is stable across call
|
|
//--- sites and this logs once per real change rather than once per era per context.
|
|
if(m_indicatorDepthCapBars != capped)
|
|
{
|
|
m_indicatorDepthCapBars = capped;
|
|
PrintFormat("%s: indicator history CAPPED to %d bars (%s asked for %d) - the price series has"
|
|
" that much, but the least-ready tunable indicator has only calculated %d. Past what"
|
|
" an indicator has calculated CopyBuffer does not short-read, it FAILS, so the buffer"
|
|
" holds NOTHING and EVERY index reads EMPTY_VALUE - indistinguishable from a cold"
|
|
" indicator. Per-indicator depth:%s",
|
|
ID, capped, context, want, servable, IndicatorDepthReport());
|
|
}
|
|
return capped;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See the declaration. ServableBars() with the WAIT in front of it. |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalAIBase::SettledBars(int want, string context)
|
|
{
|
|
if(want <= 0)
|
|
return want;
|
|
int enabled = 0;
|
|
int servable = TunableBarsCalculated(enabled);
|
|
//--- Same three-states-one-branch defect ServableBars() carried (see the long note there):
|
|
//--- `servable < 0` was read as "nothing tunable is on", but it is ALSO what a dead handle
|
|
//--- answers.
|
|
if(enabled == 0 || servable >= want)
|
|
{
|
|
m_depthSettleStart = 0;
|
|
m_depthProbeStable = 0;
|
|
m_depthProbeLast = 0;
|
|
return want;
|
|
}
|
|
if(servable <= 0)
|
|
{
|
|
m_depthSettleStart = 0;
|
|
m_depthProbeStable = 0;
|
|
m_depthProbeLast = 0;
|
|
//--- Routed through ServableBars() rather than answering here, and that detour is the whole
|
|
//--- point: the training sweep - the ONLY caller that reaches the dead-handle state in
|
|
//--- practice - calls SettledBars, not ServableBars.
|
|
ServableBars(want, context);
|
|
//--- 0 = HOLD, and this is the one place the two functions deliberately disagree. Whether the
|
|
//--- handle was just recreated (cold, will climb) or is still dead (repair failed), holding
|
|
//--- is right; Train() reports the hold every minute and the era-barrier liveness escape
|
|
//--- releases the rest of the ensemble if it never resolves.
|
|
return 0;
|
|
}
|
|
uint now = GetTickCount();
|
|
//--- First shortfall: start the clock and let the priming request above do its work. Deliberately
|
|
//--- no sweep this call - a 50k-bar feature sweep is exactly what starves the indicator threads we
|
|
//--- are waiting on, which is how the old loop sustained itself for 40 minutes at a time.
|
|
if(m_depthSettleStart == 0)
|
|
{
|
|
m_depthSettleStart = now;
|
|
m_depthProbeTick = now;
|
|
m_depthProbeLast = servable;
|
|
m_depthProbeStable = 0;
|
|
PrintFormat("%s: PRIMING indicator history for the %s - %d of %d bars calculated so far. Holding"
|
|
" the sweep until the count stops rising (probe every %ds, needs %d steady probes,"
|
|
" gives up after %ds and uses whatever is there). Per-indicator depth:%s",
|
|
ID, context, servable, want, DEPTH_SETTLE_PROBE_MS / 1000,
|
|
DEPTH_SETTLE_STABLE_PROBES, DEPTH_SETTLE_TIMEOUT_MS / 1000, IndicatorDepthReport());
|
|
return 0;
|
|
}
|
|
//--- Unsigned subtraction, so this is correct across GetTickCount()'s 49-day wrap (same idiom as
|
|
//--- m_coldSweepTick's backoff).
|
|
if(now - m_depthProbeTick < DEPTH_SETTLE_PROBE_MS)
|
|
return 0;
|
|
m_depthProbeTick = now;
|
|
if(servable != m_depthProbeLast)
|
|
{
|
|
//--- STILL MOVING. Growing is the terminal working through the history; shrinking happens when a
|
|
//--- handle is rebuilt under us and starts over. Either way it is not settled, so the streak
|
|
//--- restarts rather than counting a change as a steady observation.
|
|
PrintFormat("%s: priming %s - %d of %d bars (was %d), still moving", ID, context, servable, want,
|
|
m_depthProbeLast);
|
|
m_depthProbeLast = servable;
|
|
m_depthProbeStable = 0;
|
|
return 0;
|
|
}
|
|
m_depthProbeStable++;
|
|
bool steady = (m_depthProbeStable >= DEPTH_SETTLE_STABLE_PROBES);
|
|
bool expired = ((now - m_depthSettleStart) >= DEPTH_SETTLE_TIMEOUT_MS);
|
|
if(!steady && !expired)
|
|
return 0;
|
|
//--- Settled (or waited long enough) BELOW what was asked. This is the real depth, not a snapshot of
|
|
//--- a value still climbing, so it is now safe to clamp to it and get on with training.
|
|
PrintFormat("%s: priming %s DONE - depth settled at %d of %d bars after %ds%s. Training proceeds on"
|
|
" the %d bars the indicators can actually serve.",
|
|
ID, context, servable, want, (int)((now - m_depthSettleStart) / 1000),
|
|
expired && !steady ? " (gave up waiting - it never went steady)" : "", servable);
|
|
m_depthSettleStart = 0;
|
|
m_depthProbeStable = 0;
|
|
m_depthProbeLast = 0;
|
|
return ServableBars(want, context);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See the declaration. What this configuration would have to FIRE |
|
|
//| before any edge of a given size becomes certifiable. |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalAIBase::ReportDetectability(int oosBars)
|
|
{
|
|
if(m_detectabilityReported || oosBars <= 0)
|
|
return;
|
|
m_detectabilityReported = true;
|
|
double L = MeanLabelLifespan();
|
|
//--- CAPACITY, restated against the sample that actually exists. That leaves the first build's
|
|
//--- warning quoting the optimistic figure, so it is restated HERE, at the first moment L is
|
|
//--- real.
|
|
int fanIn = FirstLayerFanIn();
|
|
double firstLayerW = (double)(fanIn + 1) * (double)m_initialNeuronsCount;
|
|
double indepRows = EstimatedInSampleBars();
|
|
if(fanIn > 0 && indepRows > 0.0)
|
|
PrintFormat("%s: CAPACITY against the same sample the gate uses - first dense layer is %d x %d"
|
|
" = %.0f weights against ~%.0f independent in-sample observations (%.0f rows / mean"
|
|
" label lifespan %.1f) = %.1f weights per observation. One per observation is already"
|
|
" generous for a signal this weak. The two multipliers are the input window and the"
|
|
" feature count (%d bars x %d readings); pooling instruments is the third lever and"
|
|
" the only one that ADDS observations instead of removing parameters.",
|
|
ID, fanIn + 1, m_initialNeuronsCount, firstLayerW, indepRows,
|
|
EstimatedInSampleBarsRaw(), L, firstLayerW / indepRows,
|
|
(int)m_historyBars, m_neuronsCount);
|
|
double p = CostAdjustedBreakEvenPct() / 100.0;
|
|
if(p <= 0.0 || p >= 1.0)
|
|
return;
|
|
//--- Invert the deploy gate. Everything on the right-hand side is a property of the
|
|
//--- CONFIGURATION (geometry via p, horizon via L, window via oosBars), not of the model, which
|
|
//--- is the whole point: no amount of training moves it.
|
|
string ladder = "";
|
|
double edges[3] = {2.0, 5.0, 10.0};
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
double d = edges[i] / 100.0;
|
|
double needEff = BinomialCallsForEdge(p, d, EDGE_MIN_SIGMAS);
|
|
double needRaw = needEff * L;
|
|
double needCoverage = 100.0 * needRaw / (double)oosBars;
|
|
ladder += StringFormat(" %+.0fpp:%.0f indep=%.0f calls=%.0f%% of window%s |",
|
|
edges[i], needEff, needRaw, needCoverage,
|
|
needCoverage > 100.0 ? " IMPOSSIBLE" : "");
|
|
}
|
|
PrintFormat("%s: DETECTABILITY of this configuration (break-even %.1f%%, mean label lifespan %.1f"
|
|
" bars, OOS window %d bars) - to certify an edge of X the gate needs:%s"
|
|
" Read it as a budget, not a target: these are properties of the GEOMETRY, the HORIZON"
|
|
" and the WINDOW, so a better model cannot change any of them. Where a rung says"
|
|
" IMPOSSIBLE, no win rate this model could ever produce would clear the deploy bar on"
|
|
" this window - the answer there is more instruments, a lower timeframe or a narrower"
|
|
" barrier, never more eras. Coverage is also not free in the other direction: firing on"
|
|
" more bars buys independent calls at the cost of precision, so the reachable band is"
|
|
" bounded at both ends.",
|
|
ID, 100.0 * p, L, oosBars, ladder);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See the declaration. The per-class COLLAPSE floor, derived rather |
|
|
//| than configured. |
|
|
//+------------------------------------------------------------------+
|
|
double CExpertSignalAIBase::CollapseRecallFloorPct(int classTrueCount)
|
|
{
|
|
//--- Chance recall is 1/K for K classes and does not depend on the class priors: a zero-skill
|
|
//--- model that emits class c with probability q gets recall q on EVERY true class, and the
|
|
//--- uniform zero-skill model has q = 1/K.
|
|
double chance = 100.0 / 3.0;
|
|
double effN = EffectiveSampleSize((double)classTrueCount);
|
|
if(effN <= 0.0)
|
|
return (double)m_minDirectionalRecallPct;
|
|
double se = BinomialSEPct(1.0 / 3.0, effN);
|
|
double floorPct = chance - EDGE_MIN_SIGMAS * se;
|
|
//--- Never negative, and never so high it becomes the unreachable bar this replaced.
|
|
if(floorPct < 0.0)
|
|
floorPct = 0.0;
|
|
return floorPct;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RE-CREATE any enabled tunable indicator whose handle has gone |
|
|
//| INVALID underneath us. See the declaration for the evidence. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::RepairDeadIndicatorHandles(void)
|
|
{
|
|
if(m_indicatorsPtr == NULL)
|
|
return false;
|
|
uint now = GetTickCount();
|
|
//--- Cooldown, because every consumer of ServableBars() can reach this - the training sweep, live
|
|
//--- inference on every tick, online learning - and a repair storm against a terminal that is
|
|
//--- genuinely refusing to create the indicator would be worse than the outage it is fixing.
|
|
if(m_handleRepairTick != 0 && now - m_handleRepairTick < HANDLE_REPAIR_COOLDOWN_MS)
|
|
return false;
|
|
m_handleRepairTick = now;
|
|
//--- NOT released first, deliberately - but NOT because -1 proves the handle is gone.
|
|
int repaired = 0, h = 0;
|
|
string moves = "";
|
|
if(m_useMA && m_MA.BarsCalculated() < 0)
|
|
{
|
|
h = m_MA.Handle();
|
|
if(InitMA(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("MA", h, m_MA.Handle(), moves);
|
|
}
|
|
if(m_useRSI && m_RSI.BarsCalculated() < 0)
|
|
{
|
|
h = m_RSI.Handle();
|
|
if(InitRSI(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("RSI", h, m_RSI.Handle(), moves);
|
|
}
|
|
if(m_useMACD && m_MACDFeature.BarsCalculated() < 0)
|
|
{
|
|
h = m_MACDFeature.Handle();
|
|
if(InitMACDFeature(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("MACD", h, m_MACDFeature.Handle(), moves);
|
|
}
|
|
if(m_useIchimoku && m_Ichimoku.BarsCalculated() < 0)
|
|
{
|
|
h = m_Ichimoku.Handle();
|
|
if(InitIchimoku(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("Ichi", h, m_Ichimoku.Handle(), moves);
|
|
}
|
|
if(m_useADCumulativeDelta && m_ADCumulativeDelta.BarsCalculated() < 0)
|
|
{
|
|
h = m_ADCumulativeDelta.Handle();
|
|
if(InitADCumulativeDelta(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("CumDelta", h, m_ADCumulativeDelta.Handle(), moves);
|
|
}
|
|
if(m_useADShorteningOfThrust && m_ADShorteningOfThrust.BarsCalculated() < 0)
|
|
{
|
|
h = m_ADShorteningOfThrust.Handle();
|
|
if(InitADShorteningOfThrust(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("SoT", h, m_ADShorteningOfThrust.Handle(), moves);
|
|
}
|
|
if(m_useADWyckoffEventStream && m_ADWyckoffEventStream.BarsCalculated() < 0)
|
|
{
|
|
h = m_ADWyckoffEventStream.Handle();
|
|
if(InitADWyckoffEventStream(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("WES", h, m_ADWyckoffEventStream.Handle(), moves);
|
|
}
|
|
if(m_useADWyckoffFailedStructure && m_ADWyckoffFailedStructure.BarsCalculated() < 0)
|
|
{
|
|
h = m_ADWyckoffFailedStructure.Handle();
|
|
if(InitADWyckoffFailedStructure(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("WFS", h, m_ADWyckoffFailedStructure.Handle(), moves);
|
|
}
|
|
if(m_useADWyckoffSignificantBarInversion && m_ADWyckoffSignificantBarInversion.BarsCalculated() < 0)
|
|
{
|
|
h = m_ADWyckoffSignificantBarInversion.Handle();
|
|
if(InitADWyckoffSignificantBarInversion(m_indicatorsPtr, false))
|
|
repaired += NoteHandleMove("WSBI", h, m_ADWyckoffSignificantBarInversion.Handle(), moves);
|
|
}
|
|
if(repaired == 0)
|
|
return false;
|
|
//--- Every cached feature row was computed against the handle that just got replaced.
|
|
ArrayInitialize(m_featureCacheHasValue, false);
|
|
PrintFormat("%s: RECREATED %d indicator handle(s) that answered no calculated bars, so CopyBuffer"
|
|
" failed at every index and every bar of the sweep was rejected.%s A CHANGED number means"
|
|
" the old instance really was gone and this member now holds a new one; SAME means MT5"
|
|
" returned the same refcounted instance, so it was never dead - it had simply not"
|
|
" calculated yet, and this repair was a no-op that cost one reference. Depth is"
|
|
" deliberately NOT re-reported here: a new handle calculates asynchronously and reads -1"
|
|
" until it does, which is the value that triggered the repair. If this line repeats on a"
|
|
" cycle with CHANGING numbers, something is releasing the handle out from under this"
|
|
" member and the recreate is only papering over it.",
|
|
ID, repaired, moves);
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Append " NAME hOLD->hNEW" (or "->hNEW SAME") to a repair report. |
|
|
//| Always counts one repair - the caller only calls it on a Create() |
|
|
//| that succeeded. |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalAIBase::NoteHandleMove(const string name, const int oldHandle, const int newHandle, string &moves)
|
|
{
|
|
moves += StringFormat(" %s h%d->h%d%s", name, oldHandle, newHandle,
|
|
(oldHandle == newHandle ? " SAME" : ""));
|
|
return 1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| One " name=depth(hN)" field of the depth report. |
|
|
//+------------------------------------------------------------------+
|
|
string CExpertSignalAIBase::IndicatorDepthField(const string name, const int depth, const int handle)
|
|
{
|
|
return StringFormat(" %s=%d(h%d)", name, depth, handle);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See the declaration. Blocks in the order BufferTempDataCompute |
|
|
//| emits them, widths as Topology.mqh's m_neuronsCount sum declares |
|
|
//| them - those two are the authority and this must track both. |
|
|
//+------------------------------------------------------------------+
|
|
string CExpertSignalAIBase::FeatureSlotName(const int slot)
|
|
{
|
|
string names[18];
|
|
int widths[18];
|
|
int n = 0;
|
|
names[n] = "candle"; widths[n++] = 4;
|
|
names[n] = "swing"; widths[n++] = (m_useSwingContext ? 9 : 0);
|
|
names[n] = "volume"; widths[n++] = (m_useVolumes ? 4 : 0);
|
|
names[n] = "time"; widths[n++] = (m_useTime ? 6 : 0);
|
|
names[n] = "atr"; widths[n++] = (m_useATR ? 1 : 0);
|
|
names[n] = "ma"; widths[n++] = (m_useMA ? 5 : 0);
|
|
names[n] = "rsi"; widths[n++] = (m_useRSI ? 1 : 0);
|
|
names[n] = "macd"; widths[n++] = (m_useMACD ? 3 : 0);
|
|
names[n] = "ichimoku"; widths[n++] = (m_useIchimoku ? 8 : 0);
|
|
names[n] = "news"; widths[n++] = (m_useNews ? 2 : 0);
|
|
names[n] = "spread"; widths[n++] = (m_useSpreadFeature ? 2 : 0);
|
|
names[n] = "crossasset"; widths[n++] = (m_useCrossAsset ? CROSSASSET_FEATURES : 0);
|
|
names[n] = "cumdelta"; widths[n++] = (m_useADCumulativeDelta ? 6 : 0);
|
|
names[n] = "sot"; widths[n++] = (m_useADShorteningOfThrust ? 4 : 0);
|
|
names[n] = "wyckoffEvent"; widths[n++] = (m_useADWyckoffEventStream ? 16 : 0);
|
|
names[n] = "wyckoffFail"; widths[n++] = (m_useADWyckoffFailedStructure ? 5 : 0);
|
|
names[n] = "wyckoffBarInv";widths[n++] = (m_useADWyckoffSignificantBarInversion ? 5 : 0);
|
|
names[n] = "alt"; widths[n++] = (m_useAltData ? m_altData.FeatureCount() : 0);
|
|
int total = 0;
|
|
for(int i = 0; i < n; i++)
|
|
total += widths[i];
|
|
if(total != m_neuronsCount)
|
|
return StringFormat("f%d?", slot);
|
|
int from = 0;
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
if(widths[i] > 0 && slot < from + widths[i])
|
|
return StringFormat("%s[%d]", names[i], slot - from);
|
|
from += widths[i];
|
|
}
|
|
return StringFormat("f%d", slot);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See the declaration. Names WHICH indicator is short, so the next |
|
|
//| occurrence is read off the log instead of inferred. |
|
|
//+------------------------------------------------------------------+
|
|
string CExpertSignalAIBase::IndicatorDepthReport(void)
|
|
{
|
|
string s = StringFormat(" price=%d", Bars(m_symbol.Name(), PERIOD_CURRENT));
|
|
//--- HANDLE NUMBER beside every depth, not just MA's. A depth alone cannot say whether a handle
|
|
//--- was never created or was created and later released out from under this member; the number
|
|
//--- can.
|
|
if(m_useMA)
|
|
s += IndicatorDepthField("MA", m_MA.BarsCalculated(), m_MA.Handle());
|
|
if(m_useRSI)
|
|
s += IndicatorDepthField("RSI", m_RSI.BarsCalculated(), m_RSI.Handle());
|
|
if(m_useMACD)
|
|
s += IndicatorDepthField("MACD", m_MACDFeature.BarsCalculated(), m_MACDFeature.Handle());
|
|
if(m_useIchimoku)
|
|
s += IndicatorDepthField("Ichi", m_Ichimoku.BarsCalculated(), m_Ichimoku.Handle());
|
|
if(m_useADCumulativeDelta)
|
|
s += IndicatorDepthField("CumDelta", m_ADCumulativeDelta.BarsCalculated(), m_ADCumulativeDelta.Handle());
|
|
if(m_useADShorteningOfThrust)
|
|
s += IndicatorDepthField("SoT", m_ADShorteningOfThrust.BarsCalculated(), m_ADShorteningOfThrust.Handle());
|
|
if(m_useADWyckoffEventStream)
|
|
s += IndicatorDepthField("WES", m_ADWyckoffEventStream.BarsCalculated(), m_ADWyckoffEventStream.Handle());
|
|
if(m_useADWyckoffFailedStructure)
|
|
s += IndicatorDepthField("WFS", m_ADWyckoffFailedStructure.BarsCalculated(), m_ADWyckoffFailedStructure.Handle());
|
|
if(m_useADWyckoffSignificantBarInversion)
|
|
s += IndicatorDepthField("WSBI", m_ADWyckoffSignificantBarInversion.BarsCalculated(), m_ADWyckoffSignificantBarInversion.Handle());
|
|
//--- Not tunable, so absent from TunableBarsCalculated() - but the swing block reads it on every bar
|
|
//--- and neutral-fills when it is short, which is silent. Worth seeing next to the others.
|
|
s += IndicatorDepthField("ZigZag", m_ADZigZag.BarsCalculated(), m_ADZigZag.Handle());
|
|
s += IndicatorDepthField("ATR", m_ATR.BarsCalculated(), m_ATR.Handle());
|
|
return s;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Adopt a saved indicator-param set, rebuilding handles only on a |
|
|
//| REAL change. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::AdoptIndicatorParams(const double &loaded[], CIndicators *indicators)
|
|
{
|
|
double current[];
|
|
m_indicatorTuner.Flatten(current);
|
|
bool changed = (ArraySize(current) != ArraySize(loaded));
|
|
if(!changed)
|
|
for(int k = 0; k < ArraySize(loaded); k++)
|
|
if(current[k] != loaded[k])
|
|
{
|
|
changed = true;
|
|
break;
|
|
}
|
|
//--- the tuner mirrors the model's params either way - it feeds the .nnw save and the fingerprint
|
|
m_indicatorTuner.Unflatten(loaded);
|
|
if(!changed)
|
|
{
|
|
PrintVerbose(ID + ": saved indicator params match the live indicators - keeping the existing"
|
|
" instances (no handle rebuild).");
|
|
return true;
|
|
}
|
|
Print(ID + ": saved indicator params differ from the live defaults - rebuilding the tunable"
|
|
" indicator handles to match the model they trained.");
|
|
return ReInitADIndicators(indicators);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::ReInitADIndicators(CIndicators *indicators)
|
|
{
|
|
bool result = true;
|
|
//--- RELEASE THE HANDLE EACH Create() IS ABOUT TO REPLACE. HYBRID only survived because those
|
|
//--- two died first and freed the memory.
|
|
int hCD = m_useADCumulativeDelta ? m_ADCumulativeDelta.Handle() : INVALID_HANDLE;
|
|
int hSOT = m_useADShorteningOfThrust ? m_ADShorteningOfThrust.Handle() : INVALID_HANDLE;
|
|
int hWES = m_useADWyckoffEventStream ? m_ADWyckoffEventStream.Handle() : INVALID_HANDLE;
|
|
int hWFS = m_useADWyckoffFailedStructure ? m_ADWyckoffFailedStructure.Handle() : INVALID_HANDLE;
|
|
int hWSBI = m_useADWyckoffSignificantBarInversion ? m_ADWyckoffSignificantBarInversion.Handle() : INVALID_HANDLE;
|
|
int hMA = m_useMA ? m_MA.Handle() : INVALID_HANDLE;
|
|
int hRSI = m_useRSI ? m_RSI.Handle() : INVALID_HANDLE;
|
|
int hMACD = m_useMACD ? m_MACDFeature.Handle() : INVALID_HANDLE;
|
|
int hIchi = m_useIchimoku ? m_Ichimoku.Handle() : INVALID_HANDLE;
|
|
if(m_useADCumulativeDelta)
|
|
result = InitADCumulativeDelta(indicators, false) && result;
|
|
if(m_useADShorteningOfThrust)
|
|
result = InitADShorteningOfThrust(indicators, false) && result;
|
|
if(m_useADWyckoffEventStream)
|
|
result = InitADWyckoffEventStream(indicators, false) && result;
|
|
if(m_useADWyckoffFailedStructure)
|
|
result = InitADWyckoffFailedStructure(indicators, false) && result;
|
|
if(m_useADWyckoffSignificantBarInversion)
|
|
result = InitADWyckoffSignificantBarInversion(indicators, false) && result;
|
|
if(m_useMA)
|
|
result = InitMA(indicators, false) && result;
|
|
if(m_useRSI)
|
|
result = InitRSI(indicators, false) && result;
|
|
if(m_useMACD)
|
|
result = InitMACDFeature(indicators, false) && result;
|
|
if(m_useIchimoku)
|
|
result = InitIchimoku(indicators, false) && result;
|
|
//--- AFTER the re-creates, never before: releasing first can drop the terminal's last reference
|
|
//--- and make it tear the instance down, so an identical-params Create() would then rebuild it
|
|
//--- from scratch instead of re-using the live one - turning a refcount bump into a full
|
|
//--- recalculation over the whole history, 324 times over.
|
|
if(hCD != INVALID_HANDLE)
|
|
IndicatorRelease(hCD);
|
|
if(hSOT != INVALID_HANDLE)
|
|
IndicatorRelease(hSOT);
|
|
if(hWES != INVALID_HANDLE)
|
|
IndicatorRelease(hWES);
|
|
if(hWFS != INVALID_HANDLE)
|
|
IndicatorRelease(hWFS);
|
|
if(hWSBI != INVALID_HANDLE)
|
|
IndicatorRelease(hWSBI);
|
|
if(hMA != INVALID_HANDLE)
|
|
IndicatorRelease(hMA);
|
|
if(hRSI != INVALID_HANDLE)
|
|
IndicatorRelease(hRSI);
|
|
if(hMACD != INVALID_HANDLE)
|
|
IndicatorRelease(hMACD);
|
|
if(hIchi != INVALID_HANDLE)
|
|
IndicatorRelease(hIchi);
|
|
//--- Indicator params just changed, so every cached feature row is now stale (the feature values
|
|
//--- depend on these indicators; the LABELS do not - they come from ADZigZag - so the label cache is
|
|
//--- deliberately left intact and reused). Without this, a tuner candidate would silently train and be
|
|
//--- scored on the PREVIOUS candidate's features. Cheap: just flags rows for lazy recompute on next read.
|
|
ArrayInitialize(m_featureCacheHasValue, false);
|
|
return result;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::ResizeBuffers(int barIndex)
|
|
{
|
|
//--- The Ichimoku feature's Chikou term reads m_Close at idx + ichiKijun (see its block in
|
|
//--- BufferTempDataCompute() for why that direction, and only that direction, is lookahead-
|
|
//--- free), which is further back than any other consumer of the close series reaches.
|
|
int maxBars = Bars(m_symbol.Name(), PERIOD_CURRENT);
|
|
int closeBars = m_useIchimoku ? (int)MathMin(barIndex + m_indicatorTuner.ichiKijun, maxBars) : barIndex;
|
|
if(!m_Open.BufferResize(barIndex) || !m_Close.BufferResize(closeBars) || !m_High.BufferResize(barIndex) || !m_Low.BufferResize(barIndex))
|
|
return false;
|
|
if(m_useVolumes)
|
|
{
|
|
if(!m_Volumes.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
// Unconditional - see InitTime()'s call site in InitIndicators() for why m_Time must always be live.
|
|
if(!m_Time.BufferResize(barIndex))
|
|
return false;
|
|
if(m_useMA)
|
|
{
|
|
//--- NOT barIndex + 1, though the MA block does read GetData(idx) AND GetData(idx + 1) for
|
|
//--- its bar-over-bar change. The read at the OLDEST bar is SUPPOSED to fail: there is no
|
|
//--- older bar to difference against.
|
|
if(!m_MA.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useRSI)
|
|
{
|
|
if(!m_RSI.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useMACD)
|
|
{
|
|
if(!m_MACDFeature.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useIchimoku)
|
|
{
|
|
// + m_indicatorTuner.ichiKijun: the cloud reads reach that many bars FURTHER back than every other
|
|
// indicator here does (see the m_useIchimoku feature block for why the offset exists), so sizing
|
|
// this buffer to barIndex alone would leave the oldest requested bars' cloud values unavailable.
|
|
if(!m_Ichimoku.BufferResize((int)MathMin(barIndex + m_indicatorTuner.ichiKijun, maxBars)))
|
|
return false;
|
|
}
|
|
// Unconditional (not gated by m_useATR): the ATR-normalization in BufferTempData() reads
|
|
// m_ATR.Main() regardless of whether ATR is enabled as an explicit extra input feature -
|
|
// m_useATR only controls that feature-count opt-in (see InitIndicators()'s "already init in the
|
|
// base class" comment), not whether ATR data itself needs to be kept live.
|
|
if(!m_ATR.BufferResize(barIndex))
|
|
return false;
|
|
// Unconditional, same reasoning as m_ATR above - m_ADZigZag drives the swing-context features AND
|
|
// ComputeBarrierHorizonBars()'s measurement, not an opt-in feature, so it's never gated by an
|
|
// m_use* flag. (It was also the training-label source until the 2026-08-01 triple-barrier relabel.)
|
|
if(!m_ADZigZag.BufferResize(barIndex))
|
|
return false;
|
|
if(m_useADCumulativeDelta)
|
|
{
|
|
if(!m_ADCumulativeDelta.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useADShorteningOfThrust)
|
|
{
|
|
if(!m_ADShorteningOfThrust.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useADWyckoffEventStream)
|
|
{
|
|
if(!m_ADWyckoffEventStream.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useADWyckoffFailedStructure)
|
|
{
|
|
if(!m_ADWyckoffFailedStructure.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
if(m_useADWyckoffSignificantBarInversion)
|
|
{
|
|
if(!m_ADWyckoffSignificantBarInversion.BufferResize(barIndex))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::RefreshData()
|
|
{
|
|
//--- CSeries/CIndicator::Refresh() is void - there is no per-call success/failure signal to
|
|
//--- propagate here.
|
|
m_Open.Refresh(OBJ_ALL_PERIODS);
|
|
m_Close.Refresh(OBJ_ALL_PERIODS);
|
|
m_High.Refresh(OBJ_ALL_PERIODS);
|
|
m_Low.Refresh(OBJ_ALL_PERIODS);
|
|
if(m_useVolumes)
|
|
{
|
|
m_Volumes.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
// Unconditional - see InitTime()'s call site in InitIndicators() for why m_Time must always be live.
|
|
m_Time.Refresh(OBJ_ALL_PERIODS);
|
|
if(m_useMA)
|
|
{
|
|
m_MA.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useRSI)
|
|
{
|
|
m_RSI.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useMACD)
|
|
{
|
|
m_MACDFeature.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useIchimoku)
|
|
{
|
|
m_Ichimoku.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
// Unconditional - see the matching BufferResize() comment above.
|
|
m_ATR.Refresh(OBJ_ALL_PERIODS);
|
|
m_ADZigZag.Refresh(OBJ_ALL_PERIODS);
|
|
if(m_useADCumulativeDelta)
|
|
{
|
|
m_ADCumulativeDelta.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useADShorteningOfThrust)
|
|
{
|
|
m_ADShorteningOfThrust.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useADWyckoffEventStream)
|
|
{
|
|
m_ADWyckoffEventStream.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useADWyckoffFailedStructure)
|
|
{
|
|
m_ADWyckoffFailedStructure.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
if(m_useADWyckoffSignificantBarInversion)
|
|
{
|
|
m_ADWyckoffSignificantBarInversion.Refresh(OBJ_ALL_PERIODS);
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| Cache-or-compute wrapper around BufferTempDataCompute(): a given |
|
|
//| now-relative bar index's feature vector is invariant until the |
|
|
//| next candle close (see m_featureCache's declaration comment), so |
|
|
//| a cache hit just replays the m_neuronsCount values already |
|
|
//| computed for this idx straight into TempData instead of re- |
|
|
//| deriving them from price/ATR/AD-indicator buffers again. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::BufferTempData(int idx)
|
|
{
|
|
int width = m_neuronsCount;
|
|
bool cacheable = (idx >= 0 && idx < ArraySize(m_featureCacheHasValue) && width > 0);
|
|
if(cacheable && m_featureCacheHasValue[idx])
|
|
{
|
|
if(!m_featureCacheValid[idx])
|
|
return false;
|
|
int base = idx * width;
|
|
for(int f = 0; f < width; f++)
|
|
if(!TempData.Add(m_featureCache[base + f]))
|
|
return false;
|
|
return true;
|
|
}
|
|
int startTotal = TempData.Total();
|
|
bool ok = BufferTempDataCompute(idx);
|
|
//--- WIDTH CONTRACT. Caught here rather than left to surface as BuildFeatureWindow's length
|
|
//--- check, which cannot say which bar or which block was responsible.
|
|
if(ok)
|
|
{
|
|
int produced = TempData.Total() - startTotal;
|
|
if(produced != width)
|
|
{
|
|
ok = false;
|
|
m_featureFailTransient = false; // a width fault is structural, never "not ready yet"
|
|
if(!m_featureWidthWarned)
|
|
{
|
|
m_featureWidthWarned = true;
|
|
PrintFormat("%s: FEATURE WIDTH MISMATCH at bar %d - the enabled blocks produced %d values"
|
|
" but m_neuronsCount says %d. Every feature after the short block would have"
|
|
" landed in the wrong slot, so the bar is rejected rather than trained on."
|
|
" A block that can be conditionally unavailable must emit neutral values, not"
|
|
" nothing. Check the optional blocks first (cross-asset XA, spread SPR, swing"
|
|
" context) - those are the ones with an availability test.",
|
|
ID, idx, produced, width);
|
|
}
|
|
//--- Roll back the partial bar so the caller's window cannot contain half of it.
|
|
while(TempData.Total() > startTotal)
|
|
TempData.Delete(TempData.Total() - 1);
|
|
}
|
|
}
|
|
//--- ONLY SUCCESSES ARE CACHED. A miss is never stored, in any form.
|
|
if(cacheable && ok)
|
|
{
|
|
m_featureCacheHasValue[idx] = true;
|
|
m_featureCacheValid[idx] = true;
|
|
int base = idx * width;
|
|
int count = TempData.Total() - startTotal;
|
|
for(int f = 0; f < count && f < width; f++)
|
|
m_featureCache[base + f] = TempData.At(startTotal + f);
|
|
}
|
|
return ok;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| THE ONE PLACE a feature WINDOW is assembled. |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalAIBase::ReportFeatureHealth(int bars)
|
|
{
|
|
if(m_featureHealthReported || m_neuronsCount <= 0)
|
|
return;
|
|
m_featureHealthReported = true;
|
|
int per = m_neuronsCount; // features per BAR
|
|
int lo = MathMax((int)m_historyBars + MathMax(m_barrierHorizonBars, 1) + 2, 2);
|
|
int hi = MathMax(bars - 2, lo);
|
|
if(hi <= lo)
|
|
return;
|
|
//--- Evenly spaced sample across the whole range, so a block that dies only in the deep history
|
|
//--- (the alt-coverage case) is caught as surely as one that is dead everywhere (the cold-indicator
|
|
//--- case). 400 bars is enough to call a feature constant and costs a fraction of one era.
|
|
int want = 400;
|
|
int step = MathMax((hi - lo) / want, 1);
|
|
double vmin[], vmax[];
|
|
int zeroCnt[], seen = 0;
|
|
ArrayResize(vmin, per);
|
|
ArrayResize(vmax, per);
|
|
ArrayResize(zeroCnt, per);
|
|
for(int j = 0; j < per; j++)
|
|
{
|
|
vmin[j] = DBL_MAX;
|
|
vmax[j] = -DBL_MAX;
|
|
zeroCnt[j] = 0;
|
|
}
|
|
for(int i = lo; i <= hi; i += step)
|
|
{
|
|
//--- Read ONE bar's block, not a whole window: the per-bar row is what the blocks produce, and
|
|
//--- BuildFeatureWindow would just replicate it historyBars times.
|
|
TempData.Clear();
|
|
if(!BufferTempData(i))
|
|
continue;
|
|
if(TempData.Total() < per)
|
|
continue;
|
|
//--- The bar's own row is the LAST `per` values (BufferTempData appends).
|
|
int base = TempData.Total() - per;
|
|
for(int j = 0; j < per; j++)
|
|
{
|
|
double v = TempData.At(base + j);
|
|
if(!MathIsValidNumber(v))
|
|
continue;
|
|
if(v < vmin[j]) vmin[j] = v;
|
|
if(v > vmax[j]) vmax[j] = v;
|
|
if(v == 0.0) zeroCnt[j]++;
|
|
}
|
|
seen++;
|
|
}
|
|
if(seen < 20)
|
|
{
|
|
Print(ID + StringFormat(": feature health - only %d of %d sampled bars produced a readable row;"
|
|
" too few to judge. This is itself a warning: if it persists the feature"
|
|
" path is rejecting nearly everything.", seen, want));
|
|
return;
|
|
}
|
|
string deadList = "", zeroList = "";
|
|
int dead = 0, mostlyZero = 0;
|
|
for(int j = 0; j < per; j++)
|
|
{
|
|
if(vmin[j] > vmax[j])
|
|
continue; // never read
|
|
bool isConst = (vmax[j] - vmin[j]) <= 1e-12;
|
|
bool isZeroy = (zeroCnt[j] * 2 > seen);
|
|
//--- Named, not numbered. "slot 30 is mostly zero" is a puzzle; "spread[1] is mostly zero" is
|
|
//--- an answer, and here a benign one - a spread CHANGE ratio is exactly 0 whenever the broker
|
|
//--- quotes the same spread two bars running.
|
|
string tag = FeatureSlotName(j);
|
|
if(isConst)
|
|
{
|
|
dead++;
|
|
if(dead <= 12)
|
|
deadList += StringFormat("%s%s=%.4g", (deadList == "" ? "" : " "), tag, vmin[j]);
|
|
}
|
|
else
|
|
if(isZeroy)
|
|
{
|
|
mostlyZero++;
|
|
if(mostlyZero <= 12)
|
|
zeroList += StringFormat("%s%s(%.0f%%)", (zeroList == "" ? "" : " "), tag,
|
|
100.0 * zeroCnt[j] / seen);
|
|
}
|
|
}
|
|
Print(ID + StringFormat(": FEATURE HEALTH on %d sampled bars x %d features%s - %d CONSTANT%s%s |"
|
|
" %d mostly-zero (>50%%)%s%s. A constant feature contributes nothing but"
|
|
" still consumes a first-layer column and a BatchNorm slot; a block that is"
|
|
" constant AND zero is usually a source that failed silently rather than a"
|
|
" quiet market.",
|
|
seen, per,
|
|
//--- Every slot below is named by its block now, so the old "alt block =
|
|
//--- slots N..M" hint has nothing left to disambiguate.
|
|
"",
|
|
dead, (deadList == "" ? "" : ": "), deadList,
|
|
mostlyZero, (zeroList == "" ? "" : ": "), zeroList));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::BuildFeatureWindow(int r)
|
|
{
|
|
int width = (int)m_historyBars * m_neuronsCount;
|
|
TempData.Clear();
|
|
TempData.Reserve(width);
|
|
if(r < 0 || m_historyBars <= 0 || m_neuronsCount <= 0)
|
|
return false;
|
|
//--- Live-only freshness probe for the external block: two comparisons when quiet, a reload at
|
|
//--- most hourly once the chart outruns the exported data. Never fires in the tester (the newest
|
|
//--- bar is historical there).
|
|
if(m_useAltData)
|
|
m_altData.EnsureFresh((datetime)m_Time.GetData(0));
|
|
//--- b counts bars BACK from r, so (m_historyBars - 1 - b) emits the deepest lookback first and
|
|
//--- lands on r itself on the final iteration. Identical set of bars as before, opposite order.
|
|
for(int b = 0; b < (int)m_historyBars; b++)
|
|
if(!BufferTempData(r + ((int)m_historyBars - 1 - b)))
|
|
{
|
|
//--- Which lookback slot rejected, and how much of the window had been assembled. Without
|
|
//--- this the pass-1 stall report can only say "0 of 54681 usable", which is true of a cold
|
|
//--- ATR, a missing optional block and an out-of-range index alike.
|
|
m_windowFailSlot = b;
|
|
m_windowFailTotal = TempData.Total();
|
|
return false;
|
|
}
|
|
if(TempData.Total() < width)
|
|
{
|
|
//--- Nothing rejected the bar and the window is still short.
|
|
m_windowFailSlot = -1;
|
|
m_windowFailTotal = TempData.Total();
|
|
return false;
|
|
}
|
|
//--- THE ANCHOR BAR'S EXTERNAL READING ENTERS THE WINDOW ONCE, NOT ONCE PER BAR OF ITS DAY
|
|
//--- (2026-08-16).
|
|
if(m_useAltData)
|
|
{
|
|
int an = m_altData.FeatureCount();
|
|
if(an > 0 && an <= m_neuronsCount && (int)m_historyBars > 1)
|
|
{
|
|
double anchor[];
|
|
ArrayResize(anchor, an);
|
|
int newest = ((int)m_historyBars - 1) * m_neuronsCount + (m_neuronsCount - an);
|
|
for(int k = 0; k < an; k++)
|
|
anchor[k] = TempData.At(newest + k);
|
|
for(int b = (int)m_historyBars - 2; b >= 0; b--)
|
|
{
|
|
int altBase = b * m_neuronsCount + (m_neuronsCount - an);
|
|
bool same = true;
|
|
for(int k = 0; k < an && same; k++)
|
|
if(TempData.At(altBase + k) != anchor[k])
|
|
same = false;
|
|
if(!same)
|
|
break; // a different reading: this bar and everything older keep their values as-is
|
|
for(int k = 0; k < an; k++)
|
|
TempData.Update(altBase + k, 0.0);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| (Re)build the cross-asset panel over `bars` bars. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::BuildCrossAssetPanel(int bars)
|
|
{
|
|
if(!m_useCrossAsset)
|
|
return true;
|
|
if(bars <= 0)
|
|
return false;
|
|
//--- Deep enough AND anchored to the current newest bar.
|
|
datetime anchor = m_Time.GetData(0);
|
|
if(m_crossAsset.IsReady() && m_crossAsset.Bars() >= bars && m_crossAssetAnchor == anchor && anchor > 0)
|
|
return true;
|
|
//--- A trained model builds from the pair set it was trained on (adopted from the .cfg), never
|
|
//--- from whatever Market Watch holds today - see m_crossAssetPairsPinned.
|
|
if(m_crossAssetPairsPinned != "" && !m_crossAsset.HasPinnedPairs())
|
|
m_crossAsset.SetPinnedPairs(m_crossAssetPairsPinned);
|
|
if(!m_crossAsset.Build(m_symbol.Name(), (ENUM_TIMEFRAMES)m_period, bars))
|
|
{
|
|
m_crossAssetAnchor = 0;
|
|
return false;
|
|
}
|
|
m_crossAssetAnchor = anchor;
|
|
//--- FIRST successful build of a model with no pinned set yet: this pair set is now this model's
|
|
//--- pair set for life.
|
|
if(m_crossAssetPairsPinned == "" && m_crossAsset.UsedPairsCsv() != "")
|
|
{
|
|
m_crossAssetPairsPinned = m_crossAsset.UsedPairsCsv();
|
|
m_crossAsset.SetPinnedPairs(m_crossAssetPairsPinned);
|
|
if(!m_crossAssetCfgSaved && m_activeFileName != "")
|
|
{
|
|
m_crossAssetCfgSaved = true;
|
|
if(SaveTopologyConfiguration(m_activeFileName, m_initialNeuronsCount, m_hiddenLayersCount,
|
|
m_neuronsReduction, m_minNeuronsCount, m_optimizationAlgo,
|
|
m_historyBars, m_outputNeuronsCount, m_neuronsCount,
|
|
LEGACY_STUDY_PERIOD_SLOT, m_minTrainYear, m_isInitialized,
|
|
LEGACY_CONVERGE_WR_SLOT, m_fractalPeriods, m_convFilterCount,
|
|
m_lstmHiddenSize, m_activeFileCommon))
|
|
Print(ID + ": cross-asset pair set PINNED to the .cfg - [" + m_crossAssetPairsPinned +
|
|
"]. Restarts and redeploys now build the panel from exactly this set; Market Watch "
|
|
"changes no longer alter what a trained model's features mean.");
|
|
else
|
|
Print(ID + ": WARNING - failed to pin the cross-asset pair set to the .cfg; a restart "
|
|
"will re-discover Market Watch instead of adopting the trained set.");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy the historical spread series onto the current bar grid. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::EnsureSpreadSeries(int bars)
|
|
{
|
|
//--- The meta target's setup descriptor reads spread/ATR at the candidate's fire bar regardless of
|
|
//--- whether spread is enabled as a per-bar WINDOW feature, so the series must exist for it.
|
|
if(!m_useSpreadFeature && !IsMetaTarget())
|
|
return true;
|
|
if(bars <= 0)
|
|
return false;
|
|
//--- Length alone is NOT a sufficient cache key - see m_spreadSeriesAnchor's declaration comment.
|
|
datetime anchor = m_Time.GetData(0);
|
|
if(m_spreadSeriesBars >= bars && m_spreadSeriesAnchor == anchor && anchor > 0)
|
|
return true;
|
|
ArraySetAsSeries(m_spreadSeries, true); // index 0 = newest, matching every other buffer here
|
|
int got = CopySpread(m_symbol.Name(), (ENUM_TIMEFRAMES)m_period, 0, bars, m_spreadSeries);
|
|
if(got <= 0)
|
|
{
|
|
m_spreadSeriesBars = 0;
|
|
m_spreadSeriesAnchor = 0;
|
|
Print(__FUNCTION__ + ": CopySpread returned " + IntegerToString(got) + " for " + m_symbol.Name() +
|
|
" - spread features 0-filled this run.");
|
|
return false;
|
|
}
|
|
m_spreadSeriesBars = got;
|
|
m_spreadSeriesAnchor = anchor;
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::BufferTempDataCompute(int idx)
|
|
{
|
|
//--- Where THIS bar's block starts. The function appends m_neuronsCount values below; remembering
|
|
//--- the offset lets the whole vector be validated in one place at the end instead of at each of
|
|
//--- the ~60 Add() call sites.
|
|
int featureStart = TempData.Total();
|
|
//--- Cleared here, set by the two NOT-READY-YET guards below. See BufferTempData() for what it
|
|
//--- controls: a rejection caused by data that has not arrived yet must not be cached, because the
|
|
//--- cache never re-tries a miss.
|
|
m_featureFailTransient = false;
|
|
//--- Cleared alongside it, and written by every guard below that can return false - see the
|
|
//--- declaration for why a value COUNT was never enough to identify the block.
|
|
m_featureFailBlock = "";
|
|
m_featureFailIdx = idx;
|
|
double open = m_Open.GetData(idx);
|
|
double close = m_Close.GetData(idx);
|
|
double high = m_High.GetData(idx);
|
|
double low = m_Low.GetData(idx);
|
|
MqlDateTime sTime;
|
|
TimeToStruct(m_Time.GetData(idx), sTime);
|
|
if(open == EMPTY_VALUE)
|
|
{
|
|
m_featureFailTransient = true;
|
|
m_featureFailBlock = "price/open (m_Open.GetData == EMPTY_VALUE)";
|
|
return false;
|
|
}
|
|
//--- ATR-normalize every raw-price-unit feature below instead of feeding e.g. 0.0005 on EURUSD
|
|
//--- vs.
|
|
double atr = m_ATR.Main(idx);
|
|
if(atr <= 0.0 || atr == EMPTY_VALUE)
|
|
{
|
|
//--- TRANSIENT BY NATURE, and the reason resumed models could never train. A FRESH model
|
|
//--- never saw this: it sits through m_warmupPassesRemaining separately-scheduled Train()
|
|
//--- calls before anything touches a feature, which is exactly what those passes are for.
|
|
m_featureFailTransient = true;
|
|
m_featureFailBlock = StringFormat("ATR (m_ATR.Main=%.10g, needs > 0)", atr);
|
|
return false;
|
|
}
|
|
if(!TempData.Add((close - open) / atr) ||
|
|
!TempData.Add((high - open) / atr) ||
|
|
!TempData.Add((low - open) / atr) ||
|
|
// Explicit bullish/bearish flag - (close-open)/atr already encodes direction *and* magnitude
|
|
// together, which asks the network to disentangle "which way" from "how much" out of a single
|
|
// continuous value. Giving direction its own clean +1/-1/0 signal removes that ambiguity.
|
|
!TempData.Add(close > open ? 1.0 : (close < open ? -1.0 : 0.0)))
|
|
{
|
|
return false;
|
|
}
|
|
if(m_useSwingContext)
|
|
{
|
|
//--- Most recent CONFIRMED swing pivot as of bar idx - "confirmed" meaning at least
|
|
//--- m_swingConfirmationBars MORE bars have closed after it (see m_swingConfirmationBars' and
|
|
//--- m_useSwingContext's declaration comments).
|
|
int pivotIdx = -1;
|
|
double pivotPrice = 0.0;
|
|
bool pivotIsLow = false;
|
|
if(!FindConfirmedZigZagPivot(idx + MathMax(m_swingConfirmationBars, 1), pivotIdx, pivotPrice, pivotIsLow))
|
|
{
|
|
// No confirmed pivot within the scan cap (e.g. right at the start of available history) -
|
|
// this is legitimately "no swing context yet", not bad/missing data, so a neutral 0-fill
|
|
// keeps the bar usable rather than rejecting it outright like the ATR/EMPTY_VALUE guards do.
|
|
if(!TempData.Add(0.0) || !TempData.Add(0.0) || !TempData.Add(0.0) || !TempData.Add(0.0) || !TempData.Add(0.0))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Direction of the CURRENT leg: the last confirmed pivot being a bottom means price has been
|
|
// rising away from it (an up-leg) ever since, and vice versa - same +1/-1 convention as the
|
|
// bullish/bearish flag above, just at swing scale instead of single-bar scale.
|
|
double direction = pivotIsLow ? 1.0 : -1.0;
|
|
// How far price has travelled since that pivot, ATR-normalized and signed (+ve above the
|
|
// pivot price, -ve below) - clamped generously since an extended trending leg has no natural
|
|
// ceiling the way a single bar's range does.
|
|
double distSincePivot = MathMax(-10.0, MathMin(10.0, (close - pivotPrice) / atr));
|
|
//--- Magnitude of the PRIOR completed leg (the pivot immediately before pivotIdx) - a
|
|
//--- scale reference for "is the current move big or small relative to the last full
|
|
//--- swing".
|
|
int priorPivotIdx = -1;
|
|
double priorPivotPrice = 0.0;
|
|
bool priorPivotIsLow = false;
|
|
bool havePrior = FindConfirmedZigZagPivot(pivotIdx + 1, priorPivotIdx, priorPivotPrice, priorPivotIsLow);
|
|
double priorLegMagnitude = havePrior ? MathMax(0.0, MathMin(10.0, MathAbs(pivotPrice - priorPivotPrice) / atr)) : 0.0;
|
|
//--- Retracement/extension ratio (current distance relative to the prior leg's own size) -
|
|
//--- Fibonacci-style relative position, often more informative than either raw magnitude
|
|
//--- alone since it's comparable across both quiet and volatile regimes.
|
|
double retracementRatio = (havePrior && priorLegMagnitude > 0.0001) ?
|
|
MathMax(-5.0, MathMin(5.0, distSincePivot / priorLegMagnitude)) : 0.0;
|
|
// Swing age (bars since the pivot) - a maturity/exhaustion proxy, same +/- style clamp
|
|
// convention as the volume-ratio feature below.
|
|
double barsSincePivot = MathMax(0.0, MathMin(5.0, (double)(pivotIdx - idx) / 100.0));
|
|
if(!TempData.Add(direction) ||
|
|
!TempData.Add(distSincePivot) ||
|
|
!TempData.Add(priorLegMagnitude) ||
|
|
!TempData.Add(retracementRatio) ||
|
|
!TempData.Add(barsSincePivot))
|
|
return false;
|
|
}
|
|
//--- Recent price-action context (4 features), computed from CLOSED bars at idx or older only
|
|
//--- - no ZigZag confirmation, so no repainting and NO embargo, and never stale, unlike the
|
|
//--- five pivot-anchored features above whose confirmed anchor is always >=
|
|
//--- m_swingConfirmationBars (~100) bars old.
|
|
double hi20 = high, lo20 = low, hi50 = high, lo50 = low;
|
|
double sum20 = close, oldestClose20 = close;
|
|
int cnt20 = 1;
|
|
for(int w = 1; w < 50; w++)
|
|
{
|
|
int j = idx + w;
|
|
double jc = m_Close.GetData(j);
|
|
double jh = m_High.GetData(j);
|
|
double jl = m_Low.GetData(j);
|
|
// ran off the oldest edge of loaded history (out-of-range reads back as 0/EMPTY_VALUE) -
|
|
// use whatever window we gathered so far rather than rejecting the bar; a shorter early-
|
|
// history window is degraded-but-usable, same spirit as the pivot 0-fill above.
|
|
if(jh == EMPTY_VALUE || jh <= 0.0 || jl <= 0.0)
|
|
break;
|
|
if(jh > hi50)
|
|
hi50 = jh;
|
|
if(jl < lo50)
|
|
lo50 = jl;
|
|
if(w < 20)
|
|
{
|
|
if(jh > hi20)
|
|
hi20 = jh;
|
|
if(jl < lo20)
|
|
lo20 = jl;
|
|
sum20 += jc;
|
|
oldestClose20 = jc;
|
|
cnt20++;
|
|
}
|
|
}
|
|
//--- Donchian position: where close sits inside the recent high/low range, rescaled to
|
|
//--- [-1,+1] (-1 = at the range low / bottom candidate, +1 = at the range high / top
|
|
//--- candidate, 0 = mid- range / mid-trend).
|
|
double range20 = hi20 - lo20;
|
|
double range50 = hi50 - lo50;
|
|
double donchPos20 = (range20 > 0.0) ? ((close - lo20) / range20 - 0.5) * 2.0 : 0.0;
|
|
double donchPos50 = (range50 > 0.0) ? ((close - lo50) / range50 - 0.5) * 2.0 : 0.0;
|
|
// Net directional displacement over the recent window, ATR-normalized and signed - the
|
|
// prevailing-trend strength/direction the counter-trend clusters were ignoring.
|
|
double recentReturn = MathMax(-10.0, MathMin(10.0, (close - oldestClose20) / atr));
|
|
// Distance from the recent mean (SMA), ATR-normalized - a stretch/exhaustion proxy distinct
|
|
// from the net return (a move can be far from its mean with little net displacement, or vice
|
|
// versa); genuine reversals tend to be over-extended from equilibrium.
|
|
double smaExtension = MathMax(-10.0, MathMin(10.0, (close - sum20 / cnt20) / atr));
|
|
if(!TempData.Add(donchPos20) ||
|
|
!TempData.Add(donchPos50) ||
|
|
!TempData.Add(recentReturn) ||
|
|
!TempData.Add(smaExtension))
|
|
return false;
|
|
}
|
|
if(m_useVolumes)
|
|
{
|
|
//--- FOUR values, not one.
|
|
double vNow = m_Volumes.Main(idx);
|
|
double prevVolume = m_Volumes.Main(idx + 1);
|
|
double volumeDelta = vNow - prevVolume;
|
|
//--- Relative change - trading activity magnitude varies wildly across symbols/timeframes, so
|
|
//--- the previous bar's own volume is the scale reference, same logic as ATR-normalizing
|
|
//--- price above.
|
|
double volumeChangeRatio = prevVolume > 0.0 ? volumeDelta / prevVolume : 0.0;
|
|
// Baseline over the trailing 50 bars, walking toward OLDER bars only (increasing index), so
|
|
// nothing here can see the future. Degraded-but-usable at the oldest edge, same convention as
|
|
// the swing-context window above: a short early-history baseline beats rejecting the bar.
|
|
double volSum = vNow;
|
|
int volCnt = 1;
|
|
for(int w = 1; w < 50; w++)
|
|
{
|
|
double jv = m_Volumes.Main(idx + w);
|
|
if(jv <= 0.0)
|
|
break;
|
|
volSum += jv;
|
|
volCnt++;
|
|
}
|
|
double volBase = volSum / volCnt;
|
|
// LEVEL: is this an active bar or a dead one? The change ratio cannot express this at all -
|
|
// two consecutive dead bars and two consecutive frantic ones both read as ~0 change.
|
|
double volLevel = (volBase > 0.0) ? vNow / volBase : 1.0;
|
|
double rangeAtr = (high - low) / atr;
|
|
// ABSORPTION: range delivered per unit of activity. A low value means heavy participation that
|
|
// went nowhere - supply meeting demand - which is a categorically different bar from heavy
|
|
// participation that travelled. The single change ratio conflates the two.
|
|
double absorption = (volLevel > 0.05) ? rangeAtr / volLevel : 0.0;
|
|
// ...and its converse, effort AND result together, which is the continuation reading.
|
|
double volXrange = volLevel * rangeAtr;
|
|
if(!TempData.Add(MathMax(-5.0, MathMin(5.0, volumeChangeRatio))) ||
|
|
!TempData.Add(MathMax(0.0, MathMin(5.0, volLevel))) ||
|
|
!TempData.Add(MathMax(0.0, MathMin(5.0, absorption))) ||
|
|
!TempData.Add(MathMax(0.0, MathMin(5.0, volXrange))))
|
|
return false;
|
|
}
|
|
if(m_useTime)
|
|
{
|
|
// Normalize time (cyclical encoding)
|
|
if(!TempData.Add(sin(2 * M_PI * sTime.hour / 24.0)))
|
|
return false;
|
|
if(!TempData.Add(cos(2 * M_PI * sTime.hour / 24.0)))
|
|
return false;
|
|
if(!TempData.Add(sin(2 * M_PI * sTime.day_of_week / 7.0)))
|
|
return false;
|
|
if(!TempData.Add(cos(2 * M_PI * sTime.day_of_week / 7.0)))
|
|
return false;
|
|
if(!TempData.Add(sin(2 * M_PI * sTime.mon / 12.0)))
|
|
return false;
|
|
if(!TempData.Add(cos(2 * M_PI * sTime.mon / 12.0)))
|
|
return false;
|
|
}
|
|
if(m_useATR)
|
|
{
|
|
// ATR/close (volatility as a fraction of price), not raw ATR - the raw absolute value is
|
|
// itself unnormalized (e.g. ~0.0012 on EURUSD vs. ~1.5 on gold, and drifts over time even on
|
|
// one symbol as its price level changes), which is exactly the kind of scale-dependent
|
|
// feature this whole normalization pass is fixing everywhere else.
|
|
if(!TempData.Add(close != 0.0 ? atr / close : 0.0))
|
|
return false;
|
|
}
|
|
if(m_useMA)
|
|
{
|
|
//--- Same ATR-normalized distance-from-level convention as the base OHLC-from-open features
|
|
//--- above, just measured against the MA instead of the bar's own open - lets the network
|
|
//--- read where price sits relative to the same MA Signals\SignalMA.mqh votes on.
|
|
double maNow = m_MA.GetData(0, idx);
|
|
double maPrev = m_MA.GetData(0, idx + 1);
|
|
if(maNow == EMPTY_VALUE || maPrev == EMPTY_VALUE)
|
|
{
|
|
//--- TRANSIENT, for exactly the reason spelled out at the ATR guard above, and this is the
|
|
//--- guard that proved it: 2026-08-17, six fresh instances on USDJPY and XAUUSD swept
|
|
//--- 33,965-50,162 bars and produced ZERO usable windows, over and over, for 40 minutes.
|
|
m_featureFailTransient = true;
|
|
//--- WHICH of the two reads failed, and whether the indicator is empty EVERYWHERE or only
|
|
//--- here.
|
|
string maNewest = "reads (buffer live; this is a history-edge miss)";
|
|
if(m_MA.GetData(0, 0) == EMPTY_VALUE)
|
|
maNewest = "ALSO EMPTY (whole buffer unreadable - cold or dead handle, NOT a depth shortfall)";
|
|
m_featureFailBlock = StringFormat("MA (iMA) - GetData(%d)=%s GetData(%d)=%s,"
|
|
" newest bar %s, BarsCalculated=%d",
|
|
idx, maNow == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
idx + 1, maPrev == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
maNewest, m_MA.BarsCalculated());
|
|
return false;
|
|
}
|
|
if(!TempData.Add((open - maNow) / atr) ||
|
|
!TempData.Add((high - maNow) / atr) ||
|
|
!TempData.Add((low - maNow) / atr) ||
|
|
!TempData.Add((close - maNow) / atr) ||
|
|
!TempData.Add((maNow - maPrev) / atr))
|
|
return false;
|
|
}
|
|
if(m_useRSI)
|
|
{
|
|
// Already a 0-100 oscillator - /100 is the only transform needed to match the rest of the
|
|
// feature vector's scale (see m_useRSI's declaration comment).
|
|
double rsiNow = m_RSI.Main(idx);
|
|
if(rsiNow == EMPTY_VALUE)
|
|
{
|
|
m_featureFailTransient = true; // not-ready, not no-data - see the MA guard above
|
|
m_featureFailBlock = StringFormat("RSI - Main(%d)=EMPTY, newest bar %s, BarsCalculated=%d", idx,
|
|
m_RSI.Main(0) == EMPTY_VALUE ? "ALSO EMPTY" : "reads",
|
|
m_RSI.BarsCalculated());
|
|
return false;
|
|
}
|
|
if(!TempData.Add(rsiNow / 100.0))
|
|
return false;
|
|
}
|
|
if(m_useMACD)
|
|
{
|
|
//--- Main and signal lines are price-domain differences of two EMAs, so the same ATR
|
|
//--- normalization every other price-unit feature here uses applies unchanged.
|
|
double macdMain = m_MACDFeature.Main(idx);
|
|
double macdSignal = m_MACDFeature.Signal(idx);
|
|
if(macdMain == EMPTY_VALUE || macdSignal == EMPTY_VALUE)
|
|
{
|
|
m_featureFailTransient = true; // not-ready, not no-data - see the MA guard above
|
|
m_featureFailBlock = StringFormat("MACD - Main(%d)=%s Signal(%d)=%s, newest bar %s,"
|
|
" BarsCalculated=%d", idx,
|
|
macdMain == EMPTY_VALUE ? "EMPTY" : "ok", idx,
|
|
macdSignal == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
m_MACDFeature.Main(0) == EMPTY_VALUE ? "ALSO EMPTY" : "reads",
|
|
m_MACDFeature.BarsCalculated());
|
|
return false;
|
|
}
|
|
if(!TempData.Add(macdMain / atr) ||
|
|
!TempData.Add(macdSignal / atr) ||
|
|
!TempData.Add((macdMain - macdSignal) / atr))
|
|
return false;
|
|
}
|
|
if(m_useIchimoku)
|
|
{
|
|
//--- LOOKAHEAD, the one thing that matters in this block. It is never read. The lookahead-
|
|
//--- free statement of the same reading is "how far is this close from the close Kijun bars
|
|
//--- ago", the last feature below.
|
|
int kijunShift = m_indicatorTuner.ichiKijun;
|
|
double tenkan = m_Ichimoku.TenkanSen(idx);
|
|
double kijun = m_Ichimoku.KijunSen(idx);
|
|
double spanA = m_Ichimoku.SenkouSpanA(idx + kijunShift); // cloud AS PLOTTED AT bar idx
|
|
double spanB = m_Ichimoku.SenkouSpanB(idx + kijunShift);
|
|
double futureSpanA = m_Ichimoku.SenkouSpanA(idx); // cloud projected AHEAD of bar idx
|
|
double futureSpanB = m_Ichimoku.SenkouSpanB(idx);
|
|
double closeLagRef = m_Close.GetData(idx + kijunShift); // Chikou reference, never idx - kijunShift
|
|
if(tenkan == EMPTY_VALUE || kijun == EMPTY_VALUE ||
|
|
spanA == EMPTY_VALUE || spanB == EMPTY_VALUE ||
|
|
futureSpanA == EMPTY_VALUE || futureSpanB == EMPTY_VALUE ||
|
|
closeLagRef == EMPTY_VALUE || closeLagRef <= 0.0)
|
|
{
|
|
m_featureFailTransient = true; // not-ready, not no-data - see the MA guard above
|
|
//--- closeLagRef is called out separately because it is the one term here that reads the CLOSE
|
|
//--- series at idx + kijunShift, so it fails on the oldest kijunShift bars by construction (see
|
|
//--- ResizeBuffers' clamp note) rather than because Ichimoku is unready.
|
|
m_featureFailBlock = StringFormat("Ichimoku - tenkan=%s kijun=%s spanA=%s spanB=%s fA=%s fB=%s"
|
|
" closeLag(idx+%d)=%s, newest bar %s, BarsCalculated=%d",
|
|
tenkan == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
kijun == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
spanA == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
spanB == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
futureSpanA == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
futureSpanB == EMPTY_VALUE ? "EMPTY" : "ok",
|
|
kijunShift,
|
|
(closeLagRef == EMPTY_VALUE || closeLagRef <= 0.0) ? "EMPTY" : "ok",
|
|
m_Ichimoku.TenkanSen(0) == EMPTY_VALUE ? "ALSO EMPTY" : "reads",
|
|
m_Ichimoku.BarsCalculated());
|
|
return false;
|
|
}
|
|
if(!TempData.Add((close - tenkan) / atr) || // distance to the fast line
|
|
!TempData.Add((close - kijun) / atr) || // distance to the equilibrium line
|
|
!TempData.Add((tenkan - kijun) / atr) || // TK spread: sign = cross state, size = conviction
|
|
!TempData.Add((close - spanA) / atr) || // distance to each cloud edge, so the network can
|
|
!TempData.Add((close - spanB) / atr) || // place price above / inside / below the cloud
|
|
!TempData.Add((spanA - spanB) / atr) || // signed cloud thickness here: sign = regime, size = strength
|
|
!TempData.Add((futureSpanA - futureSpanB) / atr) || // same for the projected cloud - the "twist" ahead
|
|
!TempData.Add((close - closeLagRef) / atr)) // Chikou displacement, in its lookahead-free form
|
|
return false;
|
|
}
|
|
if(m_useNews)
|
|
{
|
|
//--- Event proximity + impact only - see this member's declaration comment and
|
|
//--- System\NewsRelevance.mqh's ImpactWeightedProximity() for why the forward-looking half
|
|
//--- (searchForward=true) isn't lookahead bias despite being computed for a historical bar.
|
|
datetime barTime = m_Time.GetData(idx);
|
|
double newsRecency = ImpactWeightedProximity(m_symbol.Name(), barTime, m_newsFeatureWindowMinutes, false);
|
|
double newsProximity = ImpactWeightedProximity(m_symbol.Name(), barTime, m_newsFeatureWindowMinutes, true);
|
|
if(!TempData.Add(newsRecency) || !TempData.Add(newsProximity))
|
|
return false;
|
|
}
|
|
if(m_useSpreadFeature)
|
|
{
|
|
//--- TWO values. What this block actually encodes is worth stating precisely, because the raw
|
|
//--- measurement overstates it.
|
|
double sprRatio = 0.0, sprChange = 0.0;
|
|
if(idx + 1 < m_spreadSeriesBars)
|
|
{
|
|
double sNow = (double)m_spreadSeries[idx] * m_symbol.Point();
|
|
double sPrev = (double)m_spreadSeries[idx + 1] * m_symbol.Point();
|
|
sprRatio = sNow / atr;
|
|
if(sPrev > 0.0)
|
|
sprChange = (sNow - sPrev) / sPrev;
|
|
}
|
|
if(!TempData.Add(MathMax(0.0, MathMin(5.0, sprRatio))) ||
|
|
!TempData.Add(MathMax(-5.0, MathMin(5.0, sprChange))))
|
|
return false;
|
|
}
|
|
if(m_useCrossAsset)
|
|
{
|
|
//--- What every OTHER instrument was doing at this bar's timestamp - the one feature block
|
|
//--- here that is not a function of this symbol's own series. See System\CrossAsset.mqh.
|
|
double xa[];
|
|
m_crossAsset.Features(idx, xa);
|
|
for(int k = 0; k < CROSSASSET_FEATURES; k++)
|
|
if(!TempData.Add(xa[k]))
|
|
return false;
|
|
}
|
|
if(m_useADCumulativeDelta)
|
|
{
|
|
//--- COLD IS TRANSIENT, NOT ZERO (2026-08-11). ADIndicatorCold probes the NEWEST bar:
|
|
//--- EMPTY_VALUE there means the async calculation hasn't filled yet -> transient reject
|
|
//--- (never cached, retried like the cold-ATR guard above).
|
|
if(ADIndicatorCold(m_ADCumulativeDelta, "ADCumulativeDelta"))
|
|
return false;
|
|
//--- buffers: 0=Pressure, 1=CumulativeDelta, 2=BullishPressure, 3=BearishPressure,
|
|
//--- 4=Absorption, 5=Initiative.
|
|
if(!TempData.Add(m_ADCumulativeDelta.GetData(0, idx)) || // Pressure
|
|
!TempData.Add(m_ADCumulativeDelta.GetData(1, idx)) || // CumulativeDelta
|
|
!TempData.Add(m_ADCumulativeDelta.GetData(2, idx)) || // BullishPressure
|
|
!TempData.Add(m_ADCumulativeDelta.GetData(3, idx)) || // BearishPressure
|
|
!TempData.Add(m_ADCumulativeDelta.GetData(4, idx)) || // Absorption
|
|
!TempData.Add(m_ADCumulativeDelta.GetData(5, idx))) // Initiative
|
|
return false;
|
|
}
|
|
if(m_useADShorteningOfThrust)
|
|
{
|
|
if(ADIndicatorCold(m_ADShorteningOfThrust, "ADShorteningOfThrust")) // see the CumulativeDelta block's comment
|
|
return false;
|
|
// buffers: 0=SOT, 1=SOTEffortRegime, 2=SOTConfirmation, 3=SOTPushRegime
|
|
if(!TempData.Add(m_ADShorteningOfThrust.GetData(0, idx)) || // SOT
|
|
!TempData.Add(m_ADShorteningOfThrust.GetData(1, idx)) || // SOTEffortRegime
|
|
!TempData.Add(m_ADShorteningOfThrust.GetData(2, idx)) || // SOTConfirmation
|
|
!TempData.Add(m_ADShorteningOfThrust.GetData(3, idx))) // SOTPushRegime
|
|
return false;
|
|
}
|
|
if(m_useADWyckoffEventStream)
|
|
{
|
|
//--- buffers: 0=EventCode, 1=EventPhase, 2=ZoneTop, 3=ZoneBottom, 4=EventPrice,
|
|
//--- 5=StructuralPhase, 6=CHoCHTrendToRange, 7=CHoCHRangeToTrend, 8=SlopeAccumulationBullish,
|
|
//--- 9=SlopeAccumulationBearish, 10=SlopeDistributionBullish, 11=SlopeDistributionBearish,
|
|
//--- 12=Reaccumulation, 13=Redistribution.
|
|
if(ADIndicatorCold(m_ADWyckoffEventStream, "ADWyckoffEventStream")) // see the CumulativeDelta block's comment
|
|
return false;
|
|
double wesEvent = m_ADWyckoffEventStream.GetData(0, idx);
|
|
double wesLivePhase = m_ADWyckoffEventStream.GetData(1, idx);
|
|
double wesStructPhase = m_ADWyckoffEventStream.GetData(5, idx);
|
|
if(!TempData.Add(wesEvent > 0 ? 1.0 : (wesEvent < 0 ? -1.0 : 0.0)) || // event direction
|
|
!TempData.Add(MathMin(1.0, MathAbs(wesEvent) / 7.0)) || // event stage
|
|
!TempData.Add(wesLivePhase > 0 ? 1.0 : (wesLivePhase < 0 ? -1.0 : 0.0)) || // live-range direction
|
|
!TempData.Add(MathMin(1.0, MathAbs(wesLivePhase) / 5.0)) || // live-range phase
|
|
!TempData.Add((m_ADWyckoffEventStream.GetData(2, idx) - close) / atr) || // ZoneTop
|
|
!TempData.Add((m_ADWyckoffEventStream.GetData(3, idx) - close) / atr) || // ZoneBottom
|
|
!TempData.Add(wesStructPhase > 0 ? 1.0 : (wesStructPhase < 0 ? -1.0 : 0.0)) || // struct direction
|
|
!TempData.Add(MathMin(1.0, MathAbs(wesStructPhase) / 5.0)) || // struct phase
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(6, idx)) || // CHoCHTrendToRange
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(7, idx)) || // CHoCHRangeToTrend
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(8, idx)) || // SlopeAccumulationBullish
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(9, idx)) || // SlopeAccumulationBearish
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(10, idx)) || // SlopeDistributionBullish
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(11, idx)) || // SlopeDistributionBearish
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(12, idx)) || // Reaccumulation
|
|
!TempData.Add(m_ADWyckoffEventStream.GetData(13, idx))) // Redistribution
|
|
return false;
|
|
}
|
|
if(m_useADWyckoffFailedStructure)
|
|
{
|
|
if(ADIndicatorCold(m_ADWyckoffFailedStructure, "ADWyckoffFailedStructure")) // see the CumulativeDelta block's comment
|
|
return false;
|
|
// buffers: 0=Value, 1=BullishStructuralFailure, 2=BearishStructuralFailure, 3=FailedAccumulation, 4=FailedDistribution
|
|
if(!TempData.Add(m_ADWyckoffFailedStructure.GetData(0, idx)) || // Value
|
|
!TempData.Add(m_ADWyckoffFailedStructure.GetData(1, idx)) || // BullishStructuralFailure
|
|
!TempData.Add(m_ADWyckoffFailedStructure.GetData(2, idx)) || // BearishStructuralFailure
|
|
!TempData.Add(m_ADWyckoffFailedStructure.GetData(3, idx)) || // FailedAccumulation
|
|
!TempData.Add(m_ADWyckoffFailedStructure.GetData(4, idx))) // FailedDistribution
|
|
return false;
|
|
}
|
|
if(m_useADWyckoffSignificantBarInversion)
|
|
{
|
|
if(ADIndicatorCold(m_ADWyckoffSignificantBarInversion, "ADWyckoffSignificantBarInversion")) // see the CumulativeDelta block's comment
|
|
return false;
|
|
// buffers: 0=SignificantBarQuality, 1=BullishSignificantBar, 2=BearishSignificantBar, 3=BullishControlFlip, 4=BearishControlFlip
|
|
if(!TempData.Add(m_ADWyckoffSignificantBarInversion.GetData(0, idx)) || // SignificantBarQuality
|
|
!TempData.Add(m_ADWyckoffSignificantBarInversion.GetData(1, idx)) || // BullishSignificantBar
|
|
!TempData.Add(m_ADWyckoffSignificantBarInversion.GetData(2, idx)) || // BearishSignificantBar
|
|
!TempData.Add(m_ADWyckoffSignificantBarInversion.GetData(3, idx)) || // BullishControlFlip
|
|
!TempData.Add(m_ADWyckoffSignificantBarInversion.GetData(4, idx))) // BearishControlFlip
|
|
return false;
|
|
}
|
|
if(m_useAltData)
|
|
{
|
|
//--- External publication-stamped block (COT/VIX/macro) - see System\AltData.mqh and the
|
|
//--- matching m_neuronsCount block in Topology.mqh. As-of lookup by THIS bar's open time, so
|
|
//--- a bar can only read values the live run would have had.
|
|
double av[];
|
|
m_altData.Features((datetime)m_Time.GetData(idx), av);
|
|
int an = m_altData.FeatureCount();
|
|
for(int k = 0; k < an; k++)
|
|
if(!TempData.Add(av[k]))
|
|
return false;
|
|
}
|
|
//--- ONE finiteness/plausibility gate for the whole bar, rather than 60-odd individually guarded
|
|
//--- Add() calls. Most blocks above already clamp their own output; the AD/Wyckoff blocks
|
|
//--- deliberately do not, because those indicators emit plain readings with no natural range.
|
|
int featureEnd = TempData.Total();
|
|
for(int f = featureStart; f < featureEnd; f++)
|
|
{
|
|
double v = TempData.At(f);
|
|
if((!MathIsValidNumber(v) || MathAbs(v) > FEATURE_ABS_MAX) && !TempData.Update(f, 0.0))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| "Is this AD indicator still calculating?" MT5 fills custom- |
|
|
//| indicator buffers asynchronously after the handle is created, |
|
|
//| and a cold one returns EMPTY_VALUE for EVERY index - including |
|
|
//| the newest bar, which a warm indicator always has. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::ADIndicatorCold(CiCustom &ind, string block)
|
|
{
|
|
if(ind.GetData(0, 0) != EMPTY_VALUE)
|
|
return false;
|
|
m_featureFailTransient = true;
|
|
m_featureFailBlock = StringFormat("%s - COLD (newest bar EMPTY, whole buffer unreadable),"
|
|
" BarsCalculated=%d", block, ind.BarsCalculated());
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Open indicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitOpen(CIndicators * indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(!indicators.Add(GetPointer(m_Open)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object
|
|
if(!m_Open.Create(m_symbol.Name(), m_period))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Close indicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitClose(CIndicators * indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(!indicators.Add(GetPointer(m_Close)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object
|
|
if(!m_Close.Create(m_symbol.Name(), m_period))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize High indicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitHigh(CIndicators * indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(!indicators.Add(GetPointer(m_High)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object
|
|
if(!m_High.Create(m_symbol.Name(), m_period))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Low indicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitLow(CIndicators * indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(!indicators.Add(GetPointer(m_Low)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object
|
|
if(!m_Low.Create(m_symbol.Name(), m_period))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Time indicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitTime(CIndicators * indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(!indicators.Add(GetPointer(m_Time)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object
|
|
if(!m_Time.Create(m_symbol.Name(), m_period))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Volumes indicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitVolumes(CIndicators * indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(!indicators.Add(GetPointer(m_Volumes)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object
|
|
if(!m_Volumes.Create(m_symbol.Name(), m_period, VolumeData))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize MA indicator (feature use - see m_useMA). Period comes |
|
|
//| from m_indicatorTuner.maPeriod, not the raw PeriodMA input - it |
|
|
//| starts equal to it (see CADIndicatorTuner's constructor) but may |
|
|
//| diverge once AutoTuneIndicators actually searches a trial. The |
|
|
//| Classic Signals MA vote is unaffected - see m_useMA's declaration |
|
|
//| comment. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitMA(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
if(indicators == NULL)
|
|
return (false);
|
|
if(addToCollection && !indicators.Add(GetPointer(m_MA)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- built-in iMA; type AND period are both tuner-driven (m_indicatorTuner.maType/maPeriod). ma_shift
|
|
//--- is 0 - the feature reads a bar index directly, so displacing the average would only skew it.
|
|
if(!m_MA.Create(m_symbol.Name(), m_period, m_indicatorTuner.maPeriod, 0,
|
|
(ENUM_MA_METHOD)m_indicatorTuner.maType, PRICE_CLOSE))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize RSI indicator (feature use - see m_useRSI). Period |
|
|
//| comes from m_indicatorTuner.rsiPeriod - see InitMA()'s comment. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitRSI(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
if(indicators == NULL)
|
|
return (false);
|
|
if(addToCollection && !indicators.Add(GetPointer(m_RSI)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
if(!m_RSI.Create(m_symbol.Name(), m_period, m_indicatorTuner.rsiPeriod, PRICE_CLOSE))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize MACD indicator (feature use - see m_useMACD). Periods |
|
|
//| come from m_indicatorTuner.macdFast/macdSlow/macdSignal - see |
|
|
//| InitMA()'s comment for the "starts at the input, may diverge once |
|
|
//| the tuner searches" split, and note the Classic Signals MACD vote |
|
|
//| (Signals\SignalMACD.mqh) keeps its own separate instance. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitMACDFeature(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
if(indicators == NULL)
|
|
return (false);
|
|
if(addToCollection && !indicators.Add(GetPointer(m_MACDFeature)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
if(!m_MACDFeature.Create(m_symbol.Name(), m_period, m_indicatorTuner.macdFast, m_indicatorTuner.macdSlow,
|
|
m_indicatorTuner.macdSignal, PRICE_CLOSE))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Ichimoku indicator (feature use - see m_useIchimoku). |
|
|
//| Periods come from m_indicatorTuner.ichiTenkan/ichiKijun/ |
|
|
//| ichiSenkou - see InitMA()'s comment. The Classic Signals Ichimoku |
|
|
//| vote (Signals\SignalIchimoku.mqh) keeps its own instance. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitIchimoku(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
if(indicators == NULL)
|
|
return (false);
|
|
if(addToCollection && !indicators.Add(GetPointer(m_Ichimoku)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
if(!m_Ichimoku.Create(m_symbol.Name(), m_period, m_indicatorTuner.ichiTenkan, m_indicatorTuner.ichiKijun,
|
|
m_indicatorTuner.ichiSenkou))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize AD Cumulative Delta (CustomIndicators\ADCumulativeDelta.mq5) |
|
|
//| Loaded via iCustom/CiCustom, not a built-in Ci* class - the compiled |
|
|
//| indicator must be present under MQL5\Indicators\ (see |
|
|
//| ExtractCustomIndicators() in Warrior_EA.mq5). Uses the indicator's |
|
|
//| own input defaults; 6 output buffers, one TempData feature each. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitADCumulativeDelta(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(addToCollection && !indicators.Add(GetPointer(m_ADCumulativeDelta)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object; params[1..] mirror ADCumulativeDelta.mq5's own input order exactly
|
|
MqlParam params[11];
|
|
params[0].type = TYPE_STRING;
|
|
params[0].string_value = WARRIOR_CI("ADCumulativeDelta");
|
|
params[1].type = TYPE_INT;
|
|
params[1].integer_value = m_indicatorTuner.adCumDelta.lookback; // InpLookbackPeriod
|
|
params[2].type = TYPE_DOUBLE;
|
|
params[2].double_value = m_indicatorTuner.adCumDelta.volClimax; // InpVolumeClimaxMultiplier
|
|
params[3].type = TYPE_DOUBLE;
|
|
params[3].double_value = m_indicatorTuner.adCumDelta.volHigh; // InpVolumeHighMultiplier
|
|
params[4].type = TYPE_DOUBLE;
|
|
params[4].double_value = m_indicatorTuner.adCumDelta.rangeClimax; // InpRangeClimaxMultiplier
|
|
params[5].type = TYPE_DOUBLE;
|
|
params[5].double_value = m_indicatorTuner.adCumDelta.rangeSignificant; // InpRangeSignificantMult
|
|
params[6].type = TYPE_DOUBLE;
|
|
params[6].double_value = m_indicatorTuner.adCumDelta.stVolRatio; // InpSTVolumeRatio
|
|
params[7].type = TYPE_DOUBLE;
|
|
params[7].double_value = m_indicatorTuner.adCumDelta.atrMult; // InpATRMultiplier
|
|
params[8].type = TYPE_INT;
|
|
params[8].integer_value = 0; // InpContextMode - DO NOT tune
|
|
params[9].type = TYPE_INT;
|
|
params[9].integer_value = 5; // InpSessionType - DO NOT tune
|
|
params[10].type = TYPE_INT;
|
|
params[10].integer_value = 1; // InpSessionCount - DO NOT tune
|
|
if(!m_ADCumulativeDelta.Create(m_symbol.Name(), m_period, IND_CUSTOM, 11, params))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
m_ADCumulativeDelta.NumBuffers(6);
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize AD Shortening of Thrust (CustomIndicators\ADShorteningOfThrust.mq5) |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitADShorteningOfThrust(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(addToCollection && !indicators.Add(GetPointer(m_ADShorteningOfThrust)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object; params[1..] mirror ADShorteningOfThrust.mq5's own input order exactly
|
|
MqlParam params[7];
|
|
params[0].type = TYPE_STRING;
|
|
params[0].string_value = WARRIOR_CI("ADShorteningOfThrust");
|
|
params[1].type = TYPE_INT;
|
|
params[1].integer_value = m_indicatorTuner.adSOT.thrustLookback; // InpThrustLookback
|
|
params[2].type = TYPE_INT;
|
|
params[2].integer_value = m_indicatorTuner.adSOT.minImpulses; // InpMinImpulses
|
|
params[3].type = TYPE_DOUBLE;
|
|
params[3].double_value = m_indicatorTuner.adSOT.sotThreshold; // InpSOTThreshold
|
|
params[4].type = TYPE_INT;
|
|
params[4].integer_value = 0; // InpContextMode - DO NOT tune
|
|
params[5].type = TYPE_INT;
|
|
params[5].integer_value = 5; // InpSessionType - DO NOT tune
|
|
params[6].type = TYPE_INT;
|
|
params[6].integer_value = 1; // InpSessionCount - DO NOT tune
|
|
if(!m_ADShorteningOfThrust.Create(m_symbol.Name(), m_period, IND_CUSTOM, 7, params))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
m_ADShorteningOfThrust.NumBuffers(4);
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize AD Wyckoff Event Stream (CustomIndicators\ADWyckoffEventStream.mq5) |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitADWyckoffEventStream(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(addToCollection && !indicators.Add(GetPointer(m_ADWyckoffEventStream)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object; params[1..] mirror ADWyckoffEventStream.mq5's own input order exactly.
|
|
//--- MqlParam is positional, so this list follows the indicator's declaration order, not a tidier
|
|
//--- one.
|
|
MqlParam params[17];
|
|
params[0].type = TYPE_STRING;
|
|
params[0].string_value = WARRIOR_CI("ADWyckoffEventStream");
|
|
params[1].type = TYPE_INT;
|
|
params[1].integer_value = m_indicatorTuner.adWES.lookback; // InpLookback
|
|
params[2].type = TYPE_INT;
|
|
params[2].integer_value = m_indicatorTuner.adWES.zigzag; // InpZigZag
|
|
params[3].type = TYPE_DOUBLE;
|
|
params[3].double_value = m_indicatorTuner.adWES.volClimax; // InpVolClimax
|
|
params[4].type = TYPE_DOUBLE;
|
|
params[4].double_value = m_indicatorTuner.adWES.volHigh; // InpVolHigh
|
|
params[5].type = TYPE_DOUBLE;
|
|
params[5].double_value = m_indicatorTuner.adWES.rangeClimax; // InpRangeClimax
|
|
params[6].type = TYPE_DOUBLE;
|
|
params[6].double_value = m_indicatorTuner.adWES.rangeSignificant; // InpRangeSignificant
|
|
params[7].type = TYPE_DOUBLE;
|
|
params[7].double_value = m_indicatorTuner.adWES.stVolRatio; // InpSTVolRatio
|
|
params[8].type = TYPE_DOUBLE;
|
|
params[8].double_value = m_indicatorTuner.adWES.atr; // InpATR
|
|
params[9].type = TYPE_INT;
|
|
params[9].integer_value = 0; // InpContextMode - DO NOT tune
|
|
params[10].type = TYPE_INT;
|
|
params[10].integer_value = 5; // InpSessionType - DO NOT tune
|
|
params[11].type = TYPE_INT;
|
|
params[11].integer_value = 1; // InpSessionCount - DO NOT tune
|
|
params[12].type = TYPE_DOUBLE;
|
|
params[12].double_value = m_indicatorTuner.adWES.touchATR; // InpTouchATR
|
|
params[13].type = TYPE_DOUBLE;
|
|
params[13].double_value = m_indicatorTuner.adWES.arMinATR; // InpARMinATR
|
|
params[14].type = TYPE_INT;
|
|
params[14].integer_value = m_indicatorTuner.adWES.maxRangeBars; // InpMaxRangeBars
|
|
//--- InpShowLabels/InpShowZones - forced OFF, and deliberately NOT tunable. This handle exists
|
|
//--- purely to read buffers as network features; it is never the user's chart indicator.
|
|
params[15].type = TYPE_BOOL;
|
|
params[15].integer_value = 0; // InpShowLabels
|
|
params[16].type = TYPE_BOOL;
|
|
params[16].integer_value = 0; // InpShowZones
|
|
if(!m_ADWyckoffEventStream.Create(m_symbol.Name(), m_period, IND_CUSTOM, 17, params))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
m_ADWyckoffEventStream.NumBuffers(14);
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize AD Wyckoff Failed Structure (CustomIndicators\ADWyckoffFailedStructure.mq5) |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitADWyckoffFailedStructure(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(addToCollection && !indicators.Add(GetPointer(m_ADWyckoffFailedStructure)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object; params[1..] mirror ADWyckoffFailedStructure.mq5's own input order exactly
|
|
MqlParam params[12];
|
|
params[0].type = TYPE_STRING;
|
|
params[0].string_value = WARRIOR_CI("ADWyckoffFailedStructure");
|
|
params[1].type = TYPE_INT;
|
|
params[1].integer_value = m_indicatorTuner.adWFS.lookback; // InpLookbackPeriod
|
|
params[2].type = TYPE_INT;
|
|
params[2].integer_value = m_indicatorTuner.adWFS.zigzagStrength; // InpZigZagStrength
|
|
params[3].type = TYPE_DOUBLE;
|
|
params[3].double_value = m_indicatorTuner.adWFS.volClimax; // InpVolumeClimaxMultiplier
|
|
params[4].type = TYPE_DOUBLE;
|
|
params[4].double_value = m_indicatorTuner.adWFS.volHigh; // InpVolumeHighMultiplier
|
|
params[5].type = TYPE_DOUBLE;
|
|
params[5].double_value = m_indicatorTuner.adWFS.rangeClimax; // InpRangeClimaxMultiplier
|
|
params[6].type = TYPE_DOUBLE;
|
|
params[6].double_value = m_indicatorTuner.adWFS.rangeSignificant; // InpRangeSignificantMult
|
|
params[7].type = TYPE_DOUBLE;
|
|
params[7].double_value = m_indicatorTuner.adWFS.stVolRatio; // InpSTVolumeRatio
|
|
params[8].type = TYPE_DOUBLE;
|
|
params[8].double_value = m_indicatorTuner.adWFS.atrMult; // InpATRMultiplier
|
|
params[9].type = TYPE_INT;
|
|
params[9].integer_value = 0; // InpContextMode - DO NOT tune
|
|
params[10].type = TYPE_INT;
|
|
params[10].integer_value = 5; // InpSessionType - DO NOT tune
|
|
params[11].type = TYPE_INT;
|
|
params[11].integer_value = 1; // InpSessionCount - DO NOT tune
|
|
if(!m_ADWyckoffFailedStructure.Create(m_symbol.Name(), m_period, IND_CUSTOM, 12, params))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
m_ADWyckoffFailedStructure.NumBuffers(5);
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize AD Wyckoff Significant Bar Inversion (CustomIndicators\ADWyckoffSignificantBarInversion.mq5) |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitADWyckoffSignificantBarInversion(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(addToCollection && !indicators.Add(GetPointer(m_ADWyckoffSignificantBarInversion)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- initialize object; params[1..] mirror ADWyckoffSignificantBarInversion.mq5's own input order exactly
|
|
MqlParam params[8];
|
|
params[0].type = TYPE_STRING;
|
|
params[0].string_value = WARRIOR_CI("ADWyckoffSignificantBarInversion");
|
|
params[1].type = TYPE_INT;
|
|
params[1].integer_value = m_indicatorTuner.adWSBI.lookback; // InpLookback
|
|
params[2].type = TYPE_DOUBLE;
|
|
params[2].double_value = m_indicatorTuner.adWSBI.rangeSignificant; // InpRangeSignificant
|
|
params[3].type = TYPE_DOUBLE;
|
|
params[3].double_value = m_indicatorTuner.adWSBI.volumeHigh; // InpVolumeHigh
|
|
params[4].type = TYPE_DOUBLE;
|
|
params[4].double_value = m_indicatorTuner.adWSBI.atr; // InpATR
|
|
params[5].type = TYPE_INT;
|
|
params[5].integer_value = 0; // InpContextMode - DO NOT tune
|
|
params[6].type = TYPE_INT;
|
|
params[6].integer_value = 5; // InpSessionType - DO NOT tune
|
|
params[7].type = TYPE_INT;
|
|
params[7].integer_value = 1; // InpSessionCount - DO NOT tune
|
|
if(!m_ADWyckoffSignificantBarInversion.Create(m_symbol.Name(), m_period, IND_CUSTOM, 8, params))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
m_ADWyckoffSignificantBarInversion.NumBuffers(5);
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize the ZigZag - the training-label source (see |
|
|
//| m_ADZigZag's declaration comment). Always run at its stock |
|
|
//| defaults (Depth=12, Deviation=5, Backstep=3) - unlike the AD* |
|
|
//| feature indicators above, this has no tunable-param struct and is |
|
|
//| never touched by AutoTuneIndicators. |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalAIBase::InitADZigZag(CIndicators * indicators, bool addToCollection)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return (false);
|
|
//--- add object to collection
|
|
if(addToCollection && !indicators.Add(GetPointer(m_ADZigZag)))
|
|
{
|
|
printf(__FUNCTION__ + ": error adding object");
|
|
return (false);
|
|
}
|
|
//--- params[1..] mirror ZigZag.mq5's own input order exactly - stock defaults, intentionally not
|
|
//--- sourced from a tunable params struct (see this function's declaration comment)
|
|
MqlParam params[4];
|
|
params[0].type = TYPE_STRING;
|
|
params[0].string_value = WARRIOR_STOCK_ZIGZAG;
|
|
params[1].type = TYPE_INT;
|
|
params[1].integer_value = 12; // InpDepth
|
|
params[2].type = TYPE_INT;
|
|
params[2].integer_value = 5; // InpDeviation
|
|
params[3].type = TYPE_INT;
|
|
params[3].integer_value = 3; // InpBackstep
|
|
if(!m_ADZigZag.Create(m_symbol.Name(), m_period, IND_CUSTOM, 4, params))
|
|
{
|
|
printf(__FUNCTION__ + ": error initializing object");
|
|
return (false);
|
|
}
|
|
// Must match ZigZag.mq5's #property indicator_buffers exactly (3: main ZigZag buffer + 2
|
|
// internal INDICATOR_CALCULATIONS buffers), even though only buffer 0 is ever read via
|
|
// GetData() - see the working AD Wyckoff indicators' InitAD*() for the same pattern.
|
|
m_ADZigZag.NumBuffers(3);
|
|
//--- ok
|
|
return (true);
|
|
}
|
|
#endif // WARRIOR_AIBASE_FEATURES_MQH
|