Warrior_EA/System/BarCache.mqh

248 lines
9.4 KiB
MQL5

//+------------------------------------------------------------------+
//| 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