Article-22063-Alternative-B.../AlternativeBars/CRunsBar.mqh
PatrickNjoroge743 fd8bd31837 new files added
2026-06-10 00:09:09 +03:00

244 lines
10 KiB
MQL5

//+------------------------------------------------------------------+
//| CRunsBar.mqh |
//| Run bar constructor — tick, volume, and dollar variants. |
//| Derives from CBarConstructor; shares ENUM_IMBALANCE_METRIC and |
//| CTickRule (state persisted via SetState) from CImbalanceBars. |
//| |
//| Runs bars sample when the sequence of buy or sell volumes |
//| (runs) exceeds dynamic expectations tracked via EWMA. |
//+------------------------------------------------------------------+
#include <AlternativeBars\CImbalanceBars.mqh>
//+------------------------------------------------------------------+
//| Class CRunsBar |
//| Generates bars based on the occurrence of long runs of trades |
//| happening on the same side (buy or sell). |
//+------------------------------------------------------------------+
class CRunsBar : public CBarConstructor
{
private:
CTickRule m_tick_rule; // Classifies ticks as buy (+1) or sell (-1)
ENUM_IMBALANCE_METRIC
m_metric_type; // Defines whether we track ticks, volume, or dollars
//--- Accumulators
double m_theta_buy; // Cumulative metric for buy-side runs
double m_theta_sell; // Cumulative metric for sell-side runs
//--- EWM (Exponentially Weighted Moving Average) state
double m_ewm_T; // EWMA of the number of ticks per bar
double m_ewm_run_buy; // EWMA of the buy-side theta per bar
double m_ewm_run_sell; // EWMA of the sell-side theta per bar
double m_exp_T; // Current expectation of ticks per bar E[T]
double m_exp_buy; // Current expectation of buy run proportions E[theta+/T]
double m_exp_sell; // Current expectation of sell run proportions E[theta-/T]
double m_ewm_alpha; // Smoothing/decay factor = 2 / (span + 1)
long m_bar_tick_count; // Counter for the total ticks in the current forming
// bar
//--- Calculates the value of the current tick based on the chosen metric
double TickMetric(const MqlTick &tick, const double sign) const;
public:
//--- Constructor setting initial estimates and EWM span
CRunsBar(ENUM_IMBALANCE_METRIC metric_type, double exp_ticks_init,
double exp_run_buy_init, double exp_run_sell_init,
int ewm_span = 20);
//--- Core tick processing logic. Returns true if a new bar was formed.
virtual bool ProcessTick(const MqlTick &tick, const long tick_num,
SBar &out_bar) override;
//--- State persistence for uninterrupted operation across restarts
virtual bool SaveState(const int file_handle) override;
virtual bool LoadState(const int file_handle) override;
//--- Diagnostic accessors
double ThetaBuy(void) const { return m_theta_buy; }
double ThetaSell(void) const { return m_theta_sell; }
//--- Calculates the dynamic threshold that must be breached to form a bar
double CurrentThreshold(void) const
{
return m_exp_T * MathMax(m_exp_buy, m_exp_sell);
}
double ExpT(void) const { return m_exp_T; }
double ExpBuy(void) const { return m_exp_buy; }
double ExpSell(void) const { return m_exp_sell; }
};
//+------------------------------------------------------------------+
//| Constructor |
//| Initializes variables and sets up the base EWMA values ensuring |
//| they do not start at zero to avoid division/threshold errors. |
//+------------------------------------------------------------------+
CRunsBar::CRunsBar(ENUM_IMBALANCE_METRIC metric_type, double exp_ticks_init,
double exp_run_buy_init, double exp_run_sell_init,
int ewm_span)
: CBarConstructor(metric_type == IMB_METRIC_TICK ? "tick_run"
: metric_type == IMB_METRIC_VOLUME ? "volume_run"
: "dollar_run"),
m_metric_type(metric_type), m_theta_buy(0.0), m_theta_sell(0.0),
m_bar_tick_count(0)
{
// Compute standard EWMA alpha from the specified span
m_ewm_alpha = 2.0 / (ewm_span + 1);
//--- Initialize expectations with minimum bounds
m_ewm_T = MathMax(exp_ticks_init, 1.0);
m_ewm_run_buy = MathMax(exp_run_buy_init, 1e-10) * m_ewm_T;
m_ewm_run_sell = MathMax(exp_run_sell_init, 1e-10) * m_ewm_T;
m_exp_T = m_ewm_T;
m_exp_buy = MathMax(exp_run_buy_init, 1e-10);
m_exp_sell = MathMax(exp_run_sell_init, 1e-10);
}
//+------------------------------------------------------------------+
//| Signed metric for the current tick |
//| Returns the raw value that will be added to the run accumulators |
//| depending on whether we are forming Tick, Volume, or Dollar bar. |
//| |
//| For dollar bars, mid-price ((bid+ask)/2) is used instead of bid |
//| to obtain a fairer valuation and reduce the impact of bid-ask |
//| bounce on run detection. |
//+------------------------------------------------------------------+
double CRunsBar::TickMetric(const MqlTick &tick, const double sign) const
{
switch(m_metric_type)
{
case IMB_METRIC_TICK:
return sign; // +1 or -1
case IMB_METRIC_VOLUME:
return sign * tick.volume; // Signed volume
case IMB_METRIC_DOLLAR:
{
double mid = (tick.bid + tick.ask) / 2.0;
return sign * mid * tick.volume; // Signed monetary value (mid‑price)
}
default:
return sign;
}
}
//+------------------------------------------------------------------+
//| ProcessTick — core bar construction loop |
//| |
//| Boundary semantic matches Python _detect_run_boundaries(): |
//| UpdateAccumulator() folds the boundary tick into the closing |
//| bar's OHLC; SeedBar() then starts a new bar at that same tick. |
//+------------------------------------------------------------------+
bool CRunsBar::ProcessTick(const MqlTick &tick, const long tick_num,
SBar &out_bar)
{
//--- If this is the very first tick, initialize the bar and skip accumulation
if(!m_initialized)
{
SeedBar(tick, tick_num);
return false;
}
//--- Expand current bar's High/Low/Close and add tick volume
UpdateAccumulator(tick, tick_num);
m_bar_tick_count++;
//--- Determine trade direction (+1 for buy, -1 for sell)
double sign = m_tick_rule.Classify(tick.bid);
//--- Get the absolute run metric (ticks, volume, or dollars)
double metric = TickMetric(tick, sign);
//--- Route to buy or sell accumulator (>= 0 matches Python's "if v >= 0.0")
//--- Notice that sell runs are accumulated as positive values (-metric when
//metric is negative)
if(metric >= 0.0)
m_theta_buy += metric;
else
m_theta_sell += -metric;
//--- Dynamic threshold: Expected Ticks * Max Expected Run Proportion
double threshold = m_exp_T * MathMax(m_exp_buy, m_exp_sell);
// Check if either buy run or sell run has exceeded the threshold
if(MathMax(m_theta_buy, m_theta_sell) >= threshold)
{
//--- Finalize the bar data into the output structure
FillBar(out_bar);
//--- EWM update
//--- Update our expectations based on the bar that just closed
double bar_len = (double)m_bar_tick_count;
double one_minus_a = 1.0 - m_ewm_alpha;
//--- Update EWMA for Ticks, Buy Run, and Sell Run
m_ewm_T = m_ewm_alpha * bar_len + one_minus_a * m_ewm_T;
m_ewm_run_buy = m_ewm_alpha * m_theta_buy + one_minus_a * m_ewm_run_buy;
m_ewm_run_sell = m_ewm_alpha * m_theta_sell + one_minus_a * m_ewm_run_sell;
//--- Re-calculate the expected values for the next bar
m_exp_T = m_ewm_T;
m_exp_buy = m_ewm_run_buy / MathMax(m_exp_T, 1.0);
m_exp_sell = m_ewm_run_sell / MathMax(m_exp_T, 1.0);
//--- Reset for next bar
//--- Clear accumulators for the new run
m_theta_buy = 0.0;
m_theta_sell = 0.0;
m_bar_tick_count = 0;
//--- The tick that triggered the bar closure is also the seed for the new
//bar
SeedBar(tick, tick_num);
return true; // Indicate a new bar has been minted
}
return false; // No bar minted yet
}
//+------------------------------------------------------------------+
//| Persist full run-bar state |
//| Used for saving the object's memory state to disk to survive |
//| terminal restarts or data feeds drops. |
//+------------------------------------------------------------------+
bool CRunsBar::SaveState(const int fh)
{
if(!CBarConstructor::SaveState(fh))
return false;
FileWriteDouble(fh, m_theta_buy);
FileWriteDouble(fh, m_theta_sell);
FileWriteDouble(fh, m_ewm_T);
FileWriteDouble(fh, m_ewm_run_buy);
FileWriteDouble(fh, m_ewm_run_sell);
FileWriteDouble(fh, m_exp_T);
FileWriteDouble(fh, m_exp_buy);
FileWriteDouble(fh, m_exp_sell);
FileWriteLong(fh, m_bar_tick_count);
FileWriteDouble(fh, m_tick_rule.PrevPrice());
FileWriteDouble(fh, m_tick_rule.PrevSign());
return true;
}
//+------------------------------------------------------------------+
//| Restore full run-bar state |
//| Used for resuming the EWMA and accumulators from disk. |
//+------------------------------------------------------------------+
bool CRunsBar::LoadState(const int fh)
{
if(!CBarConstructor::LoadState(fh))
return false;
m_theta_buy = FileReadDouble(fh);
m_theta_sell = FileReadDouble(fh);
m_ewm_T = FileReadDouble(fh);
m_ewm_run_buy = FileReadDouble(fh);
m_ewm_run_sell = FileReadDouble(fh);
m_exp_T = FileReadDouble(fh);
m_exp_buy = FileReadDouble(fh);
m_exp_sell = FileReadDouble(fh);
m_bar_tick_count = FileReadLong(fh);
double prev_price = FileReadDouble(fh);
double prev_sign = FileReadDouble(fh);
m_tick_rule.SetState(prev_price, prev_sign);
return true;
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+