Skip to main content

Haldrup: Cointegration Analysis with I(1) and I(2) Variables

PyPI version Python 3.8+ License: MIT

A Python implementation of the econometric methods from:

Haldrup, N. (1994). "The asymptotics of single-equation cointegration regressions with I(1) and I(2) variables." Journal of Econometrics, 63(1), 153-181.

Overview

This package provides tools for cointegration analysis when the underlying data-generating process contains both I(1) and I(2) variables. The key contributions include:

  • Critical values for residual-based cointegration tests (Table 1 from Haldrup 1994)
  • Cointegration regression with proper handling of mixed integration orders
  • Spurious regression diagnostics following Theorem 1
  • Unit root tests including tests for double unit roots (I(2))
  • Monte Carlo simulation for computing critical values

Installation

pip install haldrup

Or install from source:

git clone https://github.com/merwanroudane/haldrup.git
cd haldrup
pip install -e .

Quick Start

Basic Cointegration Test

import numpy as np
from haldrup import cointegration_regression, get_critical_value

# Generate example data
np.random.seed(42)
n = 100

# I(2) process
x2 = np.cumsum(np.cumsum(np.random.randn(n)))

# Cointegrated y (I(2) + I(0) error)
y = 2.0 * x2 + np.random.randn(n)

# Run cointegration regression
result = cointegration_regression(y, x2=x2)
print(result.summary())

Get Critical Values

from haldrup import get_critical_value, get_critical_values_table

# Get specific critical value
cv = get_critical_value(m1=1, m2=1, n=100, alpha=0.05)
print(f"5% critical value: {cv.critical_value}")

# Print full table
print(get_critical_values_table(m2=1))

Test for Cointegration

from haldrup import adf_test

# Compute residuals from your cointegration regression
residuals = result.residuals

# Perform ADF test with proper critical values
test_result = adf_test(residuals, m1=1, m2=1)
print(test_result.summary())

Determine Integration Order

from haldrup import determine_integration_order, generate_i2_process

# Generate an I(2) process
x = generate_i2_process(n=500, seed=42)

# Determine integration order
order = determine_integration_order(x)
print(f"Estimated integration order: {order}")

Theoretical Background

The Model (Haldrup 1994, Section 2)

The package handles the general regression model:

$$y_t = \hat{\beta}'_0 c_t + \hat{\beta}'1 x{1t} + \hat{\beta}'2 x{2t} + \hat{u}_t$$

where:

  • $c_t$ = deterministic components (constant, trend)
  • $x_{1t}$ = I(1) regressors (m₁ variables)
  • $x_{2t}$ = I(2) regressors (m₂ variables)

Key Results from Theorem 1 (pp. 159-161)

For the conditional model with integration order $d$:

Statistic d = 0 (full cointegration) d = 1 (partial) d = 2 (no cointegration)
$\hat{\beta}_1$ $O_p(n^{-1})$-consistent Nondegenerate Diverges $O_p(n)$
$\hat{\beta}_2$ $O_p(n^{-2})$-superconsistent $O_p(n^{-1})$-consistent Nondegenerate
F-test $\chi^2$ Diverges $O_p(n)$ Diverges $O_p(n)$
DW → 0 rate $O_p(n^{-3})$ → 0 rate $O_p(n^{-1})$ → 0 rate $O_p(n^{-1})$
R² → 1 rate $O_p(n^{-3})$ → 1 rate $O_p(n^{-2})$ Nondegenerate

Critical Values (Table 1, p. 168)

The ADF test critical values depend on both m₁ (I(1) regressors) and m₂ (I(2) regressors). For large samples, critical values are similar for given m₁ + m₂, but for small samples, critical values become more negative as m₂ increases.

API Reference

Critical Values

from haldrup import get_critical_value, get_critical_values_table

# Get single critical value
cv = get_critical_value(m1, m2, n, alpha=0.05, deterministic="intercept")

# Get formatted table
table = get_critical_values_table(m2=1)

Cointegration Tests

from haldrup import adf_test, phillips_z_test, cointegration_test

# ADF test (Theorem 4)
result = adf_test(residuals, m1, m2, lags=None, deterministic="intercept")

# Phillips Z test
result = phillips_z_test(residuals, m1, m2, truncation=None)

# Unified interface
result = cointegration_test(residuals, m1, m2, method="adf")

Regression

from haldrup import cointegration_regression, polynomial_cointegration_regression

# Basic cointegration regression
result = cointegration_regression(y, x1=None, x2=x2, deterministic="c")

# Polynomial cointegration (with differences of I(2) variables)
result = polynomial_cointegration_regression(y, x2=x2, include_dx2=True)

Unit Root Tests

from haldrup import (
    adf_unit_root_test,
    hasza_fuller_test,
    double_unit_root_test,
    determine_integration_order
)

# Standard ADF test
result = adf_unit_root_test(y, deterministic="ct")

# Hasza-Fuller test for I(2)
result = hasza_fuller_test(y, deterministic="ct")

# Sequential testing for double unit root
test_i2, test_i1 = double_unit_root_test(y)

# Automatic integration order determination
order = determine_integration_order(y, max_order=2)

Diagnostics

from haldrup import durbin_watson, r_squared, spurious_regression_diagnostics

# Durbin-Watson statistic
dw = durbin_watson(residuals)

# R-squared
r2 = r_squared(y, y_fitted)

# Comprehensive spurious regression check
diagnostics = spurious_regression_diagnostics(y, y_fitted, k=2)
print(diagnostics.summary())

Simulation

from haldrup import (
    simulate_critical_values,
    generate_i1_process,
    generate_i2_process
)

# Generate processes
x1 = generate_i1_process(n=100, seed=42)
x2 = generate_i2_process(n=100, seed=42)

# Simulate critical values
result = simulate_critical_values(m1=1, m2=1, n=100, replications=10000)
print(result.summary())

Examples

Example 1: UK Money Demand (Section 5)

Replicating the empirical application from Haldrup (1994, pp. 169-173):

import numpy as np
from haldrup import (
    cointegration_regression,
    determine_integration_order,
    adf_test
)

# Load your data (m_t, p_t, y_t, R_t*)
# m_t and p_t are I(2), y_t and R_t* are I(1)

# Test integration orders
order_m = determine_integration_order(m)
order_p = determine_integration_order(p)
print(f"m_t integration order: {order_m}")
print(f"p_t integration order: {order_p}")

# Run cointegration regression
# y_t = m_t, x2 = p_t, x1 = [y_t, R_t*]
result = cointegration_regression(
    m,
    x1=np.column_stack([y, R]),
    x2=p.reshape(-1, 1),
    deterministic="ct"
)

print(result.summary())

Example 2: Detecting Spurious Regression

import numpy as np
from haldrup import (
    generate_i1_process,
    cointegration_regression,
    spurious_regression_diagnostics
)

np.random.seed(42)
n = 200

# Generate independent random walks
y = generate_i1_process(n)
x = generate_i1_process(n)

# Run regression (spurious!)
result = cointegration_regression(y, x1=x.reshape(-1, 1))

# Check for spurious regression
diag = spurious_regression_diagnostics(
    y, result.fitted_values, k=1
)
print(diag.summary())

# Note: Low DW + high R² = likely spurious!

Example 3: Monte Carlo Replication of Table 1

from haldrup import simulate_critical_values, get_critical_value

# Replicate Table 1 entry for m1=1, m2=1, n=100
simulated = simulate_critical_values(
    m1=1, m2=1, n=100,
    replications=10000,
    seed=42
)

# Compare with published values
table_cv = get_critical_value(m1=1, m2=1, n=100, alpha=0.05)

print(f"Table 1 (5%):   {table_cv.critical_value:.4f}")
print(f"Simulated (5%): {simulated.critical_values[0.05]:.4f}")

Citation

If you use this package in your research, please cite both the original paper and this implementation:

@article{haldrup1994asymptotics,
  title={The asymptotics of single-equation cointegration regressions with {I}(1) and {I}(2) variables},
  author={Haldrup, Niels},
  journal={Journal of Econometrics},
  volume={63},
  number={1},
  pages={153--181},
  year={1994},
  publisher={Elsevier}
}

@software{roudane2024haldrup,
  author = {Roudane, Merwan},
  title = {haldrup: Python Implementation of Haldrup (1994) Cointegration Methods},
  year = {2024},
  url = {https://github.com/merwanroudane/haldrup}
}

References

  • Haldrup, N. (1994). The asymptotics of single-equation cointegration regressions with I(1) and I(2) variables. Journal of Econometrics, 63(1), 153-181.

  • Phillips, P.C.B. and Ouliaris, S. (1990). Asymptotic properties of residual based tests for cointegration. Econometrica, 58(1), 165-193.

  • Engle, R.F. and Granger, C.W.J. (1987). Co-integration and error correction: Representation, estimation and testing. Econometrica, 55(2), 251-276.

  • Hasza, D.P. and Fuller, W.A. (1979). Estimation of autoregressive processes with unit roots. Annals of Statistics, 7(5), 1106-1120.

  • Dickey, D.A. and Pantula, S.G. (1987). Determining the order of differencing in autoregressive processes. Journal of Business and Economic Statistics, 5(4), 455-461.

License

MIT License - see LICENSE file.

Author

Dr Merwan Roudane

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Changelog

Version 0.0.1 (Initial Release)

  • Implementation of Table 1 critical values
  • ADF and Phillips Z cointegration tests
  • Cointegration regression with diagnostics
  • Unit root tests including Hasza-Fuller
  • Monte Carlo simulation for critical values
  • Comprehensive test suite

Metadata

Release files for haldrup 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for haldrup 0.0.2
File Size Uploaded Provenance
haldrup-0.0.2.tar.gz 41.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for haldrup 0.0.2
File Interpreter ABI Platform
haldrup-0.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 84.0 kB

Release files / haldrup-0.0.2.tar.gz

Download URL haldrup-0.0.2.tar.gz
Size 41.4 kB
Tags Source
SHA-256 checksum
How to use checksums
4d58e609943ca2bd8097c8795717d834540f35803fa4d31a055020480e86ca57
BLAKE2b-256 checksum
How to use checksums
1cab3aae24dac18d386cbb5c5b5c2e14f5b372d8dd3e9fe6e13f0a5744bcafc5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release files / haldrup-0.0.2-py3-none-any.whl

Download URL haldrup-0.0.2-py3-none-any.whl
Size 42.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
89f050965b37b6972927b57ef80af38be585d342cddd5db3492bc9f05a4c320d
BLAKE2b-256 checksum
How to use checksums
f4130a84f18313891a29a993535e6178151609ca46ad38d4f7f03f1dbfb5de89
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Security for haldrup 0.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page