2026-09-13 14:32:40 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| WarriorExpert.mqh |
|
| | | //| AnimateDread |
|
| | | //| |
|
| | | //| CExpert WITH A SESSION-AWARE BAR CLOCK. One override, Refresh(). |
|
| | | //| |
|
| | | //| WHAT THE STDLIB DOES. With EveryTick = false, CExpert::Refresh() |
|
| | | //| lets Processing() run only on ticks TimeframesFlags() marks as a |
|
| | | //| new bar of the chart period, and for D1 that is the first tick of |
|
| | | //| each calendar day - whatever hour it arrives at. The tick is then |
|
| | | //| CONSUMED: m_last_tick_time moves to it, and every later tick that |
|
| | | //| day compares against it and reads as "same day, no new bar". |
|
| | | //| |
|
| | | //| WHY THAT FAILS ON A CFD. An index CFD quotes a tick at 00:00 (or |
|
| | | //| 01:00 across the DST seam) while its trading session is still |
|
| | | //| shut, so the one evaluation the day gets lands on a tick the |
|
| | | //| broker will not fill. Measured on SP500 D1 2012-2026: 1,647 of |
|
| | | //| 3,887 daily entries declined at the gate - "SP500 has no open |
|
| | | //| trading session at 2012.01.04 00:00" - and every exit attempted at |
|
| | | //| 01:00 failed with [Market closed] and was retried a day later. |
|
| | | //| NAS100 lost 189 the same way. The signal was right; the clock was |
|
| | | //| reading it during the maintenance hour. |
|
| | | //| |
|
| | | //| THE FIX. A new-bar tick that arrives while the session is shut is |
|
| | | //| NOT consumed: m_last_tick_time stays where it was, so the next |
|
| | | //| tick still compares as a new bar, and the first tick INSIDE a |
|
| | | //| session is the one that gets processed. The signals read bar 1 - |
|
| | | //| the closed bar - through StartIndex() either way, so what is |
|
| | | //| evaluated is identical; only the minute it is acted on moves, |
|
| | | //| from the maintenance hour to the open. Every-tick mode is left |
|
| | | //| exactly as the stdlib has it. |
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | //| |
|
| | | //| THE ACCOUNT GUARD (System\AccountGuard.mqh), optional. When one |
|
| | | //| is attached, every tick first updates the kill switch and, while |
|
| | | //| the account or this symbol must be flat (kill switch, Friday |
|
| | | //| window), closes this chart's position and refuses entries. Every |
|
| | | //| open goes through the cross-chart lock and the open-risk cap. |
|
| | | //| |
|
| | | //| A BAR IS NOT CONSUMED BY A FAILED OPEN. With EveryTick = false |
|
| | | //| the stdlib processes a bar once; an entry refused for a TRANSIENT |
|
| | | //| reason (another chart holds the lock, requote, market closed at |
|
| | | //| the server) would be lost for good. Here the bar stays "new" and |
|
| | | //| is processed again on the next tick - the reference EA's retry. |
|
2026-09-13 14:32:40 -04:00 | | | //+------------------------------------------------------------------+
|
| | | #ifndef WARRIOR_EXPERT_MQH
|
| | | #define WARRIOR_EXPERT_MQH
|
| | |
|
| | | #include <Expert\Expert.mqh>
|
| | | #include "..\System\TradeChecks.mqh"
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | #include "..\System\AccountGuard.mqh"
|
2026-09-13 14:32:40 -04:00 | | |
|
| | | class CWarriorExpert : public CExpert
|
| | | {
|
| | | protected:
|
| | | int m_deferred; // new-bar ticks held back because the session was shut
|
| | | int m_deferredBars; // ...and how many distinct bars that was
|
| | | datetime m_deferredBar; // the bar being held back, so it is counted once
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | CAccountGuard *m_guard; // not owned; NULL = no account-level rules
|
| | | MqlDateTime m_prevTick; // m_last_tick_time before this bar was taken, for a retry
|
| | | bool m_retry; // an open failed transiently: process this bar again
|
| | | int m_retries; // how often that happened, reported at deinit
|
| | | int m_flattened; // positions closed by the guard (kill switch / Friday)
|
| | |
|
| | | bool Open(const bool isLong, double price, double sl, double tp);
|
| | | void FlattenMine(void);
|
2026-09-13 14:32:40 -04:00 | | |
|
| | | public:
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | CWarriorExpert(void) : m_deferred(0), m_deferredBars(0), m_deferredBar(0),
|
| | | m_guard(NULL), m_retry(false), m_retries(0),
|
| | | m_flattened(0) {}
|
2026-09-13 14:32:40 -04:00 | | | ~CWarriorExpert(void) {}
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | void Guard(CAccountGuard *guard) { m_guard = guard; }
|
| | | virtual void OnTick(void) override;
|
2026-09-13 14:32:40 -04:00 | | | virtual void Deinit(void) override;
|
| | |
|
| | | protected:
|
| | | virtual bool Refresh(void) override;
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | virtual bool OpenLong(double price, double sl, double tp) override
|
| | | { return Open(true, price, sl, tp); }
|
| | | virtual bool OpenShort(double price, double sl, double tp) override
|
| | | { return Open(false, price, sl, tp); }
|
2026-09-13 14:32:40 -04:00 | | | };
|
| | | //+------------------------------------------------------------------+
|
| | | bool CWarriorExpert::Refresh(void)
|
| | | {
|
| | | //--- Every-tick mode: nothing to defer, the stdlib is right as it is.
|
| | | if(m_period_flags == WRONG_VALUE || m_period_flags == 0)
|
| | | return CExpert::Refresh();
|
| | | if(!m_symbol.RefreshRates())
|
| | | return false;
|
| | | MqlDateTime time;
|
| | | TimeToStruct(m_symbol.Time(), time);
|
| | | if((m_period_flags & TimeframesFlags(time)) == 0)
|
| | | return false; // same bar as the last one processed
|
| | | //--- A new bar - but is the market open to act on it? If not, leave m_last_tick_time alone so
|
| | | //--- this bar is still "new" on the next tick, and say nothing per tick: the count is reported
|
| | | //--- once at deinit, the way the vote reports its gates.
|
| | | string why = "";
|
| | | if(!TCMarketIsOpen(m_symbol.Name(), m_symbol.Time(), why))
|
| | | {
|
| | | m_deferred++;
|
| | | const datetime bar = iTime(m_symbol.Name(), m_period, 0);
|
| | | if(bar != m_deferredBar)
|
| | | {
|
| | | m_deferredBar = bar;
|
| | | m_deferredBars++;
|
| | | }
|
| | | return false;
|
| | | }
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | m_prevTick = m_last_tick_time;
|
2026-09-13 14:32:40 -04:00 | | | m_last_tick_time = time;
|
| | | m_indicators.Refresh();
|
| | | return true;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | //| The guard runs on EVERY tick - a kill switch or a Friday close |
|
| | | //| cannot wait for the next H4 bar - and the stdlib's bar-clocked |
|
| | | //| processing after it. |
|
| | | //+------------------------------------------------------------------+
|
| | | void CWarriorExpert::OnTick(void)
|
| | | {
|
| | | if(m_guard != NULL)
|
| | | {
|
| | | m_guard.Update();
|
| | | if(m_guard.MustBeFlat())
|
| | | FlattenMine();
|
| | | }
|
| | | if(!m_on_tick_process || !Refresh())
|
| | | return;
|
| | | m_retry = false;
|
| | | Processing();
|
| | | //--- Only the bar clock can be rewound; in every-tick mode the next tick re-evaluates anyway.
|
| | | if(m_retry && m_period_flags != WRONG_VALUE && m_period_flags != 0)
|
| | | {
|
| | | m_last_tick_time = m_prevTick;
|
| | | m_retries++;
|
| | | }
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| CExpert::OpenLong/OpenShort, with the guard around the send and a |
|
| | | //| transient failure turned into a retry instead of a lost signal. |
|
| | | //| The body is the stdlib's (lot, LotCheck, send), written out so |
|
| | | //| the retcode read afterwards is this request's, never a stale one. |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CWarriorExpert::Open(const bool isLong, double price, double sl, double tp)
|
| | | {
|
| | | if(price == EMPTY_VALUE)
|
| | | return false;
|
| | | if(m_guard != NULL)
|
| | | {
|
| | | if(m_guard.MustBeFlat())
|
| | | return false;
|
| | | if(!m_guard.LockTake())
|
| | | {
|
| | | m_guard.NoteLockBusy();
|
| | | m_retry = true; // another chart is mid-open: try again next tick
|
| | | return false;
|
| | | }
|
| | | if(!m_guard.RoomForOne())
|
| | | {
|
| | | m_guard.LockDrop();
|
| | | return false; // dropped, not queued - the dip will be gone
|
| | | }
|
| | | }
|
| | | double lot = isLong ? LotOpenLong(price, sl) : LotOpenShort(price, sl);
|
| | | lot = LotCheck(lot, price, (isLong ? ORDER_TYPE_BUY : ORDER_TYPE_SELL));
|
| | | bool ok = false;
|
| | | if(lot > 0.0)
|
| | | {
|
| | | ok = isLong ? m_trade.Buy(lot, price, sl, tp) : m_trade.Sell(lot, price, sl, tp);
|
| | | const uint rc = m_trade.ResultRetcode();
|
| | | if(ok && rc != TRADE_RETCODE_DONE && rc != TRADE_RETCODE_PLACED && rc != TRADE_RETCODE_DONE_PARTIAL)
|
| | | ok = false;
|
| | | string why = "";
|
| | | if(!ok && (rc == TRADE_RETCODE_MARKET_CLOSED || rc == TRADE_RETCODE_TRADE_DISABLED ||
|
| | | TCRetcodeAdvice(rc, why) > 0))
|
| | | m_retry = true;
|
| | | }
|
| | | if(m_guard != NULL)
|
| | | m_guard.LockDrop();
|
| | | return ok;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Close THIS chart's position - every other chart closes its own. |
|
| | | //| Only inside a session: outside one the server refuses, and the |
|
| | | //| next in-session tick will do it. |
|
| | | //+------------------------------------------------------------------+
|
| | | void CWarriorExpert::FlattenMine(void)
|
| | | {
|
| | | if(!SelectPosition())
|
| | | return;
|
| | | string why = "";
|
| | | if(!TCMarketIsOpen(m_symbol.Name(), TimeCurrent(), why))
|
| | | return;
|
| | | if(m_trade.PositionClose(m_position.Ticket()))
|
| | | m_flattened++;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-13 14:32:40 -04:00 | | | void CWarriorExpert::Deinit(void)
|
| | | {
|
| | | if(m_deferredBars > 0)
|
| | | PrintFormat("CWarriorExpert: BAR CLOCK - %d bar(s) opened with the session shut and were"
|
| | | " processed at the first tick inside a session instead (%d closed ticks skipped).",
|
| | | m_deferredBars, m_deferred);
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | if(m_retries > 0)
|
| | | PrintFormat("CWarriorExpert: %d bar(s) re-processed after a transient open failure.", m_retries);
|
| | | if(m_guard != NULL)
|
| | | {
|
| | | PrintFormat("CWarriorExpert: %d position(s) closed by the account guard (kill switch / Friday).",
|
| | | m_flattened);
|
| | | m_guard.Report();
|
| | | }
|
2026-09-13 14:32:40 -04:00 | | | CExpert::Deinit();
|
| | | }
|
| | | #endif // WARRIOR_EXPERT_MQH
|