//+------------------------------------------------------------------+ //| 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[]; double m_o[], m_h[], m_l[], m_c[], m_tr[], m_atr[], m_gk[], m_sig[]; 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]; } double Close(const int i) const { return m_c[i]; } double Atr(const int i) const { return m_atr[i]; } double Sigma(const int i) const { return m_sig[i]; } //--- 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); } //+------------------------------------------------------------------+ //| 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; //--- 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); } //+------------------------------------------------------------------+ 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