Research#
Pair discovery and cointegration screening.
PairFinder#
- class spreadpy.research.pairFinder.PairFinder(series: list[PriceTimeSeries], significance: float = 0.05, npd_threshold: float | None = 0.3, log_prices: bool = False)[source]#
Scan a universe of assets and rank candidate cointegrated pairs.
For each pair (s₁, s₂) the following sequential pipeline is applied:
NPD pre-filter — normalised price distance. Skips pairs whose rebased price paths diverge too much, avoiding expensive statistical tests on clearly non-cointegrated pairs.
Engle-Granger cointegration test — bivariate OLS + ADF on the OLS residuals. Reports the EG p-value.
ADF on OLS residuals — direct stationarity test on the spread.
Half-life — mean-reversion speed via AR(1) on the spread.
Hurst exponent — degree of mean-reversion from the log-variance method.
Only pairs passing both the EG and ADF significance thresholds are returned.
- Parameters:
series (list[PriceTimeSeries]) – Universe of price series to scan.
significance (float) – p-value threshold applied to both the EG and ADF tests (default 0.05).
npd_threshold (float) – Maximum normalised price distance to pass the pre-filter. Set to
Noneto disable the pre-filter.log_prices (bool) – If
True, apply log-transform to all series before running any test. Recommended when prices span very different scales or when using the Kalman filter downstream.
- scan() DataFrame[source]#
Scan all pairs in the universe and return a ranked DataFrame.
Iterates over all C(n, 2) pairs, applies the NPD pre-filter, runs the Engle-Granger and ADF tests, and retains only pairs that pass both significance thresholds. Results are sorted by EG p-value ascending (strongest cointegration first).
- Returns:
DataFrame with one row per qualifying pair, columns:
x,y,npd,eg_stat,eg_pvalue,adf_stat,adf_pvalue,adf_crit_1%,adf_crit_5%,adf_crit_10%,half_life,hurst,hedge_ratio. Returns an empty DataFrame (with the same columns) if no pair passes both thresholds.- Return type:
pd.DataFrame