Warrior_EA/Mind/ModalityWyckoff.mqh
AnimateDread e9c562b39f Add Feature Scaling and Regime Math Classes; Implement Mind Trading Logic
- Introduced `FeatureScale.mqh` with `FeatSquash` function for stateless feature scaling.
- Added `RegimeMath.mqh` class for regime arithmetic, including efficiency and variance calculations.
- Documented the Mind trading logic in `MIND.md`, detailing the trading process and modes.
- Created `VOLNORM_PLAN.md` and `VOLNORM_RESULTS.md` for tick-volume normalization testing.
- Implemented `read_book.py` for analyzing trade book data and correlations.
- Developed `volnorm.py` for testing tick-volume normalization with new and old methods.
2026-09-30 18:36:33 -04:00

150 lines
7.1 KiB
MQL5

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