haldrup 0.0.2
Cointegration analysis with I(1) and I(2) variables based on Haldrup (1994)
License: MIT
Requires Python: >=3.8
Keywords: econometrics, cointegration, time series, unit root, I(2) variables, spurious regression, Dickey-Fuller, Phillips-Ouliaris

# Haldrup: Cointegration Analysis with I(1) and I(2) Variables

[![PyPI version](https://badge.fury.io/py/haldrup.svg)](https://badge.fury.io/py/haldrup)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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

```bash
pip install haldrup
```

Or install from source:

```bash
git clone https://github.com/merwanroudane/haldrup.git
cd haldrup
pip install -e .
```

## Quick Start

### Basic Cointegration Test

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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):

```python
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

```python
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

```python
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:

```bibtex
@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](LICENSE) file.

## Author

**Dr Merwan Roudane**
- Email: merwanroudane920@gmail.com
- GitHub: [merwanroudane](https://github.com/merwanroudane)

## 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