Day-Ahead Forecasting & Dispatch Model

Grid-scale batteries earn from several markets at once. This model covers the day-ahead decision layer: given yesterday's auction results and price data, how should an operator split capacity between frequency response & wholesale arbitrage, and how much does forecast quality actually change the outcome?

One day, step by step

The walkthrough below follows a single winter day — 8 January 2026 — from the raw price curve through to the dispatch the model settles on. Scroll, or select any step directly.

Step 1

A day of wholesale prices

To the right are half-hourly APXMIDP prices for a random GB winter day (January 8th 2026 here). Prices run from £73 to £291/MWh, defined by an overnight trough, a morning rise, and the evening peak when demand is highest, wind may ease, and there is no solar generation.

That spread is the raw arbitrage opportunity: buy low, sell high.

Step 2

Charge cheap, discharge at the peak

The obvious arbitrage value for battery sites: Charge through the overnight trough, discharge into the evening peak.

The catch is that a 2-hour battery can only shift so much energy, and every cycle costs something in degradation — so the model has to pick which periods are worth trading, not simply trade the extremes.

Step 3

But frequency response pays for sitting still

NESO pays a £/MW/h availability fee for capacity held ready to respond within seconds, whether or not it is ever called. That income is contracted a day ahead: offers for day D close at 14:00 on D-1.

Each contract constrains the asset. A Low contract needs enough energy in store to discharge for its full delivery window — 15 minutes for DC, 30 for DM, 60 for DR — and a High contract needs the same again as headroom to absorb. The power a contract uses is also unavailable for trading.

And contracts get called on. As frequency wanders, a battery holding DR moves energy in and out almost all the time. That energy is neither paid for nor charged, so whatever a Low contract gives away has to be bought back, and whatever a High contract absorbs can be sold on.

Step 4

Stage 1 — decide what to offer

For each of the six EFA blocks, the model offers every product at its opportunity cost: the trading that capacity would otherwise do, plus the expected cost of the energy the product will deliver. It prices the trading the way a dispatcher would, by planning: the day's holdings are chosen together with a half-hourly trading plan from 14:00 to the end of the next day, so a holding costs whatever it takes out of that plan. Holding a High product overnight, when the plan needs to charge, costs more than holding it at the evening peak, when the plan is selling. The plan uses only the forecast that existed at 14:00, pulled halfway towards its daily mean so that it does not pay to keep capacity free for spreads the forecast merely imagines. It keeps the combination that earns most at the clearing prices, within NESO's rules:

  • the MW it sells in each direction, plus the share NESO reserves on the other side for recovering delivered energy, fit within the battery's power rating
  • there is enough energy in store to deliver every Low contract for its full window, and enough empty space to absorb every High one
  • starting from how full the battery actually is at 14:00, it can get into the charge range each block needs in time, using only the power its contracts leave free
  • it holds no more than a fifth of what any auction cleared, small enough that its own offers would not have set the price

Before EAC went live in November 2023, a unit could offer only one service per block, chosen here on the previous day's prices.

Step 5

Stage 2 — dispatch under constraint

With the offers set, a linear programme plans charge and discharge at half-hourly resolution, re-solving every period and executing only the first — model predictive control. Each plan runs to the end of tomorrow, the furthest any forecast yet exists for: today on the day-ahead forecast, tomorrow on the one made a day earlier.

The shaded band is the range NESO requires: enough energy in store for the Low contracts and enough headroom for the High ones. Each block starts at the full amount. Delivering response lowers it, it climbs back by at most a fifth each half-hour, and the battery trades to keep up. Starting a half-hour outside it counts as unavailability and forfeits that period's payment.

Step 6

Forecast quality is the variable under test

All three strategies run the same allocation and dispatch engine. Only the price signal differs: Perfect Foresight sees actual day-D prices, Naive reuses the last complete day's, and the ML model — a Random Forest on lagged prices, generation mix and cyclical time features — predicts them from data that already existed when each decision was made.

Where the traces diverge is the cost of forecast error. The analysis below quantifies it.

The model in full

Everything below runs that same engine across the whole backtest window rather than one day. Choose the price signal the asset trades on, the markets it may bid into and the dates; every figure on the rest of the page follows the selection.

Every figure comes from a precomputed run for one MW / MWh reference asset. Revenue per MW is the figure to compare against other assets, though it is not exactly independent of size: the model never holds more than a fifth of an auction, and that limit binds a larger battery more often ([methodology](./methodology#known-limitations)). Which products the battery holds is decided by the model under NESO's rules rather than chosen here; the revenue breakdown below shows what it held.

Results

Total net revenue

Annualised net

per year

Revenue per MW

per MW per year

Top revenue stream

Modelling a MW / MWh asset (h duration, % round-trip efficiency) in , using price signals and MPC dispatch over a rolling horizon to the end of tomorrow.

Monthly revenue stack

Each bar shows gross revenue by stream for that month (positive) and cycling wear cost (negative, dark red). Net revenue is the algebraic sum of all segments — months with heavier arbitrage dispatch carry larger cycling deductions.

State of charge: single days, and the average week

The traces include the energy the battery delivers when its contracts are called on, worked out from GB frequency second by second, and the trades it makes to recover that energy. Delivery lowers the requirement as it happens, so the teal band dips where contracts are called on most and climbs back as the battery recovers.

Cumulative revenue by stream

Strategy comparison

Three price-signal strategies run the same allocation and dispatch engine on the same asset, isolating how much forecast quality — not the optimiser — affects operational revenue.

Strategy Price signal What it represents
Perfect Foresight Actual day-D wholesale prices Theoretical ceiling — needs advance knowledge of the future
Naive* The last complete day's prices Zero-skill floor — any real model must beat this
ML Model Random Forest forecast Realistic best case, using only data that existed at each decision

Reading the chart

The three bars define a range. Naive* sets the floor — what you would earn by reusing the last complete day's prices. Perfect Foresight is the ceiling, the revenue available if you knew the future. ML Model sits between them, and the question is how close it gets to the ceiling.

*The floor is not quite zero-skill: its offers, like the model's, plan on a forecast pulled halfway towards its daily mean, a setting chosen on the years before 2025. That one parameter is worth about £4k/MW/yr to it, which makes it a stronger and fairer benchmark.

The foresight ratio quantifies this as a fraction of the capturable improvement: (ML − Naive) / (PF − Naive). The industry's usual measure, Percent of Perfect, subtracts no floor and reads 80.9% here — but the naive floor alone reads 78.2% on it, because most of the revenue is frequency response availability that no forecast moves. The harder ratio is the one reported for that reason.

Resampled in four-week blocks, the model's lead is £3.26k/MW/yr with a 95% interval of £2.13k to £4.45k. Its accuracy edge over persistence is not distinguishable from noise (Diebold-Mariano p = 0.26 on squared error); what it is worth, it earns through allocation.

It is a share of the capturable gap, so it moves when that gap moves: charging the model for the energy its contracts deliver lifted the floor towards the ceiling. The bigger shift came from retraining. While a single fixed split left most of the backtest forecast by a model that had trained on it, this read near 66%; with every forecast out-of-sample it fell to a fifth. Then the engine improved: pricing offers from a trading plan raised the ceiling more than either forecast, and the shrink gave the floor the caution the model's trees already had. It now reads near an eighth — a better engine made the forecast matter less, not a worse forecast.


foresight ratio

ML model detail — Random Forest

The ML strategy predicts the 48 half-hourly APXMIDP prices for day D from the last complete day's data: D-1 for dispatch on the day, and D-2 for the offers made at 14:00 on D-1 and for the next day in each dispatch plan, from a second walk-forward table built one day further back. Tree-based ensembles suit this problem: the feature set is tabular (lagged prices, generation-mix ratios, temporal encodings) rather than sequential, they need no feature scaling, and they yield interpretable importances.

Every forecast behind the revenue figures is out-of-sample. The model is refit every months on the history available at that point and predicts only the days that follow, so no day is forecast by a model that trained on it — refits across the backtest. The other two columns are diagnostics, not the basis of anything: the in-sample fit shows how well the model reproduces days it has already seen, and the single held-out split is the conventional one-boundary estimate. The distance between them is why this page reports the first column.

Error per refit, against the pooled walk-forward RMSE (dashed). Spearman ρ ranges – across folds, which is the spread a single split cannot show.

Spike-RMSE measures error on top-decile price periods, where arbitrage revenue concentrates. Spearman ρ matters more than RMSE for dispatch quality — the LP only needs the ordering of prices to be right.

Spread bias is the row to watch. It is the mean signed error in each day's predicted price spread — max minus min — which is what arbitrage actually trades on. Negative means the forecast is conservative, under-calling how wide the day will be; positive means it invents spread that never arrives. The asymmetry matters: a phantom spread costs a bad trade and then makes the offer stage value trading headroom it will never use, declining frequency response contracts worth having, while missing a real spread only forgoes upside. This is the metric that decided the model choice, and none of the rows above it can see that failure — spike-RMSE scores error on spikes that happened, so inventing them is free. See why the model is not chosen by accuracy.

Read the Spearman row with care: each column pools every period in its own window, so the three cover different spans and are not directly comparable, and pooling across years mixes the variation between days into a number meant to describe ordering within a day. The per-fold range above is the better guide, and a within-day measure would be better still.

Known limitations: tree-based models cannot extrapolate beyond price ranges seen in training; electricity price forecasting is inherently noisy; the model improves dispatch quality on average without eliminating error on individual days; and hyperparameters were chosen once rather than re-selected inside each fold.

Sensitivity

Cycling wear cost

Battery degradation is a real operating cost, but modelling it precisely needs a full electrochemical model and site-specific data. A flat £/MWh cycled figure is used as a financial proxy, consistent with industry practice. The NESO/Modo consensus for modern Li-ion sits near £/MWh, with a plausible range from under £1/MWh to £8–10/MWh on aggressive cycling.

Service mix

How the revenue stack changes depending on which markets the asset participates in.

Each scenario is its own backtest rather than the full stack with a stream removed: without arbitrage the allocation gives FR every MW it can use, and without FR the battery trades its whole rating. FR only is a site with no interest in wholesale arbitrage: it trades only to recover the energy its contracts deliver, at market prices. The last row holds every block before November 2023 in DC, which is what most of the 2022–23 fleet actually did; the main run picks each block's service on the previous day's prices. See the methodology for the evidence behind both.

The price signal matters here too. FR is offered at the arbitrage value the forecast expects, so a forecast that overstates arbitrage holds back capacity that trading then fails to earn back. With a weak forecast, the full stack can earn less than FR alone.