tvccointreg 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dr 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,516 @@
1
+ Metadata-Version: 2.4
2
+ Name: tvccointreg
3
+ Version: 0.1.0
4
+ Summary: Time-Varying Coefficient regression and Generalized Cointegration (Hall, Swamy & Tavlas)
5
+ Author-email: Dr Merwan Roudane <merwanroudane920@gmail.com>
6
+ Maintainer-email: Dr Merwan Roudane <merwanroudane920@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/merwanroudane/tvccointreg
9
+ Project-URL: Repository, https://github.com/merwanroudane/tvccointreg
10
+ Project-URL: Issues, https://github.com/merwanroudane/tvccointreg/issues
11
+ Keywords: econometrics,cointegration,generalized-cointegration,time-varying-coefficient,random-coefficient-model,nonstationary,Swamy,coefficient-drivers,nonlinear-cointegration
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: numpy>=1.21
26
+ Requires-Dist: scipy>=1.7
27
+ Requires-Dist: pandas>=1.3
28
+ Requires-Dist: matplotlib>=3.5
29
+ Provides-Extra: adf
30
+ Requires-Dist: statsmodels>=0.13; extra == "adf"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0; extra == "dev"
33
+ Requires-Dist: statsmodels>=0.13; extra == "dev"
34
+ Requires-Dist: build; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # tvccointreg
38
+
39
+ **Time-Varying-Coefficient regression and Generalized Cointegration in Python.**
40
+
41
+ `tvccointreg` implements the *generalized cointegration* framework of
42
+
43
+ > **Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2015).**
44
+ > *A Note on Generalizing the Concept of Cointegration.*
45
+
46
+ together with the surrounding Swamy time-varying-coefficient (TVC) literature
47
+ (Swamy & Mehta 1975; Swamy, Tavlas, Hall & co-authors 2003–2014; Granger 2008).
48
+
49
+ Conventional cointegration (Engle–Granger 1987) is an *inherently linear*
50
+ concept: it can only recover a structural relationship if that relationship
51
+ happens to be linear in unit-root variables. Most economic theory, however,
52
+ implies **nonlinear** relationships among variables that are **nonstationary but
53
+ not necessarily unit-root**. `tvccointreg` lets you:
54
+
55
+ - estimate a model that is **linear in variables but with time-varying
56
+ coefficients** — the Swamy–Mehta / Granger representation of *any* nonlinear
57
+ relationship (eq. 7);
58
+ - **decompose** each coefficient into a **bias-free structural component** plus
59
+ **omitted-variable bias** and **measurement-error bias** using *coefficient
60
+ drivers* (eq. 8);
61
+ - **test for generalized cointegration** — i.e. whether the bias-free structural
62
+ partial derivative is nonzero — with **standard** χ²/normal inference (no
63
+ Dickey–Fuller tables);
64
+ - produce **journal-quality tables** (text / LaTeX booktabs / HTML) and
65
+ **publication-quality plots** with the MATLAB **Parula** colormap by default.
66
+
67
+ ---
68
+
69
+ ## Table of contents
70
+
71
+ - [Installation](#installation)
72
+ - [The 60-second tour](#the-60-second-tour)
73
+ - [Concepts: how the paper maps to the code](#concepts-how-the-paper-maps-to-the-code)
74
+ - [The estimator](#the-estimator)
75
+ - [API reference](#api-reference)
76
+ - [Detailed syntax](#detailed-syntax)
77
+ - [Visualizations](#visualizations)
78
+ - [Worked example: spurious vs. real](#worked-example-spurious-vs-real)
79
+ - [Testing](#testing)
80
+ - [Citing](#citing)
81
+ - [References](#references)
82
+
83
+ ---
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ # from a clone of the repo
89
+ git clone https://github.com/merwanroudane/tvccointreg.git
90
+ cd tvccointreg
91
+ pip install -e .
92
+
93
+ # with the optional ADF / test extras
94
+ pip install -e ".[dev]"
95
+ ```
96
+
97
+ Requirements: `numpy`, `scipy`, `pandas`, `matplotlib`. `statsmodels` is optional
98
+ (used only for the ADF stationarity diagnostic and some examples).
99
+
100
+ ---
101
+
102
+ ## The 60-second tour
103
+
104
+ ```python
105
+ from tvccointreg import TVCModel, DriverSpec
106
+ from tvccointreg.datasets import simulate_nonlinear_cointegration
107
+
108
+ # 1) A nonlinear relationship between nonstationary variables.
109
+ sim = simulate_nonlinear_cointegration(T=300, seed=1, nonlinearity=0.4)
110
+
111
+ # 2) Partition the drivers into the three sets (Assumption 1 / eq. 8).
112
+ spec = DriverSpec(
113
+ names=list(sim.drivers.columns),
114
+ bias_free=["x_lag", "y_lag"], # true coefficient variation
115
+ omitted=["w"], # omitted-variable bias
116
+ measurement=[], # measurement-error bias
117
+ )
118
+
119
+ # 3) Fit by iteratively rescaled GLS.
120
+ res = TVCModel(sim.y, sim.X, sim.drivers, driver_spec=spec).fit()
121
+
122
+ # 4) Journal table + generalized cointegration test.
123
+ print(res.summary())
124
+ res.coint_test()
125
+ ```
126
+
127
+ ```
128
+ ==============================================================================
129
+ Time-Varying-Coefficient Regression / Generalized Cointegration
130
+ ==============================================================================
131
+ Dep. variable: y No. observations: 300
132
+ No. coefficients: 2 No. drivers: 3
133
+ Estimator: Iteratively rescaled GLS Covariance: GLS
134
+ R-squared: 0.8311 Log-likelihood: -78.07
135
+ Converged: True Resid ADF p-value: 0.0000
136
+ ==============================================================================
137
+ Variable Coef (mean) Bias-free Std.Err t p-value G-Coint
138
+ ------------------------------------------------------------------------------
139
+ x 0.3283 0.3392 0.0698 4.8614 0.0000 *** Yes
140
+ ==============================================================================
141
+ Signif.: *** p<0.01 ** p<0.05 * p<0.10
142
+ Bias-free = structural derivative; G-Coint via Wald test on it.
143
+ ==============================================================================
144
+ ```
145
+
146
+ On this DGP the recovered bias-free coefficient correlates **0.99** with the
147
+ true time-varying derivative, and the residuals are stationary (ADF p ≈ 0) —
148
+ exactly the standard-inference result the paper proves.
149
+
150
+ ---
151
+
152
+ ## Concepts: how the paper maps to the code
153
+
154
+ | Paper (Hall–Swamy–Tavlas 2015) | Symbol | In `tvccointreg` |
155
+ |---|---|---|
156
+ | TVC model, eq. (7) | `y_t = γ_0t + γ_1t x_1t + … + γ_{K-1,t} x_{K-1,t}` | `TVCModel(y, X, drivers)` |
157
+ | Coefficient-driver eq., eq. (8) | `γ_jt = π_j0 + Σ_d π_jd z_dt + ε_jt` | `DriverSpec(...)` + the drivers matrix |
158
+ | Three components of a coefficient | bias-free / omitted-variable / measurement-error | `DriverSpec(bias_free=, omitted=, measurement=)` |
159
+ | Concentrated model | `y_t = w_t'π + u_t`, `u_t = Σ_j x_jt ε_jt` | `estimation.build_design`, `estimation.irgls` |
160
+ | Variance structure | `Var(u_t) = Σ_j σ_j² x_jt²` | `estimation.variance_components` (Hildreth–Houck–Swamy) |
161
+ | Consistent estimator (§3.3) | iteratively rescaled GLS | `TVCModel.fit(method="irgls")` |
162
+ | Bias-free component | `γ_jt^BF = π_j0 + Σ_{d∈BF} π_jd z_dt` | `res.bias_free_coefficients()` |
163
+ | Generalized cointegration, eqs. (5)–(6) | `∂y/∂x ≠ 0` ⇔ bias-free component ≠ 0 | `res.coint_test()` |
164
+ | Standard inference (§3.3) | χ² Wald / normal, **not** Dickey–Fuller | Wald + delta-method t in `cointegration.py` |
165
+ | Drivers vs. instruments (Table 1) | drivers *should* be correlated with the misspecification | the `omitted` / `measurement` sets |
166
+
167
+ ### The definition, in one line
168
+
169
+ > *y* and *x* are **generalized-cointegrated** if, holding all other relevant
170
+ > preexisting conditions *w* constant, the **bias-free** part of `∂y/∂x` is
171
+ > nonzero.
172
+
173
+ Cointegration is thus a property of the **real-world structural relationship**,
174
+ not of any particular statistical model — and it survives nonlinearity *and*
175
+ omitted regressors.
176
+
177
+ ---
178
+
179
+ ## The estimator
180
+
181
+ Substituting the driver equation (8) into the TVC model (7) gives a model that
182
+ is linear in the stacked parameter vector `π`:
183
+
184
+ ```
185
+ y_t = w_t' π + u_t , w_t = ( x_0t·z_t , x_1t·z_t , … ) , u_t = Σ_j x_jt ε_jt
186
+ Var(u_t | x_t) = Σ_j σ_j² x_jt² (Hildreth–Houck–Swamy structure)
187
+ ```
188
+
189
+ `tvccointreg` then:
190
+
191
+ 1. **OLS** start on the concentrated design `W`.
192
+ 2. **Variance components** `σ_j²` from a non-negative regression of squared
193
+ residuals on squared regressors (`scipy.optimize.nnls`).
194
+ 3. **GLS** with weights `1/h_t`, `h_t = Σ_j σ_j² x_jt²`; iterate to convergence
195
+ (*iteratively rescaled GLS*).
196
+ 4. **Recover** the time-varying coefficients
197
+ `γ_jt = z_t'π_j + ε̂_jt`, where the random part is the best linear predictor
198
+ `ε̂_jt = σ_j² x_jt u_t / h_t`.
199
+ 5. **Decompose** `γ_jt` into bias-free / omitted / measurement parts using the
200
+ driver-set masks, and **test** the bias-free block.
201
+
202
+ Inference uses the GLS covariance `(W'Ω⁻¹W)⁻¹` (the paper's standard-inference
203
+ result) or a heteroskedasticity-robust sandwich (`cov_type="robust"`).
204
+
205
+ > **Note / honest caveat.** As in the paper, validity hinges on *Assumption 1* —
206
+ > that the chosen drivers genuinely span the bias components. This is an
207
+ > identifying assumption, not something the data can confirm; choose drivers that
208
+ > are plausibly correlated with the suspected misspecification (Table 1).
209
+
210
+ ---
211
+
212
+ ## API reference
213
+
214
+ ### `TVCModel(y, X, drivers, driver_spec=None, driver_sets=None, add_const=True, index=None)`
215
+ Build a model. `y` (T,), `X` (T, K−1), `drivers` (T, m). A constant regressor and
216
+ a constant driver are added automatically. Accepts NumPy arrays or pandas
217
+ objects (column names are picked up automatically).
218
+
219
+ - **`.fit(method="irgls", max_iter=100, tol=1e-8, cov_type="gls", verbose=False)`**
220
+ → `TVCResults`.
221
+
222
+ ### `DriverSpec(names, bias_free=[], omitted=[], measurement=[])`
223
+ Three-set partition of the drivers. Any driver not listed defaults to
224
+ `bias_free`. `.describe()` prints the partition.
225
+
226
+ ### `TVCResults`
227
+ | Method | Returns |
228
+ |---|---|
229
+ | `.summary(fmt="text"|"latex"|"html")` | journal-style table |
230
+ | `.coef_table()` | per-regressor average coefficient frame |
231
+ | `.coint_test(alpha=0.05, skip_const=True)` | tidy DataFrame of test results (and `.coint_results_`) |
232
+ | `.tv_coefficients(include_random=True)` | T×K time-varying coefficients |
233
+ | `.bias_free_coefficients()` | T×K bias-free (structural) coefficients |
234
+ | `.components(regressor)` | bias-free / omitted / measurement / random / total |
235
+ | `.coefficient_se()` | pointwise standard errors |
236
+ | `.diagnostics()` | R², log-lik, convergence, residual ADF |
237
+ | `.plot_coefficients(...)` | TVC paths with CI bands |
238
+ | `.plot_decomposition(regressor)` | the three-component decomposition |
239
+ | `.plot_fit()` | actual vs fitted + residuals |
240
+ | `.plot_coint_heatmap()` | Parula heatmap of bias-free paths |
241
+
242
+ ### Datasets
243
+ - `simulate_nonlinear_cointegration(T, seed, nonlinearity, omitted, measurement_error)`
244
+ - `simulate_spurious(T, seed)`
245
+
246
+ ### Colormaps
247
+ `parula_colors(n)`, `matlab_jet_colors(n)`, `turbo_colors(n)`, `bluered_colors(n)`,
248
+ `sinha_colors(n)`, `resolve_colorscale("Parula", n)`, `get_cmap("parula")`.
249
+
250
+ ---
251
+
252
+ ## Detailed syntax
253
+
254
+ **Using raw NumPy arrays and reading off a specific test:**
255
+
256
+ ```python
257
+ import numpy as np
258
+ from tvccointreg import TVCModel, DriverSpec
259
+
260
+ res = TVCModel(y, X, Z, # y:(T,), X:(T,K-1), Z:(T,m)
261
+ driver_sets={"bias_free": ["z1"],
262
+ "omitted": ["z2"],
263
+ "measurement": ["z3"]}).fit()
264
+
265
+ ct = res.coint_test()
266
+ print(ct.loc["x1", "cointegrated"], ct.loc["x1", "p_value"])
267
+ ```
268
+
269
+ **Robust covariance and OLS (homoskedastic) comparison:**
270
+
271
+ ```python
272
+ res_robust = TVCModel(y, X, Z, driver_spec=spec).fit(cov_type="robust")
273
+ res_ols = TVCModel(y, X, Z, driver_spec=spec).fit(method="ols")
274
+ ```
275
+
276
+ **Exporting a LaTeX table for a paper:**
277
+
278
+ ```python
279
+ with open("table1.tex", "w") as f:
280
+ f.write(res.summary(fmt="latex"))
281
+ ```
282
+
283
+ **Pulling the time-varying coefficient path of one regressor:**
284
+
285
+ ```python
286
+ gamma_x = res.tv_coefficients()["x"] # pandas Series indexed by t
287
+ bf_x = res.bias_free_coefficients()["x"]
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Visualizations
293
+
294
+ All plots default to the MATLAB **Parula** colormap and return a matplotlib
295
+ `Figure`.
296
+
297
+ | | |
298
+ |---|---|
299
+ | `plot_coefficients()` | `plot_decomposition("x")` |
300
+ | ![coefficients](https://raw.githubusercontent.com/merwanroudane/tvccointreg/main/docs/img/coefficients.png) | ![decomposition](https://raw.githubusercontent.com/merwanroudane/tvccointreg/main/docs/img/decomposition.png) |
301
+ | `plot_fit()` | `plot_coint_heatmap()` |
302
+ | ![fit](https://raw.githubusercontent.com/merwanroudane/tvccointreg/main/docs/img/fit.png) | ![heatmap](https://raw.githubusercontent.com/merwanroudane/tvccointreg/main/docs/img/heatmap.png) |
303
+
304
+ ---
305
+
306
+ ## Worked example: spurious vs. real
307
+
308
+ `examples/spurious_vs_real.py` reproduces the central point of the paper:
309
+
310
+ ```
311
+ ================ SPURIOUS (independent random walks) ================
312
+ Naive OLS slope = 1.125 (p = 5.97e-46) <- spuriously 'significant'
313
+ Generalized cointegration: NO (p = 0.708)
314
+
315
+ ================ GENUINE generalized cointegration ================
316
+ Generalized cointegration: YES (p = 1.34e-35)
317
+ ```
318
+
319
+ A naive OLS regression of one random walk on another is wildly "significant",
320
+ yet the generalized-cointegration test on the bias-free component correctly
321
+ finds **no** structural relationship.
322
+
323
+ ---
324
+
325
+ ## Empirical application: the US consumption function (real data)
326
+
327
+ `examples/real_data_consumption.py` applies the method to **real US quarterly
328
+ macro data** (`statsmodels` `macrodata`, 1959Q1–2009Q3, 202 obs after lagging).
329
+ We model the long-run relationship between **real personal consumption** and
330
+ **real disposable income** (both in logs, both strongly trending / I(1)):
331
+
332
+ ```python
333
+ import numpy as np, pandas as pd
334
+ from statsmodels.datasets import macrodata
335
+ from tvccointreg import TVCModel, DriverSpec
336
+
337
+ d = macrodata.load_pandas().data
338
+ d.index = pd.period_range("1959Q1", periods=len(d), freq="Q")
339
+ zscore = lambda s: (s - s.mean()) / s.std(ddof=0)
340
+
341
+ y = np.log(d["realcons"]).rename("log_cons")
342
+ X = np.log(d[["realdpi"]]).rename(columns={"realdpi": "log_inc"})
343
+ drivers = pd.DataFrame({
344
+ "inc_lag": zscore(np.log(d["realdpi"]).shift(1)),
345
+ "trend": zscore(pd.Series(np.arange(len(d)), index=d.index)),
346
+ "realint": zscore(d["realint"]),
347
+ "unemp": zscore(d["unemp"]),
348
+ "cons_lag": zscore(np.log(d["realcons"]).shift(1)),
349
+ })
350
+ df = pd.concat([y, X, drivers], axis=1).dropna()
351
+
352
+ spec = DriverSpec(
353
+ names=["inc_lag", "trend", "realint", "unemp", "cons_lag"],
354
+ bias_free=["inc_lag", "trend"], # true elasticity variation
355
+ omitted=["realint", "unemp"], # interest-rate / labour-market channels
356
+ measurement=["cons_lag"], # dynamics / measurement
357
+ )
358
+ res = TVCModel(df["log_cons"], df[["log_inc"]], df[spec.names],
359
+ driver_spec=spec, index=df.index.astype(str)).fit()
360
+ print(res.summary())
361
+ print(res.coint_test())
362
+ ```
363
+
364
+ **Result (verbatim output):**
365
+
366
+ ```
367
+ ==============================================================================
368
+ Time-Varying-Coefficient Regression / Generalized Cointegration
369
+ ==============================================================================
370
+ Dep. variable: log_cons No. observations: 202
371
+ No. coefficients: 2 No. drivers: 5
372
+ Estimator: Iteratively rescaled GLS Covariance: GLS
373
+ R-squared: 0.9999 Log-likelihood: 752.32
374
+ Converged: True Resid ADF p-value: 0.0000
375
+ ==============================================================================
376
+ Variable Coef (mean) Bias-free Std.Err t p-value G-Coint
377
+ ------------------------------------------------------------------------------
378
+ log_inc 0.3886 0.3886 0.0528 7.3626 0.0000 *** Yes
379
+ ==============================================================================
380
+ ```
381
+
382
+ | regressor | avg_bias_free | std_err | t_stat | wald | df | p_value | cointegrated |
383
+ |---|---|---|---|---|---|---|---|
384
+ | log_inc | 0.3886 | 0.0528 | 7.3626 | 56.9504 | 3 | 0.0000 | **True** |
385
+
386
+ **Reading the result.**
387
+
388
+ - **Generalized cointegration is confirmed** between consumption and income
389
+ (Wald = 56.95, 3 df, *p* ≈ 0): the bias-free structural elasticity is firmly
390
+ nonzero, so the long-run relationship is genuine, not spurious.
391
+ - **Standard inference is valid here**: the composite residuals are stationary
392
+ (ADF *p* = 1.9 × 10⁻⁷), exactly the condition under which Hall–Swamy–Tavlas
393
+ show the TVC test uses ordinary χ²/normal critical values rather than
394
+ Dickey–Fuller ones.
395
+ - The **bias-free income elasticity drifts down over the sample**, from **0.43
396
+ (1959Q2)** to **0.34 (2009Q3)** — the *direct* income channel after the
397
+ dynamics (`cons_lag`) and omitted business-cycle conditions (`realint`,
398
+ `unemp`) are stripped out into the bias terms. A declining direct sensitivity
399
+ of consumption to current income over five decades is consistent with greater
400
+ consumption smoothing / financial deepening.
401
+
402
+ | Bias-free income elasticity over time | Coefficient decomposition |
403
+ |---|---|
404
+ | ![mpc](https://raw.githubusercontent.com/merwanroudane/tvccointreg/main/docs/img/consumption_mpc.png) | ![decomp](https://raw.githubusercontent.com/merwanroudane/tvccointreg/main/docs/img/consumption_decomp.png) |
405
+
406
+ Run it yourself:
407
+
408
+ ```bash
409
+ python examples/real_data_consumption.py
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Testing
415
+
416
+ ```bash
417
+ pip install -e ".[dev]"
418
+ pytest -q
419
+ ```
420
+
421
+ The suite checks coefficient recovery, that the three components sum exactly to
422
+ the total coefficient, detection of genuine generalized cointegration, rejection
423
+ of spurious relationships, and the Parula palette/colorscale helpers.
424
+
425
+ ---
426
+
427
+ ## Publishing (maintainer notes)
428
+
429
+ The package is PyPI-ready (`python -m build` + `twine check` both pass).
430
+
431
+ **First release — manual upload with an API token**
432
+
433
+ 1. Create accounts on [TestPyPI](https://test.pypi.org/) and
434
+ [PyPI](https://pypi.org/), and generate an API token for each
435
+ (Account settings → API tokens).
436
+ 2. Build and check:
437
+ ```bash
438
+ python -m build
439
+ python -m twine check dist/*
440
+ ```
441
+ 3. Dry-run on TestPyPI first, then install from there to confirm:
442
+ ```bash
443
+ python -m twine upload --repository testpypi dist/*
444
+ pip install --index-url https://test.pypi.org/simple/ \
445
+ --extra-index-url https://pypi.org/simple/ tvccointreg
446
+ ```
447
+ 4. Upload to the real PyPI:
448
+ ```bash
449
+ python -m twine upload dist/*
450
+ ```
451
+ When prompted, use `__token__` as the username and the API token (starting
452
+ with `pypi-`) as the password.
453
+
454
+ **Subsequent releases — automated via Trusted Publishing (recommended)**
455
+
456
+ `.github/workflows/publish.yml` publishes automatically when you publish a
457
+ GitHub Release. One-time setup: on PyPI add a *Trusted Publisher*
458
+ (Project → Publishing) for repository `merwanroudane/tvccointreg`, workflow
459
+ `publish.yml`, environment `pypi`. After that, no tokens or secrets are needed —
460
+ just bump `version` in `pyproject.toml` and `__version__`, tag, and publish a
461
+ Release.
462
+
463
+ > **Remember:** a version number can only be uploaded to PyPI **once**. Bump the
464
+ > version for every release.
465
+
466
+ ---
467
+
468
+ ## Citing
469
+
470
+ If you use this package, please cite both the package and the underlying paper.
471
+
472
+ ```bibtex
473
+ @software{roudane_tvccointreg,
474
+ author = {Merwan Roudane},
475
+ title = {tvccointreg: Time-Varying-Coefficient Regression and
476
+ Generalized Cointegration in Python},
477
+ year = {2026},
478
+ url = {https://github.com/merwanroudane/tvccointreg}
479
+ }
480
+
481
+ @incollection{hall_swamy_tavlas_2015,
482
+ author = {Hall, Stephen G. and Swamy, P. A. V. B. and Tavlas, George S.},
483
+ title = {A Note on Generalizing the Concept of Cointegration},
484
+ year = {2015}
485
+ }
486
+ ```
487
+
488
+ ---
489
+
490
+ ## References
491
+
492
+ - Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2015). *A Note on
493
+ Generalizing the Concept of Cointegration.*
494
+ - Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2014). *Time Varying
495
+ Coefficient Models; A Proposal for Selecting the Coefficient Driver Sets.*
496
+ University of Leicester WP 14/18.
497
+ - Swamy, P. A. V. B., & Mehta, J. S. (1975). *Bayesian and non-Bayesian Analysis
498
+ of Switching Regressions and of Random Coefficient Regression Models.* JASA.
499
+ - Swamy, P. A. V. B., Tavlas, G. S., Hall, S. G., et al. (2010). *Nonparametric
500
+ Nonstationary Regression.*
501
+ - Granger, C. W. J. (2008). *Non-linear Models: Where Do We Go Next —
502
+ Time-Varying Parameter Models?* Studies in Nonlinear Dynamics & Econometrics.
503
+ - Hildreth, C., & Houck, J. P. (1968). *Some Estimators for a Linear Model with
504
+ Random Coefficients.* JASA.
505
+ - Engle, R. F., & Granger, C. W. J. (1987). *Co-integration and Error
506
+ Correction.* Econometrica.
507
+ - Cramér, H. (1946). *Mathematical Methods of Statistics.*
508
+
509
+ ---
510
+
511
+ ## Author
512
+
513
+ **Dr Merwan Roudane** · merwanroudane920@gmail.com ·
514
+ [github.com/merwanroudane](https://github.com/merwanroudane)
515
+
516
+ Released under the MIT License.