pricecast 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.
- pricecast-0.1.0/LICENSE +21 -0
- pricecast-0.1.0/PKG-INFO +177 -0
- pricecast-0.1.0/README.md +140 -0
- pricecast-0.1.0/pyproject.toml +60 -0
- pricecast-0.1.0/setup.cfg +4 -0
- pricecast-0.1.0/src/pricecast/__init__.py +60 -0
- pricecast-0.1.0/src/pricecast/data/BE.json +60 -0
- pricecast-0.1.0/src/pricecast/data/DE.json +60 -0
- pricecast-0.1.0/src/pricecast/data/ES.json +60 -0
- pricecast-0.1.0/src/pricecast/data/FR.json +60 -0
- pricecast-0.1.0/src/pricecast/data/PL.json +60 -0
- pricecast-0.1.0/src/pricecast/data/__init__.py +1 -0
- pricecast-0.1.0/src/pricecast/entsoe.py +93 -0
- pricecast-0.1.0/src/pricecast/features.py +85 -0
- pricecast-0.1.0/src/pricecast/model.py +174 -0
- pricecast-0.1.0/src/pricecast/pretrained.py +30 -0
- pricecast-0.1.0/src/pricecast/scenario.py +133 -0
- pricecast-0.1.0/src/pricecast/structure.py +83 -0
- pricecast-0.1.0/src/pricecast.egg-info/PKG-INFO +177 -0
- pricecast-0.1.0/src/pricecast.egg-info/SOURCES.txt +25 -0
- pricecast-0.1.0/src/pricecast.egg-info/dependency_links.txt +1 -0
- pricecast-0.1.0/src/pricecast.egg-info/requires.txt +19 -0
- pricecast-0.1.0/src/pricecast.egg-info/top_level.txt +1 -0
- pricecast-0.1.0/tests/test_model.py +66 -0
- pricecast-0.1.0/tests/test_pretrained.py +31 -0
- pricecast-0.1.0/tests/test_scenario.py +68 -0
- pricecast-0.1.0/tests/test_structure.py +49 -0
pricecast-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mattéo Hauglustaine
|
|
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.
|
pricecast-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pricecast
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Synthetic hourly electricity price years for prospective energy system studies
|
|
5
|
+
Author-email: Matteo Hauglustaine <matteo@abeo.be>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Matt-haug/pricecast
|
|
8
|
+
Project-URL: Repository, https://github.com/Matt-haug/pricecast
|
|
9
|
+
Project-URL: Issues, https://github.com/Matt-haug/pricecast/issues
|
|
10
|
+
Keywords: electricity,day-ahead price,price profile,synthetic data,energy system modelling,techno-economic analysis,duck curve,time series
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy>=1.21
|
|
23
|
+
Requires-Dist: pandas>=1.3
|
|
24
|
+
Provides-Extra: entsoe
|
|
25
|
+
Requires-Dist: entsoe-py>=0.6; extra == "entsoe"
|
|
26
|
+
Provides-Extra: plot
|
|
27
|
+
Requires-Dist: matplotlib>=3.5; extra == "plot"
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: mkdocs>=1.5; extra == "docs"
|
|
32
|
+
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
|
|
33
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pricecast[docs,entsoe,plot,test]; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/pricecast/)
|
|
39
|
+
[](https://pricecast.readthedocs.io/en/latest/)
|
|
40
|
+
[](https://github.com/Matt-haug/pricecast/actions/workflows/tests.yml)
|
|
41
|
+
[](https://github.com/Matt-haug/pricecast/blob/main/LICENSE)
|
|
42
|
+
[](https://doi.org/10.5281/zenodo.23264989)
|
|
43
|
+
|
|
44
|
+
# pricecast
|
|
45
|
+
|
|
46
|
+
**Synthetic hourly electricity price time-series for prospective energy system studies**
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import pricecast
|
|
50
|
+
from pricecast.entsoe import fetch_year
|
|
51
|
+
|
|
52
|
+
prices, drivers = fetch_year("BE", 2025) # day-ahead price, load, solar, wind
|
|
53
|
+
model = pricecast.load("BE") # or pricecast.fit(prices, drivers)
|
|
54
|
+
|
|
55
|
+
future = pricecast.scale_drivers(drivers, solar=1.39, wind=1.38, load=1.30)
|
|
56
|
+
year = model.predict(future) # the shape of a 2030 year
|
|
57
|
+
year = pricecast.rescale_to_level(year, level=88) # its level, from a fuel-and-carbon scenario
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Returns an hourly series of day-ahead prices in EUR/MWh, ready to feed an LP or
|
|
61
|
+
MILP design model: a heat pump with storage, a battery, a thermal store, an
|
|
62
|
+
electrolyser.
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
*The average day of 2025, each series over its own annual mean. The model (fitted
|
|
67
|
+
on 2023-24, driven by 2025's load, solar and wind) keeps the midday trough;
|
|
68
|
+
reusing a past year cannot know about the panels installed since.*
|
|
69
|
+
|
|
70
|
+
## Why
|
|
71
|
+
|
|
72
|
+
A study that sizes equipment for 2030 needs a 2030 price year. The usual
|
|
73
|
+
stand-in is a past year reused as it was, which carries the past's solar fleet,
|
|
74
|
+
the past's price level, and the past's weather. `pricecast` instead generates the
|
|
75
|
+
year from what a scenario *does* provide - installed solar and wind, demand and
|
|
76
|
+
fuel prices - so the features a design reacts to follow the scenario:
|
|
77
|
+
|
|
78
|
+
- how deep the price falls at midday, and how far it climbs in the evening;
|
|
79
|
+
- what a solar kilowatt-hour is worth against the average one;
|
|
80
|
+
- what the electricity for a kilowatt-hour of heat costs.
|
|
81
|
+
|
|
82
|
+
The model is one least-squares regression of hourly price on the clock and on
|
|
83
|
+
three drivers - load, solar and wind generation - fitted on recent years. A full
|
|
84
|
+
year is generated in one shot, with no autoregression on the price.
|
|
85
|
+
|
|
86
|
+
**It is not a forecaster.** Nobody has next decade's hourly solar output, so it
|
|
87
|
+
does not compete with forecasting models. It produces a plausible *shape* and
|
|
88
|
+
leaves the *level* to an outside scenario, because the level is set by fuel and
|
|
89
|
+
carbon prices and cannot be extrapolated from price history.
|
|
90
|
+
|
|
91
|
+
## What it has been tested on
|
|
92
|
+
|
|
93
|
+
Five European markets (BE, DE, FR, ES, PL), fitted on 2023-24 and judged on 2025
|
|
94
|
+
by what a design optimiser decides, not by hourly error:
|
|
95
|
+
|
|
96
|
+
| | this model | last year's prices reused |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| daily storage, share of achievable arbitrage captured | 88% | 60% |
|
|
99
|
+
| household with PV, battery and heat pump, share of savings captured | 93% | 82% |
|
|
100
|
+
| sizing penalty with the price level given, data 1 / 2 / 4 / 6 years old (EUR/yr) | 2 / 29 / 16 / 7 | 8 / 94 / 147 / 198 |
|
|
101
|
+
| midday price / annual mean, mean absolute error across countries | 0.09 | 0.11 |
|
|
102
|
+
| solar value factor, mean absolute error | 0.07 | 0.11 |
|
|
103
|
+
|
|
104
|
+
Full results, and where it falls short, are in the accompanying paper.
|
|
105
|
+
|
|
106
|
+
## Building a future year
|
|
107
|
+
|
|
108
|
+
The [recipe](docs/recipe.md), in short:
|
|
109
|
+
|
|
110
|
+
1. **Fit** on the two most recent complete years, or start from `pricecast.load`.
|
|
111
|
+
2. **Choose one weather year** and take every weather-dependent input from it:
|
|
112
|
+
the model's drivers, and your asset's PV output, heat demand and ambient
|
|
113
|
+
temperature. Prices respond to the same weather; mixing years makes heat
|
|
114
|
+
look 2-10% cheaper than it is.
|
|
115
|
+
3. **Scale the drivers** by target-over-weather-year capacity and demand
|
|
116
|
+
(`scale_drivers`), from ERAA, TYNDP or national plans.
|
|
117
|
+
4. **Run the model** (`PriceModel.predict`).
|
|
118
|
+
5. **Set the level** as the market's recent ratio times a gas plant's running
|
|
119
|
+
cost in the target year (`level_ratio`, `gas_plant_cost`,
|
|
120
|
+
`rescale_to_level`). **Err low**: an overestimated level costs a design far
|
|
121
|
+
more than an underestimated one.
|
|
122
|
+
6. **Check the reach** (`share_beyond_training`): where several percent of
|
|
123
|
+
hours lie beyond the net load the model was fitted on, floor the year at the
|
|
124
|
+
training years' 1% price quantile (`training_floor`).
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
*2030 from ERAA 2025 capacities and demand on 2025 weather. Germany and Poland
|
|
129
|
+
(13% and 7% of hours beyond the training net load) need the floor.*
|
|
130
|
+
|
|
131
|
+
## What it does not do
|
|
132
|
+
|
|
133
|
+
- **Negative hours.** It smooths away part of the midday collapse in
|
|
134
|
+
solar-heavy markets: 1.8% negative hours against 5.7% in reality in 2025. The
|
|
135
|
+
residual noise (`noise=True`) restores the share, but in hours uncorrelated
|
|
136
|
+
with the real ones - use it for revenue estimates, never for sizing.
|
|
137
|
+
- **Extrapolation.** It is linear in its drivers; far beyond them it must be
|
|
138
|
+
floored, and it has no storage driver, so future spreads are upper bounds.
|
|
139
|
+
- **Drifting coefficients.** The price response to solar steepens as the fleet
|
|
140
|
+
grows. Correcting the coefficients for that was tested and does not pay;
|
|
141
|
+
refit on recent years instead.
|
|
142
|
+
- **Other markets.** Day-ahead only - not intraday, balancing or forward.
|
|
143
|
+
|
|
144
|
+
## Install
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install git+https://github.com/Matt-haug/pricecast
|
|
148
|
+
pip install "pricecast[entsoe] @ git+https://github.com/Matt-haug/pricecast" # with ENTSO-E download
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Requires Python 3.9+, numpy and pandas. Downloading data needs `entsoe-py` and a
|
|
152
|
+
free ENTSO-E Transparency Platform token, passed as `token=` or set as the
|
|
153
|
+
`ENTSOE_TOKEN` environment variable.
|
|
154
|
+
|
|
155
|
+
## Data
|
|
156
|
+
|
|
157
|
+
The shipped models are fitted on ENTSO-E Transparency Platform data for
|
|
158
|
+
2024-2025: day-ahead prices, actual total load and actual generation per
|
|
159
|
+
production type. What ships is the fitted coefficients only; the raw series are
|
|
160
|
+
not redistributed and are downloaded with your own token.
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Issues and pull requests are welcome - see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
165
|
+
Reports that a generated year disagrees with a market you know well are
|
|
166
|
+
especially welcome. This project follows the
|
|
167
|
+
[Contributor Covenant](CODE_OF_CONDUCT.md). Generative AI was used in building
|
|
168
|
+
it; see [AI_USAGE.md](AI_USAGE.md).
|
|
169
|
+
|
|
170
|
+
## Citing
|
|
171
|
+
|
|
172
|
+
Use the "Cite this repository" button, or `CITATION.cff`. Each release is
|
|
173
|
+
archived on Zenodo with a DOI.
|
|
174
|
+
|
|
175
|
+
## Licence
|
|
176
|
+
|
|
177
|
+
MIT - see [LICENSE](LICENSE). Changes are recorded in [CHANGELOG.md](CHANGELOG.md).
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
[](https://pypi.org/project/pricecast/)
|
|
2
|
+
[](https://pricecast.readthedocs.io/en/latest/)
|
|
3
|
+
[](https://github.com/Matt-haug/pricecast/actions/workflows/tests.yml)
|
|
4
|
+
[](https://github.com/Matt-haug/pricecast/blob/main/LICENSE)
|
|
5
|
+
[](https://doi.org/10.5281/zenodo.23264989)
|
|
6
|
+
|
|
7
|
+
# pricecast
|
|
8
|
+
|
|
9
|
+
**Synthetic hourly electricity price time-series for prospective energy system studies**
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import pricecast
|
|
13
|
+
from pricecast.entsoe import fetch_year
|
|
14
|
+
|
|
15
|
+
prices, drivers = fetch_year("BE", 2025) # day-ahead price, load, solar, wind
|
|
16
|
+
model = pricecast.load("BE") # or pricecast.fit(prices, drivers)
|
|
17
|
+
|
|
18
|
+
future = pricecast.scale_drivers(drivers, solar=1.39, wind=1.38, load=1.30)
|
|
19
|
+
year = model.predict(future) # the shape of a 2030 year
|
|
20
|
+
year = pricecast.rescale_to_level(year, level=88) # its level, from a fuel-and-carbon scenario
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Returns an hourly series of day-ahead prices in EUR/MWh, ready to feed an LP or
|
|
24
|
+
MILP design model: a heat pump with storage, a battery, a thermal store, an
|
|
25
|
+
electrolyser.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
*The average day of 2025, each series over its own annual mean. The model (fitted
|
|
30
|
+
on 2023-24, driven by 2025's load, solar and wind) keeps the midday trough;
|
|
31
|
+
reusing a past year cannot know about the panels installed since.*
|
|
32
|
+
|
|
33
|
+
## Why
|
|
34
|
+
|
|
35
|
+
A study that sizes equipment for 2030 needs a 2030 price year. The usual
|
|
36
|
+
stand-in is a past year reused as it was, which carries the past's solar fleet,
|
|
37
|
+
the past's price level, and the past's weather. `pricecast` instead generates the
|
|
38
|
+
year from what a scenario *does* provide - installed solar and wind, demand and
|
|
39
|
+
fuel prices - so the features a design reacts to follow the scenario:
|
|
40
|
+
|
|
41
|
+
- how deep the price falls at midday, and how far it climbs in the evening;
|
|
42
|
+
- what a solar kilowatt-hour is worth against the average one;
|
|
43
|
+
- what the electricity for a kilowatt-hour of heat costs.
|
|
44
|
+
|
|
45
|
+
The model is one least-squares regression of hourly price on the clock and on
|
|
46
|
+
three drivers - load, solar and wind generation - fitted on recent years. A full
|
|
47
|
+
year is generated in one shot, with no autoregression on the price.
|
|
48
|
+
|
|
49
|
+
**It is not a forecaster.** Nobody has next decade's hourly solar output, so it
|
|
50
|
+
does not compete with forecasting models. It produces a plausible *shape* and
|
|
51
|
+
leaves the *level* to an outside scenario, because the level is set by fuel and
|
|
52
|
+
carbon prices and cannot be extrapolated from price history.
|
|
53
|
+
|
|
54
|
+
## What it has been tested on
|
|
55
|
+
|
|
56
|
+
Five European markets (BE, DE, FR, ES, PL), fitted on 2023-24 and judged on 2025
|
|
57
|
+
by what a design optimiser decides, not by hourly error:
|
|
58
|
+
|
|
59
|
+
| | this model | last year's prices reused |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| daily storage, share of achievable arbitrage captured | 88% | 60% |
|
|
62
|
+
| household with PV, battery and heat pump, share of savings captured | 93% | 82% |
|
|
63
|
+
| sizing penalty with the price level given, data 1 / 2 / 4 / 6 years old (EUR/yr) | 2 / 29 / 16 / 7 | 8 / 94 / 147 / 198 |
|
|
64
|
+
| midday price / annual mean, mean absolute error across countries | 0.09 | 0.11 |
|
|
65
|
+
| solar value factor, mean absolute error | 0.07 | 0.11 |
|
|
66
|
+
|
|
67
|
+
Full results, and where it falls short, are in the accompanying paper.
|
|
68
|
+
|
|
69
|
+
## Building a future year
|
|
70
|
+
|
|
71
|
+
The [recipe](docs/recipe.md), in short:
|
|
72
|
+
|
|
73
|
+
1. **Fit** on the two most recent complete years, or start from `pricecast.load`.
|
|
74
|
+
2. **Choose one weather year** and take every weather-dependent input from it:
|
|
75
|
+
the model's drivers, and your asset's PV output, heat demand and ambient
|
|
76
|
+
temperature. Prices respond to the same weather; mixing years makes heat
|
|
77
|
+
look 2-10% cheaper than it is.
|
|
78
|
+
3. **Scale the drivers** by target-over-weather-year capacity and demand
|
|
79
|
+
(`scale_drivers`), from ERAA, TYNDP or national plans.
|
|
80
|
+
4. **Run the model** (`PriceModel.predict`).
|
|
81
|
+
5. **Set the level** as the market's recent ratio times a gas plant's running
|
|
82
|
+
cost in the target year (`level_ratio`, `gas_plant_cost`,
|
|
83
|
+
`rescale_to_level`). **Err low**: an overestimated level costs a design far
|
|
84
|
+
more than an underestimated one.
|
|
85
|
+
6. **Check the reach** (`share_beyond_training`): where several percent of
|
|
86
|
+
hours lie beyond the net load the model was fitted on, floor the year at the
|
|
87
|
+
training years' 1% price quantile (`training_floor`).
|
|
88
|
+
|
|
89
|
+

|
|
90
|
+
|
|
91
|
+
*2030 from ERAA 2025 capacities and demand on 2025 weather. Germany and Poland
|
|
92
|
+
(13% and 7% of hours beyond the training net load) need the floor.*
|
|
93
|
+
|
|
94
|
+
## What it does not do
|
|
95
|
+
|
|
96
|
+
- **Negative hours.** It smooths away part of the midday collapse in
|
|
97
|
+
solar-heavy markets: 1.8% negative hours against 5.7% in reality in 2025. The
|
|
98
|
+
residual noise (`noise=True`) restores the share, but in hours uncorrelated
|
|
99
|
+
with the real ones - use it for revenue estimates, never for sizing.
|
|
100
|
+
- **Extrapolation.** It is linear in its drivers; far beyond them it must be
|
|
101
|
+
floored, and it has no storage driver, so future spreads are upper bounds.
|
|
102
|
+
- **Drifting coefficients.** The price response to solar steepens as the fleet
|
|
103
|
+
grows. Correcting the coefficients for that was tested and does not pay;
|
|
104
|
+
refit on recent years instead.
|
|
105
|
+
- **Other markets.** Day-ahead only - not intraday, balancing or forward.
|
|
106
|
+
|
|
107
|
+
## Install
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install git+https://github.com/Matt-haug/pricecast
|
|
111
|
+
pip install "pricecast[entsoe] @ git+https://github.com/Matt-haug/pricecast" # with ENTSO-E download
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Requires Python 3.9+, numpy and pandas. Downloading data needs `entsoe-py` and a
|
|
115
|
+
free ENTSO-E Transparency Platform token, passed as `token=` or set as the
|
|
116
|
+
`ENTSOE_TOKEN` environment variable.
|
|
117
|
+
|
|
118
|
+
## Data
|
|
119
|
+
|
|
120
|
+
The shipped models are fitted on ENTSO-E Transparency Platform data for
|
|
121
|
+
2024-2025: day-ahead prices, actual total load and actual generation per
|
|
122
|
+
production type. What ships is the fitted coefficients only; the raw series are
|
|
123
|
+
not redistributed and are downloaded with your own token.
|
|
124
|
+
|
|
125
|
+
## Contributing
|
|
126
|
+
|
|
127
|
+
Issues and pull requests are welcome - see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
128
|
+
Reports that a generated year disagrees with a market you know well are
|
|
129
|
+
especially welcome. This project follows the
|
|
130
|
+
[Contributor Covenant](CODE_OF_CONDUCT.md). Generative AI was used in building
|
|
131
|
+
it; see [AI_USAGE.md](AI_USAGE.md).
|
|
132
|
+
|
|
133
|
+
## Citing
|
|
134
|
+
|
|
135
|
+
Use the "Cite this repository" button, or `CITATION.cff`. Each release is
|
|
136
|
+
archived on Zenodo with a DOI.
|
|
137
|
+
|
|
138
|
+
## Licence
|
|
139
|
+
|
|
140
|
+
MIT - see [LICENSE](LICENSE). Changes are recorded in [CHANGELOG.md](CHANGELOG.md).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pricecast"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Synthetic hourly electricity price years for prospective energy system studies"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Matteo Hauglustaine", email = "matteo@abeo.be" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"electricity",
|
|
17
|
+
"day-ahead price",
|
|
18
|
+
"price profile",
|
|
19
|
+
"synthetic data",
|
|
20
|
+
"energy system modelling",
|
|
21
|
+
"techno-economic analysis",
|
|
22
|
+
"duck curve",
|
|
23
|
+
"time series",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.9",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Topic :: Scientific/Engineering",
|
|
34
|
+
]
|
|
35
|
+
dependencies = [
|
|
36
|
+
"numpy>=1.21",
|
|
37
|
+
"pandas>=1.3",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
entsoe = ["entsoe-py>=0.6"]
|
|
42
|
+
plot = ["matplotlib>=3.5"]
|
|
43
|
+
test = ["pytest>=7.0"]
|
|
44
|
+
docs = ["mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24"]
|
|
45
|
+
dev = ["pricecast[entsoe,plot,test,docs]"]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/Matt-haug/pricecast"
|
|
49
|
+
Repository = "https://github.com/Matt-haug/pricecast"
|
|
50
|
+
Issues = "https://github.com/Matt-haug/pricecast/issues"
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
where = ["src"]
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
pricecast = ["data/*.json"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
addopts = "-q"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""pricecast -- synthetic hourly electricity price years for prospective studies.
|
|
2
|
+
|
|
3
|
+
A full year of hourly day-ahead prices, generated in one shot from three
|
|
4
|
+
physical drivers -- load, solar and wind generation -- by a regression fitted
|
|
5
|
+
on recent years. It is built to be an *input* to LP and MILP design studies
|
|
6
|
+
(heat pumps, batteries, thermal stores, electrolysers), not to forecast
|
|
7
|
+
tomorrow's price.
|
|
8
|
+
|
|
9
|
+
import pricecast
|
|
10
|
+
|
|
11
|
+
model = pricecast.fit(prices, drivers) # or pricecast.load("BE")
|
|
12
|
+
year = model.predict(future_drivers) # the shape of the year
|
|
13
|
+
year = pricecast.rescale_to_level(year, level=80) # the level, from outside
|
|
14
|
+
|
|
15
|
+
What it is for
|
|
16
|
+
--------------
|
|
17
|
+
Because the price is driven by physical quantities, a scenario can be posed
|
|
18
|
+
directly: double the solar fleet, grow the demand, and read off what happens to
|
|
19
|
+
the *shape* of the price year -- the midday trough, the evening ramp, the value
|
|
20
|
+
of a solar kilowatt-hour. The level of the year is set from outside, from a
|
|
21
|
+
fuel-and-carbon scenario, because it cannot be extrapolated from price history.
|
|
22
|
+
|
|
23
|
+
What it is not
|
|
24
|
+
--------------
|
|
25
|
+
Not a forecaster: there are no future solar and wind series to forecast from.
|
|
26
|
+
It smooths away part of the midday price collapse in solar-heavy markets and
|
|
27
|
+
produces too few negative hours unless its residual noise is switched on --
|
|
28
|
+
which should stay off for sizing. It is linear in its drivers, so far outside
|
|
29
|
+
the range it was fitted on it must be floored (`share_beyond_training`).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from .features import DEFAULT_FEATURES, DRIVERS, design_matrix
|
|
35
|
+
from .model import PriceModel, fit, residual_noise
|
|
36
|
+
from .pretrained import available, load
|
|
37
|
+
from .scenario import (
|
|
38
|
+
CCGT_EFFICIENCY,
|
|
39
|
+
GAS_EMISSION_FACTOR,
|
|
40
|
+
gas_plant_cost,
|
|
41
|
+
level_ratio,
|
|
42
|
+
mid_year_capacity,
|
|
43
|
+
net_load,
|
|
44
|
+
rescale_to_level,
|
|
45
|
+
scale_drivers,
|
|
46
|
+
share_beyond_training,
|
|
47
|
+
training_floor,
|
|
48
|
+
)
|
|
49
|
+
from .structure import METRICS, compare, lay_on_calendar, structure_metrics
|
|
50
|
+
|
|
51
|
+
__version__ = "0.1.0"
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"CCGT_EFFICIENCY", "DEFAULT_FEATURES", "DRIVERS", "GAS_EMISSION_FACTOR",
|
|
55
|
+
"METRICS", "PriceModel", "available", "compare", "design_matrix", "fit",
|
|
56
|
+
"gas_plant_cost", "lay_on_calendar", "level_ratio", "load",
|
|
57
|
+
"mid_year_capacity", "net_load", "rescale_to_level", "residual_noise",
|
|
58
|
+
"scale_drivers", "share_beyond_training", "structure_metrics",
|
|
59
|
+
"training_floor", "__version__",
|
|
60
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"coefficients": {
|
|
3
|
+
"const": -56.5235359601092,
|
|
4
|
+
"hour_1": -0.7669196170514694,
|
|
5
|
+
"hour_2": -2.731116500299967,
|
|
6
|
+
"hour_3": -3.4527653674331056,
|
|
7
|
+
"hour_4": -1.6429779999073855,
|
|
8
|
+
"hour_5": 0.7686290646237683,
|
|
9
|
+
"hour_6": 2.595466752009719,
|
|
10
|
+
"hour_7": -3.1358589017332994,
|
|
11
|
+
"hour_8": -10.676045837259961,
|
|
12
|
+
"hour_9": -14.506601010880328,
|
|
13
|
+
"hour_10": -16.673921806288583,
|
|
14
|
+
"hour_11": -18.520858353391933,
|
|
15
|
+
"hour_12": -18.298984811653252,
|
|
16
|
+
"hour_13": -13.698092038253144,
|
|
17
|
+
"hour_14": -8.675930491839814,
|
|
18
|
+
"hour_15": -3.661220973966519,
|
|
19
|
+
"hour_16": 5.2232791877790055,
|
|
20
|
+
"hour_17": 15.749246737076014,
|
|
21
|
+
"hour_18": 17.400630672850472,
|
|
22
|
+
"hour_19": 9.296905386332488,
|
|
23
|
+
"hour_20": 4.1710832202910435,
|
|
24
|
+
"hour_21": 1.1066447217502757,
|
|
25
|
+
"hour_22": -0.32508525239284314,
|
|
26
|
+
"hour_23": -0.49413458536332555,
|
|
27
|
+
"dow_0": 9.264707146449423,
|
|
28
|
+
"dow_1": 8.976551496191302,
|
|
29
|
+
"dow_2": 8.43460328506335,
|
|
30
|
+
"dow_3": 6.333805991988828,
|
|
31
|
+
"dow_4": 5.728469942510179,
|
|
32
|
+
"dow_5": 2.6026486669747912,
|
|
33
|
+
"load": 0.020011661479056832,
|
|
34
|
+
"solar": -0.017238509794863417,
|
|
35
|
+
"wind": -0.018118779297168295,
|
|
36
|
+
"net_load_sq": -2.1177924502957524e-07
|
|
37
|
+
},
|
|
38
|
+
"residual_std": 27.2859419579615,
|
|
39
|
+
"student_df": 4.2522262189900415,
|
|
40
|
+
"features": {
|
|
41
|
+
"hour_dummies": true,
|
|
42
|
+
"weekday_dummies": true,
|
|
43
|
+
"net_load_curvature": true,
|
|
44
|
+
"driver_lags": [],
|
|
45
|
+
"timezone": "UTC"
|
|
46
|
+
},
|
|
47
|
+
"metadata": {
|
|
48
|
+
"country": "BE",
|
|
49
|
+
"bidding_zone": "BE",
|
|
50
|
+
"train_years": [
|
|
51
|
+
2024,
|
|
52
|
+
2025
|
|
53
|
+
],
|
|
54
|
+
"hours": 17544,
|
|
55
|
+
"source": "ENTSO-E Transparency Platform: day-ahead prices, actual total load, actual generation per production type (solar; wind onshore + offshore), hourly means",
|
|
56
|
+
"fitted": "2026-10-09",
|
|
57
|
+
"pricecast_version": "0.1.0",
|
|
58
|
+
"r2_in_sample": 0.645
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"coefficients": {
|
|
3
|
+
"const": 7.19026510279194,
|
|
4
|
+
"hour_1": -1.20148543350422,
|
|
5
|
+
"hour_2": -2.767557895420281,
|
|
6
|
+
"hour_3": -3.169483224275901,
|
|
7
|
+
"hour_4": -0.14403996269299663,
|
|
8
|
+
"hour_5": 5.268354869113266,
|
|
9
|
+
"hour_6": 8.805548839332554,
|
|
10
|
+
"hour_7": 9.178618955644064,
|
|
11
|
+
"hour_8": 6.862625763288568,
|
|
12
|
+
"hour_9": 5.06106822195469,
|
|
13
|
+
"hour_10": 3.13431433336983,
|
|
14
|
+
"hour_11": 0.3874876888249261,
|
|
15
|
+
"hour_12": -0.3579715679936854,
|
|
16
|
+
"hour_13": 1.4653212233822654,
|
|
17
|
+
"hour_14": 4.1527596040978265,
|
|
18
|
+
"hour_15": 8.399679937223057,
|
|
19
|
+
"hour_16": 15.501495821102045,
|
|
20
|
+
"hour_17": 21.82537980978079,
|
|
21
|
+
"hour_18": 18.968835983927757,
|
|
22
|
+
"hour_19": 6.8344665793981125,
|
|
23
|
+
"hour_20": 1.06408943971504,
|
|
24
|
+
"hour_21": -0.26925928084918693,
|
|
25
|
+
"hour_22": 0.4301775232787318,
|
|
26
|
+
"hour_23": 0.9847530077836216,
|
|
27
|
+
"dow_0": 4.52046221906403,
|
|
28
|
+
"dow_1": 5.354188120736446,
|
|
29
|
+
"dow_2": 4.519355385955019,
|
|
30
|
+
"dow_3": 2.9186525163558947,
|
|
31
|
+
"dow_4": 3.1473642291647206,
|
|
32
|
+
"dow_5": 2.9664983842534376,
|
|
33
|
+
"load": 0.0022518423956802624,
|
|
34
|
+
"solar": -0.0027591543389505445,
|
|
35
|
+
"wind": -0.0023582761389554195,
|
|
36
|
+
"net_load_sq": 6.597765889182483e-09
|
|
37
|
+
},
|
|
38
|
+
"residual_std": 28.829962242404854,
|
|
39
|
+
"student_df": 4.2,
|
|
40
|
+
"features": {
|
|
41
|
+
"hour_dummies": true,
|
|
42
|
+
"weekday_dummies": true,
|
|
43
|
+
"net_load_curvature": true,
|
|
44
|
+
"driver_lags": [],
|
|
45
|
+
"timezone": "UTC"
|
|
46
|
+
},
|
|
47
|
+
"metadata": {
|
|
48
|
+
"country": "DE",
|
|
49
|
+
"bidding_zone": "DE_LU",
|
|
50
|
+
"train_years": [
|
|
51
|
+
2024,
|
|
52
|
+
2025
|
|
53
|
+
],
|
|
54
|
+
"hours": 17544,
|
|
55
|
+
"source": "ENTSO-E Transparency Platform: day-ahead prices, actual total load, actual generation per production type (solar; wind onshore + offshore), hourly means",
|
|
56
|
+
"fitted": "2026-10-09",
|
|
57
|
+
"pricecast_version": "0.1.0",
|
|
58
|
+
"r2_in_sample": 0.7006
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"coefficients": {
|
|
3
|
+
"const": -68.94520315102986,
|
|
4
|
+
"hour_1": 2.320155607076745,
|
|
5
|
+
"hour_2": 2.5050613582949826,
|
|
6
|
+
"hour_3": 1.8798222606992945,
|
|
7
|
+
"hour_4": -0.8283885716266107,
|
|
8
|
+
"hour_5": -5.4451690988078,
|
|
9
|
+
"hour_6": -11.433150624700835,
|
|
10
|
+
"hour_7": -14.159816410276242,
|
|
11
|
+
"hour_8": -13.889161759628301,
|
|
12
|
+
"hour_9": -10.944550285386548,
|
|
13
|
+
"hour_10": -8.048337710439503,
|
|
14
|
+
"hour_11": -7.456152458198253,
|
|
15
|
+
"hour_12": -8.279015036613128,
|
|
16
|
+
"hour_13": -6.669495626779215,
|
|
17
|
+
"hour_14": -3.86999751808008,
|
|
18
|
+
"hour_15": -1.4653887129495744,
|
|
19
|
+
"hour_16": -3.034383058522941,
|
|
20
|
+
"hour_17": -6.727737759258486,
|
|
21
|
+
"hour_18": -12.90116969919224,
|
|
22
|
+
"hour_19": -17.297175837922904,
|
|
23
|
+
"hour_20": -22.455224904517042,
|
|
24
|
+
"hour_21": -17.25102664098335,
|
|
25
|
+
"hour_22": -8.764447369468675,
|
|
26
|
+
"hour_23": -4.0222397937104,
|
|
27
|
+
"dow_0": -5.155731171679184,
|
|
28
|
+
"dow_1": -6.476037042409885,
|
|
29
|
+
"dow_2": -5.680993359830503,
|
|
30
|
+
"dow_3": -4.964853006794855,
|
|
31
|
+
"dow_4": -4.594212528162333,
|
|
32
|
+
"dow_5": -0.07443642823720201,
|
|
33
|
+
"load": 0.008847771880458202,
|
|
34
|
+
"solar": -0.006384871806866504,
|
|
35
|
+
"wind": -0.007110465646601332,
|
|
36
|
+
"net_load_sq": -3.9232280091283656e-08
|
|
37
|
+
},
|
|
38
|
+
"residual_std": 28.049732208308708,
|
|
39
|
+
"student_df": 30.0,
|
|
40
|
+
"features": {
|
|
41
|
+
"hour_dummies": true,
|
|
42
|
+
"weekday_dummies": true,
|
|
43
|
+
"net_load_curvature": true,
|
|
44
|
+
"driver_lags": [],
|
|
45
|
+
"timezone": "UTC"
|
|
46
|
+
},
|
|
47
|
+
"metadata": {
|
|
48
|
+
"country": "ES",
|
|
49
|
+
"bidding_zone": "ES",
|
|
50
|
+
"train_years": [
|
|
51
|
+
2024,
|
|
52
|
+
2025
|
|
53
|
+
],
|
|
54
|
+
"hours": 17544,
|
|
55
|
+
"source": "ENTSO-E Transparency Platform: day-ahead prices, actual total load, actual generation per production type (solar; wind onshore + offshore), hourly means",
|
|
56
|
+
"fitted": "2026-10-09",
|
|
57
|
+
"pricecast_version": "0.1.0",
|
|
58
|
+
"r2_in_sample": 0.6406
|
|
59
|
+
}
|
|
60
|
+
}
|