//+------------------------------------------------------------------+ //| Warrior_EA | //| AnimateDread | //| | //| Indicator creation and the per-bar input feature vector. | //| | //| PARTIAL IMPLEMENTATION FILE - not standalone. | //| This holds CExpertSignalAIBase method BODIES only. The class | //| declaration lives in Expert\ExpertSignalAIBase.mqh, which | //| #includes this file at the bottom, after the declaration. Do not | //| include it anywhere else and do not compile it on its own. | //| | //| Split out purely to make the 8216-line original navigable; the | //| code inside was moved verbatim, not rewritten. | //+------------------------------------------------------------------+ #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(). Deliberately far above every clamp used inside that function (the widest //--- is +/-10) - this is not a normalization knob, it is the "no legitimate feature looks like this" //--- line. 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. Re-Create()-ing | //| the existing CiCustom object (rather than removing/re-adding it | //| to indicators) avoids adding the same pointer into the CIndicators| //| collection twice, which would risk it being deleted twice on | //| teardown - MQL5's CIndicators has no documented single-item | //| remove, and CiCustom.Create() already releases its old handle. | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| See the declaration. Minimum over the enabled tunable indicators, | //| because the feature vector is only as ready as its least-ready | //| component; -1 when nothing tunable is switched on. | //+------------------------------------------------------------------+ int CExpertSignalAIBase::TunableBarsCalculated(void) { int worst = INT_MAX; if(m_useMA) worst = (int)MathMin(worst, m_MA.BarsCalculated()); if(m_useRSI) worst = (int)MathMin(worst, m_RSI.BarsCalculated()); if(m_useMACD) worst = (int)MathMin(worst, m_MACDFeature.BarsCalculated()); if(m_useIchimoku) worst = (int)MathMin(worst, m_Ichimoku.BarsCalculated()); if(m_useADCumulativeDelta) worst = (int)MathMin(worst, m_ADCumulativeDelta.BarsCalculated()); if(m_useADShorteningOfThrust) worst = (int)MathMin(worst, m_ADShorteningOfThrust.BarsCalculated()); if(m_useADWyckoffEventStream) worst = (int)MathMin(worst, m_ADWyckoffEventStream.BarsCalculated()); if(m_useADWyckoffFailedStructure) worst = (int)MathMin(worst, m_ADWyckoffFailedStructure.BarsCalculated()); if(m_useADWyckoffSignificantBarInversion) worst = (int)MathMin(worst, m_ADWyckoffSignificantBarInversion.BarsCalculated()); return (worst == INT_MAX) ? -1 : worst; } //+------------------------------------------------------------------+ bool CExpertSignalAIBase::ReInitADIndicators(CIndicators *indicators) { bool result = true; 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; //--- 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. Grow the close buffer to match // when that feature is on, so the oldest requested bars resolve from real data instead of tripping // that block's EMPTY_VALUE guard and being rejected as unusable examples. int closeBars = m_useIchimoku ? barIndex + m_indicatorTuner.ichiKijun : 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) { 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(barIndex + m_indicatorTuner.ichiKijun)) 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. The real data-validity check happens downstream, per value, in // BufferTempDataCompute() (EMPTY_VALUE / atr<=0 guards) - this function's job is only to ask // every buffer to refresh, unconditionally, before that per-value check runs. 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); if(cacheable) { m_featureCacheHasValue[idx] = true; m_featureCacheValid[idx] = ok; if(ok) { 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. Every consumer - | //| training pass 1/2/3, live inference, online learning, the OOS | //| continual simulation, the chart rescan and the CPU-inference | //| self-check - goes through here, because the thing this function | //| fixes is a contract that eight hand-rolled copies of the same | //| loop cannot hold on their own. | //| | //| ORDER IS CHRONOLOGICAL: OLDEST BAR FIRST, bar `r` (the bar being | //| predicted) LAST. That is the whole point of this function. | //| | //| MQL5 timeseries indices run BACKWARDS - index 0 is the newest bar | //| and increasing index walks into the past. So the obvious loop, | //| `for(b = 0..T-1) BufferTempData(r + b)`, appends the window in | //| REVERSE chronological order: the newest bar lands in block 0 and | //| the oldest in block T-1. That is what every call site used to do. | //| | //| For the dense (PAI) and convolutional stacks it is harmless - a | //| dense layer learns a weight per position either way, and a conv | //| just learns time-mirrored kernels. For the RECURRENT stacks it is | //| not, and it is not a subtlety: | //| - CNeuronLSTMOCL walks steps t = 0..T-1 reading `inputs + t*Iw` | //| (AI\Network.cl, LSTM_SeqStepForward), so step t consumes the | //| t-th block in buffer order. | //| - Its visible output is the LAST hidden state only - the kernel | //| writes `output[id]` solely when `t == steps - 1`. | //| - The cell state decays toward the start of the sequence: | //| c_t = f*c_{t-1} + i*g. DirectML\lstm_seq_flowcheck.cpp measured | //| block 0's influence on the output, relative to block T-1, at | //| 1.2e-2 for the shipped LSTM_FORGET_BIAS_INIT of 1.0 (see that | //| constant's comment for the full sweep). | //| Fed newest-first, that put the bar being PREDICTED at the far end | //| of the decay and handed the output to the OLDEST bar in the window | //| - roughly 80x backwards, and the exact inverse of what the window | //| exists for ("everything known as of this bar's close", see Train() | //| 's r comment). Reversing it here makes the final timestep the | //| current bar, which is the standard arrangement and the one the | //| forget-bias sweep was implicitly reasoning about. | //| | //| Nothing downstream reads a fixed block position, so this is safe | //| for every topology; it re-keys the weight fingerprint (see | //| ConfigFingerprint's WIN token) precisely BECAUSE the input vector | //| now means something different, and models trained under the old | //| order must never load into it. | //| | //| Returns true only when the COMPLETE, correctly-sized window is in | //| TempData - callers must not feedForward on a partial one (a stale | //| output layer would be scored against this bar's label). | //+------------------------------------------------------------------+ 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; //--- 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))) return false; return (TempData.Total() >= width); } //+------------------------------------------------------------------+ //| (Re)build the cross-asset panel over `bars` bars. | //| | //| Called from the same places that size the price buffers, because | //| the panel is aligned to exactly that bar grid and a stale panel | //| would silently mis-index. Cheap to call redundantly: Build() is | //| one CopyClose per reference pair, not per bar. | //| | //| A failure here is NOT fatal. The panel logs its own reason and | //| every Features() call then 0-fills, so the run continues without | //| the cross-asset block instead of refusing to train. | //+------------------------------------------------------------------+ bool CExpertSignalAIBase::BuildCrossAssetPanel(int bars) { if(!m_useCrossAsset) return true; if(bars <= 0) return false; //--- Deep enough AND anchored to the current newest bar. Depth alone would leave the panel's //--- index 0 pointing at a bar that is no longer the newest as soon as one candle closes, so //--- every cross-asset feature would be read one bar out of step with the price features beside //--- it - see m_crossAssetAnchor's declaration comment. datetime anchor = m_Time.GetData(0); if(m_crossAsset.IsReady() && m_crossAsset.Bars() >= bars && m_crossAssetAnchor == anchor && anchor > 0) return true; if(!m_crossAsset.Build(m_symbol.Name(), (ENUM_TIMEFRAMES)m_period, bars)) { m_crossAssetAnchor = 0; return false; } m_crossAssetAnchor = anchor; return true; } //+------------------------------------------------------------------+ //| Copy the historical spread series onto the current bar grid. | //| | //| CopySpread is a RANGE call, so this runs once wherever the price | //| buffers are sized - never per bar. Values are in POINTS (int); | //| the feature block converts with m_symbol.Point(). | //| | //| Non-fatal: a short or failed copy leaves m_spreadSeriesBars at | //| whatever was actually obtained and the feature block 0-fills past | //| it, matching the degraded-but-usable convention used by the swing | //| and cross-asset blocks. Refusing to train because one auxiliary | //| series came up short would be a far worse failure. | //+------------------------------------------------------------------+ bool CExpertSignalAIBase::EnsureSpreadSeries(int bars) { if(!m_useSpreadFeature) 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(); 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) return false; // ATR-normalize every raw-price-unit feature below instead of feeding e.g. 0.0005 on EURUSD vs. // 50.0 on a JPY pair or an index straight into the network - with Adam and hardcoded, scale- // sensitive activations (TANH saturates, PRELU's 0.01 leak only means anything relative to the // input's own scale), an unnormalized feature either vanishes into rounding noise or dominates // the weighted sum depending on which symbol/timeframe happens to be loaded. Dividing by the // bar's own ATR expresses every price-based feature as "fraction of typical volatility", which // is comparable across symbols/timeframes and centered near zero. No ATR reading yet (e.g. the // first few bars of history) means every price feature this bar would be meaningless - reject // the bar via the same "return false" convention as the EMPTY_VALUE check above. double atr = m_ATR.Main(idx); if(atr <= 0.0 || atr == EMPTY_VALUE) 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). This is now the ONLY consumer of that embargo: the // label side stopped needing it when the target became the triple barrier, whose own lookahead is // m_barrierHorizonBars. Skipping this embargo here - e.g. reading // m_ADZigZag's raw current buffer value instead - would leak information a live bar at idx // could never actually have had yet, since ZigZag's most recent 1-3 legs are still provisional // and can be revised as new bars arrive. 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". No // additional embargo needed here (see FindConfirmedZigZagPivot()'s declaration comment) - // anything at or before an already-confirmed pivot is necessarily even older. 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. 0 when there's no prior leg // to compare against yet. 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. Those describe the OLD structure well but say nothing about the recent leg // the bar actually sits in - which is exactly what's needed to tell a genuine reversal at a // range extreme from a mid-trend bar that merely looks like a bottom/top (the "clustered // counter-trend signals" failure mode). These locate the bar within its recent range and // trend so the network can learn that a directional call belongs at an extreme of an extended // move, not anywhere the local candle shape resembles a pivot. All windows walk toward OLDER // bars (increasing index), so nothing here can see the future. 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). Two scales - a short 20-bar and a medium 50-bar view - so the network // sees both local and swing-scale extremity. 0 (mid) when the range is degenerate. 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. This block used to feed only the bar-over-bar change ratio below. // research/test_volume.py measured all four against the barrier label with a block-permutation // null (blocks = the barrier horizon, because adjacent labels share almost their whole outcome // window and a free shuffle produces a null far too tight): the LEVEL and the two // volume-vs-range interactions each carry information the first difference does not, and the // level beats the shipped feature outright on 4 of 6 instrument/geometry cells. // // Read the magnitudes before expecting much: the excess mutual information is ~2e-4 nats // against a label entropy near 1.05, i.e. well under a tenth of one percent of the label's // uncertainty. This is real and repeatable across instruments, and it is nowhere near an edge. // It is worth having because it costs one 50-bar loop, not because it changes the answer. 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. // Guard against a zero previous-bar volume (e.g. a holiday-thin session) instead of dividing by // it. Clamped to +/-5: unlike the ATR-normalized price features this ratio has no natural // ceiling (a 1-tick bar followed by a normal one produces a huge outlier). 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. Plus the MA's own // bar-over-bar change (also ATR-normalized, since the MA lives in price units and ATR is // already this codebase's scale reference for that - see m_useMA's declaration comment for why // this isn't volume's previous-bar-ratio scheme instead). double maNow = m_MA.GetData(0, idx); double maPrev = m_MA.GetData(0, idx + 1); if(maNow == EMPTY_VALUE || maPrev == EMPTY_VALUE) 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) 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. The third value is the histogram // (main - signal): algebraically derivable from the first two, but handed over explicitly for the // same reason the bullish/bearish flag is handed to the network alongside (close-open)/atr - a // value the network would otherwise have to learn to subtract is better given directly, and the // histogram (momentum ACCELERATION) is the one term nothing else in this vector carries. double macdMain = m_MACDFeature.Main(idx); double macdSignal = m_MACDFeature.Signal(idx); if(macdMain == EMPTY_VALUE || macdSignal == EMPTY_VALUE) 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. MT5's iIchimoku does NOT pre-shift its // buffers - it stores raw per-bar values and shifts only the DRAWING (Ichimoku.mq5 sets // PLOT_SHIFT=+Kijun on the Senkou A/B cloud plot and -Kijun on the Chikou plot). In series // indexing that means: // - SenkouSpan*(i) is computed FROM bar i and drawn Kijun bars into the FUTURE, so the cloud // actually sitting under bar idx is SenkouSpan*(idx + kijun) - built from bar idx+kijun and // older, hence strictly past data. Reading SenkouSpan*(idx) as "the cloud here" is the classic // Ichimoku backtest bug and would leak Kijun bars of future information into every example. // - SenkouSpan*(idx) with NO offset IS legitimate as the PROJECTED cloud - the part of the chart // already drawn ahead of the current bar. A live bar at idx genuinely knows it (it is computed // from bar idx), which is why it appears below as its own feature rather than being avoided. // - ChinkouSpan(i) is just Close(i) drawn at i+Kijun, so the Chikou plotted AT bar idx would be // Close(idx - kijun) - a FUTURE bar. 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. // Signals\SignalIchimoku.mqh's class comment documents the identical convention for the vote side. 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) 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. // What is DELIBERATELY not here is actual-vs-forecast surprise. Release TIMES are published // in advance and never revised, so reading them for a historical bar is legitimate; released // VALUES are neither. MqlCalendarValue.actual_value returns the FINAL figure, and the calendar // keeps no as-of-release snapshot (revised_prev_value exists precisely because revisions // happen), so a surprise feature computed for a 2019 bar would be built from a number nobody // had in 2019. That is the same class of leak that made the RSI/MACD divergence models read // +4 sigma in research/test_classic.py until two bars of lookahead were closed - except this // one would survive into production and be paid for in real money. 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. // // spr/atr measured as the single strongest feature in this codebase (research/test_spread.py): // significant on 5 of 8 instrument/geometry cells at 2-4x any volume feature. But the barrier // LABEL is computed with the spread charged inside it, so a high-spread bar has its barriers // shifted adversely and is mechanically likelier to resolve as a loss - the feature would // partly be predicting its own cost model, which is not tradeable. Re-labelling at zero cost // and re-measuring the identical feature showed 20-40% of the signal WAS that tautology and // the majority was not (XAUUSD kept 97%). // // What survives is a VOLATILITY-REGIME reading: the spread is near-fixed while ATR is not, so // this ratio runs high exactly when realised volatility is below its own ATR estimate - which // genuinely predicts whether ATR-scaled barriers get reached at all. Note it is UNSIGNED, like // volume: it informs Neutral-vs-directional and can never pick a side. 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. // A panel that failed to build (no Market Watch pairs, unsynchronised history) yields a // neutral 0-fill rather than rejecting the bar: the block is additive context, and losing // every bar of training because a reference symbol was missing would be a far worse failure // than training without the context. Features() reports that by returning false, which is // logged once at build time rather than per bar. 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) { // buffers: 0=Pressure, 1=CumulativeDelta, 2=BullishPressure, 3=BearishPressure, 4=Absorption, 5=Initiative. // CumulativeDelta (buffer 1) is now cumulativeDelta/sumVolume clamped +/-2 (same scale as every // other buffer here, see ADCumulativeDelta.mq5) - a pure order-flow-imbalance ratio, distinct from // Pressure (buffer 0), which is this same term further adjusted by Initiative/Absorption. 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) { // 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. // Buffer 4 (EventPrice) is deliberately skipped below - per the indicator's own source // (ADWyckoffEventStream.mq5: "BufColor[wi]=(ev!=0)?C[i]:0;"), it's just this bar's close price // echoed back when an event fires (0 otherwise), kept only so a charting/backtesting tool like // StrategyQuant can anchor an arrow to a price. It carries no information the network doesn't // already have (EventCode already flags whether an event fired; the close is already in the // base OHLC features), and normalizing it as a "distance from close" like ZoneTop/ZoneBottom // below would be actively wrong: it's close-close=0 on event bars but 0-close=-close (a raw, // ATR-blown-up price) on every other bar - a huge, meaningless outlier feature. // Buffer 1 (EventPhase) USED to be skipped for the same kind of reason - it was written as // "BufPhase[wi]=(double)ev;", a byte-for-byte copy of EventCode. The 2026-08-02 rewrite made it a // real reading: "BufPhase[wi]=(double)(phaseNow*((dirNow>=0)?1:-1))", i.e. the live range's own // Wyckoff phase 1..5 signed by whether that range is accumulation (+) or distribution (-). That is // NOT what StructuralPhase (buffer 5) carries: StructuralPhase is derived from the EVENT on this // bar (MapStructuralPhase(ev)) and so is 0 on every bar where nothing fires, while EventPhase // persists for the whole life of the range. The pair gives the network both "an event just put us // in phase C" and "we are still in phase C" - so it is included below. if(!TempData.Add(m_ADWyckoffEventStream.GetData(0, idx)) || // EventCode !TempData.Add(m_ADWyckoffEventStream.GetData(1, idx)) || // EventPhase !TempData.Add((m_ADWyckoffEventStream.GetData(2, idx) - close) / atr) || // ZoneTop !TempData.Add((m_ADWyckoffEventStream.GetData(3, idx) - close) / atr) || // ZoneBottom !TempData.Add(m_ADWyckoffEventStream.GetData(5, idx)) || // StructuralPhase !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) { // 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) { // 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; } //--- 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. But //--- MQL5's CDoubleBuffer::At() returns EMPTY_VALUE (DBL_MAX) for any index it holds no data for, //--- and (EMPTY_VALUE - close) / atr is ~1e307: still FINITE, so it sails through every downstream //--- isfinite() check, and still large enough to overflow the first batch-norm layer's running //--- variance and latch that layer to NaN permanently (see NormalizeHost in AI\NeuronBatchNorm.mqh - //--- that is the 2026-08-02 "BufferWrite failed for buffer 3" run). Neutral-fill rather than reject //--- the bar: "this indicator has no reading here" is the degraded-but-usable case that the swing, //--- cross-asset and spread blocks above all already handle the same way. 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; } //+------------------------------------------------------------------+ //| 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); } //--- unified MA custom indicator (CustomIndicators\ADMovingAverage.mq5); type AND period are both tuner- //--- driven (m_indicatorTuner.maType/maPeriod). params[1..] mirror the indicator's own input order. MqlParam params[9]; params[0].type = TYPE_STRING; params[0].string_value = WARRIOR_CI("ADMovingAverage"); params[1].type = TYPE_INT; params[1].integer_value = m_indicatorTuner.maType; // InpType params[2].type = TYPE_INT; params[2].integer_value = m_indicatorTuner.maPeriod; // InpPeriod params[3].type = TYPE_INT; params[3].integer_value = PRICE_CLOSE; // InpAppliedPrice params[4].type = TYPE_DOUBLE; params[4].double_value = 0.85; // InpOffset (ALMA) params[5].type = TYPE_DOUBLE; params[5].double_value = 6.0; // InpSigma (ALMA) params[6].type = TYPE_DOUBLE; params[6].double_value = 0.7; // InpVolumeFactor (T3) params[7].type = TYPE_DOUBLE; params[7].double_value = 0.001; // InpProcessNoise (Kalman) params[8].type = TYPE_DOUBLE; params[8].double_value = 0.1; // InpMeasurementNoise (Kalman) if(!m_MA.Create(m_symbol.Name(), m_period, IND_CUSTOM, 9, params)) { printf(__FUNCTION__ + ": error initializing object"); return (false); } m_MA.NumBuffers(1); 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. //--- NOTE the order is NOT grouped by meaning: the three range-lifecycle knobs the indicator gained on //--- 2026-08-02 were appended AFTER the session inputs, not next to the thresholds they belong with. //--- 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. Left at their own //--- `true` defaults the indicator would litter the traded chart with AWY_-prefixed labels and range //--- rectangles that the EA does not own and its OnDeinit chart sweep does not know to remove. 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 AD ZigZag (CustomIndicators\ADZigZag.mq5) - the | //| training-label source (see m_ADZigZag's declaration comment). | //| Always run at its own 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); } //--- initialize object; params[1..] mirror ADZigZag.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_CI("ADZigZag"); 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 ADZigZag.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