CECadence Cannibalisation-aware GB battery revenue forecasting
connecting…

Scenario run

Whole-GB fleet, cannibalisation fixed point solved to convergence. Prices, fleet build-out and BESS dispatch are solved jointly — not in sequence.

Convergence

No run in progress.

The model iterates prices and dispatch until they stop moving — this chart shows the solve settling to the cannibalisation equilibrium.

Technical detail

KM iteration λk=2/(k+2), step capped at 2/fleet GW · stop when the price path has settled: ‖π(k)−π(k−100)‖∞ < £1/MWh at the first k ≥ 101 · no smoothing aid · the loop iterate is the equilibrium · each stop publishes its iteration, drift and full-step violation; a run that never settles is flagged NOT CONVERGED

GB fleet map

Schematic coastline · dot area ∝ MW · operational construction consented

Registry

Seed registry compiled from public sources — see cecadence/data/FLEET_SOURCES.md. Licensing is not the blocker: the open-data complement is already fetched and cached under the Open Government Licence v3.0 (DESNZ renewable-energy planning data + the NESO TEC register), and the committed battery-BMU registry is built by cross-referencing the Elexon BMU register with NESO auction and Capacity Market records. What remains is merging those feeds into this seed registry.

Site Operator MW MWh Class Zone Year Status
Manage runs — every stored run (all kinds): label, report, data pack, delete
CreatedKindScenario · yearsWk AnchorLabelStatusActions

Labels are display names only — the audit manifest is never modified. Deleting a run removes its stored results permanently.

Analyse a new candidate site

The candidate joins the whole-GB fixed point as an extra asset class — its revenue is evaluated at the with-candidate equilibrium, never against exogenous prices.

optional — enables CEAtlas bus assignment + interconnection cost

Batch variants — sweep this site across durations / zones / costs in one submit (up to 8 variants)

The form above is the base. Each variant row re-runs the base with one field overridden — every variant is a full appraisal, saved to the portfolio like any other. Solver and price anchor are batch-level: set them above, they cannot vary per variant.

Portfolio (all analysed sites — per-asset attribution)

⇄ SiteMWDurZone Scenario · year Price basis Eq £/kW·yr Total £/kW·yr BiasDSCR P90 DSCR Min DSCR NPV £m

No sites analysed yet — every analysis is saved to the portfolio automatically.

tick 2–4 sites to compare head-to-head

Methodology — a model you can interrogate

Diligence pack — the evidence a diligence analyst needs to verify this product: the held-out 2025 note ↗ · underwriting note ↗ · hindcast 2022–2025 ↗ · validation dossier (sign-in) ↗ · limitations register (sign-in) ↗ · pre-registration record ↗ · measured quarterly index ↗ · per-asset lookup ↗. GB fleet & pipeline map ↗. living index (provisional rolling series) ↗. long-duration storage monitor (sign-in) ↗. We publish the failures too — that is the point.

Cannibalisation fixed point

Wholesale prices and whole-fleet BESS dispatch are solved to a self-consistent equilibrium π* = Ψ(Φ(π*)) — a true fixed point, not a one-pass adjustment. The iteration scheme carries a convergence guarantee, every run ships its audited convergence trace, and naive schemes demonstrably fail at GB fleet scale — which is why price-taker tools cannot produce this number at all. The naive (price-taker) forecast is always reported alongside the equilibrium — the bias between them is the number that decides DSCRs.

prices π per-cohort dispatch Φ (half-hourly LP, whole fleet) price formation Ψ (supply curve / system LP) iterate until self-consistent — iterate 0 = the naive price-taker forecast; the gap = the bias fleet net load b = Σ(charge − discharge)
Price-formation modeWhat it isWhen it runs
supply_curve (default) parametric inverse supply curve π = S(residual + b), calibrated so the no-BESS price duration curve hits the stylised 2026 GB anchors; fast — no LP in the price step interactive runs, candidate appraisals, the committed backtests
system_lp merit-order welfare LP with interconnectors and VOLL; π = the power-balance dual (SRMC basis) scenario-grade cross-checks; FR co-optimisation applies to supply_curve only (disclosed per run)

Why this is hard to fake: a price-taker spreadsheet cannot produce the naive-vs-equilibrium bias number at all — it has no fleet feedback to measure. The bias only exists if prices and the whole fleet's dispatch are solved jointly, and its magnitude is checkable against the measured index below.

Dispatch

Per-cohort half-hourly LP: SoC dynamics with one-way efficiencies √η, self-discharge, degradation as a marginal cost (£2.50/MWh discharged, LFP default), one-sided cyclic boundary eT ≥ ½E, least-cycling lexicographic tie-break. Whole-GB fleet aggregated into BESS_<year>_<duration> cohorts from the site registry.

What is reduced-form in v1 (disclosed by design)

  • Balancing Mechanism: statistical proxy per TNUoS zone × duration class — NOT dispatch-based. The zone factors are measured from the CE index trailing-year EBOCF offer-leg zonal split (replacing an earlier hand-set Scottish-premium guess with measured per-zone factors — the values render in the assumptions register below, sign-in required; thin zones are shrunk toward neutral in proportion to their measured capacity, per-zone counts disclosed in the index JSON, and the zonal variation rides the measured offer leg only). The per-class bases and the zone factors were re-fitted in October 2026 to the re-based index's measured trailing year (2025Q4–2026Q3), under a rule registered in July 2026 before any of those targets existed — every old→new parameter ships in the calibration evidence below, and the verdicts publish as they landed — with the bases fitted to those targets, a BM pass or fail grades the stress-growth residual to 2026, not the level. The re-based 4h class is thin — two units, 45 MW over that window — so its base rests on few unit-days and a 4h appraisal's BM line should be read with that caveat; the classes of 8h and above are unmeasured and carry the nearest fully-measured class's lines as a prior at zero credibility weight, disclosed on every appraisal. Caveat for Scottish-boundary sites (Z2–Z4): the measured NET zonal BM is zero or negative in curtailment-absorbing zones (the value lands in cheap charging, i.e. the wholesale leg) — read the zonal split in the quarterly index alongside any Z2–Z4 appraisal. Under the adopted two-leg structure (pre-registered; shipped with its published no-flattery checks) a 1h or 2h Z2/Z4 appraisal's BM line floors at £0 where the measured net is negative (the thin 4h class's larger re-based base keeps it above that floor, though still no higher than the zone-agnostic GB line), and the measured net values still travel with the appraisal payload.
  • Frequency response (DC/DM/DR): saturation price model anchored to the NESO EAC volume-weighted clearing prices of the index trailing year, with participations re-fitted (October 2026, under the same pre-registered rule) to the measured per-class revenue from the response services only — the θDC 2021→2024 collapse rate is preserved; DR-H cleared negative over the trailing year and is unremunerated (disclosed; the measured product mix is rotating DC → DR-L quarter on quarter, visible in the quarterly index). Reserve products are out of the model's scope: it carries no reserve line, a conservative omission stated beside every class benchmark. The FR verdict cells below are graded on that response row and wear a fit (in-sample) badge, never "pass" — they were fit to those targets. Separately, each index quarter from 2026Q2 grades the revenue lines frozen in July 2026 OUT-OF-SAMPLE under the standing pre-registered rule (protocol §13 — the record below).
  • Capacity Market: NESO duration de-rating on a cleared-where-cleared price basis (pre-registered rule, executed 2026-07) — delivery years whose T-4 auctions have cleared use the final clearing prices from the pinned auction register (auction results are facts, not forecasts), carried into delivery-year money by the statutory CPI indexation of the Capacity Market rules (applied in the model since October 2026) — the same indexation the measured index applies, so model and index price CM in one money, and a new-build candidate's own capacity agreement is indexed the same way. Beyond the cleared horizon the central line is the deterministic conditional expectation of the bounded random walk, anchored at the last cleared price before indexation, as registered — so the central line steps down in the first year past the cleared horizon (disclosed) — and no seeded realisation. Future-auction uncertainty is NOT yet in the Monte Carlo bands (the draws inherit the central CM line — disclosed).
  • Calibration constants kD = 1.0 (BM/CM/FR are modelled explicitly, so kD ≠ 1 would double-count). The calibration harness is implemented: the 2h cycles/day check runs against the public Modo 1.2–1.5 band (PASS/FAIL in the backtest panel below) with the CE GB BESS index pilot as cross-check, and the spread-capture reference now comes from the CE index (primary-data proxy basis, disclosed below) — no third-party benchmark licence.
  • Forward-run demand/wind/solar profiles are synthetic-but-calibrated: the no-BESS price duration curve is fitted to the stylised 2026 GB targets (mean £88, P50 £70, P95 £216, max £676/MWh) under the default stylised anchor; the selectable realised-2024 anchor replaces the curve base with the Elexon-calibrated 2024 fit (in-sample), and the selectable trailing-12m anchor recalibrates the same machinery to the trailing 12 months of realised Elexon prices through the latest settled day (window disclosed per run; staleness flagged after 35 days — see the Forward anchor freshness card below). Realised Elexon MID prices drive the backtest and both realised anchors. Realised NESO/Elexon demand-wind-solar profiles (public no-auth APIs, cached in-repo) are integrated and selectable for the 2022–25 backtests, with a fitted 2-parameter affine basis alignment (in-sample, disclosed per run) that closes the realised TSD-basis level gap to ~4–6% on the window mean; synthetic remains the default because the remaining gap is curve SHAPE on the realised basis (P50 cold, P95 hot — measured numbers in the run caveats). An experimental shape-fit calibration (k, σ freed; payload calibrated_shape) closes 2022 to sub-1% but pins a parameter at a bound every year and was not adopted under the pre-registered rule. On 2023/24 the below-band calibrated cycles coincide with price spreads matching realised within 10% (payload spread_diagnostics) — spread amplitude is ruled out as the shortfall there, and double-counting of history's storage response, dispatch-cost assumptions and band composition are indistinguishable to that diagnostic; 2022's spreads run 37% wide with cycles still below band (unresolved, dispatch-side). The cycles check is excluded from the realised-default-flip rule unless evaluated on a no-BESS counterfactual basis. FORWARD runs remain on synthetic-but-calibrated profiles; live forward profile feeds are the remaining data step.

Assumptions register — the values the model actually runs with

Rendered live from the running configuration (GET /api/assumptions, single-sourced from cecadence.config) — prose can never silently rot against code. Basis column: spec = specification default · measured = from the CE index · calibrated = fit in-sample, disclosed.

loading assumptions…

Uncertainty & finance conventions

  • Exceedance convention for revenue bands: PXX is the value exceeded in XX% of Monte Carlo draws, so P90 is the conservative (low) band. Price statistics (e.g. the P95 price) remain percentile-convention scarcity tails.
  • Monte Carlo design: N bootstrap weather realisations, each solved to its own cannibalisation equilibrium; bands carry standard errors of the mean.
  • Finance targets: P50 DSCR target 1.40× and P90 (exceedance) lender covenant 1.10×.
  • Multi-year revenue track: candidate finance runs on the cannibalisation-aware year-by-year revenue trajectory against the growing fleet, with the minimum DSCR and its binding year reported.

Validation invariants (scenario runs and New Site appraisals; a violation halts that result)

SoC bounds · round-trip consistency · price sanity −£200 ≤ π ≤ £5,000/MWh · zero-storage limit

Checked on each year of a scenario run (a violating year is withheld and marked failed) and on a New Site appraisal's evaluation-year solve (a violation stops the appraisal); Monte Carlo draws, sensitivities and backtests are not put through this suite. Every dispatch and price LP must also come back with an optimal status or the run stops — the reporting-stage tie-break excepted: it keeps the already-optimal schedule. The cross-solver oracle (CEMeridian vs HiGHS, 1e-6 relative on the objective) is a build-time parity gate run in CI, not a per-solve invariant: a run resolves one solver for its dispatch and price solves and for its reporting-stage tie-break, and never dual-solves, so the oracle cannot halt a run the way the checks above do. HiGHS is kept to compare and benchmark; the graded evidence and the index calibration are still produced on it until a registration moves them.

CE GB BESS index — the product's own benchmark from open primary data

The product's benchmarks are computed from Elexon BMRS and NESO open data under their open licences — no third-party benchmark licence. The battery-BMU registry (pinned, committed, disclosed per row) is built by cross-referencing the Elexon BMU register with NESO auction and Capacity Market records. The index measures, from primary data only: fleet cycles/day (B1610 metered volumes ÷ energy capacity), a wholesale revenue floor per MW (metered volumes × MID half-hourly prices — an ex-post proxy, not traded positions), BM revenue per MW (EBOCF bid-offer cashflows) and FR revenue per MW (EAC clearing price × executed quantity per product), plus revenue shares by stream. The index covers the trailing four fully-settled quarters (one registry, per-quarter active-unit scoping handles mid-period commissioning, active-MW-day weighted aggregation) plus the Capacity Market stream from the CM register's awarded de-rated obligations × auction clearing prices (delivery years overlap-weighted; initial-auction basis). BM/FR numerators cover exactly the ACTIVE unit set of the per-MW denominator; excluded revenue is quantified, never silent. The single-quarter pilot that preceded the trailing window remains in docs/evidence/bess_index_pilot.json for provenance — the trailing-year card below supersedes it (different basis: trailing active-MW-day weighted vs one pilot quarter).

The re-based measurement basis (from the 2025Q4–2026Q3 window). The changes to how the index measures were registered in July 2026, before the data they apply to existed, and applied at the 2026Q3 window advance; the previous basis is published in full beside the new one in the index evidence, and earlier windows are never re-stated on the new basis. In plain terms:

  • Membership — a unit counts only if its metered behaviour is that of a battery (a behavioural screen), and battery units are recognised from the Elexon register and the government's public planning database even where they never entered a NESO response auction.
  • Duration classes come from how long each unit actually discharges in its metered data, not from its Capacity Market rating.
  • The wholesale floor adjusts metered volume for transmission losses and removes the energy already paid for through accepted Balancing Mechanism actions and balancing services, so it no longer double-counts with BM — still an ex-post floor, not traded positions, and it can be negative for a response-heavy class.
  • Capacity Market revenue is priced in delivery-year money by the statutory CPI indexation and accrues from each unit's real start.
  • A unit commissioning mid-quarter counts from its first metered day.
  • Frequency revenue is split into response and reserve services.

One measured finding was disclosed, then corrected under its own pre-registered rule (protocol §40): some units admitted by the wider membership did clear response auctions, and the 2026-10-09 record counted that frequency revenue as zero — about 1.8% of measured FR, an understatement, listed per unit in the index evidence. From the 2026-10 methods release (applied 2026-10-10) it is counted in the published FR, with the uncounted figure published beside it for four advances. Restatement: at each window advance the previous four quarters are re-fetched on Elexon's later settlement runs and the changes publish beside the record, never over it — the first table, in the 2025Q4–2026Q3 report, moved quarter totals by −1.7% to +0.0% (on the basis those quarters were published on). Every number below renders live from the committed index evidence (bess_index_trailing.json).

📄 The latest measured quarter is published as the CECadence Quarterly GB BESS Index report ↗ — on-brand, print-to-PDF, built from this same open data. Per-unit, the same record is browsable in the per-asset lookup ↗ — measured revenue, strategy class and within-class percentile for every gated unit.

loading CE GB BESS index…

Calibration harness — model (2026 CE-CENTRAL) vs CE index trailing year

The calibration harness grades the model's per-duration-class BM and FR revenue lines and cycles/day against the index-measured trailing year, like-for-like by stream, with the CALIBRATION_TOLERANCES verdicts (per-duration revenue 15%/20% soft/hard; cycles 10%/15%, 25% reject). kD stays 1.0 — the harness validates the explicit reduced forms and publishes pass/fail honestly; the index wholesale floor and spread capture are context, never graded (basis mismatch, disclosed). Model side: a claim-grade 26-week 2026 CE-CENTRAL run on the stylised anchor. Since the October 2026 re-fit the targets are the re-based index's 2025Q4–2026Q3 trailing year, and the FR cells are graded on the measured response services — the line the model was fitted to.

loading calibration verdicts…

Cross-walk on the index's own conventions — model vs measured, stream by stream (informational)

The same realised-anchor 2025 dispatch that reproduces the committed realised backtest cells, valued under the index's own ex-post proxy convention (model volumes marked at the realised MID series — the metered-at-MID FLOOR applied to model volumes) and set against the measured trailing-year wholesale floor, cycles and spread capture per duration class — the first like-for-like per-class wholesale comparison, published straight. On the re-based floor the direction differs by class, so compare the £ values; a ratio over a zero or negative measured floor is marked n/m. Nothing here is graded (no tolerance rule exists for these streams; one would be pre-registered first). The graded BM/FR cells are in the calibration table directly above.

loading cross-walk…

The standing out-of-sample record (§13)

Each quarter after 2026Q1, the revenue lines FROZEN in July 2026 under the pre-registered rule are graded against that quarter's measured index — data the frozen parameterisation has never seen. The record is append-only and survives every recalibration verbatim, the October 2026 re-fit included; grading verdicts begin once four out-of-sample quarters have accumulated. Two are on the record so far (2026Q2 and 2026Q3), so every row is still informational, published as measured.

loading out-of-sample record…

The cycles gap, step by step (informational)

Every step below is already published in the calibration evidence under its own disclosures — assembled here into one table so the graded REJECT is legible as attributed causes. The graded verdict stays the synthetic-anchor cycles REJECT on every class it grades (1h and 2h on the 2025Q4–2026Q3 window; the re-based 4h class carries no cycles target).

Where the model fails — published deliberately

Backtest evidence — measured record (2022–25 at 26 weeks + held-out 2026H1 at 13)

Pre-computed through the same run_backtest pipeline as the live form below, on the pinned public-data caches. The 2022–25 cells re-run live on a paid plan at the same 26-week convention the committed cells use (free Explore verification runs are capped at 8 weeks, so they sample a smaller window than the committed cell). The two 2026H1 cells carry the pre-registered 13-week window horizon and were produced through the harness's window path — the year form below takes full settlement years 2022–25 only, so those two rows are not reproducible from it. Generated stamp and honest caveats travel with the data. 2025 is the pre-registered HELD-OUT validation year: every rule (alignment bounds/quantiles, check targets, cycles rules, week sampling, coverage gates, fuel-multiplier derivation) was frozen in committed code before any 2025 backtest input was fetched, and the results publish regardless of outcome — protocol served at /heldout-protocol.html. The pre-registered H1-2026 held-out price window (§14) has since been EXECUTED — its rows render below (window mean PASS, P50/P95 FAIL, published unmodified); the standing quarterly out-of-sample revenue record (§13) now holds 2026Q2 and 2026Q3 and stays informational until four quarters accumulate (the record above).

📄 Evidence report (print)

loading evidence summary…

Where the error lives — sequential decomposition (informational)

Week-sampling sensitivity — 13/26/52-week horizons (disclosure)

Forward anchor freshness — trailing-12m calibration

The selectable realised_trailing price anchor is the supply curve recalibrated to the trailing 12 months of realised Elexon MID prices through the latest settled day (the same equilibrium-refit machinery as the realised-2024 anchor; in-sample for the window). Every anchored result discloses its calibration window; a window end older than 35 days flags runs STALE (they still work) until the calibration is refreshed here.

loading trailing-anchor calibration…

Backtest vs realised (Elexon MID) — run it live

The model is re-run for a historical year with the fleet that actually existed (site registry, no scenario top-up) and compared against realised GB half-hourly market-index prices from the public Elexon Insights API — annual mean within 5% is the credibility target (measured PASS/FAIL per year on the Evidence card above; the held-out 2025 year FAILS in both modes (P95 tail in both; synthetic also misses the 5% mean at +12.0%), published unmodified per the pre-registered protocol). Model-side profiles default to the calibrated stylised synthetic basis the supply curve is anchored to; realised historic demand/wind/solar (NESO data portal + Elexon FUELHH, cached in-repo) is selectable — a fitted affine basis alignment (in-sample for prices, disclosed in the payload) closes the measured cold level gap to ~4–6% on the window mean, but P50 stays cold and the P95 tail runs hot, so synthetic remains the default (the run's caveats carry the measured numbers). On 2023/24, below-band calibrated cycles coincide with price spreads matching realised — spread amplitude is ruled out as the cause, while double-counting of history's storage response, dispatch-cost assumptions and band composition are indistinguishable to that diagnostic (the dispatch-check table carries the applicability note verbatim); 2022 is unresolved (spreads 37% wide).

No backtest run this session — run one above to check the model against realised Elexon prices.

Latest run manifest

No runs yet.