Methodology & Data Sources

How the backtest works, the market rules it runs under, and where the data comes from. The model describes the two decisions the battery makes each day and the price forecast the signal they run on; the sections between them give the NESO rules those decisions obey, down to the clause. What that forecast is worth, and the experiments measured against it, are on Research Experiments.

The model

Every day the battery makes two decisions on the same price forecast: at the bid deadline, what frequency response (FR) to offer for tomorrow, and every half-hour, how to trade the power its contracts leave free. Revenue is always settled at actual prices.

Reference asset
Size MW / MWh, starting half full
Round-trip efficiency , applied on the charge side
Wear £ per MWh discharged, whether by a trade or by delivery
Availability , applied to every revenue stream and cost
Backtest –
Wholesale price APXMIDP market index

Stage 1: what to offer

Offers for all six EFA blocks of day D close the afternoon before: 14:00 on D-1 since the Enduring Auction Capability (EAC) went live on 2 November 2023, 14:30 before it. At that moment the model decides how many MW to hold in each of the six products (DC, DM and DR, each High and Low) in each block.

It offers every product at its opportunity cost, the trading the same capacity would otherwise do, and works that out the way a dispatcher would: by planning. At the deadline it chooses the six blocks' holdings together with a half-hourly trading plan to the end of day D, on the forecast that existed at that moment, so a holding costs whatever it takes out of that plan. Three things follow that one price per block cannot capture:

Each offer also carries the expected cost of the energy the product will deliver (see response delivery). NESO accepts an order when the clearing price covers its offer, so a battery bidding at cost ends up holding whichever permitted combination earns most at the clearing prices. Solving the plan against them reproduces cost-reflective bidding without assuming foresight, for a price-taker whose offers do not move clearing prices. The plan is a linear programme of about 270 variables, solved in two milliseconds by HiGHS, a simplex solver, because holdings often tie and only a vertex solution breaks the ties cleanly.

Two refinements. The plan's forecast is pulled halfway towards its daily mean first, because a plan built on a forecast overstates what keeping capacity free is worth (see discounting the forecast at the offer); the weight was chosen from 0.25, 0.5, 0.75 and 1 on the years before 2025, and held up from 2025. And energy left at the end of day D is valued at the day's mean forecast price less wear. Valued at nothing, the plan would empty the store in the last block and make holding Low response there look expensive for no real reason.

Stage 2: dispatch

Dispatch is a rolling Model Predictive Control (MPC) linear programme, re-solved every settlement period. At each period it plans to the end of tomorrow, the furthest any forecast yet exists for (between 49 and periods), executes only the first period's decision, and re-solves: dispatch must be committed before future prices are known.

maximise  Σ price[t] × (p_dis[t] − p_chg[t]) × 0.5h  −  cycling_cost × Σ p_dis[t] × 0.5h

subject to the state-of-charge equation, the physical store, the power the contracts leave free on each side, and the state-of-energy range the contracts require at the start of every period, which makes the battery pre-position for upcoming blocks. That range is soft: missing it costs £5,000 per MWh, about 2.5× the highest price in the data, so the LP always moves towards compliance, never breaches a contract to capture a spread, and stays solvable when a requirement genuinely cannot be reached. A larger penalty would buy no safety; at £50,000 the solver returned 1% of solves as inaccurate. Charging and discharging at once is never optimal at a positive spread, so no binary variables are needed. Solved with the Clarabel interior-point solver through cvxpy (Diamond & Boyd, 2016). A half-hour whose LP fails to solve trades nothing and leaves delivery unrecovered, so failures are counted: across the dispatch runs behind the published figures.

A day's commitments enter dispatch at its bid deadline: an operator must be able to deliver everything it offered, so it positions for its offers before results publish, and for a price-taker the offers are exactly what clears. A settlement period that starts outside the requirement counts as unavailable (Service Terms 6.12) and loses an eighth of the block's availability payment; the Forecasting & Dispatch page reports how many periods each strategy missed. Misses cluster at block boundaries, where a new block restores the full requirement, so every plan meets each later block's start a margin inside it: one half-hour of delivery at its recent 90th-percentile rate for what is held. DR delivery is bursty, and a store run up to the limit has no slack for a half-hour above the average. Trades execute at actual prices, so a forecast error can lose money on the day; that is intended.

NESO's rules

src/analysis/neso_rules.py holds each rule with its clause. They decide which combinations are permitted:

Two of these rules changed during the backtest:

Service days from Rule
Start of data (Sep 2021) One service per unit per EFA block: DC, DM or DR, with High and Low of that service allowed together (DR Auction Rules 7.3.1)
2 Nov 2023, EAC go-live Capacity can be split across all three services in the same block. The model holds Reserved Capacity from here
15 Nov 2024 Reserved Capacity becomes a rule (Procurement Rules v2.0)

Reserved Capacity before it was a rule. NESO introduced the reserve together with the running energy requirement described under response delivery: proposed in June 2024, binding from 15 November 2024. The model applies that requirement throughout, and a stack across services can use the whole rating in both directions, leaving no power to recover the energy it delivers: without the reserve the battery would have missed its requirement in about a third of half-hours between EAC go-live and November 2024. A careful operator would not hold such a stack, so the model holds the reserve from EAC go-live. Before EAC a unit held one service per block and stayed within its requirement without it.

Pre-EAC service choice

Before 2 November 2023 a unit could offer only one of DC, DM and DR into each EFA block, and had to pick before the auction cleared. For each block the model picks the service that would have earned most at the previous day's clearing prices for the same block, the latest a bidder had at the 14:30 deadline, and is paid the day's actual prices for what it offered. That uses only what a bidder knew.

Operators behaved differently. Across 247,893 unit-blocks of NESO's legacy order data (March 2022 – October 2023, from the NESO Data Portal), no unit offered two services into one block and 86.5% of orders went in after the previous day's results; but 91% of unit-blocks repeated the previous day's service, 82% were DC, and units picked yesterday's best payer no more often (33%) than the day's eventual best (32%). A plausible reason is duration: the fleet averaged about 1.1 hours in 2022 (Modo Energy), and DR's 60-minute requirement binds hard on a one-hour battery, while the 2-hour reference battery holds DR comfortably. A sensitivity run holding every pre-EAC block in DC was dropped: a full-power DC stack in both directions cannot recover the energy it delivers, and spent a fifth of the pre-EAC half-hours unavailable.

Response delivery

A contract is not only a promise to stand ready. Whenever frequency leaves the deadband, a battery holding response has to deliver, and the energy that moves changes its state of charge. The model works that energy out from GB frequency itself.

From frequency to energy. NESO publishes system frequency at one-second resolution. Each second goes through each service's response curve (Service Terms, Table 1) and is summed into MWh per MW contracted for every settlement period, service and direction. Low products answer frequency below 50 Hz by discharging; High products answer frequency above it by charging.

Service Nothing within 5% at 100% at
DC ±0.015 Hz ±0.2 Hz ±0.5 Hz
DM ±0.015 Hz ±0.1 Hz ±0.2 Hz
DR ±0.015 Hz — (a straight line) ±0.2 Hz

Frequency sits outside DR's deadband most of the time, so DR moves far more energy than DC: about 2.5 MWh per MW per day in each direction in 2021, rising to 3.6 in 2026, against 0.13–0.18 for DC.

Who pays for that energy. Nobody. Delivery volumes adjust the unit's imbalance position (Service Terms 16), so energy a Low product gives away is gone and has to be bought back, and energy a High product absorbs arrives free and can be sold on. Every MWh discharged in delivery also wears the battery.

The requirement moves with delivery. The Minimum State of Energy Requirement (Service Terms 6.11) starts each block at the Contracted Response Energy Volume, falls by the energy delivered in each settlement period, and climbs back by the Energy Recovery Adjustment Volume: the shortfall three periods earlier, at most 20% of the volume per period. The Service Terms' own example, which the tests reproduce: a 50 MWh contract that delivers 2 MWh in its first half-hour needs 48 MWh until the sixth period, then 50 again. The Reserved Capacity exists to recover that energy (SOE Monitoring Guidance), so dispatch may use it in any period to keep a requirement reachable, including ahead of a block that restores the full requirement. Recovery through the reserve earns at the price, so it can wait for a good one, but only on energy delivery has put in play: an account per side adds what High products absorb and Low products give away, and every trade, through the reserve or not, spends it first. The reserve recovers delivered energy when the price is right and never becomes trading capacity (see timing recovery). A requirement at or below zero counts as allowed unavailability.

Planning without knowing frequency. Within a block, delivery lowers state of charge and the requirement together, so the LP plans on no further delivery. Where a later block restores the full requirement, it plans on delivery continuing at the previous day's average.

Pricing delivery into offers. Each product's expected delivery is its average over the previous 28 days for that EFA block, valued at the block's average price over the previous 7 days, p̄. Holding a MW of a Low product costs E_low × (p̄ / η + wear), the energy bought back plus wear; holding a MW of a High product earns η × E_high × (p̄ − wear), the free energy sold on less wear. That is why the model will hold DR High at a negative price when the energy it absorbs is worth more than the price it pays, and why DR Low is worth much less than its clearing price suggests.

A site with no interest in arbitrage. The FR-only scenario follows every rule above, gives arbitrage no value when choosing offers, and trades only to keep its contracts deliverable, moving as little energy as its requirement needs. Those trades still settle at market prices, so its net revenue is availability payments less the cost of recovering delivered energy, losses and wear.

Revenue accounting

Negative clearing prices

13.5% of auction records (8,134 of 60,054) clear below zero, concentrated in DR High (5,205) and DM High (2,816), and all of them after EAC went live: the legacy auctions never did. The model holds a negative-priced product only when its delivery pays for it. Low products carry a positive delivery cost, so they are never held below zero; DR High absorbs energy the battery can sell on, and when that energy is worth more than the negative price the model holds it. Its revenue breakdown can therefore show negative DR High revenue, with the matching income in wholesale trading.

Year DRH mean DRL mean DRH negative DR pair (H+L)
2022 +£11.53 +£13.00 0% +£24.52
2023 +£1.10 +£10.66 14% +£11.75
2024 −£4.80 +£8.18 88% +£3.38
2025 −£2.42 +£13.21 77% +£10.79
2026 −£8.31 +£16.41 92% +£8.10

£/MW/h. The pair stays reliably positive: fewer than 11% of blocks price negative as a pair.

Why DR High went negative. The break is sharp: no DR High block cleared below zero in October 2023, 87% did in November, the month EAC went live, and 83–89% have since (DM High broke the same way, to about half). EAC allows prices below zero, and lets a provider put several products in one order at a single price, accepted all-or-nothing when the order's total surplus is non-negative (EAC Detailed Market Design). A provider wanting DR Low can pair it with DR High, whose leg then clears below zero while the order as a whole pays, which fits the pair staying positive. The free energy DR High absorbs adds to the case. And its prices rise with the daily renewable share (ρ = +0.43, against −0.18 for DR Low), as the charge leg's role predicts: surplus renewable output pushes frequency up, which is when it is called.

The price forecast

Three price signals

All three run the identical allocation and dispatch engine; only the forecast differs. It sets the opportunity cost FR is offered at as well as driving dispatch.

Strategy Price signal fed to the LP What it represents
Perfect Foresight Actual day-D wholesale prices The ceiling; needs knowledge of the future
Naive The last complete day's 48 half-hourly prices The floor; any real model must beat this
ML Model Random Forest forecast for day D The realistic case, using only data that existed at each decision

The Random Forest

A Random Forest regressor predicts the 48 half-hourly APXMIDP prices for day D. The features are tabular rather than sequential, trees need no scaling, and tree methods are competitive with deep learning on short-horizon day-ahead tasks (Lago et al., 2021; Weron, 2014); it was kept over more accurate models for a reason the experiments explain. Features, all from the last complete day or earlier:

Reading the REPD fleet series. REPD is a planning database published quarterly, with two consequences. Recent months are revised upward as projects are confirmed operational — the Q4 2025 extract put March 2026 at 3.4 GW, while the Q2 2026 extract puts the same month at 5.0 GW, so figures for the most recent year are provisional. And the extract always lags the half-hourly price data, so trailing months are projected from a 12-month linear trend anchored at the last measured value, keeping the cumulative series monotonic. Projected months are flagged is_extrapolated: currently of months (measured through , projected to ). Months before the first REPD entry are zero; months after the last carry the most recent measured capacity forward rather than dropping to zero.

What each forecast is allowed to know

A forecast of day D built from all of D-1 exists only once D-1 has ended. That is right for dispatch on day D and too late for two other uses. Offers for D close at 14:00 on D-1, so they are priced on a forecast built from data to D-2: D-2's own prices for naive, and for the model a second walk-forward table whose features all sit one day further back. The same early forecast drives tomorrow's periods in each dispatch plan, and the plan stops there, because no honest forecast yet exists for the day after. Perfect foresight runs the same engine and horizon on actual prices.

The rule is stricter than reality. At 14:00 on D-1 a real operator has also seen D-1's morning, and the hourly day-ahead auctions for D have already cleared (results by 10:00). The model uses neither, so its offer-stage information is conservative.

Walk-forward validation

Every forecast behind the revenue figures is out-of-sample. The model is refit at quarterly origins on the history available at each and used only for the days until the next, so no day is ever predicted by a model that trained on it. The feature matrix begins in January 2019 and the backtest in September 2021, so the first origin already has two and a half years to learn from. Before this, one fixed split trained on everything before , which left 42 of the 60 months forecast by a model fitted on those very days; it survives only as a diagnostic on the Forecasting & Dispatch page, and as the fit that supplies feature importances.

Known limitations

Not modelled

Approximations

Data sources

Coverage is read from the processed datasets at build time, so this table cannot drift out of date when the pipeline is re-run. Both NESO and Elexon APIs are public and need no key. NESO Data Portal · Elexon Insights · DESNZ REPD

Literature & references

NESO frequency response rules and market evidence

Electricity price forecasting

Probabilistic forecasting and conformal prediction

MPC dispatch engine & LP solver

BESS dispatch optimisation & co-optimisation

Battery degradation modelling

GB BESS market context