forked from animatedread/Warrior_EA
- 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.
150 lines
7.1 KiB
MQL5
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
|