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.
- peltwtcn-1.0.0/CITATION.cff +43 -0
- peltwtcn-1.0.0/LICENSE +21 -0
- peltwtcn-1.0.0/MANIFEST.in +31 -0
- peltwtcn-1.0.0/PKG-INFO +487 -0
- peltwtcn-1.0.0/README.md +428 -0
- peltwtcn-1.0.0/docs/REPLICATION_NOTES.md +305 -0
- peltwtcn-1.0.0/docs/STEP_BY_STEP_GUIDE.md +657 -0
- peltwtcn-1.0.0/docs/SYNTAX.md +718 -0
- peltwtcn-1.0.0/docs/TUTORIAL_OUTPUT.md +358 -0
- peltwtcn-1.0.0/examples/run_full_replication.py +256 -0
- peltwtcn-1.0.0/examples/tutorial_step_by_step.py +383 -0
- peltwtcn-1.0.0/peltwtcn/__init__.py +118 -0
- peltwtcn-1.0.0/peltwtcn/breaks.py +610 -0
- peltwtcn-1.0.0/peltwtcn/datasets.py +561 -0
- peltwtcn-1.0.0/peltwtcn/features.py +318 -0
- peltwtcn-1.0.0/peltwtcn/metrics.py +309 -0
- peltwtcn-1.0.0/peltwtcn/models.py +357 -0
- peltwtcn-1.0.0/peltwtcn/pipeline.py +481 -0
- peltwtcn-1.0.0/peltwtcn/plots.py +567 -0
- peltwtcn-1.0.0/peltwtcn/py.typed +0 -0
- peltwtcn-1.0.0/peltwtcn/tables.py +386 -0
- peltwtcn-1.0.0/peltwtcn/wavelet.py +327 -0
- peltwtcn-1.0.0/peltwtcn.egg-info/PKG-INFO +487 -0
- peltwtcn-1.0.0/peltwtcn.egg-info/SOURCES.txt +38 -0
- peltwtcn-1.0.0/peltwtcn.egg-info/dependency_links.txt +1 -0
- peltwtcn-1.0.0/peltwtcn.egg-info/requires.txt +35 -0
- peltwtcn-1.0.0/peltwtcn.egg-info/top_level.txt +1 -0
- peltwtcn-1.0.0/pyproject.toml +89 -0
- peltwtcn-1.0.0/requirements.txt +24 -0
- peltwtcn-1.0.0/setup.cfg +4 -0
- peltwtcn-1.0.0/tests/conftest.py +78 -0
- peltwtcn-1.0.0/tests/test_breaks.py +156 -0
- peltwtcn-1.0.0/tests/test_datasets.py +210 -0
- peltwtcn-1.0.0/tests/test_features.py +173 -0
- peltwtcn-1.0.0/tests/test_metrics.py +182 -0
- peltwtcn-1.0.0/tests/test_models.py +186 -0
- peltwtcn-1.0.0/tests/test_pipeline.py +273 -0
- peltwtcn-1.0.0/tests/test_plots.py +179 -0
- peltwtcn-1.0.0/tests/test_tables.py +214 -0
- 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
|
peltwtcn-1.0.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://www.python.org/)
|
|
66
|
+
[](LICENSE)
|
|
67
|
+
[](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
|
+

|
|
263
|
+
|
|
264
|
+
Every model's forecast against the realised price on the test window
|
|
265
|
+
(Figure 14):
|
|
266
|
+
|
|
267
|
+

|
|
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
|
+

|
|
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.
|