Article-22063-Alternative-B.../AlternativeBars/CStandardBars.mqh

228 lines
7 KiB
MQL5
Raw Permalink Normal View History

2026-06-09 22:26:00 +03:00
//+------------------------------------------------------------------+
//| 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 <AlternativeBars\CBarConstructor.mqh>
//+------------------------------------------------------------------+
//| 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__