| //+------------------------------------------------------------------+
|
| //| 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[], m_v[];
|
|
|
| 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 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]; }
|
| double Close(const int i) const { return m_c[i]; }
|
| //--- 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]; }
|
| double Atr(const int i) const { return m_atr[i]; }
|
| double Sigma(const int i) const { return m_sig[i]; }
|
| //--- 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;
|
| //--- 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);
|
| ArrayResize(m_v, 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;
|
| m_v[k] = (double)b.tick_volume;
|
| //--- 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);
|
| }
|
| //+------------------------------------------------------------------+
|
| 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;
|
| }
|
| //+------------------------------------------------------------------+
|
| 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
|