204 lines
7.7 KiB
MQL5
204 lines
7.7 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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;
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|