fjohansen 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to **fjohansen** will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-05-22
9
+
10
+ ### Added
11
+ - Initial public release.
12
+ - Johansen cointegration test with Fourier-type smooth nonlinear trends
13
+ restricted to cointegrating relations (Kurita & Shintani 2025).
14
+ - Six model variants: `CNR`, `LNR`, `CNU`, `LNU`, `constant`, `linear`.
15
+ - Bundled Table B1 critical values (CNR) and pre-computed moment tables
16
+ for the other model variants.
17
+ - Vectorised limit-distribution simulator with persistent disk cache.
18
+ - Gamma-approximation p-values (Doornik 1998).
19
+ - Perron-Shintani-Yabu (2021) FGLS Wald test with Prais-Winsten
20
+ transformation, Roy-Fuller bias correction and super-efficient AR(1)
21
+ estimator.
22
+ - General-to-specific frequency selection for univariate and multivariate
23
+ panels.
24
+ - Publication-quality output: ASCII summary, LaTeX (booktabs), HTML, and
25
+ Rich-coloured terminal tables.
26
+ - Paper-style figures: eigenvalues, long-run relations, recursive
27
+ rejection curves, limit-distribution densities, residual diagnostics,
28
+ risk-premium decomposition.
29
+ - Section 5 DGPs (NF-DGP-1..4, F-DGP-1, F-DGP-2) and a synthetic JGB-yield
30
+ surrogate.
31
+ - Three runnable example scripts reproducing Sections 5, 6 and the
32
+ limit-distribution figures of the paper.
33
+ - Test suite (13 tests).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Merwan Roudane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ include CHANGELOG.md
5
+ recursive-include fjohansen *.py
6
+ recursive-include examples *.py
7
+ recursive-include tests *.py
8
+ recursive-exclude * __pycache__
9
+ recursive-exclude * *.py[cod]
10
+ recursive-exclude * *.so
11
+ recursive-exclude * .DS_Store
12
+ prune .git
13
+ prune .github
14
+ prune build
15
+ prune dist
16
+ prune *.egg-info
17
+ prune .pytest_cache
@@ -0,0 +1,571 @@
1
+ Metadata-Version: 2.2
2
+ Name: fjohansen
3
+ Version: 0.1.0
4
+ Summary: Johansen cointegration test with Fourier-type smooth nonlinear trends in cointegrating relations (Kurita & Shintani, 2025).
5
+ Author: Merwan Roudane
6
+ Maintainer: Merwan Roudane
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/merwanroudane/fjohansen
9
+ Project-URL: Repository, https://github.com/merwanroudane/fjohansen
10
+ Project-URL: Issues, https://github.com/merwanroudane/fjohansen/issues
11
+ Keywords: cointegration,Johansen,Fourier,nonlinear trend,VAR,econometrics,time series,unit root
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: numpy>=1.22
25
+ Requires-Dist: scipy>=1.8
26
+ Requires-Dist: pandas>=1.4
27
+ Requires-Dist: matplotlib>=3.5
28
+ Requires-Dist: statsmodels>=0.13
29
+ Provides-Extra: pretty
30
+ Requires-Dist: rich>=13.0; extra == "pretty"
31
+ Requires-Dist: tabulate>=0.9; extra == "pretty"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0; extra == "dev"
34
+ Requires-Dist: black; extra == "dev"
35
+ Requires-Dist: ruff; extra == "dev"
36
+
37
+ # fjohansen
38
+
39
+ [![PyPI version](https://img.shields.io/pypi/v/fjohansen.svg)](https://pypi.org/project/fjohansen/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/fjohansen.svg)](https://pypi.org/project/fjohansen/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
+ [![GitHub](https://img.shields.io/badge/repo-merwanroudane%2Ffjohansen-black)](https://github.com/merwanroudane/fjohansen)
43
+
44
+ **`fjohansen`** is a Python implementation of the **Johansen cointegration test
45
+ with Fourier-type smooth nonlinear deterministic trends restricted to
46
+ cointegrating relations**, as developed in
47
+
48
+ > **Kurita, T. & Shintani, M. (2025)**. *Johansen test with Fourier-type
49
+ > smooth nonlinear trends in cointegrating relations.* **Econometric Reviews**,
50
+ > 44(10), 1589–1616. [DOI: 10.1080/07474938.2025.2530640](https://doi.org/10.1080/07474938.2025.2530640)
51
+
52
+ The library also bundles the FGLS Wald test of
53
+
54
+ > **Perron, P., Shintani, M. & Yabu, T. (2017, 2021)**. *Testing for flexible
55
+ > nonlinear trends with an integrated or stationary noise component.*
56
+
57
+ used by Kurita & Shintani as a frequency-selection pre-step.
58
+
59
+ > **Author:** [Merwan Roudane](https://github.com/merwanroudane)
60
+ > **Repository:** <https://github.com/merwanroudane/fjohansen>
61
+ > **PyPI:** <https://pypi.org/project/fjohansen/>
62
+
63
+ ---
64
+
65
+ ## 📋 Table of contents
66
+
67
+ 1. [Features](#-features)
68
+ 2. [Installation](#-installation)
69
+ 3. [Quick start](#-quick-start)
70
+ 4. [Mathematical background](#-mathematical-background)
71
+ 5. [Model variants](#-model-variants)
72
+ 6. [Full API reference](#-full-api-reference)
73
+ - [Core test class](#core-test-class-fjjohansenfourier)
74
+ - [Frequency selection](#frequency-selection-perron-shintani-yabu-2021)
75
+ - [Critical values & p-values](#critical-values--p-values)
76
+ - [Limit-distribution simulator](#limit-distribution-simulator)
77
+ - [Tables and exports](#tables-and-exports)
78
+ - [Plotting](#plotting)
79
+ - [Data-generating processes](#data-generating-processes)
80
+ - [Low-level utilities](#low-level-utilities)
81
+ 7. [Reproducing the paper](#-reproducing-the-paper)
82
+ 8. [Performance & caching](#-performance--caching)
83
+ 9. [Citation](#-citation)
84
+ 10. [License](#-license)
85
+
86
+ ---
87
+
88
+ ## ✨ Features
89
+
90
+ * **Six model variants** — `CNR`, `LNR`, `CNU`, `LNU` (Fourier in / out of the
91
+ cointegrating space) plus standard constant- and linear-trend restricted
92
+ Johansen models for direct comparison.
93
+ * **Trace statistics** with hard-coded critical values from Table B1 of the
94
+ paper and Gamma-approximation *p-values* (Doornik 1998).
95
+ * **Sequential cointegrating-rank selection** following Johansen's standard
96
+ procedure.
97
+ * **PSY (2021) FGLS Wald test** with Prais–Winsten transformation, Roy–Fuller
98
+ bias correction and super-efficient AR(1) estimator + general-to-specific
99
+ algorithm for picking the number of Fourier frequencies.
100
+ * **Publication-quality output**
101
+ * ASCII table summary (`.summary()`)
102
+ * LaTeX booktabs export (`.to_latex()`)
103
+ * Styled HTML export (`.to_html()`)
104
+ * Colourised terminal output (`rich_print()`)
105
+ * Paper-style figures (eigenvalues, long-run relations, recursive
106
+ rejection curves, limit-distribution densities, residual diagnostics,
107
+ risk-premium decomposition).
108
+ * **All paper DGPs** (NF-DGP-1..4, F-DGP-1, F-DGP-2) plus a synthetic JGB-yield
109
+ surrogate for replication.
110
+ * **Fast** — vectorised simulator, bundled moment tables for every common
111
+ cell, persistent disk cache. Cold-start fit on the 6-variable JGB panel
112
+ is < 0.05 s.
113
+
114
+ ---
115
+
116
+ ## 📦 Installation
117
+
118
+ ### From PyPI (recommended)
119
+
120
+ ```bash
121
+ pip install fjohansen
122
+ ```
123
+
124
+ with optional pretty-printing extras:
125
+
126
+ ```bash
127
+ pip install "fjohansen[pretty]" # rich + tabulate
128
+ ```
129
+
130
+ ### From GitHub (latest dev)
131
+
132
+ ```bash
133
+ git clone https://github.com/merwanroudane/fjohansen.git
134
+ cd fjohansen
135
+ pip install -e .
136
+ ```
137
+
138
+ ### Dependencies
139
+
140
+ * Python ≥ 3.9
141
+ * `numpy`, `scipy`, `pandas`, `matplotlib`, `statsmodels`
142
+ * optional: `rich`, `tabulate` (for `pretty` console output)
143
+
144
+ ---
145
+
146
+ ## 🚀 Quick start
147
+
148
+ ```python
149
+ import fjohansen as fj
150
+
151
+ # 1. Load (or simulate) a multivariate I(1) panel
152
+ data = fj.sample_jgb_data(T=108)
153
+
154
+ # 2. Pick the number of Fourier frequencies (PSY 2021)
155
+ sel = fj.select_frequencies(data, n_max=5, p_d=1, sig_level=0.10)
156
+ print(f"Selected n = {sel.n_selected}")
157
+
158
+ # 3. Run the Johansen-Fourier (CNR) test
159
+ res = fj.JohansenFourier(data, k=3, n=sel.n_selected, model="CNR").fit()
160
+
161
+ # 4. Output
162
+ print(res.summary()) # Table-3 style ASCII
163
+ fj.rich_print(res) # coloured terminal
164
+ open("out.tex", "w").write(res.to_latex())
165
+ open("out.html","w").write(res.to_html())
166
+
167
+ # 5. Figures
168
+ res.plot_eigenvalues()
169
+ res.plot_long_run()
170
+ res.plot_residual_diagnostics()
171
+ res.plot_risk_premium()
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 🔬 Mathematical background
177
+
178
+ For a *p*-dimensional I(1) process $X_t$ the package estimates
179
+
180
+ $$
181
+ \Delta X_t \;=\; \alpha\!\left(\beta' X_{t-1} + \delta' F_{t,T} + \gamma'\right)
182
+ \;+\;\sum_{i=1}^{k-1}\Gamma_i\,\Delta X_{t-i}\;+\;\Phi D_t\;+\;\varepsilon_t,
183
+ $$
184
+
185
+ where the Fourier basis
186
+
187
+ $$
188
+ F_{t,T} = \bigl[\sin\tfrac{2\pi t}{T},\ \cos\tfrac{2\pi t}{T},\ldots,
189
+ \sin\tfrac{2\pi n t}{T},\ \cos\tfrac{2\pi n t}{T}\bigr]'
190
+ $$
191
+
192
+ captures slow, smooth nonlinear movement *inside* the cointegrating
193
+ relations. The test statistic
194
+
195
+ $$
196
+ -2\log\!\mathit{LR}(r\mid p; n) \;=\; -T\!\!\sum_{i=r+1}^{p}\log\bigl(1-\hat\lambda_i\bigr)
197
+ $$
198
+
199
+ obtained from the Gaussian reduced-rank regression follows the limiting
200
+ distribution
201
+
202
+ $$
203
+ \operatorname{tr}\!\left\{\int_0^1\!\mathrm{d}B_u\,G_u'
204
+ \!\left(\!\int_0^1 G_u G_u'\,\mathrm{d}u\!\right)^{-1}\!\!\int_0^1 G_u\,\mathrm{d}B_u'\right\}
205
+ $$
206
+
207
+ (Proposition 3.1 of Kurita & Shintani, 2025). `fjohansen` simulates this
208
+ distribution, fits a Gamma matching its two leading moments (Doornik 1998),
209
+ and returns the resulting *p*-values.
210
+
211
+ ---
212
+
213
+ ## 🧩 Model variants
214
+
215
+ | Code | $Z_1$ (cointegrating space) | $Z_2$ (unrestricted) |
216
+ |-------------|-------------------------------------------------------|------------------------------------------------|
217
+ | `CNR` | $(X_{t-1}',\ F_{t,T}',\ 1)'$ | $\Delta X$-lags only |
218
+ | `LNR` | $(X_{t-1}',\ F_{t,T}',\ t)'$ | $\Delta X$-lags + constant |
219
+ | `CNU` | $(X_{t-1}',\ 1)'$ | $\Delta X$-lags + $F_{t,T}$ |
220
+ | `LNU` | $(X_{t-1}',\ t)'$ | $\Delta X$-lags + constant + $F_{t,T}$ |
221
+ | `constant` | $(X_{t-1}',\ 1)'$ | $\Delta X$-lags only |
222
+ | `linear` | $(X_{t-1}',\ t)'$ | $\Delta X$-lags + constant |
223
+
224
+ * `CNR` / `LNR` correspond to eqs. (6)–(7) of the paper (the main contribution).
225
+ * `CNU` / `LNU` correspond to Section 4 (Fourier unrestricted, no cointegration possible inside it).
226
+ * `constant` / `linear` are the classical Johansen specifications.
227
+
228
+ ---
229
+
230
+ ## 📚 Full API reference
231
+
232
+ ### Core test class: `fj.JohansenFourier`
233
+
234
+ ```python
235
+ class JohansenFourier(
236
+ data, # array-like or DataFrame, shape (T, p)
237
+ k: int, # VAR order in levels
238
+ n: int = 1, # number of Fourier frequencies (0 = standard Johansen)
239
+ model: str = "CNR", # one of CNR | LNR | CNU | LNU | constant | linear
240
+ )
241
+ ```
242
+
243
+ Methods:
244
+
245
+ ```python
246
+ .fit(
247
+ sig_level: float = 0.05,
248
+ select_rank: bool = True,
249
+ compute_pvalues: bool = True,
250
+ compute_estimates: bool = True,
251
+ n_sims: int = 5_000,
252
+ ) -> JohansenFourierResults
253
+ ```
254
+
255
+ `JohansenFourierResults` exposes:
256
+
257
+ | Attribute | Type | Description |
258
+ |--------------------------|-----------------|---------------------------------------------------------|
259
+ | `eigenvalues` | `ndarray` | Generalised eigenvalues $\hat\lambda_i$, descending. |
260
+ | `trace_stats` | `DataFrame` | Per-null trace, $\lambda$, p-value, 5% and 1% c.v. |
261
+ | `selected_rank` | `int` | Rank chosen by the sequential 5%-rule. |
262
+ | `alpha`, `beta` | `ndarray` | Loading and cointegrating-vector matrices. |
263
+ | `delta` | `ndarray` | Coefficients on the Fourier block ($2n \times r$). |
264
+ | `gamma_const`, `gamma_trend` | `ndarray` | Restricted intercept / slope inside $\beta$-space. |
265
+ | `Gamma` | `list[ndarray]` | Coefficients on $\Delta X_{t-i}$. |
266
+ | `Phi_unrestricted` | `ndarray` | Coefficients on the $Z_2$ block. |
267
+ | `Sigma` | `ndarray` | Residual covariance matrix. |
268
+ | `residuals` | `ndarray` | $T_{\text{eff}}\times p$ residuals. |
269
+ | `fitted_long_run` | `ndarray` | Estimated cointegrating relations $\beta'X+\delta'F+\gamma$. |
270
+ | `t_index` | `ndarray` | Effective-sample time index. |
271
+
272
+ ```python
273
+ .summary(sig_level=0.05) -> str # ASCII (Table-3 style)
274
+ .to_latex(caption=None, label=None) -> str
275
+ .to_html(caption=None) -> str
276
+ .plot_eigenvalues()
277
+ .plot_long_run()
278
+ .plot_residual_diagnostics()
279
+ .plot_risk_premium(index=None, titles=None)
280
+ ```
281
+
282
+ #### Example
283
+
284
+ ```python
285
+ res = fj.JohansenFourier(data, k=3, n=5, model="CNR").fit(sig_level=0.05)
286
+ print(res.summary())
287
+
288
+ # r = res.selected_rank -- number of cointegrating relations
289
+ # beta = res.beta -- (p, r) cointegrating vectors
290
+ # delta = res.delta -- (2n, r) Fourier loadings
291
+ # alpha = res.alpha -- (p, r) adjustment speeds
292
+ ```
293
+
294
+ ---
295
+
296
+ ### Frequency selection (Perron-Shintani-Yabu 2021)
297
+
298
+ ```python
299
+ fj.psy_wald_test(
300
+ y, # univariate series, shape (T,)
301
+ k_freqs, # sequence of frequency indices, e.g. [1, 2, 3]
302
+ p_d: int = 1, # polynomial order: 0 = const, 1 = const+t
303
+ subset_freq=None, # if int, test only this frequency's two coefficients
304
+ version="upper-biased",
305
+ p_T_max=None,
306
+ )
307
+ ```
308
+
309
+ Returns a `PSYTestResult` with `.stat`, `.df`, `.p_value`,
310
+ `.alpha_hat`, `.alpha_corrected`, `.alpha_S`.
311
+
312
+ ```python
313
+ fj.select_frequencies_univariate(
314
+ y, # univariate
315
+ n_max=5,
316
+ p_d=1,
317
+ sig_level=0.10,
318
+ version="upper-biased",
319
+ ) -> FrequencySelectionResult
320
+
321
+ fj.select_frequencies(
322
+ data, # multivariate (T, p)
323
+ n_max=5,
324
+ p_d=1,
325
+ sig_level=0.10,
326
+ version="upper-biased",
327
+ ) -> FrequencySelectionResult
328
+ ```
329
+
330
+ `FrequencySelectionResult` attributes: `n_selected`, `per_series` (DataFrame
331
+ of per-column n), `detail` (DataFrame of every step's W-stat / p-value).
332
+
333
+ #### Example
334
+
335
+ ```python
336
+ sel = fj.select_frequencies(data, n_max=5, p_d=1, sig_level=0.10)
337
+ print(sel.per_series) # per-series chosen n
338
+ print(sel.detail) # full step-by-step table
339
+ print(f"Final n = {sel.n_selected}")
340
+ ```
341
+
342
+ ---
343
+
344
+ ### Critical values & p-values
345
+
346
+ ```python
347
+ fj.quantile(level, p_minus_r, n, model="CNR")
348
+ # level can be 0.95 or '95%'
349
+ # Uses Table B1 of the paper when available, otherwise Gamma approx.
350
+
351
+ fj.p_value(stat, p_minus_r, n, model="CNR")
352
+ # Gamma-approximation p-value of an observed trace statistic.
353
+
354
+ fj.moments(p_minus_r, n, model="CNR") -> (mean, var)
355
+ # First two moments of the limiting distribution.
356
+ ```
357
+
358
+ The full Table B1 is also exposed as `fj.CNR_TABLE_B1[(p_minus_r, n)]`.
359
+
360
+ #### Example
361
+
362
+ ```python
363
+ cv95 = fj.quantile("95%", p_minus_r=5, n=1, model="CNR") # 110.64
364
+ pv = fj.p_value(120.0, p_minus_r=5, n=1, model="CNR") # ~0.03
365
+ ```
366
+
367
+ ---
368
+
369
+ ### Limit-distribution simulator
370
+
371
+ ```python
372
+ fj.simulate_limit_distribution(
373
+ p_minus_r,
374
+ n,
375
+ model="CNR",
376
+ n_sims=5_000,
377
+ grid_size=300,
378
+ seed=12345,
379
+ use_cache=True,
380
+ ) -> ndarray of shape (n_sims,)
381
+
382
+ fj.simulate_limit_moments(p_minus_r, n, model="CNR", ...) -> (mean, var, draws)
383
+
384
+ fj.clear_cache() -> int # wipe ~/.fjohansen/cache
385
+ ```
386
+
387
+ The simulator is **fully vectorised**: all `n_sims` replications run in a
388
+ single `numpy.matmul` call. Results are cached on disk under
389
+ `~/.fjohansen/cache/` (override via the `FJOHANSEN_CACHE` env var) so
390
+ identical calls are instant.
391
+
392
+ #### Example
393
+
394
+ ```python
395
+ draws = fj.simulate_limit_distribution(p_minus_r=3, n=2, model="LNR",
396
+ n_sims=10_000)
397
+ import matplotlib.pyplot as plt
398
+ plt.hist(draws, bins=100, density=True)
399
+ ```
400
+
401
+ ---
402
+
403
+ ### Tables and exports
404
+
405
+ ```python
406
+ fj.format_trace_table(df, sig_level=0.05) -> str
407
+ fj.format_trace_latex(df, spec, n, caption=None, label=None) -> str
408
+ fj.format_trace_html(df, spec, n, caption=None) -> str
409
+ fj.rich_print(results) -> None # Rich-coloured terminal output
410
+ ```
411
+
412
+ #### Example
413
+
414
+ ```python
415
+ print(res.summary())
416
+ # ========================================================================
417
+ # Johansen-Fourier Cointegration Test (Kurita & Shintani, 2025)
418
+ # ========================================================================
419
+ # Model : CNR (Constant + Nonlinear restricted)
420
+ # Variables (p) : 6
421
+ # Lag order (k) : 3
422
+ # ...
423
+ # H0 eigenvalue trace stat p-value 5% c.v. 1% c.v.
424
+ # --------------------------------------------------------------------
425
+ # r <= 0 0.567 376.154 <0.001** 224.55 238.17
426
+ # r <= 1 0.534 293.944 <0.001** 177.87 190.44
427
+ # ...
428
+ ```
429
+
430
+ ---
431
+
432
+ ### Plotting
433
+
434
+ ```python
435
+ fj.set_paper_style() # called automatically on import
436
+
437
+ fj.plot_series(data, title=None, ncols=2) # Fig. 11
438
+ fj.plot_limit_density(p_minus_r, n_values=(0,1,2,3,4), model="CNR") # Figs. 1-2
439
+ fj.plot_recursive_rejection({label: (Ts, rates)}, nominal=0.05) # Figs. 3-10
440
+
441
+ # Bound on a JohansenFourierResults:
442
+ res.plot_eigenvalues()
443
+ res.plot_long_run()
444
+ res.plot_residual_diagnostics() # Fig. 12
445
+ res.plot_risk_premium(index=data.index) # Fig. 13
446
+ ```
447
+
448
+ Every function returns a `matplotlib.figure.Figure` you can save with
449
+ `fig.savefig("out.pdf")`.
450
+
451
+ ---
452
+
453
+ ### Data-generating processes
454
+
455
+ ```python
456
+ # Non-Fourier DGPs from Section 5.1 (bivariate, T x 2 DataFrame)
457
+ fj.generate_nf_dgp1(T=400, seed=0) # no cointegration
458
+ fj.generate_nf_dgp2(T=400, seed=0) # level shift
459
+ fj.generate_nf_dgp3(T=400, seed=0) # exponential transition (0.4 T)
460
+ fj.generate_nf_dgp4(T=400, seed=0) # sharper transition (0.8 T)
461
+
462
+ # Fourier DGPs from Section 5.2 (4-variate)
463
+ fj.generate_f_dgp1(T=400, seed=0) # r=1, n=1
464
+ fj.generate_f_dgp2(T=400, seed=0) # r=2, n=2
465
+
466
+ # Synthetic JGB-yield surrogate matching Fig. 11
467
+ fj.sample_jgb_data(T=108, seed=7) # 6 yields, monthly 1986-12..1995-11
468
+ ```
469
+
470
+ ---
471
+
472
+ ### Low-level utilities
473
+
474
+ ```python
475
+ fj.fourier_basis(T, n, t_start=1) -> ndarray of shape (T, 2n)
476
+
477
+ fj.build_design_matrices(X, k, n, model) -> (Z0, Z1, Z2, info)
478
+ # Build the matrices used in Johansen's reduced rank regression.
479
+
480
+ # Inspect or extend the model registry:
481
+ fj.MODELS # dict[str, ModelSpec]
482
+ fj.ModelSpec # dataclass describing a deterministic spec
483
+ ```
484
+
485
+ ---
486
+
487
+ ## 🧪 Reproducing the paper
488
+
489
+ | Paper section | API entry-point |
490
+ |------------------------------|-------------------------------------------------------------|
491
+ | §3 / Prop. 3.1 limit dist. | `simulate_limit_distribution`, `plot_limit_density` |
492
+ | §4 unrestricted Fourier | `model='CNU'`, `model='LNU'` |
493
+ | §5 Monte Carlo DGPs | `generate_nf_dgp1..4`, `generate_f_dgp1..2` |
494
+ | §6 JGB application | `sample_jgb_data` + `JohansenFourier(..., 'CNR')` |
495
+ | App. B critical values | `CNR_TABLE_B1`, `quantile(...)` |
496
+
497
+ The `examples/` directory has three runnable scripts:
498
+
499
+ | File | What it reproduces |
500
+ |----------------------------------------------|--------------------------------------------------------------------------|
501
+ | `examples/example_section6_jgb.py` | Full §6 workflow on the synthetic JGB data; writes LaTeX + HTML tables. |
502
+ | `examples/example_section5_monte_carlo.py` | F-DGP-1 rank-selection rates: CNR(n=1) vs. standard linear model. |
503
+ | `examples/example_limit_distributions.py` | A simplified version of Fig. 1. |
504
+
505
+ ---
506
+
507
+ ## ⚡ Performance & caching
508
+
509
+ Cold-start times on a laptop (Windows, NumPy with OpenBLAS, 6-variable JGB panel):
510
+
511
+ | Operation | Cold start | Warm cache |
512
+ |--------------------------------------------|------------|------------|
513
+ | `JohansenFourier(..., 'CNR', n=3).fit()` | 0.01 s | 0.01 s |
514
+ | `JohansenFourier(..., 'LNR', n=3).fit()` | 0.01 s | 0.01 s |
515
+ | Any 4-d / 8-d cell in the bundled tables | 0.01 s | 0.01 s |
516
+ | `simulate_limit_distribution(.., 5000)` | 0.5–2 s | 0.02 s |
517
+ | `select_frequencies(panel, n_max=5)` | 0.06 s | 0.06 s |
518
+
519
+ The disk cache lives under `~/.fjohansen/cache/`. Wipe it with
520
+ `fj.clear_cache()` or set the `FJOHANSEN_CACHE` environment variable to
521
+ relocate it.
522
+
523
+ ---
524
+
525
+ ## 📑 Citation
526
+
527
+ If you use `fjohansen` in academic work, please cite the underlying paper
528
+ **and** the package:
529
+
530
+ ```bibtex
531
+ @article{kurita_shintani_2025,
532
+ author = {Takamitsu Kurita and Mototsugu Shintani},
533
+ title = {Johansen test with Fourier-type smooth nonlinear trends in cointegrating relations},
534
+ journal = {Econometric Reviews},
535
+ year = {2025},
536
+ volume = {44},
537
+ number = {10},
538
+ pages = {1589--1616},
539
+ doi = {10.1080/07474938.2025.2530640}
540
+ }
541
+
542
+ @article{perron_shintani_yabu_2017,
543
+ author = {Pierre Perron and Mototsugu Shintani and Tomoyoshi Yabu},
544
+ title = {Testing for flexible nonlinear trends with an integrated or stationary noise component},
545
+ journal = {Oxford Bulletin of Economics and Statistics},
546
+ volume = {79},
547
+ pages = {822--850},
548
+ year = {2017},
549
+ doi = {10.1111/obes.12169}
550
+ }
551
+
552
+ @software{roudane_fjohansen,
553
+ author = {Merwan Roudane},
554
+ title = {fjohansen: Johansen test with Fourier-type smooth nonlinear trends (Python)},
555
+ year = {2025},
556
+ url = {https://github.com/merwanroudane/fjohansen}
557
+ }
558
+ ```
559
+
560
+ ---
561
+
562
+ ## 🐛 Issues & contributions
563
+
564
+ Bug reports, questions and pull requests welcome at
565
+ <https://github.com/merwanroudane/fjohansen/issues>.
566
+
567
+ ---
568
+
569
+ ## 📜 License
570
+
571
+ MIT — see [LICENSE](LICENSE).