Warrior_EA/Signals/Wyckoff/WyckoffFeed.mqh
AnimateDread 47a5ef338b Refactor Warrior EA: Integrate custom signal modules, enhance voting mechanism, and improve management features
- Replaced standard library signal modules with custom implementations to allow for named patterns and improved voting.
- Added new input parameters for module weights, allowing for optimization of individual signal contributions.
- Enhanced the management of trades with new options for breakeven and management cut.
- Introduced a mechanism for dynamic ranking of signal weights based on historical performance.
- Improved initialization logic to ensure proper registration of filters and handling of trading conditions.
- Added detailed logging for trading permissions and account status during initialization.
2026-09-13 14:32:40 -04:00

342 lines
19 KiB
MQL5

//+------------------------------------------------------------------+
//| WyckoffFeed.mqh |
//| AnimateDread |
//| |
//| THE ONE PLACE THE ADWyckoff INDICATOR API LIVES. |
//| |
//| Three custom indicators, three separate handles, one accessor. |
//| Every iCustom parameter list, every buffer index and every |
//| sentinel is stated here and nowhere else, because the indicators |
//| are under active revision and their parameter counts have already |
//| changed once (v4.x -> v5.5) while the callers went on passing the |
//| old list. A wrong parameter ORDER is silent: MQL5 fills the |
//| missing ones with defaults and the indicator computes something |
//| plausible from the wrong numbers. |
//| |
//| VERSIONED BY BUFFER COUNT, checked at Create(). If an indicator |
//| comes back with a different number of buffers than this file was |
//| written against, the feed refuses to load and says so - it does |
//| NOT read whatever is there. A shifted buffer map reads a real |
//| number from the wrong column, which is the failure that cannot be |
//| seen in a result. Refusing is recoverable; guessing is not. |
//| |
//| CAUSALITY. The indicators zero the forming bar and only advance |
//| their state machine on closed bars (ADWyckoffEventStream.mq5:22, |
//| and the write loop stamps bar si with the event detected AT si - |
//| nothing is back-filled to the pivot that started it). So shift 1 |
//| is the newest safely readable bar and every accessor here |
//| defaults to it. Shift 0 is always zero by construction. |
//+------------------------------------------------------------------+
#ifndef WARRIOR_WYCKOFFFEED_MQH
#define WARRIOR_WYCKOFFFEED_MQH
#include <Indicators\Custom.mqh>
//--- Indicator file names, relative to MQL5\Indicators
#define WYK_IND_EVENTS "ADWyckoffEventStream"
#define WYK_IND_FAILURE "ADWyckoffFailedStructure"
#define WYK_IND_INVERSION "ADWyckoffSignificantBarInversion"
//--- The buffer counts this file was written against. See the version note above.
#define WYK_EVENTS_BUFFERS 26 // ADWyckoffEventStream v5.5
#define WYK_FAILURE_BUFFERS 5 // ADWyckoffFailedStructure v2.00
#define WYK_INVERSION_BUFFERS 5 // ADWyckoffSignificantBarInversion v2.00
//--- EventStream buffer map (SetIndexBuffer, ADWyckoffEventStream.mq5:1734-1746)
#define WYK_B_EVENT 0 // sparse, -9..+9, 0 = no event
#define WYK_B_PHASE 1 // persistent, phase * sign(direction), +-1..+-5 = A..E
#define WYK_B_ZONE_TOP 2 // continuous while a range is open, 0 once it closes
#define WYK_B_ZONE_BOTTOM 3
#define WYK_B_EVENT_PRICE 4 // close on an event bar, else 0
#define WYK_B_STRUCT_PHASE 5 // sparse, event-derived phase
#define WYK_B_CHOCH_TR 6 // trend -> range change of character
#define WYK_B_CHOCH_RT 7 // range -> trend
#define WYK_B_SLOPE_A_BULL 8 // context flags, 0/1, mutually exclusive
#define WYK_B_SLOPE_A_BEAR 9
#define WYK_B_SLOPE_D_BULL 10
#define WYK_B_SLOPE_D_BEAR 11
#define WYK_B_REACC 12 // 0/1, only once resolved with the trend
#define WYK_B_REDIS 13
#define WYK_B_SPRING_GRADE 14 // +-1..+-3 on the shake bar, else 0
#define WYK_B_CHARACTER 15 // +1 accumulation / -1 distribution / 0 not anchored
#define WYK_B_TEST_PLACE 16 // +1 upper half / -1 below climax / 0 neutral
#define WYK_B_AR_STRENGTH 17 // 1 when the AR reached 2x recent legs
#define WYK_B_CREEK 18 // HELD levels - these do NOT reset to 0 when the range closes
#define WYK_B_ICE 19
#define WYK_B_RANGE_MID 20
#define WYK_B_STOP_LONG 21 // Ice - tolerance
#define WYK_B_STOP_SHORT 22 // Creek + tolerance
#define WYK_B_TARGET_LONG 23 // Creek + range height
#define WYK_B_TARGET_SHORT 24 // Ice - range height
#define WYK_B_EVENT_LEVEL 25 // held close of the last event bar
//--- FailedStructure buffer map
#define WYK_F_VALUE 0 // signed quality, clamped +-2.5
#define WYK_F_BULL_FAILURE 1 // 0/1
#define WYK_F_BEAR_FAILURE 2
#define WYK_F_FAILED_ACCUM 3
#define WYK_F_FAILED_DISTRIB 4
//--- SignificantBarInversion buffer map
#define WYK_I_QUALITY 0 // signed, +bull / -bear, +-0..2, 0 when not significant
#define WYK_I_BULL_SIG 1 // 0/1
#define WYK_I_BEAR_SIG 2
#define WYK_I_BULL_FLIP 3
#define WYK_I_BEAR_FLIP 4
//--- EVENT VOCABULARY. Defined in the indicator only as switch cases in EvName()
//--- (ADWyckoffEventStream.mq5:435-445) - there is no enum to include, so it is mirrored here.
//--- Sign is the structure's direction: + bullish, - bearish.
#define WYK_EV_NONE 0
#define WYK_EV_PS 1 // preliminary support / PSY preliminary supply
#define WYK_EV_SC 2 // selling climax / BC buying climax
#define WYK_EV_AR 3 // automatic rally / automatic reaction
#define WYK_EV_ST 4 // secondary test
#define WYK_EV_SPRING 5 // spring / UTAD upthrust after distribution
#define WYK_EV_LPS 6 // last point of support / LPSY last point of supply
#define WYK_EV_SOS 7 // sign of strength / SOW sign of weakness
#define WYK_EV_TEST 8 // test
#define WYK_EV_BU 9 // back-up / BD back-down
//--- Phases carried on WYK_B_PHASE, signed by direction: 1=A 2=B 3=C 4=D 5=E
#define WYK_PHASE_A 1
#define WYK_PHASE_B 2
#define WYK_PHASE_C 3
#define WYK_PHASE_D 4
#define WYK_PHASE_E 5
//+------------------------------------------------------------------+
//| CWyckoffFeed |
//+------------------------------------------------------------------+
class CWyckoffFeed
{
protected:
CiCustom m_events;
CiCustom m_failure;
CiCustom m_inversion;
bool m_ok;
string m_why;
string m_symbolName;
ENUM_TIMEFRAMES m_periodValue;
string m_createdFor; // "symbol/period" this feed was built for, "" if never built
bool Build(CiCustom &ind, CIndicators *owner, const string name,
const int wantBuffers, MqlParam &params[]);
public:
CWyckoffFeed(void) : m_ok(false), m_why("not created"), m_createdFor("") {}
~CWyckoffFeed(void) {}
//--- Creates all three handles against `symbol`/`period` and registers them with `owner`.
//--- Returns false and leaves Why() set if any one of them fails or reports the wrong width.
bool Create(CIndicators *owner, const string symbol, const ENUM_TIMEFRAMES period,
const int lookback = 50, const int zigzag = 3, const int arLegs = 3,
const double boundaryTol = 0.50, const int maxRangeBars = 200);
bool Ok(void) const { return m_ok; }
string Why(void) const { return m_why; }
string CreatedFor(void) const { return m_createdFor; }
//--- Grow all three handles past the stdlib's 1024-bar buffer, for a caller that walks deep
//--- history (the neural module's training sweep). Same contract as CWarriorSignal::
//--- DeepenPrices(): grows only, and `size` must be bars that already exist or CSeries prints
//--- a failure line. A feed left at its default still ANSWERS past shift 1023 - with 0.0 - and
//--- zero is a legitimate "no structure here" for every one of these reads, so the shortfall
//--- is indistinguishable from data unless the buffer is grown to match the caller's reach.
bool Deepen(const int size);
//--- Raw reads. Shift 1 = the newest CLOSED bar and the newest that carries anything.
double Event(const int shift = 1) { return m_events.GetData(WYK_B_EVENT, shift); }
double Phase(const int shift = 1) { return m_events.GetData(WYK_B_PHASE, shift); }
double SpringGrade(const int shift = 1){ return m_events.GetData(WYK_B_SPRING_GRADE, shift);}
double Character(const int shift = 1) { return m_events.GetData(WYK_B_CHARACTER, shift); }
double TestPlace(const int shift = 1) { return m_events.GetData(WYK_B_TEST_PLACE, shift); }
double ARStrength(const int shift = 1) { return m_events.GetData(WYK_B_AR_STRENGTH, shift);}
double Reaccumulation(const int shift = 1) { return m_events.GetData(WYK_B_REACC, shift); }
double Redistribution(const int shift = 1) { return m_events.GetData(WYK_B_REDIS, shift); }
double ChochTrendToRange(const int shift = 1) { return m_events.GetData(WYK_B_CHOCH_TR, shift); }
double ChochRangeToTrend(const int shift = 1) { return m_events.GetData(WYK_B_CHOCH_RT, shift); }
//--- The four slope-context flags. Mutually exclusive by construction in the indicator
//--- (ADWyckoffEventStream.mq5:1860-1863 picks exactly one branch), so at most one is ever 1.
double SlopeAccumBull(const int shift = 1) { return m_events.GetData(WYK_B_SLOPE_A_BULL, shift); }
double SlopeAccumBear(const int shift = 1) { return m_events.GetData(WYK_B_SLOPE_A_BEAR, shift); }
double SlopeDistribBull(const int shift = 1){ return m_events.GetData(WYK_B_SLOPE_D_BULL, shift); }
double SlopeDistribBear(const int shift = 1){ return m_events.GetData(WYK_B_SLOPE_D_BEAR, shift); }
double ZoneTop(const int shift = 1) { return m_events.GetData(WYK_B_ZONE_TOP, shift); }
double ZoneBottom(const int shift = 1) { return m_events.GetData(WYK_B_ZONE_BOTTOM, shift);}
//--- HELD price levels. These persist after the range closes, so a non-zero value is NOT proof
//--- that a range is currently open - test RangeOpen() when that matters.
double Creek(const int shift = 1) { return m_events.GetData(WYK_B_CREEK, shift); }
double Ice(const int shift = 1) { return m_events.GetData(WYK_B_ICE, shift); }
double RangeMid(const int shift = 1) { return m_events.GetData(WYK_B_RANGE_MID, shift); }
double StopLong(const int shift = 1) { return m_events.GetData(WYK_B_STOP_LONG, shift); }
double StopShort(const int shift = 1) { return m_events.GetData(WYK_B_STOP_SHORT, shift); }
double TargetLong(const int shift = 1) { return m_events.GetData(WYK_B_TARGET_LONG, shift);}
double TargetShort(const int shift = 1){ return m_events.GetData(WYK_B_TARGET_SHORT, shift);}
double FailValue(const int shift = 1) { return m_failure.GetData(WYK_F_VALUE, shift); }
double BullFailure(const int shift = 1) { return m_failure.GetData(WYK_F_BULL_FAILURE, shift); }
double BearFailure(const int shift = 1) { return m_failure.GetData(WYK_F_BEAR_FAILURE, shift); }
double FailedAccum(const int shift = 1) { return m_failure.GetData(WYK_F_FAILED_ACCUM, shift); }
double FailedDistrib(const int shift = 1){ return m_failure.GetData(WYK_F_FAILED_DISTRIB, shift); }
double BarQuality(const int shift = 1) { return m_inversion.GetData(WYK_I_QUALITY, shift); }
double BullBar(const int shift = 1) { return m_inversion.GetData(WYK_I_BULL_SIG, shift); }
double BearBar(const int shift = 1) { return m_inversion.GetData(WYK_I_BEAR_SIG, shift); }
double BullFlip(const int shift = 1) { return m_inversion.GetData(WYK_I_BULL_FLIP, shift);}
double BearFlip(const int shift = 1) { return m_inversion.GetData(WYK_I_BEAR_FLIP, shift);}
//--- Derived, so that no caller has to re-derive them differently.
//--- The event CODE without its direction, and the direction on its own.
int EventCode(const int shift = 1) { return (int)MathAbs(Event(shift)); }
int EventDir(const int shift = 1)
{
const double e = Event(shift);
return (e > 0.0) ? 1 : ((e < 0.0) ? -1 : 0);
}
//--- A range is OPEN only while both boundaries are being published; they zero when it closes.
bool RangeOpen(const int shift = 1)
{
const double t = ZoneTop(shift), b = ZoneBottom(shift);
return (t > 0.0 && b > 0.0 && t > b);
}
//--- Phase letter 1..5 and its direction, split out of the signed buffer.
int PhaseLetter(const int shift = 1) { return (int)MathAbs(Phase(shift)); }
int PhaseDir(const int shift = 1)
{
const double p = Phase(shift);
return (p > 0.0) ? 1 : ((p < 0.0) ? -1 : 0);
}
};
//+------------------------------------------------------------------+
bool CWyckoffFeed::Build(CiCustom &ind, CIndicators *owner, const string name,
const int wantBuffers, MqlParam &params[])
{
if(owner == NULL)
{
m_why = "no indicator collection";
return false;
}
if(!ind.NumBuffers(wantBuffers))
{
m_why = name + ": NumBuffers(" + IntegerToString(wantBuffers) + ") refused";
return false;
}
if(!owner.Add(GetPointer(ind)))
{
m_why = name + ": could not be added to the indicator collection";
return false;
}
if(!ind.Create(m_symbolName, m_periodValue, IND_CUSTOM, ArraySize(params), params))
{
m_why = name + ": Create() failed - is the indicator compiled and present in MQL5\\Indicators?";
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Create all three handles. |
//| |
//| The parameter lists are the indicators' OWN input order, checked |
//| against source on 2026-09-10: |
//| EventStream (9): Lookback, ZigZag, ARLegs, BoundaryTol, |
//| MaxRangeBars, ShowLabels, ShowZones, |
//| ShowPhases, ShowPanel |
//| Failure (3): LookbackPeriod, ZigZagStrength, ShowMarkers |
//| Inversion (2): Lookback, ShowMarkers |
//| |
//| Every display flag is passed FALSE. The EA's handle exists to be |
//| read, not drawn; an indicator that also renders labels, zones, |
//| phase bands and a panel on every tester bar costs real time and |
//| draws objects nobody looks at. The operator's own chart copy is |
//| a separate handle with its own inputs and is unaffected. |
//+------------------------------------------------------------------+
bool CWyckoffFeed::Create(CIndicators *owner, const string symbol, const ENUM_TIMEFRAMES period,
const int lookback, const int zigzag, const int arLegs,
const double boundaryTol, const int maxRangeBars)
{
m_ok = false;
m_symbolName = symbol;
m_periodValue = period;
MqlParam ev[10];
ev[0].type = TYPE_STRING; ev[0].string_value = WYK_IND_EVENTS;
ev[1].type = TYPE_INT; ev[1].integer_value = lookback;
ev[2].type = TYPE_INT; ev[2].integer_value = zigzag;
ev[3].type = TYPE_INT; ev[3].integer_value = arLegs;
ev[4].type = TYPE_DOUBLE; ev[4].double_value = boundaryTol;
ev[5].type = TYPE_INT; ev[5].integer_value = maxRangeBars;
ev[6].type = TYPE_BOOL; ev[6].integer_value = 0; // ShowLabels
ev[7].type = TYPE_BOOL; ev[7].integer_value = 0; // ShowZones
ev[8].type = TYPE_BOOL; ev[8].integer_value = 0; // ShowPhases
ev[9].type = TYPE_BOOL; ev[9].integer_value = 0; // ShowPanel
if(!Build(m_events, owner, WYK_IND_EVENTS, WYK_EVENTS_BUFFERS, ev))
return false;
MqlParam fs[4];
fs[0].type = TYPE_STRING; fs[0].string_value = WYK_IND_FAILURE;
fs[1].type = TYPE_INT; fs[1].integer_value = lookback;
fs[2].type = TYPE_INT; fs[2].integer_value = zigzag;
fs[3].type = TYPE_BOOL; fs[3].integer_value = 0; // ShowMarkers
if(!Build(m_failure, owner, WYK_IND_FAILURE, WYK_FAILURE_BUFFERS, fs))
return false;
MqlParam sb[3];
sb[0].type = TYPE_STRING; sb[0].string_value = WYK_IND_INVERSION;
sb[1].type = TYPE_INT; sb[1].integer_value = lookback;
sb[2].type = TYPE_BOOL; sb[2].integer_value = 0; // ShowMarkers
if(!Build(m_inversion, owner, WYK_IND_INVERSION, WYK_INVERSION_BUFFERS, sb))
return false;
m_ok = true;
m_why = "ok";
m_createdFor = symbol + "/" + IntegerToString((int)period);
PrintFormat("CWyckoffFeed: %s %s - EventStream(%d buf) FailedStructure(%d buf)"
" SignificantBarInversion(%d buf) | lookback %d zigzag %d arLegs %d tol %.2f"
" maxRange %d | reads shift 1 (newest closed bar)",
symbol, EnumToString(period), WYK_EVENTS_BUFFERS, WYK_FAILURE_BUFFERS,
WYK_INVERSION_BUFFERS, lookback, zigzag, arLegs, boundaryTol, maxRangeBars);
return true;
}
//+------------------------------------------------------------------+
bool CWyckoffFeed::Deepen(const int size)
{
if(!m_ok || size <= 0)
return false;
bool ok = true;
if(size > m_events.BufferSize())
ok = m_events.BufferResize(size) && ok;
if(size > m_failure.BufferSize())
ok = m_failure.BufferResize(size) && ok;
if(size > m_inversion.BufferSize())
ok = m_inversion.BufferResize(size) && ok;
return ok;
}
//+------------------------------------------------------------------+
//| ONE FEED PER CHART, SHARED BY EVERY WYCKOFF MODULE. |
//| |
//| Three modules read these indicators. Each building its own feed |
//| would issue three identical iCustom requests - MT5 de-duplicates |
//| handles with identical parameters, so it would work, but it also |
//| prints the load line three times and makes "how many handles am I |
//| holding" unanswerable from the log. More importantly it invites |
//| the modules to drift apart: two feeds created with different |
//| lookbacks are two different opinions wearing one name. |
//| |
//| Global, like g_metaLabel, and guarded by the symbol/period it was |
//| built for so a chart change rebuilds it rather than silently |
//| serving another instrument's numbers. |
//+------------------------------------------------------------------+
CWyckoffFeed g_wyckoffFeed;
bool WyckoffFeedEnsure(CIndicators *owner, const string symbol, const ENUM_TIMEFRAMES period,
const int lookback = 50, const int zigzag = 3, const int arLegs = 3,
const double boundaryTol = 0.50, const int maxRangeBars = 200)
{
const string want = symbol + "/" + IntegerToString((int)period);
if(g_wyckoffFeed.Ok() && g_wyckoffFeed.CreatedFor() == want)
return true;
return g_wyckoffFeed.Create(owner, symbol, period, lookback, zigzag, arLegs,
boundaryTol, maxRangeBars);
}
#endif // WARRIOR_WYCKOFFFEED_MQH