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.
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.
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.
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.
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.
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.
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.
Results
Total net revenue
Annualised net
Revenue per MW
Top revenue stream
Modelling a
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
Error per refit, against the pooled walk-forward RMSE (dashed). Spearman ρ
ranges
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 £
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.