refactor: Added support for dynamic anchored window sizes

This commit is contained in:
Toh4iem9
2026-06-16 16:24:44 +02:00
parent 865c0a8744
commit 1f17cc67c8
+30 -30
View File
@@ -1,11 +1,11 @@
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
//| PairsTrading_Calculator.mqh | //| PairsTrading_Calculator.mqh |
//| Engine for Dynamic Rolling OLS Pairs Trading Cointegration. | //| Engine for Dynamic Rolling/Anchored OLS Pairs Cointegration |
//| Strictly O(1) Incremental Optimized. | //| Strictly O(1) Incremental Optimized. |
//| Copyright 2026, xxxxxxxx | //| Copyright 2026, xxxxxxxx |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
#property copyright "Copyright 2026, xxxxxxxx" #property copyright "Copyright 2026, xxxxxxxx"
#property version "1.00" #property version "1.10" // Added support for dynamic anchored window sizes
#ifndef PAIRS_TRADING_CALCULATOR_MQH #ifndef PAIRS_TRADING_CALCULATOR_MQH
#define PAIRS_TRADING_CALCULATOR_MQH #define PAIRS_TRADING_CALCULATOR_MQH
@@ -16,7 +16,7 @@
class CPairsTradingCalculator class CPairsTradingCalculator
{ {
private: private:
int m_lookback; int m_max_window;
//--- Dynamic rolling arrays //--- Dynamic rolling arrays
double m_arr_A[]; double m_arr_A[];
@@ -32,59 +32,60 @@ public:
CPairsTradingCalculator(); CPairsTradingCalculator();
~CPairsTradingCalculator() {}; ~CPairsTradingCalculator() {};
bool Init(int lookback); bool Init(int max_window);
//--- Processes the raw synchronized prices and computes the rolling Z-Score //--- Upgraded: Accepts a dynamic window_size for VWAP-style anchored resets
double CalculateZScore(int rates_total, int current_index, double CalculateZScore(int rates_total, int current_index, int window_size,
const double &sync_price_A[], const double &sync_price_B[]); const double &sync_price_A[], const double &sync_price_B[]);
}; };
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
//| Constructor | //| Constructor |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
CPairsTradingCalculator::CPairsTradingCalculator() : m_lookback(120) {} CPairsTradingCalculator::CPairsTradingCalculator() : m_max_window(120) {}
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
//| Init | //| Init |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
bool CPairsTradingCalculator::Init(int lookback) bool CPairsTradingCalculator::Init(int max_window)
{ {
m_lookback = (lookback < 10) ? 10 : lookback; m_max_window = (max_window < 10) ? 10 : max_window;
return true; return true;
} }
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
//| CalculateZScore (OLS Rolling Hedge Ratio & Z-Score) | //| CalculateZScore (OLS Dynamic Window Cointegration) |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
double CPairsTradingCalculator::CalculateZScore(int rates_total, int current_index, double CPairsTradingCalculator::CalculateZScore(int rates_total, int current_index, int window_size,
const double &sync_price_A[], const double &sync_price_B[]) const double &sync_price_A[], const double &sync_price_B[])
{ {
if(current_index < m_lookback) // Safety 1: Enforce minimum of 15 bars for statistical significance on anchored starts
if(window_size < 15 || current_index < window_size)
return 0.0; return 0.0;
//--- Resize internal rolling buffers //--- Dynamic array allocation based on the current active anchor size
if(ArraySize(m_arr_A) != m_lookback) if(ArraySize(m_arr_A) != window_size)
{ {
ArrayResize(m_arr_A, m_lookback); ArrayResize(m_arr_A, window_size);
ArrayResize(m_arr_B, m_lookback); ArrayResize(m_arr_B, window_size);
ArrayResize(m_spread_history, m_lookback); ArrayResize(m_spread_history, window_size);
} }
//--- Extract rolling window from synchronized prices //--- Extract rolling/anchored window from synchronized prices
for(int k = 0; k < m_lookback; k++) for(int k = 0; k < window_size; k++)
{ {
int src_idx = current_index - m_lookback + 1 + k; int src_idx = current_index - window_size + 1 + k;
m_arr_A[k] = sync_price_A[src_idx]; m_arr_A[k] = sync_price_A[src_idx];
m_arr_B[k] = sync_price_B[src_idx]; m_arr_B[k] = sync_price_B[src_idx];
} }
//--- Calculate means //--- Calculate means
double mean_A = GetMean(m_arr_A, m_lookback); double mean_A = GetMean(m_arr_A, window_size);
double mean_B = GetMean(m_arr_B, m_lookback); double mean_B = GetMean(m_arr_B, window_size);
//--- Calculate Variance of Benchmark (B) and Covariance (A, B) //--- Calculate Variance of Benchmark (B) and Covariance (A, B)
double var_B = GetVariance(m_arr_B, mean_B, m_lookback); double var_B = GetVariance(m_arr_B, mean_B, window_size);
double cov_AB = GetCovariance(m_arr_A, mean_A, m_arr_B, mean_B, m_lookback); double cov_AB = GetCovariance(m_arr_A, mean_A, m_arr_B, mean_B, window_size);
if(var_B <= 1.0e-9) if(var_B <= 1.0e-9)
return 0.0; // Div-by-zero protection return 0.0; // Div-by-zero protection
@@ -93,20 +94,19 @@ double CPairsTradingCalculator::CalculateZScore(int rates_total, int current_ind
double beta = cov_AB / var_B; double beta = cov_AB / var_B;
double alpha = mean_A - (beta * mean_B); double alpha = mean_A - (beta * mean_B);
//--- Calculate the historical spreads over the window to find the standard deviation //--- Calculate the historical spreads over the active window (Mean is algebraically 0.0)
double sum_sq_spread = 0.0; double sum_sq_spread = 0.0;
for(int k = 0; k < m_lookback; k++) for(int k = 0; k < window_size; k++)
{ {
// Spread_t = A_t - Beta * B_t - Alpha (Mean is algebraically 0.0)
m_spread_history[k] = m_arr_A[k] - (beta * m_arr_B[k]) - alpha; m_spread_history[k] = m_arr_A[k] - (beta * m_arr_B[k]) - alpha;
sum_sq_spread += m_spread_history[k] * m_spread_history[k]; sum_sq_spread += m_spread_history[k] * m_spread_history[k];
} }
// Sample standard deviation of the spread // Sample standard deviation of the active spread window
double std_dev_spread = MathSqrt(sum_sq_spread / (m_lookback - 1)); double std_dev_spread = MathSqrt(sum_sq_spread / (window_size - 1));
if(std_dev_spread <= 1.0e-9) if(std_dev_spread <= 1.0e-9)
return 0.0; // Protection against flat/dead spreads return 0.0; // Protection against dead spreads
//--- Calculate the final current Z-Score //--- Calculate the final current Z-Score
double current_spread = sync_price_A[current_index] - (beta * sync_price_B[current_index]) - alpha; double current_spread = sync_price_A[current_index] - (beta * sync_price_B[current_index]) - alpha;