Skip to main content

pricecast

PyPI version Documentation Status Build Status License DOI

A parametric model of hourly electricity prices, for scenario sweeps, uncertainty propagation and future years.

pricecast turns three hourly drivers - load, solar and wind generation - into a full year of hourly day-ahead prices: 8760 values in EUR/MWh, generated in one shot and ready to feed an LP or MILP design model. The model is a regression fitted on market years, so it responds to its drivers as real prices have: more solar lowers the price in the hours when the sun shines, more wind in the windy hours, more demand in every hour.

What it is for

Use it when a design study needs hourly prices for a year that has not happened, or for many such years. You are sizing or operating something that buys or sells electricity by the hour - a heat pump with a thermal store, a battery, rooftop PV, an electrolyser. The usual stand-in is a past price year reused as it was, which carries the past's solar fleet, price level and weather, and the whole study then rests on that one year.

Because the model is parametric in its drivers, it does three things a reused year cannot:

  • Sweep a scenario. Change one driver at a time - double the solar fleet, grow demand by a third - and read what happens to the midday trough, the evening ramp and the value of a solar kilowatt-hour. Rescaling a past year moves its level or stretches its spread, but every hour keeps its rank; scaling the drivers changes which hours are cheap.
  • Propagate uncertainty. A price year takes a fraction of a second, so hundreds of them, one per sampled scenario, take seconds. A market model needs a full simulation for each.
  • Build a future year from a market-model scenario. A scenario such as ENTSO-E's TYNDP 2026 supplies the fleet, the demand and the price level; the model supplies the hourly shape that follows from them. Where the scenario publishes its hourly results, fit the model on them, and it becomes a cheap stand-in for the market model around that scenario.

It is not a forecaster, and not a market model. Nobody has next decade's hourly weather, so it does not predict the price of a given hour; and it does not dispatch storage or demand response. The scenario carries those, through its price level and, when the model is fitted on its hourly results, through the coefficients.

Install

pip install pricecast
pip install "pricecast[entsoe]"    # to download market data from ENTSO-E

Requires Python 3.9+, numpy and pandas.

Getting an ENTSO-E token

Downloading market data needs a free security token from the ENTSO-E Transparency Platform. ENTSO-E's guide is How to get security token?; in short:

  1. Register an account on the Transparency Platform.
  2. Email transparency@entsoe.eu with "Restful API access" as the subject and your registered email address in the body.
  3. Once ENTSO-E has granted access, generate the token in your account settings.
  4. Pass it as token=, or set it once as the ENTSOE_TOKEN environment variable. Keep it out of version control.

You only need a token to download data: the shipped models, fit on your own series and everything else work without one.

Quick start

import pricecast
from pricecast.entsoe import fetch_year

prices, drivers = fetch_year("BE", 2025)   # day-ahead price; load, solar, wind
model = pricecast.load("BE")               # a shipped model: BE, DE, FR, ES, PL
year = model.predict(drivers)              # an hourly price year, EUR/MWh

Or fit your own, on any market with hourly prices and drivers:

model = pricecast.fit([prices_2024, prices_2025], [drivers_2024, drivers_2025])
model.to_json("my_market.json")

A future year from TYNDP

The scenario gives the target year's fleet, demand and price level; the model gives the shape. With Belgium's 2030 figures from TYNDP 2026 (central scenario, National Trends+):

import pandas as pd

(p24, d24), (p25, d25) = fetch_year("BE", 2024), fetch_year("BE", 2025)

# 1. scale the 2025 weather year to TYNDP's 2030 fleet (GW) and demand (TWh)
solar_2025 = pricecast.mid_year_capacity(10.70, later=15.64, years_between=5)
wind_2025 = pricecast.mid_year_capacity(5.44, later=7.84, years_between=5)
future = pricecast.scale_drivers(d25, solar=15.64 / solar_2025, wind=7.84 / wind_2025,
                                 load=110.7e6 / d25["load"].sum())

# 2. generate the shape, floor it, and give it TYNDP's load-weighted mean price
floor = pricecast.training_floor(pd.concat([p24, p25]))
year = pricecast.rescale_to_level(model.predict(future), level=65.1, floor=floor,
                                  weights=future["load"])

# 3. check how far the model reaches: % of hours beyond its training net load
pricecast.share_beyond_training(future, pd.concat([d24, d25]))

The recipe explains each step and where every number comes from. For a target year beyond the model's reach, or to carry TYNDP's storage and demand response into the shape, fit the model on TYNDP's own hourly results (recipe, step 1).

Sweeps and uncertainty

Each price year costs a matrix product, so a scenario's uncertainty can be sampled rather than argued. For Belgium in 2035 - the solar and wind build-out five years early or late, demand and price level between TYNDP's lower- and higher-growth variants:

import numpy as np

rng = np.random.default_rng(1)
years = []
for solar, wind, demand, level in rng.uniform([15.6, 7.8, 122.7, 38.0],
                                              [29.5, 17.6, 145.3, 109.3], size=(200, 4)):
    future = pricecast.scale_drivers(d25, solar=solar / solar_2025, wind=wind / wind_2025,
                                     load=demand * 1e6 / d25["load"].sum())
    years.append(pricecast.rescale_to_level(model.predict(future), level, floor=floor,
                                            weights=future["load"]))

Feed each year to your design model, and fit a polynomial chaos expansion to its outputs to rank the inputs; the uncertainty page shows how, and what to check before reading the result.

Good practice

  • One weather year for everything. Take the model's drivers and your asset's PV output, heat demand and ambient temperature from the same year, so that prices and demand respond to the same sun, wind and cold.
  • Noise off for sizing. predict(..., noise=True) adds a residual process that restores price spikes and negative hours, but in hours that do not line up with the real ones. Use it for revenue and risk estimates, not to size equipment.
  • Take the level from the scenario; if unsure, choose the low end of your range. Match it as a load-weighted mean (weights=), as TYNDP reports it. Rescaling multiplies every hour, so a level set too high also widens the gap between cheap and dear hours: flexibility looks more valuable than it is, and the design costs more than one sized on a level set too low by the same amount.
  • Watch the reach. The model is linear in its drivers. Count the hours beyond its training net load (share_beyond_training) and the hours left on the floor after rescaling. While both stay at a few percent, use the year; past that, use the scenario's own hourly prices.
  • Read spreads as upper bounds when the model is fitted on market years: it holds storage and demand response at their training-year level, while a scenario adds both. Fitted on the scenario's hourly results, it carries them.
  • Day-ahead only - not intraday, balancing or forward markets.

Documentation

  • Usage: data formats, fitting, generating, checking a year's structure.
  • Recipe: a future year from TYNDP step by step, with sources for every input.
  • Sweeps and uncertainty: one driver at a time, sampled scenarios, and sensitivity indices.
  • Mathematics: the model, the residual process, the level, the floor and the reach.
  • examples/: a quick start, a future year and a sampled scenario.

Data

The shipped models are fitted on ENTSO-E Transparency Platform data for 2024-2025: day-ahead prices, actual total load and actual generation per production type. Only the fitted coefficients ship; the raw series are downloaded with your own token. The scenario figures quoted in the examples come from the TYNDP 2026 scenarios of ENTSO-E and ENTSOG.

Citing

Use the "Cite this repository" button or CITATION.cff. Every release is archived on Zenodo: 10.5281/zenodo.23264988 always points to the latest version.

Contributing

Issues and pull requests are welcome - see CONTRIBUTING.md. Reports of a generated year that disagrees with a market you know well are especially useful. This project follows the Contributor Covenant. Generative AI was used in building it; see AI_USAGE.md.

Licence

MIT - see LICENSE. Changes are recorded in CHANGELOG.md.

Metadata

Release files for pricecast 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pricecast 0.1.5
File Size Uploaded
pricecast-0.1.5.tar.gz 91.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pricecast 0.1.5
File Interpreter ABI Platform
pricecast-0.1.5-py3-none-any.whl Python 3 none any Details

Total release size: 116.8 kB

Release files / pricecast-0.1.5.tar.gz

Download URL pricecast-0.1.5.tar.gz
Size 91.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3d755d5f3c904627869797de13d2a6e26f49d58a6440f0ff393c79f604b6a0f5
BLAKE2b-256 checksum
How to use checksums
2060366328e4d2523aebac791a557430f231f623ec2d368e64237906e8b0cecc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / pricecast-0.1.5-py3-none-any.whl

Download URL pricecast-0.1.5-py3-none-any.whl
Size 25.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f93bed9f74e17e0eaef8e9991d10c1b134b67ea5adfe92797bc5b8a6b52f4885
BLAKE2b-256 checksum
How to use checksums
76b11e7bc95dbf6858e0f20bed3c7277fb8949ce64a83959d9cd2f60f3c79d7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.5 This release

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page