//+------------------------------------------------------------------+ //| CStandardBars.mqh | //| Patrick M. Njoroge | //| https://www.mql5.com/en/users/patricknjoroge743 | //+------------------------------------------------------------------+ //| Standard bar constructors: time, tick, volume, dollar. | //| Mirror the four non-imbalance branches of _make_bar_type_grouper| //| on the Python side. | //+------------------------------------------------------------------+ #property copyright "Patrick M. Njoroge" #property link "https://www.mql5.com/en/users/patricknjoroge743" #ifndef __CSTANDARD_BARS_MQH__ #define __CSTANDARD_BARS_MQH__ #include //+------------------------------------------------------------------+ //| CTimeBar — closes when the tick's floor(time/bar_seconds) | //| differs from the current window. Label is the right edge of the | //| window, matching Python's resample(closed='left', label='right').| //+------------------------------------------------------------------+ class CTimeBar : public CBarConstructor { private: int m_bar_seconds; datetime m_current_window_start; datetime BarFloor(const datetime t) const { return (datetime)((long)t - ((long)t % m_bar_seconds)); } public: CTimeBar(const int bar_seconds) : CBarConstructor("time") { m_bar_seconds = bar_seconds; m_current_window_start = 0; } virtual bool ProcessTick(const MqlTick &tick, const long tick_num, SBar &out_bar) override { datetime window = BarFloor(tick.time); if(!m_initialized) { SeedBar(tick, tick_num); m_current_window_start = window; return false; } if(window != m_current_window_start) { FillBar(out_bar); //--- Override bar.time with the right-edge label out_bar.time = m_current_window_start + m_bar_seconds; SeedBar(tick, tick_num); m_current_window_start = window; return true; } UpdateAccumulator(tick, tick_num); return false; } virtual bool SaveState(const int fh) override { if(!CBarConstructor::SaveState(fh)) return false; FileWriteLong(fh, (long)m_current_window_start); return true; } virtual bool LoadState(const int fh) override { if(!CBarConstructor::LoadState(fh)) return false; m_current_window_start = (datetime)FileReadLong(fh); return true; } }; //+------------------------------------------------------------------+ //| CTickBar — closes when tick count in the current bar reaches | //| bar_size. The (bar_size+1)-th tick is the first of the new bar, | //| matching Python's np.arange(len(df)) // bar_size semantics. | //+------------------------------------------------------------------+ class CTickBar : public CBarConstructor { private: int m_bar_size; public: CTickBar(const int bar_size) : CBarConstructor("tick") { m_bar_size = bar_size; } virtual bool ProcessTick(const MqlTick &tick, const long tick_num, SBar &out_bar) override { if(!m_initialized) { SeedBar(tick, tick_num); return false; } if(m_tick_volume >= m_bar_size) { FillBar(out_bar); SeedBar(tick, tick_num); return true; } UpdateAccumulator(tick, tick_num); return false; } }; //+------------------------------------------------------------------+ //| CCumSumBar — shared parent for volume and dollar bars. Maintains | //| a global cumulative metric and a derived bar_id that increments | //| when cum crosses a multiple of bar_size. The crossing tick is | //| the first of the new bar, matching Python's cumsum // bar_size. | //+------------------------------------------------------------------+ class CCumSumBar : public CBarConstructor { protected: double m_bar_size; double m_cum_global; long m_prev_bar_id; //--- Per-tick metric. Overridden by concrete derived classes. virtual double TickMetric(const MqlTick &tick) = 0; public: CCumSumBar(const string bar_type, const double bar_size) : CBarConstructor(bar_type) { m_bar_size = bar_size; m_cum_global = 0.0; m_prev_bar_id = -1; } virtual bool ProcessTick(const MqlTick &tick, const long tick_num, SBar &out_bar) override { double x = TickMetric(tick); m_cum_global += x; long bar_id = (long)MathFloor(m_cum_global / m_bar_size); if(!m_initialized) { SeedBar(tick, tick_num); m_prev_bar_id = bar_id; return false; } if(bar_id != m_prev_bar_id) { FillBar(out_bar); SeedBar(tick, tick_num); m_prev_bar_id = bar_id; return true; } UpdateAccumulator(tick, tick_num); return false; } virtual bool SaveState(const int fh) override { if(!CBarConstructor::SaveState(fh)) return false; FileWriteDouble(fh, m_cum_global); FileWriteLong(fh, m_prev_bar_id); return true; } virtual bool LoadState(const int fh) override { if(!CBarConstructor::LoadState(fh)) return false; m_cum_global = FileReadDouble(fh); m_prev_bar_id = FileReadLong(fh); return true; } }; //+------------------------------------------------------------------+ //| CVolumeBar — metric is tick.volume (tick-count proxy on FX). | //+------------------------------------------------------------------+ class CVolumeBar : public CCumSumBar { protected: virtual double TickMetric(const MqlTick &tick) override { return (double)tick.volume; } public: CVolumeBar(const double volume_per_bar) : CCumSumBar("volume", volume_per_bar) {} }; //+------------------------------------------------------------------+ //| CDollarBar — metric is tick.bid * tick.volume. Matches Python's | //| df["volume"] * df["bid"]. Any deviation breaks parity. | //+------------------------------------------------------------------+ class CDollarBar : public CCumSumBar { protected: virtual double TickMetric(const MqlTick &tick) override { return tick.bid * (double)tick.volume; } public: CDollarBar(const double dollar_per_bar) : CCumSumBar("dollar", dollar_per_bar) {} }; //+------------------------------------------------------------------+ #endif // __CSTANDARD_BARS_MQH__