Revenue stacking¶
A GB battery can sell more than day-ahead trading. OpenEnergy v3.5 adds the frequency response services NESO buys every day, co-optimised with trading in the same plan, and measures what the imbalance (balancing) price is worth with the information open data gives.
Frequency response¶
NESO buys three dynamic response services, each in a low direction (export when frequency falls) and a high direction (import when it rises), by day-ahead auction in four-hour EFA blocks:
| Service | Codes | Responds | Energy held |
|---|---|---|---|
| Dynamic Containment | DCL, DCH |
mostly beyond ±0.2 Hz, after a fault | 15 minutes |
| Dynamic Moderation | DML, DMH |
mostly beyond ±0.1 Hz | 30 minutes |
| Dynamic Regulation | DRL, DRH |
continuously beyond ±0.015 Hz | 60 minutes |
Add a response section to a battery scenario
(reference):
response:
path: ../data/response/gb_dynamic_response.csv
services: [DCL, DCH]
uv run openenergy run examples/gb-2025-stacked.yaml
How it is modelled¶
Each day is planned at the usual decision time (noon the day before) as one optimisation over trading and response:
- MW per block. For every block and service the plan chooses MW held, constant across the block. Blocks that run past midnight are fixed by the day they start in and carried into the next day's plan.
- Power. MW held for low services plus net discharge cannot exceed the power rating; likewise for high services and net charge. MW contracted in each direction is at most the power rating.
- Energy. The battery keeps enough stored energy (or room) to deliver each service for its full duration at every point in the block, and, for a block that runs past the horizon, to stay idle until it ends.
- Utilisation. Responding moves energy. OpenEnergy uses the monthly average delivery
per MW from NESO's one-second frequency data and the service delivery envelopes (DC
about 0.007, DM 0.03–0.04, DR 0.13–0.14 MWh per MW per hour in 2024–2025). That energy
changes the stored energy, counts towards cycles, ageing and the wear cost, and by
default is unpaid: under NESO's ABSVD adjustment a balancing-mechanism battery is
neither charged for energy it imports in high services nor paid for energy it exports
in low services (NESO, Dynamic Response A18 Submission Document, 2025).
energy: day_aheadbuys and sells it at the trading price instead. - Prices. MW held is paid the actual clearing price (GBP per MW per hour). The plan
uses
forecast: previous_day: the latest earlier block at the same UK clock time whose auction result was public at the decision time (results are assumed public at 15:00 UK time the day before the EFA day).actualplans on the clearing price itself, an upper bound, and is what the perfect-foresight benchmark uses.
Left out, and stated in summary.json: the battery is a price-taker whose offers are
always accepted at the clearing price; it is always available and never penalised for
performance; delivery follows the monthly average rather than the frequency on the day;
reserve products (balancing, quick and slow reserve) and the Balancing Mechanism are not
modelled.
Results¶
examples/gb-2025-stacked.yaml for each year and service set (1 MW / 2 MWh, 95% each
way, last week's day-ahead prices, £5/MWh wear cost in the plan); revenue per MW-year
before wear cost, as openenergy run reports it:
| Year | Day-ahead only | + DC | + DC, DM | + DC, DM, DR |
|---|---|---|---|---|
| 2022 | 76,940 | 195,927 | 217,004 | 395,148 |
| 2023 | 35,964 | 61,907 | 72,391 | 112,437 |
| 2024 | 28,677 | 45,232 | 47,437 | 51,210 |
| 2025 | 35,861 | 63,872 | 75,766 | 89,807 |
With utilisation energy bought and sold at the day-ahead price (energy: day_ahead):
| Year | + DC | + DC, DM | + DC, DM, DR |
|---|---|---|---|
| 2022 | 197,160 | 202,931 | 267,469 |
| 2023 | 61,787 | 68,775 | 112,425 |
| 2024 | 45,032 | 56,820 | 79,949 |
| 2025 | 63,788 | 83,106 | 127,976 |
DM and DR start on 26 March 2022, so their 2022 columns cover 281 days, scaled to a year.
What this shows:
- Dynamic Containment is the robust gain. Adding DC raises revenue by 58–155% every year, and the answer barely moves with how utilisation energy is settled (DC is rarely called). DC availability is 60–78% of the stacked revenue.
- The battery works less for more. With DC, equivalent cycles fall from 569–661 a year to 319–389: holding power for DC pays more than the marginal arbitrage it displaces.
- DM and DR depend on an assumption. DR moves 0.13–0.14 MWh per MW per hour, so who pays for that energy decides its value: in 2024 adding DR to DC and DM adds 8% when the energy is free and 41% when it is bought and sold at the day-ahead price. The default (free) follows NESO's ABSVD treatment of balancing-mechanism units; NESO's 2025 A18 document describes non-BM units as exposed to imbalance prices on it instead, and proposes to extend ABSVD to them.
- Thin markets. DR low cleared a median 9 MW per block in 2022 and about 480 MW in 2025, against about 1,100–1,250 MW of DC; a price-taker result for one small battery says nothing about what a fleet entering these markets would earn.
- Prices fell, then partly recovered. DC low cleared at £19.20/MW/h on average in
2022 and £2.25 in 2024, then £3.93 in 2025 (see
data/response/README.md).
Balancing¶
GB's imbalance price (the system price, one per half-hour since 2015) settles any
difference between a battery's contracted position and what it delivers. It is the
obvious "later market": a battery could take a day-ahead position and then deviate when
the imbalance price looks better.
examples/imbalance_value.py
tests this on Elexon's system prices, 2016 to October 2026 (data/balancing/), after a £5/MWh wear
cost:
| Year | Days | Follow day-ahead | Perfect imbalance foresight | Re-plan each half-hour (t−2 price) | Re-plan on known day-ahead | Imbalance only |
|---|---|---|---|---|---|---|
| 2019 | 355 | 4,417 | 52,791 | 2,682 | 9,152 | −2,169 |
| 2022 | 361 | 64,267 | 324,412 | 60,243 | 82,276 | 22,555 |
| 2024 | 366 | 17,633 | 94,200 | 12,579 | 23,390 | 5,223 |
| 2025 | 365 | 24,693 | 103,850 | 15,916 | 30,993 | 6,018 |
Days with a missing price in either market are left out.
What this shows:
- The upside is large. Knowing the day's imbalance prices would earn 4–12 times the day-ahead revenue: the price is far more volatile than day-ahead.
- A simple real-time strategy loses money. Re-planning before each half-hour on the latest published price (two periods back) earns 6–39% less than just delivering the day-ahead position, and trading imbalance alone on the same information earns at most 35% of day-ahead, and lost money in 2019. The value is all in forecasting the imbalance price, which open data does not give.
- Re-planning on known day-ahead prices gains 25–107%, but that is the value of knowing the day-ahead price (a battery bidding a curve into the auction captures much of it), not of balancing.
So OpenEnergy does not offer a balancing mode: with the information available, it would mislead.
Planning under uncertainty¶
v3.3 showed that for day-ahead trading a plan over price scenarios equals a plan on their mean. The same holds for frequency response: MW is committed before the auction clears and paid the clearing price, so revenue is linear in price and the value of a stochastic solution is zero. Balancing is the one market with a genuine later decision, and above it pays only with a forecast that open data does not support. Stochastic planning therefore stays out of OpenEnergy until a later market with an open, forecastable price is added.
Sources¶
Frequency response: NESO, Dynamic Containment, Regulation and Moderation auction results and EAC auction results; utilisation from NESO system frequency data with the delivery envelopes of NESO's Response Services Service Terms (version 6); ABSVD treatment from NESO's Dynamic Response A18 Submission Document (2025). NESO Open Data Licence v1.0; supported by National Energy SO Open Data. Imbalance prices: Elexon BMRS; contains BMRS data © Elexon Limited copyright and database right 2026. Details and known issues in Data.