peltwtcn 1.0.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.
Files changed (40) hide show
  1. peltwtcn-1.0.0/CITATION.cff +43 -0
  2. peltwtcn-1.0.0/LICENSE +21 -0
  3. peltwtcn-1.0.0/MANIFEST.in +31 -0
  4. peltwtcn-1.0.0/PKG-INFO +487 -0
  5. peltwtcn-1.0.0/README.md +428 -0
  6. peltwtcn-1.0.0/docs/REPLICATION_NOTES.md +305 -0
  7. peltwtcn-1.0.0/docs/STEP_BY_STEP_GUIDE.md +657 -0
  8. peltwtcn-1.0.0/docs/SYNTAX.md +718 -0
  9. peltwtcn-1.0.0/docs/TUTORIAL_OUTPUT.md +358 -0
  10. peltwtcn-1.0.0/examples/run_full_replication.py +256 -0
  11. peltwtcn-1.0.0/examples/tutorial_step_by_step.py +383 -0
  12. peltwtcn-1.0.0/peltwtcn/__init__.py +118 -0
  13. peltwtcn-1.0.0/peltwtcn/breaks.py +610 -0
  14. peltwtcn-1.0.0/peltwtcn/datasets.py +561 -0
  15. peltwtcn-1.0.0/peltwtcn/features.py +318 -0
  16. peltwtcn-1.0.0/peltwtcn/metrics.py +309 -0
  17. peltwtcn-1.0.0/peltwtcn/models.py +357 -0
  18. peltwtcn-1.0.0/peltwtcn/pipeline.py +481 -0
  19. peltwtcn-1.0.0/peltwtcn/plots.py +567 -0
  20. peltwtcn-1.0.0/peltwtcn/py.typed +0 -0
  21. peltwtcn-1.0.0/peltwtcn/tables.py +386 -0
  22. peltwtcn-1.0.0/peltwtcn/wavelet.py +327 -0
  23. peltwtcn-1.0.0/peltwtcn.egg-info/PKG-INFO +487 -0
  24. peltwtcn-1.0.0/peltwtcn.egg-info/SOURCES.txt +38 -0
  25. peltwtcn-1.0.0/peltwtcn.egg-info/dependency_links.txt +1 -0
  26. peltwtcn-1.0.0/peltwtcn.egg-info/requires.txt +35 -0
  27. peltwtcn-1.0.0/peltwtcn.egg-info/top_level.txt +1 -0
  28. peltwtcn-1.0.0/pyproject.toml +89 -0
  29. peltwtcn-1.0.0/requirements.txt +24 -0
  30. peltwtcn-1.0.0/setup.cfg +4 -0
  31. peltwtcn-1.0.0/tests/conftest.py +78 -0
  32. peltwtcn-1.0.0/tests/test_breaks.py +156 -0
  33. peltwtcn-1.0.0/tests/test_datasets.py +210 -0
  34. peltwtcn-1.0.0/tests/test_features.py +173 -0
  35. peltwtcn-1.0.0/tests/test_metrics.py +182 -0
  36. peltwtcn-1.0.0/tests/test_models.py +186 -0
  37. peltwtcn-1.0.0/tests/test_pipeline.py +273 -0
  38. peltwtcn-1.0.0/tests/test_plots.py +179 -0
  39. peltwtcn-1.0.0/tests/test_tables.py +214 -0
  40. peltwtcn-1.0.0/tests/test_wavelet.py +173 -0
@@ -0,0 +1,43 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite both the software and the original paper."
3
+ title: "peltwtcn: Hybrid deep-learning carbon price forecasting with PELT structural-break detection and wavelet denoising"
4
+ version: 1.0.0
5
+ type: software
6
+ authors:
7
+ - family-names: "Roudane"
8
+ given-names: "Merwan"
9
+ email: "merwanroudane920@gmail.com"
10
+ orcid: ""
11
+ repository-code: "https://github.com/merwanroudane/peltwtcn"
12
+ license: MIT
13
+ keywords:
14
+ - carbon price forecasting
15
+ - EU ETS
16
+ - structural breaks
17
+ - PELT
18
+ - wavelet denoising
19
+ - temporal convolutional network
20
+ references:
21
+ - type: article
22
+ title: "A Hybrid Deep Learning based Carbon Price Forecasting Framework with Structural Breakpoints Detection and Signal Denoising"
23
+ authors:
24
+ - family-names: "Ren"
25
+ given-names: "Runsheng"
26
+ - family-names: "Li"
27
+ given-names: "Jing"
28
+ - family-names: "Li"
29
+ given-names: "Yanxiu"
30
+ - family-names: "Huang"
31
+ given-names: "Shixun"
32
+ - family-names: "Shen"
33
+ given-names: "Jun"
34
+ - family-names: "Li"
35
+ given-names: "Wanqing"
36
+ - family-names: "Le"
37
+ given-names: "John"
38
+ - family-names: "Wang"
39
+ given-names: "Sheng"
40
+ year: 2025
41
+ journal: "arXiv preprint"
42
+ number: "2511.04988"
43
+ url: "https://arxiv.org/abs/2511.04988"
peltwtcn-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 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,31 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CITATION.cff
4
+ include requirements.txt
5
+ include pyproject.toml
6
+ include peltwtcn/py.typed
7
+ recursive-include docs *.md
8
+ recursive-include examples *.py
9
+ recursive-include tests *.py
10
+
11
+ # Deliberately NOT shipped in the distribution:
12
+ #
13
+ # data/ the EUA price series and its drivers. These come from
14
+ # investing.com, Yahoo Finance, the EPU and GPR projects and the
15
+ # ECB, each under its own terms; redistributing them through PyPI
16
+ # would be a republication this project has no licence to make.
17
+ # peltwtcn downloads them on first use into a per-user cache
18
+ # instead - see peltwtcn.cache_dir. The git repository keeps them
19
+ # so the replication is reproducible from a clone.
20
+ #
21
+ # assets/ the rendered figures, ~8 MB. They are output, not input, and
22
+ # examples/run_full_replication.py regenerates them.
23
+ #
24
+ prune data
25
+ prune assets
26
+ prune results
27
+ prune tutorial_output
28
+
29
+ global-exclude *.py[cod]
30
+ global-exclude __pycache__
31
+ global-exclude *.pdf
@@ -0,0 +1,487 @@
1
+ Metadata-Version: 2.4
2
+ Name: peltwtcn
3
+ Version: 1.0.0
4
+ Summary: Hybrid deep-learning carbon price forecasting with PELT structural-break detection and wavelet denoising
5
+ Author-email: Merwan Roudane <merwanroudane920@gmail.com>
6
+ Maintainer-email: Merwan Roudane <merwanroudane920@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/merwanroudane/peltwtcn
9
+ Project-URL: Repository, https://github.com/merwanroudane/peltwtcn
10
+ Project-URL: Issues, https://github.com/merwanroudane/peltwtcn/issues
11
+ Project-URL: Documentation, https://github.com/merwanroudane/peltwtcn/tree/main/docs
12
+ Keywords: carbon price,EU ETS,structural breaks,PELT,wavelet denoising,LSTM,GRU,temporal convolutional network,forecasting,energy economics
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
25
+ Classifier: Topic :: Office/Business :: Financial :: Investment
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: numpy>=1.21
30
+ Requires-Dist: pandas>=1.4
31
+ Requires-Dist: scipy>=1.8
32
+ Requires-Dist: matplotlib>=3.5
33
+ Requires-Dist: PyWavelets>=1.3
34
+ Requires-Dist: ruptures>=1.1.7
35
+ Requires-Dist: scikit-learn>=1.1
36
+ Requires-Dist: statsmodels>=0.13
37
+ Requires-Dist: requests>=2.25
38
+ Requires-Dist: tabulate>=0.9
39
+ Provides-Extra: deep
40
+ Requires-Dist: tensorflow>=2.12; extra == "deep"
41
+ Provides-Extra: data
42
+ Requires-Dist: yfinance>=0.2.28; extra == "data"
43
+ Requires-Dist: openpyxl>=3.1; extra == "data"
44
+ Provides-Extra: plots
45
+ Requires-Dist: seaborn>=0.12; extra == "plots"
46
+ Provides-Extra: test
47
+ Requires-Dist: pytest>=7.0; extra == "test"
48
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
49
+ Provides-Extra: docs
50
+ Requires-Dist: sphinx>=6.0; extra == "docs"
51
+ Requires-Dist: furo; extra == "docs"
52
+ Provides-Extra: all
53
+ Requires-Dist: tensorflow>=2.12; extra == "all"
54
+ Requires-Dist: yfinance>=0.2.28; extra == "all"
55
+ Requires-Dist: openpyxl>=3.1; extra == "all"
56
+ Requires-Dist: seaborn>=0.12; extra == "all"
57
+ Requires-Dist: pytest>=7.0; extra == "all"
58
+ Dynamic: license-file
59
+
60
+ # peltwtcn
61
+
62
+ **A faithful Python implementation of the PELT → wavelet → deep-learning carbon
63
+ price forecasting framework, with the replication audit the paper needs.**
64
+
65
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
66
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
67
+ [![Tests](https://img.shields.io/badge/tests-213%20passing-brightgreen.svg)](tests/)
68
+
69
+ Implements
70
+
71
+ > Ren, R., Li, J., Li, Y., Huang, S., Shen, J., Li, W., Le, J. and Wang, S.
72
+ > (2025) "A Hybrid Deep Learning based Carbon Price Forecasting Framework with
73
+ > Structural Breakpoints Detection and Signal Denoising",
74
+ > [arXiv:2511.04988](https://arxiv.org/abs/2511.04988).
75
+
76
+ **Author of this package:** Dr Merwan Roudane —
77
+ <merwanroudane920@gmail.com> —
78
+ [github.com/merwanroudane](https://github.com/merwanroudane)
79
+
80
+ Everything is built on the **real** EU ETS dataset — 6,113 daily EUA
81
+ observations from 2007-09-10 to 2024-06-04, matching the paper exactly. Nothing
82
+ here is simulated.
83
+
84
+ ---
85
+
86
+ ## Table of contents
87
+
88
+ 1. [What this package does](#1-what-this-package-does)
89
+ 2. [Installation](#2-installation)
90
+ 3. [Sixty-second quick start](#3-sixty-second-quick-start)
91
+ 4. [The three protocols](#4-the-three-protocols)
92
+ 5. [Results](#5-results)
93
+ 6. [What the replication found](#6-what-the-replication-found)
94
+ 7. [The real dataset](#7-the-real-dataset)
95
+ 8. [Module map](#8-module-map)
96
+ 9. [Documentation](#9-documentation)
97
+ 10. [Testing](#10-testing)
98
+ 11. [Citation](#11-citation)
99
+ 12. [Author](#12-author)
100
+
101
+ ---
102
+
103
+ ## 1. What this package does
104
+
105
+ The paper's pipeline, end to end, in one call — plus the diagnostics it omits.
106
+
107
+ - **Three structural break detectors** — PELT (Killick et al., 2012), ICSS
108
+ (Inclán & Tiao, 1994), Bai–Perron (2003), and the combined BP&ICSS baseline of
109
+ Lin & Zhang (2022).
110
+ - **Wavelet denoising** in the paper's two-sided form *and* a leakage-free
111
+ rolling form.
112
+ - **LSTM, GRU and TCN** in Keras, with the paper's exact hyper-parameters. The
113
+ TCN's dilated causal convolution is written from scratch so it runs on a CPU
114
+ and is verified for strict causality.
115
+ - **Live data loaders** for the EUA price and its drivers, with a bundled cache.
116
+ - **Publication-quality tables and figures** — LaTeX/booktabs, Markdown, HTML,
117
+ CSV, Excel; every figure of the paper at 300 dpi.
118
+ - **Diebold–Mariano and Model Confidence Set tests**, plus a random-walk
119
+ benchmark and Theil's U. The paper reports none of these.
120
+ - **The published Table 1 as a checkable constant**, so any run can be diffed
121
+ against it.
122
+
123
+ ---
124
+
125
+ ## 2. Installation
126
+
127
+ ```bash
128
+ git clone https://github.com/merwanroudane/peltwtcn.git
129
+ cd peltwtcn
130
+ pip install -e ".[all]"
131
+ ```
132
+
133
+ Extras: `[deep]` TensorFlow · `[data]` yfinance + openpyxl · `[plots]` seaborn ·
134
+ `[test]` pytest. Without TensorFlow everything except the three networks still
135
+ works.
136
+
137
+ Requires Python ≥ 3.9.
138
+
139
+ ---
140
+
141
+ ## 3. Sixty-second quick start
142
+
143
+ ```python
144
+ import peltwtcn as pw
145
+
146
+ # 1. the real EUA carbon price plus 13 exogenous drivers
147
+ df = pw.load_paper_dataset()
148
+ print(df.shape) # (6113, 14)
149
+
150
+ # 2. the paper's best model, in a configuration that works out of sample
151
+ pipe = pw.PELTWTPipeline(model="tcn", stationary=True).fit(df)
152
+ print(pipe.summary())
153
+
154
+ # 3. all five specifications of Table 1
155
+ res = pw.run_experiment(df, stationary=True)
156
+ print(pw.results_table(res.table, fmt="markdown"))
157
+
158
+ # 4. how does it compare with the published numbers?
159
+ print(pw.compare_with_paper(res.table))
160
+
161
+ # 5. every figure, at 300 dpi
162
+ pw.set_journal_style()
163
+ pw.save_all_figures(res, df, outdir="assets")
164
+ ```
165
+
166
+ The full replication, all three protocols, tables and figures:
167
+
168
+ ```bash
169
+ python examples/run_full_replication.py
170
+ ```
171
+
172
+ ---
173
+
174
+ ## 4. The three protocols
175
+
176
+ The paper's specification does not survive an honest chronological split. Rather
177
+ than quietly fix it, this package implements all three variants and reports them
178
+ side by side.
179
+
180
+ | Protocol | Call | What it is |
181
+ |---|---|---|
182
+ | **A** level | `mode="paper", stationary=False` | The faithful replication — the paper's specification, literally. Does not work out of sample. |
183
+ | **B** stationary | `mode="paper", stationary=True` | Same models, same hyper-parameters, applied to the first difference. Levels rebuilt as *last value + predicted change*, so metrics stay in EUR. |
184
+ | **C** causal | `mode="causal", stationary=True` | Adds a causal wavelet, a training-only scaler and a raw target. The only protocol whose numbers are an honest out-of-sample claim. |
185
+
186
+ `mode="causal"` forces the leak-free settings, so you cannot leave one in by
187
+ accident.
188
+
189
+ ---
190
+
191
+ ## 5. Results
192
+
193
+ Protocol B on the real dataset, the paper's own settings (50 epochs, early
194
+ stopping), sorted by RMSE:
195
+
196
+ | Model | MAE | RMSE | MAPE (%) | R² | Theil U | Train (s) |
197
+ |---|---:|---:|---:|---:|---:|---:|
198
+ | ***Random walk*** | ***0.8457*** | ***1.2230*** | ***1.1571*** | ***0.9934*** | ***1.0000*** | ***0.0*** |
199
+ | PELT-WT-GRU | 0.8484 | 1.2361 | 1.1611 | 0.9933 | 1.0107 | 79.1 |
200
+ | PELT-WT-LSTM (multi) | 0.8930 | 1.2615 | 1.2292 | 0.9930 | 1.0315 | 64.9 |
201
+ | BP&ICSS-WT-LSTM | 0.9210 | 1.2847 | 1.2583 | 0.9928 | 1.0505 | 76.7 |
202
+ | PELT-WT-LSTM (uni) | 1.0078 | 1.3459 | 1.4313 | 0.9920 | 1.1005 | 271.1 |
203
+ | PELT-WT-TCN | 1.6752 | 2.1012 | 2.2983 | 0.9806 | 1.7180 | 46.8 |
204
+
205
+ Two results worth stating plainly. **Every one of the five models has Theil's
206
+ U above 1**, so not one of them beats a random walk. And the paper's ranking
207
+ does not survive: it puts PELT-WT-TCN first, whereas here the TCN comes **last**
208
+ by a clear margin. The rest of the order does reproduce — GRU, then multivariate
209
+ LSTM, then BP&ICSS, then univariate LSTM.
210
+
211
+ As published in the paper (Table 1, p. 22):
212
+
213
+ | Model | MAE | RMSE | MAPE (%) | R² |
214
+ |---|---:|---:|---:|---:|
215
+ | BP&ICSS-WT-LSTM | 4.6345 | 5.3878 | 5.8731 | 0.8712 |
216
+ | PELT-WT-LSTM (uni) | 2.3627 | 2.7488 | 3.0582 | 0.9664 |
217
+ | PELT-WT-LSTM (multi) | 1.8192 | 2.2967 | 2.3267 | 0.9765 |
218
+ | PELT-WT-GRU | 1.3308 | 1.6987 | 1.7401 | 0.9872 |
219
+ | **PELT-WT-TCN** | **1.1855** | **1.5866** | **1.6451** | **0.9888** |
220
+
221
+ Available in code as `pw.PAPER_TABLE1`.
222
+
223
+ RMSE under all three protocols, so the effect of each change is visible:
224
+
225
+ | Model | Paper | A level | B stationary | C causal |
226
+ |---|---:|---:|---:|---:|
227
+ | BP&ICSS-WT-LSTM | 5.3878 | 48.8677 | 1.2847 | 1.7253 |
228
+ | PELT-WT-LSTM (uni) | 2.7488 | 43.3046 | 1.3459 | 1.7221 |
229
+ | PELT-WT-LSTM (multi) | 2.2967 | 38.2507 | 1.2615 | **1.7215** |
230
+ | PELT-WT-GRU | 1.6987 | 55.1775 | 1.2361 | 1.7269 |
231
+ | PELT-WT-TCN | 1.5866 | 37.2711 | 2.1012 | 2.2677 |
232
+ | *Random walk* | — | *1.2230* | *1.2230* | *1.7217* |
233
+
234
+ Under **protocol A**, the paper's specification applied literally, every model
235
+ lands between RMSE 37 and 55 with R² between −5.1 and −12.4, and the forecast
236
+ collapses to a flat band around EUR 20 against a truth of EUR 33–98. That is not
237
+ a bug in this implementation — [§6](#6-what-the-replication-found) explains why,
238
+ and the reasoning is reproducible.
239
+
240
+ Under **protocol C**, with every look-ahead removed, all four recurrent models
241
+ land within 0.3 % of the random walk and of each other. Theil's U is 1.000 to
242
+ three decimals. The formal tests then settle it:
243
+
244
+ - Diebold–Mariano, best model vs the random walk: **DM = −0.122, p = 0.9027**.
245
+ Not significant. No pairwise difference among the four recurrent models is
246
+ significant either.
247
+ - Model Confidence Set at α = 0.10: **five of six models survive, including the
248
+ random walk.** The only specification rejected is the paper's own preferred
249
+ one, PELT-WT-TCN — rejected for being significantly *worse* (p = 0.0000).
250
+
251
+ Once the leak is removed there is no evidence that any of the paper's five
252
+ architectures forecasts the EUA price better than assuming tomorrow's price
253
+ equals today's.
254
+
255
+ Full numbers, and the 34 figures, land in `results/` and `assets/`.
256
+
257
+ ### A few of the figures
258
+
259
+ Structural breaks in the EUA price, matched to the policy chronology
260
+ (the paper's Figure 7):
261
+
262
+ ![Detected structural breaks](assets/fig07_breakpoints.png)
263
+
264
+ Every model's forecast against the realised price on the test window
265
+ (Figure 14):
266
+
267
+ ![All forecasts](assets/fig15_all_forecasts.png)
268
+
269
+ Pairwise Diebold–Mariano p-values — the test the paper does not run. Pale cells
270
+ are pairs that cannot be distinguished:
271
+
272
+ ![Diebold-Mariano p-values](assets/fig20_dm_pvalues.png)
273
+
274
+ ---
275
+
276
+ ## 6. What the replication found
277
+
278
+ Four findings, all reproducible. Details and derivations in
279
+ [`docs/REPLICATION_NOTES.md`](docs/REPLICATION_NOTES.md).
280
+
281
+ ### The models cannot extrapolate past the training range
282
+
283
+ The EUA price trends hard: the 80 % training window tops out at **EUR 35.14**
284
+ while the test window reaches **EUR 98.01** — 2.79× higher. An LSTM or GRU
285
+ squashes its state through `tanh`, so once inputs leave the range seen in
286
+ training the state saturates and the forecast flattens. Measured, 25 epochs:
287
+
288
+ | Configuration | RMSE | R² | Prediction range |
289
+ |---|---:|---:|---|
290
+ | one-hot regimes + exogenous (the paper's spec) | 55.18 | −12.37 | EUR 15.4 – 25.9 |
291
+ | exogenous only | 24.01 | −1.53 | EUR 32.2 – 69.2 |
292
+ | denoised price only | 4.42 | 0.914 | EUR 34.3 – 90.5 |
293
+
294
+ The *more* faithful the configuration, the *worse* it does. `stationary=True`
295
+ fixes it by modelling the change instead of the level.
296
+
297
+ ### One-hot regime dummies cannot describe a future regime
298
+
299
+ PELT finds 11 breaks, so 12 regimes. After an 80/20 split, regimes **7–11 occur
300
+ only in the test window**: five of the twelve one-hot columns are identically
301
+ zero for every training row, so no network can learn a weight for them. This is
302
+ structural to the paper's `e_t` encoding — the last regime always begins after
303
+ the last training observation.
304
+
305
+ ### The wavelet filter looks ahead
306
+
307
+ `wavedec`/`waverec` over the whole series is two-sided. Perturbing the price from
308
+ `t = 3000` onwards changes the denoised value as early as `t = 2994` — a
309
+ **six-observation look-ahead**. For a one-step-ahead forecast, one would already
310
+ be too many. Use `denoise_mode="causal"`.
311
+
312
+ ### The paper never benchmarks against a random walk
313
+
314
+ On this data the no-change forecast gives **RMSE 1.223, R² 0.9934** — better
315
+ than every model in the paper's Table 1, including the winner (1.5866). Plain
316
+ OLS on the same 30-step windows reaches **RMSE 0.8189**. A high R² on a
317
+ near-unit-root series in levels is not evidence of skill.
318
+
319
+ Neither Diebold–Mariano nor a Model Confidence Set appears in the paper, so none
320
+ of its reported differences is shown to be significant. Both are provided here.
321
+
322
+ ### Two errata
323
+
324
+ - The abstract's headline **"22.35 % RMSE / 18.63 % MAE"** improvement cannot be
325
+ recovered from the paper's own Table 1. The true figures are 70.55 % / 74.42 %
326
+ against `BP&ICSS-WT-LSTM` and 6.60 % / 10.92 % against `PELT-WT-GRU`; no pair
327
+ of rows gives 22.35 % / 18.63 %. Check it with `pw.improvement_table`.
328
+ - **Training times are reported twice and disagree** (Figure 16 vs the Section
329
+ 4.3 text). Both are kept in `pw.PAPER_TRAIN_TIMES`.
330
+
331
+ ---
332
+
333
+ ## 7. The real dataset
334
+
335
+ `load_paper_dataset()` returns 6,113 rows × 14 columns, exactly the paper's
336
+ sample size, cached in `data/`.
337
+
338
+ | Block | Columns |
339
+ |---|---|
340
+ | Target | `Carbon_Price` (EUA spot, EUR/tCO₂) |
341
+ | Energy | `Europe_Coal`, `TTF_Natural_Gas`, `Henry_Hub_Gas`, `Brent_Crude` |
342
+ | Equity / FX | `Euro_Stoxx_50`, `DAX`, `VIX`, `EURUSD` |
343
+ | Uncertainty | `EPU_US`, `EPU_UK`, `GPR`, `EU_10Y_Yield` |
344
+ | Policy | `Policy` (reconstructed — see below) |
345
+
346
+ **Three features named in the paper have no free feed** and are listed in
347
+ `pw.UNAVAILABLE_FEATURES`: Epex Spot Germany (commercial licence), the Citi
348
+ Economic Surprise Index (Bloomberg/Citi), and 1-week Euribor (EMMI licence).
349
+ Epex is the paper's second-ranked driver, so its absence is material.
350
+
351
+ **The `Policy` feature is never defined in the paper**, despite being its most
352
+ important predictor (Extra-Trees importance > 0.5). `build_policy_features`
353
+ reconstructs it transparently from the twelve dated events the paper itself
354
+ lists in Section 4.1, as a signed exponentially-decaying impulse plus the EU ETS
355
+ phase number. It is documented as a reconstruction, not the original series.
356
+
357
+ Your own data works too — any dated frame with a target column:
358
+
359
+ ```python
360
+ pipe = pw.PELTWTPipeline(stationary=True).fit(my_df, price_col="Price")
361
+ ```
362
+
363
+ ---
364
+
365
+ ## 8. Module map
366
+
367
+ | Module | Contents |
368
+ |---|---|
369
+ | `peltwtcn/datasets.py` | live loaders, caching, the policy reconstruction, `POLICY_EVENTS` |
370
+ | `peltwtcn/breaks.py` | PELT, ICSS, Bai–Perron, BP&ICSS, regime encoding |
371
+ | `peltwtcn/wavelet.py` | decomposition, denoising, causal denoising, thresholds |
372
+ | `peltwtcn/features.py` | `z_t` design matrix, sliding windows, scalers |
373
+ | `peltwtcn/models.py` | LSTM, GRU, TCN, weight-normalised dilated causal conv |
374
+ | `peltwtcn/pipeline.py` | `PipelineConfig`, `PELTWTPipeline`, `run_experiment` |
375
+ | `peltwtcn/metrics.py` | MAE/RMSE/MAPE/R²/Theil U, Diebold–Mariano, MCS |
376
+ | `peltwtcn/tables.py` | Table 1, descriptives, break inventory, paper comparison |
377
+ | `peltwtcn/plots.py` | every figure of the paper, journal styling |
378
+
379
+ ---
380
+
381
+ ## 9. Documentation
382
+
383
+ Start with the tutorial. It is a single script you can run today, and the guide
384
+ is its narrative twin.
385
+
386
+ | Document | What it covers |
387
+ |---|---|
388
+ | [`examples/tutorial_step_by_step.py`](examples/tutorial_step_by_step.py) | **Start here.** A runnable 13-step tutorial, ~5 minutes, printing what every stage produced |
389
+ | [`docs/TUTORIAL_OUTPUT.md`](docs/TUTORIAL_OUTPUT.md) | The verbatim output of that script, so you can check your own run against it |
390
+ | [`docs/STEP_BY_STEP_GUIDE.md`](docs/STEP_BY_STEP_GUIDE.md) | **How to write the code, stage by stage** — 15 steps, every block runnable, with the reasoning |
391
+ | [`docs/SYNTAX.md`](docs/SYNTAX.md) | Complete API reference: every function, argument and return value |
392
+ | [`docs/REPLICATION_NOTES.md`](docs/REPLICATION_NOTES.md) | What matches the paper, what cannot, and the errata |
393
+ | [`examples/run_full_replication.py`](examples/run_full_replication.py) | All three protocols end to end at the paper's own 50-epoch settings |
394
+
395
+ ```bash
396
+ python examples/tutorial_step_by_step.py # learn it (~5 min)
397
+ python examples/run_full_replication.py # replicate it (~30 min)
398
+ ```
399
+
400
+ ---
401
+
402
+ ## 10. Testing
403
+
404
+ ```bash
405
+ pytest # everything
406
+ pytest -m "not slow" # skip the network fits
407
+ pytest -m "not network" # skip the live downloads
408
+ ```
409
+
410
+ 200 fast tests, 13 more that fit networks. Highlights of what is actually verified, rather than merely
411
+ asserted:
412
+
413
+ - the TCN's dilated convolution is **strictly causal** — perturbing `x_t` for
414
+ `t ≥ 20` leaves every output before `t = 20` bit-identical — and matches a
415
+ hand-rolled reference convolution to float32 precision;
416
+ - the wavelet reconstruction identity `A + ΣD = f`;
417
+ - the paper's filter **does** leak and the causal one **does not**;
418
+ - PELT recovers known break locations to within 5 observations, and ICSS finds a
419
+ variance shift while ignoring a mean shift;
420
+ - Theil's U of a random walk is exactly 1;
421
+ - the bundled dataset is 6,113 rows spanning the paper's exact dates;
422
+ - the 70.55 % and 6.60 % improvement figures, pinned as regression tests.
423
+
424
+ ---
425
+
426
+ ## 11. Citation
427
+
428
+ If you use this software, please cite both the implementation and the original
429
+ paper. A `CITATION.cff` is included.
430
+
431
+ ```bibtex
432
+ @software{roudane2026peltwtcn,
433
+ author = {Roudane, Merwan},
434
+ title = {peltwtcn: Hybrid deep-learning carbon price forecasting with
435
+ PELT structural-break detection and wavelet denoising},
436
+ year = {2026},
437
+ version = {1.0.0},
438
+ url = {https://github.com/merwanroudane/peltwtcn},
439
+ license = {MIT}
440
+ }
441
+
442
+ @article{ren2025hybrid,
443
+ author = {Ren, Runsheng and Li, Jing and Li, Yanxiu and Huang, Shixun and
444
+ Shen, Jun and Li, Wanqing and Le, John and Wang, Sheng},
445
+ title = {A Hybrid Deep Learning based Carbon Price Forecasting Framework
446
+ with Structural Breakpoints Detection and Signal Denoising},
447
+ journal = {arXiv preprint arXiv:2511.04988},
448
+ year = {2025},
449
+ url = {https://arxiv.org/abs/2511.04988}
450
+ }
451
+ ```
452
+
453
+ ### Key methodological references
454
+
455
+ | Reference | DOI |
456
+ |---|---|
457
+ | Bai & Perron (2003), *J. Applied Econometrics* | [10.1002/jae.659](https://doi.org/10.1002/jae.659) |
458
+ | Inclán & Tiao (1994), *JASA* | [10.2307/2290916](https://doi.org/10.2307/2290916) |
459
+ | Killick, Fearnhead & Eckley (2012), *JRSS-B* | [10.1111/j.1467-9868.2011.01004.x](https://doi.org/10.1111/j.1467-9868.2011.01004.x) |
460
+ | Mallat (1989), *IEEE TPAMI* | [10.1109/34.192463](https://doi.org/10.1109/34.192463) |
461
+ | Lin & Zhang (2022), *Process Safety and Env. Protection* | [10.1016/j.psep.2022.08.011](https://doi.org/10.1016/j.psep.2022.08.011) |
462
+ | Liu et al. (2012), *Energy Economics* | [10.1016/j.eneco.2011.09.002](https://doi.org/10.1016/j.eneco.2011.09.002) |
463
+ | Geurts, Ernst & Wehenkel (2006), *Machine Learning* | [10.1007/s10994-006-6226-1](https://doi.org/10.1007/s10994-006-6226-1) |
464
+ | Cho et al. (2014), *EMNLP* | [10.3115/v1/D14-1179](https://doi.org/10.3115/v1/D14-1179) |
465
+ | Bai, Kolter & Koltun (2018) | [arXiv:1803.01271](https://arxiv.org/abs/1803.01271) |
466
+ | Hochreiter & Schmidhuber (1997), *Neural Computation* | [10.1162/neco.1997.9.8.1735](https://doi.org/10.1162/neco.1997.9.8.1735) |
467
+
468
+ ---
469
+
470
+ ## 12. Author
471
+
472
+ **Dr Merwan Roudane**
473
+
474
+ - Email: <merwanroudane920@gmail.com>
475
+ - GitHub: [@merwanroudane](https://github.com/merwanroudane)
476
+
477
+ Other packages: `QuantileOnQuantile`, `mqqr`, `qqkrls`, `mqqcause` (CRAN).
478
+
479
+ ---
480
+
481
+ ## Licence
482
+
483
+ MIT — see [LICENSE](LICENSE).
484
+
485
+ The implementation is original work. The methodology is due to Ren et al.
486
+ (2025); please cite them. Redistribution of the underlying price series is
487
+ subject to each provider's own terms.