//+------------------------------------------------------------------+ //| ModalityBreadth.mqh | //| AnimateDread | //| | //| WHAT THE NEIGHBOURING MARKETS ARE DOING at the same bar. | //| | //| The dip-buy's losses are bear onsets, and bear onsets show up in | //| every index at once. A dip that only this index is having is a | //| local shake-out; a dip all four are having is a market-wide sell- | //| off, which is a different thing to buy. BREADTH is the fraction of | //| the peers (this symbol excluded) at z(20) <= -1 on the same bar. | //| | //| Peers are a comma list; ones the broker does not have are skipped. | //| A peer without history for this bar simply does not answer, and | //| BREADTH_N says how many did - so a reading from one peer is not | //| mistaken for one from three. | //+------------------------------------------------------------------+ #ifndef WARRIOR_MODALITYBREADTH_MQH #define WARRIOR_MODALITYBREADTH_MQH #include "Modality.mqh" class CModalityBreadth : public CModality { protected: string m_self; ENUM_TIMEFRAMES m_tf; string m_peers[]; int m_period; // z-score window double m_dipZ; // a peer at or below this z counts as dipping bool PeerZ(const string peer, const datetime when, double &z) const; public: CModalityBreadth(const string self, const ENUM_TIMEFRAMES tf, const string peerList, const int period = 20, const double dipZ = -1.0) : m_self(self), m_tf(tf), m_period(period), m_dipZ(dipZ) { string all[]; const int n = StringSplit(peerList, ',', all); for(int i = 0; i < n; i++) { StringTrimLeft(all[i]); StringTrimRight(all[i]); if(all[i] == "" || all[i] == m_self) continue; if(!SymbolSelect(all[i], true)) continue; const int k = ArraySize(m_peers); ArrayResize(m_peers, k + 1); m_peers[k] = all[i]; } } int PeerCount(void) const { return ArraySize(m_peers); } virtual string Name(void) const override { return "breadth"; } virtual bool Read(const SBarRef &bar, SMarketContext &ctx) override; }; //+------------------------------------------------------------------+ bool CModalityBreadth::PeerZ(const string peer, const datetime when, double &z) const { const int shift = iBarShift(peer, m_tf, when, false); if(shift < 0) return false; double c[]; if(CopyClose(peer, m_tf, shift, m_period, c) != m_period) return false; double s = 0.0, s2 = 0.0; for(int i = 0; i < m_period; i++) { s += c[i]; s2 += c[i] * c[i]; } const double mean = s / m_period, var = s2 / m_period - mean * mean; if(var <= 0.0) return false; z = (c[m_period - 1] - mean) / MathSqrt(var); // oldest first: the last element is `shift` return true; } //+------------------------------------------------------------------+ bool CModalityBreadth::Read(const SBarRef &bar, SMarketContext &ctx) { int answered = 0, dipping = 0; for(int i = 0; i < ArraySize(m_peers); i++) { double z; if(!PeerZ(m_peers[i], bar.time, z)) continue; answered++; if(z <= m_dipZ) dipping++; } ctx.v[CTX_BREADTH_N] = answered; if(answered == 0) return false; ctx.v[CTX_BREADTH] = (double)dipping / answered; return true; } #endif // WARRIOR_MODALITYBREADTH_MQH