//+------------------------------------------------------------------+ //| 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. | //+------------------------------------------------------------------+ #ifndef WARRIOR_EXPERT_MQH #define WARRIOR_EXPERT_MQH #include #include "..\System\TradeChecks.mqh" 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 public: CWarriorExpert(void) : m_deferred(0), m_deferredBars(0), m_deferredBar(0) {} ~CWarriorExpert(void) {} virtual void Deinit(void) override; protected: virtual bool Refresh(void) override; }; //+------------------------------------------------------------------+ 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; } m_last_tick_time = time; m_indicators.Refresh(); return true; } //+------------------------------------------------------------------+ 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); CExpert::Deinit(); } #endif // WARRIOR_EXPERT_MQH