//+------------------------------------------------------------------+ //| ModalityWyckoff.mqh | //| AnimateDread | //| | //| THE STRUCTURE READ - what phase the campaign is in, what event | //| spoke last, whether a spring or upthrust has just shaken the | //| range, whether the structure has failed. | //| | //| Two classes, two jobs: | //| CWyckoffReader the RULES a Wyckoff trader applies to that | //| reading, as a pure function of the context. | //| Changing a rule touches only this class. | //| CModalityWyckoff fetches the reading from the shared feed | //| (Signals\Wyckoff\WyckoffFeed.mqh) and stamps | //| the reader's verdict onto the context. | //| | //| WHAT THE VERDICT IS AND IS NOT. The weights below are AUTHORED | //| priors taken from the two Wyckoff books, not measurements. They | //| are never used to veto a trade on their own: the verdict is one | //| bucket dimension in the journal review, so the trades decide | //| whether "against the structure" is a worse place to buy a dip. | //| The seven-style scan found no bar-geometry edge beyond the dip | //| itself, so this is expected to be weak until the journal says | //| otherwise. | //| | //| THE FEED MAY BE ABSENT. The three indicators must be compiled in | //| MQL5\Indicators. If Create() fails the modality reports it once | //| and answers false forever; CTX_WYK_OK stays NA and the reviewer | //| treats the dimension as neutral. | //+------------------------------------------------------------------+ #ifndef WARRIOR_MODALITYWYCKOFF_MQH #define WARRIOR_MODALITYWYCKOFF_MQH #include "Modality.mqh" #include "..\Signals\Wyckoff\WyckoffFeed.mqh" //+------------------------------------------------------------------+ //| The rules. Long-side: +1 = the structure supports buying here, | //| -1 = it argues the dip is the start of markdown, not a shake-out. | //+------------------------------------------------------------------+ class CWyckoffReader { public: static double Bias(const SMarketContext &c) { if(!c.Has(CTX_WYK_OK) || c.v[CTX_WYK_OK] < 0.5) return CTX_NA; double s = 0.0; //--- What the campaign IS: accumulation supports a buy, distribution argues against it. if(c.Has(CTX_WYK_CHAR)) s += 0.35 * c.v[CTX_WYK_CHAR]; //--- Where it is. Bull C/D/E (spring done, markup starting) is where dips get bought; //--- bear D/E is the markdown a "dip" belongs to. if(c.Has(CTX_WYK_PHASE)) { const double p = c.v[CTX_WYK_PHASE]; if(p >= 3.0) s += 0.20; else if(p <= -4.0) s -= 0.40; else if(p <= -3.0) s -= 0.20; } //--- The shake-out itself: a spring under support is the textbook long; an upthrust is not. if(c.Has(CTX_WYK_SPRING) && c.v[CTX_WYK_SPRING] != 0.0) s += 0.30 * MathMax(-1.0, MathMin(1.0, c.v[CTX_WYK_SPRING] / 3.0)); //--- The most recent event, if it is fresh: SOS/LPS confirm, SOW/LPSY warn. if(c.Has(CTX_WYK_EVENT) && c.Has(CTX_WYK_AGE) && c.v[CTX_WYK_AGE] <= 10.0) { const int code = (int)MathAbs(c.v[CTX_WYK_EVENT]); const double dir = (c.v[CTX_WYK_EVENT] >= 0.0) ? 1.0 : -1.0; if(code == WYK_EV_SOS) s += 0.20 * dir; else if(code == WYK_EV_LPS) s += 0.10 * dir; else if(code == WYK_EV_SC || code == WYK_EV_ST) s += 0.05 * dir; } //--- A structure that has failed is the strongest thing a trader reads, either way. if(c.Has(CTX_WYK_FAIL)) s += 0.15 * MathMax(-2.5, MathMin(2.5, c.v[CTX_WYK_FAIL])); return MathMax(-1.0, MathMin(1.0, s)); } }; //+------------------------------------------------------------------+ class CModalityWyckoff : public CModality { protected: CIndicators m_owner; // refreshed here; the shared feed may have another owner too bool m_reported; string m_symbol; ENUM_TIMEFRAMES m_tf; int m_lookback; // bars scanned for the freshest event / spring public: CModalityWyckoff(const string symbol, const ENUM_TIMEFRAMES tf, const int lookback = 10) : m_reported(false), m_symbol(symbol), m_tf(tf), m_lookback(lookback) { //--- The handles are members of the global feed, not heap objects: a collection that frees //--- its elements on destruction would delete them. m_owner.FreeMode(false); //--- Created HERE, at init, not at the first read: a handle made mid-run has no calculated //--- buffers yet, so the first trade would be booked with a Wyckoff reading of zeros. if(!WyckoffFeedEnsure(GetPointer(m_owner), m_symbol, m_tf)) { m_reported = true; PrintFormat("CModalityWyckoff: feed unavailable (%s) - the Wyckoff dimension will read" " as neutral.", g_wyckoffFeed.Why()); } } virtual string Name(void) const override { return "wyckoff"; } virtual bool Read(const SBarRef &bar, SMarketContext &ctx) override; }; //+------------------------------------------------------------------+ bool CModalityWyckoff::Read(const SBarRef &bar, SMarketContext &ctx) { if(!g_wyckoffFeed.Ok()) return false; m_owner.Refresh(); const int s = bar.shift; ctx.v[CTX_WYK_OK] = 1.0; const double phase = g_wyckoffFeed.Phase(s); ctx.v[CTX_WYK_PHASE] = phase; ctx.v[CTX_WYK_CHAR] = g_wyckoffFeed.Character(s); ctx.v[CTX_WYK_FAIL] = g_wyckoffFeed.FailValue(s); //--- The freshest event and the strongest spring/upthrust inside the look-back window. double ev = 0.0, age = CTX_NA, spring = 0.0; for(int k = 0; k < m_lookback; k++) { const double e = g_wyckoffFeed.Event(s + k); if(ev == 0.0 && e != 0.0) { ev = e; age = k; } const double sg = g_wyckoffFeed.SpringGrade(s + k); if(MathAbs(sg) > MathAbs(spring)) spring = sg; } ctx.v[CTX_WYK_EVENT] = ev; ctx.v[CTX_WYK_AGE] = age; ctx.v[CTX_WYK_SPRING] = spring; if(g_wyckoffFeed.RangeOpen(s)) { const double top = g_wyckoffFeed.ZoneTop(s), bot = g_wyckoffFeed.ZoneBottom(s); const double c = iClose(m_symbol, m_tf, s); ctx.v[CTX_WYK_RANGEPOS] = MathMax(0.0, MathMin(1.0, (c - bot) / (top - bot))); } ctx.v[CTX_WYK_BIAS] = CWyckoffReader::Bias(ctx); return true; } #endif // WARRIOR_MODALITYWYCKOFF_MQH