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