 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| BarCache.mqh |
|
| | | //| AnimateDread |
|
| | | //| |
|
| | | //| THE WHOLE CLOSED-BAR HISTORY OF ONE SYMBOL/TIMEFRAME, in our own |
|
| | | //| arrays, oldest first - and the three series the vol-gated dip |
|
| | | //| rule is built from, computed exactly the way research/backtest.py |
|
| | | //| computes them. |
|
| | | //| |
|
| | | //| WHY NOT THE STDLIB SERIES. CSeries reads past shift 1023 return |
|
| | | //| 0.0 in silence, and the volatility gate is an EXPANDING-window |
|
| | | //| percentile: it ranks today's sigma against every sigma since the |
|
| | | //| first bar. A 1024-bar window would still produce a plausible |
|
| | | //| number - just a different rule from the one that was tested. |
|
| | | //| DeepenPrices() could grow the buffers, but the gate would then |
|
| | | //| re-walk thousands of bars through virtual accessors every bar; |
|
| | | //| here each closed bar is appended once and its derived values are |
|
| | | //| computed once. |
|
| | | //| |
|
| | | //| WHY WILDER ATR, NOT iATR. MT5's iATR is a plain SMA of the true |
|
| | | //| range. The research priced the stop with Wilder's recursion, |
|
| | | //| seeded with the mean of the first P ranges; the two differ on |
|
| | | //| every bar, so the stop - and the lot it sizes - would differ on |
|
| | | //| every trade. |
|
| | | //| |
|
| | | //| Ported rule for rule from mql5/WarriorDipZ.mq5 (CSym, Append, |
|
| | | //| Sync, MeanStd, VolPct), which reconciled with the Python backtest |
|
| | | //| at a per-trade correlation of 0.998. |
|
| | | //+------------------------------------------------------------------+
|
| | | #ifndef WARRIOR_BARCACHE_MQH
|
| | | #define WARRIOR_BARCACHE_MQH
|
| | |
|
| | | #define BARCACHE_NA -1.0 // "not yet computable" - sigma, ATR and percentile are all >= 0
|
| | |
|
| | | class CBarCache
|
| | | {
|
| | | protected:
|
| | | string m_symbol;
|
| | | ENUM_TIMEFRAMES m_tf;
|
| | | int m_atrPeriod;
|
| | | int m_volWindow;
|
| | | int m_n;
|
| | | datetime m_t[];
|
2026-09-30 18:36:33 -04:00 | | | double m_o[], m_h[], m_l[], m_c[], m_tr[], m_atr[], m_gk[], m_sig[], m_v[];
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | |
|
| | | void Grow(const int k);
|
| | | void Append(const MqlRates &b);
|
| | |
|
| | | public:
|
| | | CBarCache(void) : m_symbol(""), m_tf(PERIOD_CURRENT), m_atrPeriod(14),
|
| | | m_volWindow(30), m_n(0) {}
|
| | | ~CBarCache(void) {}
|
| | |
|
| | | void Init(const string symbol, const ENUM_TIMEFRAMES tf,
|
| | | const int atrPeriod, const int volWindow)
|
| | | {
|
| | | m_symbol = symbol; m_tf = tf; m_atrPeriod = atrPeriod; m_volWindow = volWindow; m_n = 0;
|
| | | }
|
| | | //--- Bring the cache up to the newest CLOSED bar (shift 1). False = not there yet; try again.
|
| | | bool Sync(void);
|
| | | int Count(void) const { return m_n; }
|
| | | int Last(void) const { return m_n - 1; }
|
| | | datetime Time(const int i) const { return m_t[i]; }
|
2026-09-30 18:36:33 -04:00 | | | double Open(const int i) const { return m_o[i]; }
|
| | | double High(const int i) const { return m_h[i]; }
|
| | | double Low(const int i) const { return m_l[i]; }
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | double Close(const int i) const { return m_c[i]; }
|
2026-09-30 18:36:33 -04:00 | | | //--- TICK volume (CFD real volume is 0). Raw counts: the feed's level drifts up to 20x between
|
| | | //--- years, so read it through CVolumeProfile, never on its own.
|
| | | double Volume(const int i) const { return m_v[i]; }
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | double Atr(const int i) const { return m_atr[i]; }
|
| | | double Sigma(const int i) const { return m_sig[i]; }
|
2026-09-30 18:36:33 -04:00 | | | //--- The `n` closes ending at bar i, NEWEST FIRST (out[0] = Close(i)) - the orientation
|
| | | //--- CRegimeMath takes. False when fewer than n bars exist.
|
| | | bool ClosesBack(const int i, const int n, double &out[]) const;
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | //--- Index of the bar that opened at `t`, or -1. Binary search: the cache is time-ordered.
|
| | | int IndexOf(const datetime t) const;
|
| | | //--- Mean and POPULATION deviation of the `period` closes ending at i.
|
| | | bool MeanStd(const int i, const int period, double &mean, double &sd) const;
|
| | | //--- Causal expanding-window percentile of sigma[i] against every EARLIER valid sigma.
|
| | | //--- BARCACHE_NA until `warm` bars and more than 50 samples.
|
| | | double VolPercentile(const int i, const int warm) const;
|
| | | };
|
| | | //+------------------------------------------------------------------+
|
| | | void CBarCache::Grow(const int k)
|
| | | {
|
| | | if(ArraySize(m_t) > k)
|
| | | return;
|
| | | const int cap = MathMax(1024, k * 2);
|
| | | ArrayResize(m_t, cap);
|
| | | ArrayResize(m_o, cap);
|
| | | ArrayResize(m_h, cap);
|
| | | ArrayResize(m_l, cap);
|
| | | ArrayResize(m_c, cap);
|
| | | ArrayResize(m_tr, cap);
|
| | | ArrayResize(m_atr, cap);
|
| | | ArrayResize(m_gk, cap);
|
| | | ArrayResize(m_sig, cap);
|
2026-09-30 18:36:33 -04:00 | | | ArrayResize(m_v, cap);
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| One CLOSED bar in, every derived series updated. |
|
| | | //+------------------------------------------------------------------+
|
| | | void CBarCache::Append(const MqlRates &b)
|
| | | {
|
| | | const int k = m_n;
|
| | | Grow(k + 1);
|
| | | m_t[k] = b.time;
|
| | | m_o[k] = b.open;
|
| | | m_h[k] = b.high;
|
| | | m_l[k] = b.low;
|
| | | m_c[k] = b.close;
|
2026-09-30 18:36:33 -04:00 | | | m_v[k] = (double)b.tick_volume;
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | //--- Garman-Klass variance. Non-negative for any valid bar; a bad bar is stored as 0 so it
|
| | | //--- cannot dominate the rolling mean.
|
| | | double g = 0.0;
|
| | | if(b.open > 0 && b.high > 0 && b.low > 0 && b.close > 0 && b.high >= b.low)
|
| | | {
|
| | | const double hl = MathLog(b.high / b.low);
|
| | | const double co = MathLog(b.close / b.open);
|
| | | g = 0.5 * hl * hl - (2.0 * MathLog(2.0) - 1.0) * co * co;
|
| | | }
|
| | | m_gk[k] = g;
|
| | | //--- Wilder ATR, seeded with the mean of the first P true ranges.
|
| | | if(k == 0)
|
| | | m_tr[k] = b.high - b.low;
|
| | | else
|
| | | m_tr[k] = MathMax(b.high - b.low,
|
| | | MathMax(MathAbs(b.high - m_c[k - 1]), MathAbs(b.low - m_c[k - 1])));
|
| | | const int P = m_atrPeriod;
|
| | | if(k < P - 1)
|
| | | m_atr[k] = BARCACHE_NA;
|
| | | else if(k == P - 1)
|
| | | {
|
| | | double sum = 0.0;
|
| | | for(int j = 0; j < P; j++)
|
| | | sum += m_tr[j];
|
| | | m_atr[k] = sum / P;
|
| | | }
|
| | | else
|
| | | m_atr[k] = (m_atr[k - 1] * (P - 1) + m_tr[k]) / P;
|
| | | //--- Rolling GK sigma.
|
| | | const int W = m_volWindow;
|
| | | if(k < W - 1)
|
| | | m_sig[k] = BARCACHE_NA;
|
| | | else
|
| | | {
|
| | | double sum = 0.0;
|
| | | for(int j = k - W + 1; j <= k; j++)
|
| | | sum += m_gk[j];
|
| | | m_sig[k] = MathSqrt(MathMax(sum / W, 0.0));
|
| | | }
|
| | | m_n++;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | bool CBarCache::Sync(void)
|
| | | {
|
| | | const datetime newestClosed = iTime(m_symbol, m_tf, 1);
|
| | | if(newestClosed == 0)
|
| | | return false;
|
| | | if(m_n > 0 && m_t[m_n - 1] >= newestClosed)
|
| | | return true;
|
| | | MqlRates r[];
|
| | | int got;
|
| | | if(m_n == 0)
|
| | | {
|
| | | const int total = Bars(m_symbol, m_tf);
|
| | | if(total < 3)
|
| | | return false;
|
| | | got = CopyRates(m_symbol, m_tf, 1, total - 1, r);
|
| | | }
|
| | | else
|
| | | {
|
| | | const int shift = iBarShift(m_symbol, m_tf, m_t[m_n - 1], true);
|
| | | if(shift < 0)
|
| | | return false; // our last bar vanished from history - wait
|
| | | if(shift <= 1)
|
| | | return true;
|
| | | got = CopyRates(m_symbol, m_tf, 1, shift - 1, r);
|
| | | }
|
| | | if(got <= 0)
|
| | | return false;
|
| | | //--- CopyRates into a non-series array is oldest-first, the cache's own order.
|
| | | for(int i = 0; i < got; i++)
|
| | | if(m_n == 0 || r[i].time > m_t[m_n - 1])
|
| | | Append(r[i]);
|
| | | return (m_n > 0 && m_t[m_n - 1] == newestClosed);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-30 18:36:33 -04:00 | | | bool CBarCache::ClosesBack(const int i, const int n, double &out[]) const
|
| | | {
|
| | | if(n < 1 || i < n - 1 || i >= m_n)
|
| | | return false;
|
| | | ArrayResize(out, n);
|
| | | for(int k = 0; k < n; k++)
|
| | | out[k] = m_c[i - k];
|
| | | return true;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | int CBarCache::IndexOf(const datetime t) const
|
| | | {
|
| | | int lo = 0, hi = m_n - 1;
|
| | | while(lo <= hi)
|
| | | {
|
| | | const int mid = (lo + hi) / 2;
|
| | | if(m_t[mid] == t)
|
| | | return mid;
|
| | | if(m_t[mid] < t)
|
| | | lo = mid + 1;
|
| | | else
|
| | | hi = mid - 1;
|
| | | }
|
| | | return -1;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | bool CBarCache::MeanStd(const int i, const int period, double &mean, double &sd) const
|
| | | {
|
| | | if(i < period - 1 || i >= m_n)
|
| | | return false;
|
| | | double sum = 0.0, sum2 = 0.0;
|
| | | for(int j = i - period + 1; j <= i; j++)
|
| | | {
|
| | | sum += m_c[j];
|
| | | sum2 += m_c[j] * m_c[j];
|
| | | }
|
| | | mean = sum / period;
|
| | | sd = MathSqrt(MathMax(sum2 / period - mean * mean, 0.0));
|
| | | return true;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | double CBarCache::VolPercentile(const int i, const int warm) const
|
| | | {
|
| | | if(i < warm || i >= m_n || m_sig[i] < 0.0)
|
| | | return BARCACHE_NA;
|
| | | int cnt = 0, less = 0;
|
| | | for(int k = m_volWindow - 1; k < i; k++)
|
| | | {
|
| | | if(m_sig[k] < 0.0)
|
| | | continue;
|
| | | cnt++;
|
| | | if(m_sig[k] < m_sig[i])
|
| | | less++;
|
| | | }
|
| | | if(cnt <= 50)
|
| | | return BARCACHE_NA;
|
| | | return (double)less / cnt;
|
| | | }
|
| | | #endif // WARRIOR_BARCACHE_MQH
|