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

204 lines
7.7 KiB
MQL5
Raw Permalink Normal View History

2026-06-09 22:26:00 +03:00
//+------------------------------------------------------------------+
//| CImbalanceBars.mqh |
//| Tick direction classifier and imbalance bar constructor. |
//| Updated from the previous article: CTickRule now exposes |
//| SetPrevPrice() and SetPrevSign() so that CRunBar::LoadState() |
//| can restore tick-rule state after an EA restart. |
//+------------------------------------------------------------------+
#include <AlternativeBars\CBarConstructor.mqh>
//+------------------------------------------------------------------+
//| Metric selector — shared by CImbalanceBar and CRunBar |
//+------------------------------------------------------------------+
enum ENUM_IMBALANCE_METRIC
{
IMBALANCE_TICK = 0,
IMBALANCE_VOLUME = 1,
IMBALANCE_DOLLAR = 2,
};
//+------------------------------------------------------------------+
//| CTickRule — tick-direction classifier |
//| |
//| Rule: +1 if price rose, -1 if price fell, carry forward if flat. |
//| Default state is +1 (matches the Python kernel's b[0] = 1.0). |
//+------------------------------------------------------------------+
class CTickRule
{
private:
double m_prev_price;
double m_prev_sign;
public:
CTickRule(void) : m_prev_price(0.0), m_prev_sign(1.0) {}
double Classify(const double price)
{
if(m_prev_price == 0.0) // first tick seen
{ m_prev_price = price; return m_prev_sign; }
if(price > m_prev_price)
m_prev_sign = 1.0;
else
if(price < m_prev_price)
m_prev_sign = -1.0;
// else: unchanged price → carry forward m_prev_sign
m_prev_price = price;
return m_prev_sign;
}
// ── Read-only accessors ─────────────────────────────────────────
double PrevPrice(void) const { return m_prev_price; }
double PrevSign(void) const { return m_prev_sign; }
// ── Write accessors (new) — required by CRunBar::LoadState() ───
void SetPrevPrice(const double p) { m_prev_price = p; }
void SetPrevSign(const double s) { m_prev_sign = s; }
};
//+------------------------------------------------------------------+
//| CImbalanceBar |
//| |
//| Closes when |theta_T| >= E0[T] * E0[|imbalance per tick|]. |
//| Both expectations are updated with O(1) EWM after each close. |
//+------------------------------------------------------------------+
class CImbalanceBar : public CBarConstructor
{
private:
CTickRule m_tick_rule;
ENUM_IMBALANCE_METRIC m_metric_type;
double m_theta;
double m_ewm_T;
double m_ewm_abs_theta;
double m_exp_T;
double m_exp_abs_imb;
double m_ewm_alpha;
long m_bar_tick_count;
double TickMetric(const MqlTick &tick, const double sign) const;
public:
CImbalanceBar(ENUM_IMBALANCE_METRIC metric_type,
double exp_ticks_init,
double exp_imbalance_init,
int ewm_span = 20);
virtual bool ProcessTick(const MqlTick &tick,
const long tick_num,
SBar &out_bar) override;
virtual bool SaveState(const int file_handle) override;
virtual bool LoadState(const int file_handle) override;
// ── Diagnostic accessors ─────────────────────────────────────────
double CurrentTheta(void) const { return m_theta; }
double CurrentThreshold(void) const
{ return m_exp_T * m_exp_abs_imb; }
double ExpT(void) const { return m_exp_T; }
double ExpAbsImb(void) const { return m_exp_abs_imb; }
};
//+------------------------------------------------------------------+
CImbalanceBar::CImbalanceBar(ENUM_IMBALANCE_METRIC metric_type,
double exp_ticks_init,
double exp_imbalance_init,
int ewm_span)
: CBarConstructor(EnumToString(metric_type) == "IMBALANCE_TICK" ? "tick_imbalance"
: EnumToString(metric_type) == "IMBALANCE_VOLUME" ? "volume_imbalance"
: "dollar_imbalance"),
m_metric_type(metric_type),
m_theta(0.0),
m_bar_tick_count(0)
{
m_ewm_alpha = 2.0 / (ewm_span + 1);
m_ewm_T = MathMax(exp_ticks_init, 1.0);
m_ewm_abs_theta = MathMax(MathAbs(exp_imbalance_init), 1e-10) * m_ewm_T;
m_exp_T = m_ewm_T;
m_exp_abs_imb = MathMax(MathAbs(exp_imbalance_init), 1e-10);
}
//+------------------------------------------------------------------+
double CImbalanceBar::TickMetric(const MqlTick &tick, const double sign) const
{
switch(m_metric_type)
{
case IMBALANCE_TICK:
return sign;
case IMBALANCE_VOLUME:
return sign * tick.volume;
case IMBALANCE_DOLLAR:
return sign * tick.bid * tick.volume;
default:
return sign;
}
}
//+------------------------------------------------------------------+
bool CImbalanceBar::ProcessTick(const MqlTick &tick,
const long tick_num,
SBar &out_bar)
{
if(!m_initialized)
{ SeedBar(tick, tick_num); return false; }
UpdateAccumulator(tick, tick_num);
m_bar_tick_count++;
double sign = m_tick_rule.Classify(tick.bid);
m_theta += TickMetric(tick, sign);
if(MathAbs(m_theta) >= m_exp_T * m_exp_abs_imb)
{
FillBar(out_bar);
double bar_len = (double)m_bar_tick_count;
double one_minus_a = 1.0 - m_ewm_alpha;
m_ewm_T = m_ewm_alpha * bar_len + one_minus_a * m_ewm_T;
m_ewm_abs_theta = m_ewm_alpha * MathAbs(m_theta) + one_minus_a * m_ewm_abs_theta;
m_exp_T = m_ewm_T;
m_exp_abs_imb = m_ewm_abs_theta / MathMax(m_exp_T, 1.0);
m_theta = 0.0;
m_bar_tick_count = 0;
SeedBar(tick, tick_num);
return true;
}
return false;
}
//+------------------------------------------------------------------+
bool CImbalanceBar::SaveState(const int fh)
{
if(!CBarConstructor::SaveState(fh))
return false;
FileWriteDouble(fh, m_theta);
FileWriteDouble(fh, m_ewm_T);
FileWriteDouble(fh, m_ewm_abs_theta);
FileWriteDouble(fh, m_exp_T);
FileWriteDouble(fh, m_exp_abs_imb);
FileWriteLong(fh, m_bar_tick_count);
FileWriteDouble(fh, m_tick_rule.PrevPrice());
FileWriteDouble(fh, m_tick_rule.PrevSign());
return true;
}
//+------------------------------------------------------------------+
bool CImbalanceBar::LoadState(const int fh)
{
if(!CBarConstructor::LoadState(fh))
return false;
m_theta = FileReadDouble(fh);
m_ewm_T = FileReadDouble(fh);
m_ewm_abs_theta = FileReadDouble(fh);
m_exp_T = FileReadDouble(fh);
m_exp_abs_imb = FileReadDouble(fh);
m_bar_tick_count = FileReadLong(fh);
m_tick_rule.SetPrevPrice(FileReadDouble(fh));
m_tick_rule.SetPrevSign(FileReadDouble(fh));
return true;
}
//+------------------------------------------------------------------+