fourier-johansen 0.0.1
Johansen-type Cointegration Tests with a Fourier Function
License: MIT
Requires Python: >=3.8
Keywords: cointegration, johansen, fourier, econometrics, time-series, structural-breaks, VECM, unit-root

# Fourier-Johansen: Johansen-type Cointegration Tests with a Fourier Function

[![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 Johansen-Fourier cointegration test that extends the pioneering Johansen (1991) cointegration test to allow for structural breaks using Fourier functions.

## Reference

**Pascalau, R., Lee, J., Nazlioglu, S., Lu, Y. O. (2022).** "Johansen-type Cointegration Tests with a Fourier Function". *Journal of Time Series Analysis* 43(5): 828-852. [DOI: 10.1111/jtsa.12640](https://doi.org/10.1111/jtsa.12640)

## Author

**Dr Merwan Roudane**  
📧 merwanroudane920@gmail.com  
🔗 [GitHub](https://github.com/merwanroudane/fourierjohansen)

## Installation

```bash
pip install fourier-johansen
```

Or install from source:

```bash
git clone https://github.com/merwanroudane/fourierjohansen.git
cd fourierjohansen
pip install -e .
```

## Features

- **Standard Johansen Test**: Original Johansen (1991) cointegration test
- **Johansen-Fourier Test**: Cointegration test with Fourier function for smooth structural breaks
- **SC-VECM Test**: Test for sharp/discrete breaks (Harris et al. 2016)
- **SBC Model Selection**: Automatic selection among Johansen, SC-VECM, and Fourier models
- **Union of Rejections**: Combined test strategy for unknown break types
- **Publication-ready Output**: LaTeX, Markdown, and formatted text tables

## Quick Start

```python
import numpy as np
from fourier_johansen import johansen_fourier

# Create sample data
np.random.seed(42)
T = 200
x1 = np.cumsum(np.random.randn(T))
x2 = x1 + np.random.randn(T) * 0.5  # Cointegrated with x1
X = np.column_stack([x1, x2])

# Run Johansen-Fourier test
result = johansen_fourier(X, model=3, k=2, f=1, option=1)
print(result)
```

## Detailed Examples

### 1. Standard Johansen Test

```python
from fourier_johansen import johansen

# Generate cointegrated data
np.random.seed(42)
T = 200
x1 = np.cumsum(np.random.randn(T))
x2 = x1 + np.random.randn(T) * 0.5
x3 = np.cumsum(np.random.randn(T))
X = np.column_stack([x1, x2, x3])

# Test with restricted constant model
result = johansen(X, model=2, k=2)
print(result)

# Model options:
# 1 = None (no deterministic terms)
# 2 = Restricted constant (RC) - recommended
# 3 = Unrestricted constant
# 4 = Restricted trend (RT)
# 5 = Unrestricted trend
```

**Output:**
```
=================================================================
              Johansen Cointegration Test Results
=================================================================
 # Variables : 3
 Model       : Restricted Constant (RC)
 VAR Lags    : 2
 VECM Lags   : 1
 Observations: 197
-----------------------------------------------------------------
  Rank    Eigenvalue      Lambda-max       Trace    CV(5%)
-----------------------------------------------------------------
     0                                              -4434.7013
     1      0.052138       10.5234        21.4789*    35.070
     2      0.033045        6.5678        10.8347     20.160
     3      0.021234        4.2669         4.0931      9.140
=================================================================
Note: * indicates rejection of null at 5% level
```

### 2. Johansen-Fourier Test (Main Feature)

```python
from fourier_johansen import johansen_fourier

# Create data with smooth structural break
np.random.seed(42)
T = 200
t = np.arange(T)
break_term = 5 * np.sin(2 * np.pi * t / T)  # Smooth Fourier break

x1 = np.cumsum(np.random.randn(T)) + break_term
x2 = x1 + np.random.randn(T) * 0.5 + break_term
X = np.column_stack([x1, x2])

# Single frequency
result = johansen_fourier(X, model=3, k=2, f=1, option=1)
print(result)

# Cumulative frequencies (recommended when break form is unknown)
result_cum = johansen_fourier(X, model=3, k=2, f=2, option=2)
print(result_cum)

# Model options:
# 1 = Constant (unrestricted)
# 2 = Trend (unrestricted)
# 3 = Restricted Constant (RC) - most common
# 4 = Restricted Trend (RT)

# Get cointegration rank
rank = result.get_cointegration_rank()
print(f"Estimated cointegration rank: {rank}")
```

**Output:**
```
===========================================================================
           Johansen-Fourier Cointegration Test Results
===========================================================================
 # Variables : 2
 Model       : Restricted Constant (RC)
 Frequency   : 1 (Single)
 VAR Lags    : 2
 VECM Lags   : 1
 Observations: 197
---------------------------------------------------------------------------
  Rank      Fourier      Fourier      CV(5%)      CV(5%)      Log-Lik
            Lambda        Trace      Lambda       Trace                
---------------------------------------------------------------------------
     0                                                       -856.234
     1       28.591*       57.342*     22.195      48.930    -842.123
     2       20.404*       28.751*     14.742      29.586    -831.456
===========================================================================
Note: * indicates rejection of null at 5% level
```

### 3. SC-VECM Test (For Sharp Breaks)

```python
from fourier_johansen import sc_vecm

# Create data with sharp break
np.random.seed(42)
T = 200
break_point = 100

x1 = np.cumsum(np.random.randn(T))
x1[break_point:] += 5  # Sharp level shift
x2 = x1 + np.random.randn(T) * 0.5
X = np.column_stack([x1, x2])

# Test rank 0
result = sc_vecm(r=0, y=X, max_lag=4, lambda_L=0.1)
print(result)
```

**Output:**
```
======================================================================
              SC-VECM Cointegration Test Results
======================================================================
 # Variables   : 2
 Observations  : 200
 Rank Tested   : 0
 Selected Model: Break
 Break Location: 98 (fraction: 0.490)
----------------------------------------------------------------------
                      No Break      With Break
----------------------------------------------------------------------
Trace Statistic        18.2345        55.6789
SBC                   1234.567       1198.234
Optimal Lag                 2               2
CV (5%)                 23.453          37.630
======================================================================
Decision at 5% level: Reject H0
```

### 4. SBC Model Selection

```python
from fourier_johansen import sbc_test

# Run SBC test - automatically selects best model
result = sbc_test(r=0, y=X, max_lag=4, lambda_L=0.1, f_max=3, option=2)
print(result)
```

**Output:**
```
===========================================================================
                 SBC Model Selection Test Results
===========================================================================
 # Variables   : 2
 Observations  : 200
 Rank Tested   : 0
 Selected Model: Fourier
---------------------------------------------------------------------------
                      Johansen      SC-VECM      Fourier
---------------------------------------------------------------------------
Trace Statistic        18.2345       55.6789       62.3456
SBC                  1234.5670     1198.2340     1156.7890
===========================================================================
 Selected: FOURIER
 Trace from selected model: 62.3456
 Critical value (5%): 48.9300
 Decision: Reject H0 (cointegration detected)
```

### 5. Union of Rejections Test

```python
from fourier_johansen import union_test

# Combine Fourier and SC-VECM tests
result = union_test(X, model=3, k=2, f=2, option=2, lambda_loc=0.5, r=0)
print(result)
```

**Output:**
```
======================================================================
              Union of Rejections Test Results
======================================================================
 # Variables   : 2
 Observations  : 200
 Rank Tested   : 0
 Scale Factor  : 1.0720
----------------------------------------------------------------------
                        Fourier         SC-VECM
----------------------------------------------------------------------
Trace Statistic          62.3456          55.6789
CV (5%, original)        48.9300          37.6300
CV (5%, scaled)          52.4528          40.3394
Individual Reject           Yes              Yes
======================================================================
 UNION TEST RESULT: REJECT H0
 (Rejects if either scaled test rejects)
```

### 6. Export to LaTeX for Publications

```python
from fourier_johansen import johansen_fourier, to_latex, to_markdown

result = johansen_fourier(X, model=3, k=2, f=1, option=1)

# Export as LaTeX table
latex_table = to_latex(result)
print(latex_table)

# Export as Markdown
markdown_table = to_markdown(result)
print(markdown_table)
```

**LaTeX Output:**
```latex
\begin{table}[htbp]
\centering
\caption{Johansen-Fourier Cointegration Test Results}
\begin{tabular}{ccccc}
\hline\hline
Rank & Eigenvalue & $\lambda_{max}$ & Trace & CV (5\%) \\
\hline
1 & 0.134567 & 28.5914$^{*}$ & 57.3421$^{*}$ & 48.930 \\
2 & 0.098765 & 20.4043$^{*}$ & 28.7507 & 29.586 \\
\hline\hline
\end{tabular}
\begin{tablenotes}
\small
\item Note: $^{*}$ indicates rejection of the null hypothesis at the 5\% significance level.
\item Fourier frequency: 1 (single).
\end{tablenotes}
\end{table}
```

### 7. Complete Analysis Workflow

```python
import numpy as np
import pandas as pd
from fourier_johansen import (
    johansen, johansen_fourier, sc_vecm, sbc_test, union_test,
    to_latex, to_markdown
)

# Load your data
# data = pd.read_csv('your_data.csv')
# X = data[['var1', 'var2', 'var3']].values

# For demonstration, create synthetic data
np.random.seed(42)
T = 200
t = np.arange(T)
break_term = 3 * np.sin(2 * np.pi * t / T)

x1 = np.cumsum(np.random.randn(T)) + break_term
x2 = x1 + np.random.randn(T) * 0.5 + break_term * 0.8
x3 = np.cumsum(np.random.randn(T))
X = np.column_stack([x1, x2, x3])

print("=" * 60)
print("       COMPLETE COINTEGRATION ANALYSIS")
print("=" * 60)

# Step 1: Standard Johansen (baseline)
print("\n[1] Standard Johansen Test")
print("-" * 40)
joh_result = johansen(X, model=2, k=2)
print(joh_result)

# Step 2: Johansen-Fourier with single frequency
print("\n[2] Johansen-Fourier Test (Single Frequency)")
print("-" * 40)
jf_result = johansen_fourier(X, model=3, k=2, f=1, option=1)
print(jf_result)

# Step 3: Johansen-Fourier with cumulative frequencies
print("\n[3] Johansen-Fourier Test (Cumulative Frequencies)")
print("-" * 40)
jf_cum_result = johansen_fourier(X, model=3, k=2, f=2, option=2)
print(jf_cum_result)

# Step 4: SC-VECM for comparison
print("\n[4] SC-VECM Test")
print("-" * 40)
scvecm_result = sc_vecm(r=0, y=X, max_lag=4, lambda_L=0.1)
print(scvecm_result)

# Step 5: SBC model selection
print("\n[5] SBC Model Selection")
print("-" * 40)
sbc_result = sbc_test(r=0, y=X, max_lag=4, lambda_L=0.1, f_max=3)
print(sbc_result)

# Step 6: Union test
print("\n[6] Union of Rejections Test")
print("-" * 40)
union_result = union_test(X, model=3, k=2, f=2, option=2, r=0)
print(union_result)

# Summary
print("\n" + "=" * 60)
print("       SUMMARY OF RESULTS")
print("=" * 60)
print(f"Standard Johansen rank:     {sum(joh_result.trace > joh_result.cv_trace)}")
print(f"Johansen-Fourier rank:      {jf_result.get_cointegration_rank()}")
print(f"SBC selected model:         {sbc_result.selected_model}")
print(f"Union test rejects H0:      {union_result.reject_h0}")
```

## Model Specifications

### Johansen-Fourier Models

| Model | Description | When to Use |
|-------|-------------|-------------|
| 1 | Constant (unrestricted) | General case with constant in VAR |
| 2 | Trend (unrestricted) | Data with linear trends |
| 3 | **Restricted Constant (RC)** | **Most common - constant in cointegrating eq. only** |
| 4 | Restricted Trend (RT) | Trend in cointegrating relationship |

### Fourier Frequency Options

| Option | Description | Recommendation |
|--------|-------------|----------------|
| `option=1` | Single frequency | When break type is known |
| `option=2` | Cumulative frequencies | **Recommended when break type is unknown** |

### Frequency Selection

- Use `f=1, 2, or 3` for most applications
- Higher frequencies may lead to overfitting
- Use SBC to select optimal frequency automatically

## Critical Values

All critical values are from:
1. **Johansen (1991)** for standard test
2. **Pascalau et al. (2022) Online Appendix** for Fourier tests
3. **Harris et al. (2016)** for SC-VECM

## Citation

If you use this library in your research, please cite:

```bibtex
@article{pascalau2022johansen,
  title={Johansen-type cointegration tests with a Fourier function},
  author={Pascalau, Razvan and Lee, Junsoo and Nazlioglu, Saban and Lu, Yan Olivia},
  journal={Journal of Time Series Analysis},
  volume={43},
  number={5},
  pages={828--852},
  year={2022},
  publisher={Wiley},
  doi={10.1111/jtsa.12640}
}
```

## License

MIT License

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Changelog

### Version 0.0.1 (2026-01-21)
- Initial release
- Implemented Johansen-Fourier cointegration test
- Added SC-VECM test for sharp breaks
- Added SBC model selection
- Added Union of rejections strategy
- Publication-ready output formatting