CastValue — Quantifying the EUR/MW Value of Forecasts in Energy Trading
CastValue is an open, multi-benchmark framework that measures what a forecast is actually worth in a real trading/dispatch decision — in euros, not error metrics.
This repository currently contains one fully built benchmark (castvalue.bess.daa, described below) plus a second one under active development (castvalue.pv.imbalance, a PV imbalance-cost benchmark) and a placeholder for a third (castvalue.bess.multimarket, scope only — see src/castvalue/bess/multimarket/README.md). The rest of this README describes the original, fully-built BESS day-ahead-only benchmark.
Most forecast providers sell on MAE. Most traders buy on MAE. CastValue shows why that is the wrong metric, and replaces it with one that directly reflects trading P&L.
The Problem
A forecast with a low MAE can destroy more trading value than one with a high MAE. This is not a theoretical edge case — it happens regularly, and this repository demonstrates it empirically on real German DAA price data.
The reason: what matters for BESS dispatch is not how close the forecast price is to the actual price, but whether the forecast correctly ranks the cheap and expensive hours of the day. A forecast can be numerically accurate yet systematically wrong about timing — causing the optimizer to charge when it should discharge, and vice versa.
What CastValue Measures
CastValue is defined as the annual DAA trading margin generated by optimizing BESS dispatch on a given forecast, minus the margin generated by a naive persistence baseline (yesterday's prices as today's forecast):
CastValue(k) = AnnualMargin(forecast k) − AnnualMargin(persistence)
A positive CastValue means the forecast adds real EUR/MW above what any trader could achieve with zero forecasting effort. A negative CastValue means the forecast is worse than doing nothing.
Why This Matters
For BESS traders and asset managers: Stop evaluating forecast providers on MAE. CastValue tells you directly how much each forecast contributes to your annual trading margin, in the currency that appears on your P&L. Use this tool to compare your current provider against alternatives — and against the theoretical ceiling of perfect foresight.
For forecast providers: Benchmark and showcase your forecast in the metric your clients care about. A CastValue number is a commercially credible claim: it tells potential customers exactly how many EUR/MW per year your forecast is worth, backed by a transparent, reproducible methodology.
The MAE Paradox
The backtester surfaces a systematic finding we call the MAE Paradox: days with high forecast error (high MAE) do not reliably lead to high profit loss, and days with low forecast error do not reliably lead to high profit capture.
We classify days into two categories:
- Bad Forecast, Good Capture — high MAE, but the optimizer still dispatched at the right times
- Good Forecast, Bad Capture — low MAE, but a subtle timing shift caused the optimizer to miss the most profitable spreads
This has direct implications for how forecast quality should be specified in commercial contracts.
Methodology
The backtester runs a linear programme (LP) for each day of the dataset, optimising BESS charge/discharge decisions under realistic physical and market frictions:
- Round-Trip Efficiency (85%) in the LP energy balance — every MWh delivered to the grid requires ~1.176 MWh purchased, so the LP only schedules trades that survive RTE losses.
- Opportunity-cost bidding — charge bids = avg forecast discharge price × RTE; discharge asks = avg forecast charge price ÷ RTE. These are the realistic limit prices a desk would post given the forecast.
- Out-of-the-money rejection — if the actual clearing price is unfavourable vs. the posted bid/ask, the MTU does not fill.
- Volume-balanced paired rejection — when OTM fills leave charge and discharge volumes unbalanced, worst-margin MTUs are dropped on the oversubscribed side until volumes match. This forces end-of-day SOC = start-of-day SOC, so forecast accuracy directly drives the fill rate.
- Perfect Foresight is defined as the LP optimum given RTE, with bids placed at clearing prices (100% fill rate). It still pays the RTE loss — a "perfect scheduling" benchmark, not a frictionless one.
Three scenarios are evaluated for each day:
| Scenario | Forecast used |
|---|---|
| Persistence | Yesterday's actual prices |
| Electricity Maps | Electricity Maps DAA price forecast (benchmarked provider) |
| Perfect Foresight | Actual prices (theoretical ceiling) |
Trading decisions are optimised on forecast prices. Realised margin is always calculated on actual prices.
The dataset covers DE-LU at native 15-minute resolution from 18 October 2025 through 22 February 2026 (126 days).
For the full mathematical formulation see docs/methodology/DAA_DE_LU_Formulation_v1.1.pdf.
Earlier methodology (v1.0): the original single-cycle deterministic dispatch (no RTE, no OTM, fixed daily SOC=0) is preserved at the
v1.0git tag. Check out that tag to access the v1.0 notebook, optimizer, formulation PDF, and whitepaper.
Repository Structure
CastValue/
├── README.md
├── LICENSE
├── DAA_optimizer.py # backward-compat shim -> castvalue.bess.daa.optimizer (deprecated)
├── pyproject.toml
├── src/castvalue/
│ ├── core/ # shared machinery: coalitions, Shapley/Harsanyi, pricing, ceiling checks
│ ├── bess/
│ │ ├── daa/optimizer.py # this benchmark: Pyomo/GLPK LP optimizer
│ │ └── multimarket/ # planned, not implemented — raises NotImplementedError
│ └── pv/imbalance/ # PV imbalance-cost benchmark, under active development
├── examples/
│ └── DAA_DE_LU_Bench.ipynb # main analysis notebook for this benchmark
├── data/
│ └── master_energy_data_v2.csv # 15-min DE-LU dataset (Oct 2025 → Feb 2026)
├── docs/
│ ├── methodology/ # DAA_DE_LU_Formulation_v1.1.pdf/.tex
│ ├── whitepaper/ # whitepaper PDF
│ └── img/ # wp_annual_results.png, wp_cumulative_margin.png, wp_example_day.png
└── tests/
└── bess/daa/golden/ # golden regression fixtures guarding this benchmark's numerics
The notebook is structured in four sections:
- Optimisation & backtest — runs the LP for all days and all scenarios
- Annual results — cumulative margin chart and CastValue bar chart
- Example day — price forecasts, dispatch decisions, and realised margin for a single day
- The MAE Paradox — scatter plot and deep-dive on the paradox between forecast accuracy and profit capture
How to Benchmark Your Own Forecast
To run CastValue on your own forecast data, prepare a CSV with the following columns:
| Column | Description |
|---|---|
timestamp_utc |
Quarter-hour timestamps in UTC |
daa_actuals |
Actual DAA settlement prices [EUR/MWh] |
daa_persistence_forecast |
Persistence forecast (previous day's actuals) [EUR/MWh] |
daa_em_forecast |
Your forecast [EUR/MWh] (rename to your provider) |
Replace the daa_em_forecast column in data/master_energy_data_v2.csv with your own data, update the scenario name in examples/DAA_DE_LU_Bench.ipynb, and run all cells. The notebook expects native 15-minute resolution.
Installing
pip install "castvalue_bench[bess]"
This benchmark also needs a GLPK solver binary on your system (not a pip package) — e.g. conda install -c conda-forge glpk, then point Optimizer(solverpath_exe=...) at the installed glpsol binary. Running the notebook additionally needs pandas, matplotlib, and jupyter.
About
CastValue is developed by FlexUp GmbH. For enquiries about forecast benchmarking or CastValue licensing, please get in touch.
Metadata
Release files for castvalue-bench 1.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| castvalue_bench-1.3.3.tar.gz | 67.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| castvalue_bench-1.3.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 127.2 kB
Release files / castvalue_bench-1.3.3.tar.gz
| Download URL | castvalue_bench-1.3.3.tar.gz |
|---|---|
| Size | 67.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
edf98ac43fe307d2252b9283d0b9689dbaa4de9bf29a525046da051ecd99bb20
|
|
BLAKE2b-256 checksum How to use checksums |
5810b2828a0f980bfc5baf61b3b3c52e6a28f3b54fd4c1a1311b8f5d1ba75a6d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 26, 2026.
Transparency logRelease files / castvalue_bench-1.3.3-py3-none-any.whl
| Download URL | castvalue_bench-1.3.3-py3-none-any.whl |
|---|---|
| Size | 59.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7d752790c932742bceedf721fdcebe657fa11aa382e5f92fe93735316b6fdc53
|
|
BLAKE2b-256 checksum How to use checksums |
db6f5e71924315b5844ff242e71d7984bbe04fdef7f3919f7f3c2d657f2d0c83
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 26, 2026.
Transparency logSecurity for castvalue-bench 1.3.3
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for castvalue_bench-1.3.3.tar.gz
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 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.