VOLTSTACK.ENERGY
Methodology Guide
voltstack.energy | Dublin, Ireland
Version 1.7 · Updated 1 October 2026
Reference · Methodology Guide v1.7

Methodology Guide

How every number on the Voltstack terminal and Data API is produced: what is measured, what is derived, what is modeled, and how each of those states is declared to you. Companion to the Data Dictionary (field-level reference) and the API Specification (transport and endpoints).

Scope Live feeds, settled history, derived measures, modeled figures
Principle A value is real, or it is declared — never silently fabricated
Revision Version 1.7 · Updated 1 October 2026
Contact data@voltstack.energy
01 — The Data-Integrity Model

Every payload declares its own provenance through the response envelope (API Specification §4). Three states exist, and consumers can always tell which one they are looking at:

StateMeaningHow it is declared
MeasuredReal upstream data, passed through with unit conversion and aggregation only.live: true; upstream named in meta.upstream.
DerivedComputed from measured data by a documented method (e.g. realized volatility, the five-year band).live: true; the derivation is named in meta.upstream and specified in this guide.
ModeledAn assumption, not an observation (e.g. merit-order marginal costs). Never blended into measured fields.Labeled as modeled in the payload's documentation and in the terminal UI, regardless of live status.

When a real value cannot be served, the response is a declared fallback — live: false with a machine-readable reason — never a stale or invented number presented as fresh. Composite figures follow an AND-gate: a payload built from several inputs is marked live only when every input is real. The merit-order stack, for example, requires real capacity, real derates and real demand together; two out of three is a fallback, not a partial truth.

VOLTSTACK METHODOLOGY GUIDE§1 · INTEGRITY MODEL
02 — The Settled Archive

History endpoints are served from Voltstack's own settled archive, not from request-time calls to the upstream. An ingestion worker persists each feed's settled values on the upstream's own publication cadence; one-off backfills seed the depth. The same persisted series feeds both the live headline figure and its history — one source of truth, never two independently maintained pipelines that can disagree.

SeriesUpstreamArchive depth
Day-ahead prices (11 zones)ENTSO-E A44~2 years, extended daily
Generation by fuel (8 countries)ENTSO-E A75Rolling, extended hourly
Installed capacity / outages / loadENTSO-E A68 · A77+A80 · A65Yearly · event-driven · hourly
EU gas storageGIE AGSI+From 2015-01-01
EU LNG send-out & fullnessGIE ALSI+From 2015-01-01
Commodity benchmarks (5 series)FREDFrom 2015-01-01
GB N2EX day-ahead auctionNord Pool N2EX portalFrom 2026-05-18, accrues daily
Core FBMC MaxBexJAO Publication Tool365-day backfill, extended daily
German redispatch eventsnetztransparenz.deFrom 2024-07
German settled imbalance (AEP)netztransparenz.deFrom 2022
Norwegian reservoir fillNVE MagasinstatistikkFrom 1995, weekly
EUA primary auctionsEEX (licensed)From 2020, auction-day cadence

Honesty clamp: oldestAvailable

A history request never silently returns a shorter series than asked. Every history payload reports oldestAvailable — the earliest real row held, independent of the requested window (per series for commodities, since daily and monthly cadences clamp at different depths). When real coverage starts later than the requested from, the terminal renders "history since X" instead of pretending the full window arrived.

Data age

meta.dataAgeSeconds counts from the moment the worker last wrote the rows behind the payload (their ingestion timestamp), not from when the response was computed. It is recomputed on every response — including cache hits — so a cached payload never freezes or hides its own age. It measures ingestion, not currency: the worker rewrites the timestamp whenever it re-reads a window, so a re-fetch of unchanged values resets it. For cross-border flows and the negative-price radar it is instead the age of the oldest delivery slot served.

meta.dataAsOf answers a different question: how current is the data itself. It is the instant of the newest data point behind the headline values (the delivery slot, settlement period, gas day or observation it describes; for a forecast, the moment the forecast was captured). Where a payload headlines several series it is the stalest series' newest point, so one frozen series cannot hide behind fresh ones. A published day-ahead curve runs ahead of now, and dataAsOf says so rather than being clamped.

On an as_of read both fields describe what was held at that instant, never what is held now: dataAsOf is the newest data point captured by as_of, and dataAgeSeconds counts from the newest capture at or before as_of to as_of.

The public /status page ages every feed from dataAsOf and holds it to the cadence of that row's slowest series: the current delivery slot for day-ahead prices, which always run ahead; the six-hourly NESO wind delta for the GB balancing row; a few hours for cross-border flows, whose slowest border can lag its hour; two to three days after the gas day for storage, pipeline flows and LNG; and for EU carbon auctions eleven days inside the year and the year-end pause once the silence starts in December. Each budget clears the age a healthy pipeline reaches just before its next write, so a feed should read stale only when a write is late. Some of those worst cases are measured and some are estimated from the publisher's stated schedule. A series older than its budget reads stale and is counted apart from the live feeds. A last-good fallback is shown with the age of the data it retains, not the age of the fetch that retained it. In the health payload behind the page, live keeps the envelope's meaning (real data rather than a simulated fallback) and stale says whether that data is current, so a stale row is live: true and stale: true. The page's footer counts a feed as live only when it is both, and not last-good or degraded. Widgets stamp their title bar from the same dataAsOf.

Point-in-time capture

The settled archive keeps one value per delivery interval; a source revision replaces it. Two capture layers sit alongside it. Event-grade series retain every version our polls or re-reads have seen. REMIT urgent market messages and unit outages have done so since 2026-07-14, each version dated with the publisher's own timestamp. German redispatch measures and GB balancing acceptances have done so from the date /coverage shows for each of those two series, shown there even before either one holds an actual revision: neither source gives its own publication time for a correction, so each later version is dated to when Voltstack captured it instead, never invented from the measure's own window or the acceptance's own instructed time. A GB balancing acceptance is versioned on what Elexon states in it (its levels, segment end, unit name, closing settlement period and flags); the offer or bid price we attach is our own match against Elexon's bid-offer data, a derived value rather than a published one, so it never makes a version and is corrected in place when better bid-offer data arrives. Version 1 of a redispatch measure carries the measure's own window start as published_at, and version 1 of a GB balancing acceptance carries Elexon's own acceptance time; only a later version of either is dated to when we captured it. An append-only vintage sidecar records what the worker observed: the four forecast feeds (daily weather, forecast risk, the GB NESO forecast, the FR RTE forecast) at every capture pass since 2026-07-31, the JAO flow-based domains whenever a value changes, and day-ahead prices, imbalance prices, the German AEP estimator and NRV balance, actual generation and load, AGSI+ storage, ALSI+ LNG and cross-border flows once when a value is first captured and again whenever it changes. An identical re-fetch records nothing, so a restatement can be told from a re-read. The instant recorded is when Voltstack held the value, which is never earlier than the fetch that returned it and is not the publisher's own timestamp; a value is stamped as its write begins and lands a moment later, so an answer for an instant less than 15 minutes old may still change and is never cached. Capture runs forward from each series' start and is never backfilled. The as_of parameter on the settled-history endpoints reads these vintages and refuses any instant before a series' start; no derivation in this guide reads them yet.

03 — Resolution, Completeness & Gaps

Day-ahead resolution is not assumed. The single day-ahead coupling moved from hourly (PT60M) to quarter-hourly (PT15M) market time units on 2025-10-01, so a history window can be hourly, quarter-hourly, or genuinely mixed. Resolution is inferred from the actual spacing between consecutive points in each response, and the quality figures follow from it:

04 — Daily Baseload Average

Long-range price charts and every derived measure in §5 use the daily baseload average: the mean of all delivery-slot prices in the UTC calendar day, whatever the underlying resolution. On the 1-day view, four quarter-hourly prices average into one hourly bar for legibility. A price, like any rate, is honestly summarized by an average over a shorter window — unlike an energy quantity (MWh), which would need summing instead.

GB balancing accepted prices

The per-period accepted offer and bid prices for GB (bm_accepted_offer_price_GB and bm_accepted_bid_price_GB in the elexon-bm export) average the offer or bid prices Voltstack matches to Elexon's acceptances against Elexon's bid-offer data, weighted by energy. Elexon reports an acceptance as the segments of its ramp, and a segment's level runs in a straight line from its accepted level in MW at its start to the one at its end. Each segment weighs in every half-hour settlement period it delivers in by the MWh it delivers inside that period: its absolute level integrated over the part of the segment that falls in the period, split where a ramp crosses zero. A segment spanning two periods counts in both for its share of each, and one that runs past midnight counts in the next day's periods for the energy it delivers in them. A side of a period whose priced segments deliver no energy inside it, which only a segment of no duration can do, has no figure rather than zero. The figure is not Elexon's System Buy or Sell Price, and the top-of-stack prices are maxima, not averages: each still takes the segments that start in its period. Matched prices at Elexon's placeholder values, of magnitude 9,900 and above, are left out. A period's figure is recomputed from every acceptance held for it, the latest version of each, whenever a pass touches that period, so an acceptance a later pass reads, or a corrected match, moves it. Acceptance segments for a day's last periods that are made after its last pass and end by midnight are not captured, because no later pass reads those periods. A day's last periods are numbered 47 and 48 on most days, 45 and 46 on the 46-period day when the clocks go forward in March, and 49 and 50 on the 50-period day when they go back in October, and every day starts again at period 1; a segment is placed by its own start and end, so those two days need nothing different.

The figures are computed this way from the date /coverage shows for GB balancing acceptances. Before it, each segment was weighted by its absolute level at its end, or by one where that level is zero, not by its energy, and counted in the period it starts once for every settlement period query that returned it, so one spanning two periods weighed twice there and not at all in the next; a day's last periods also took in the day before's segments that ran past midnight, which moved their top-of-stack prices and spread as well. Those segments were placed by the number of the period they started in, so on the day after the March change they went into periods 45 and 46, and on the October change day into periods 47 and 48, neither of them that day's last periods, while on the March change day and the day after the October change, which have no periods with those numbers, they were left unpriced. Values stored for earlier periods keep those weights until restated. When the repair runs it restates each day's last periods from the acceptances stored for that day, the next day's periods with the numbers those segments were placed by, and the next day's periods they run into; it can also restate every earlier period on the energy weighting, in one later pass.

VOLTSTACK METHODOLOGY GUIDE§2–4 · ARCHIVE & PRICES
05 — Realized Volatility

Why not implied volatility: no freely redistributable options data exists for European power or gas, so a strike/tenor implied-vol surface cannot be built honestly. Voltstack publishes a realized measure derived from its own settled day-ahead archive instead, and labels it as such.

Why not log returns: EU day-ahead prices go negative routinely — every zone Voltstack tracks has real negative-price hours on record. ln(P_t / P_t−1) is undefined the moment either price is zero or negative, so the equities-style annualized log-return vol is unusable here — not merely fragile.

vol(w, d) = sample std dev ( ΔP over the trailing w real trading days ending at d )
where ΔP = (day-N daily baseload average) − (day-N−1 daily baseload average), in ccy/MWh
06 — The Five-Year Storage Band

The EU gas-storage "normal range" is computed, not simulated. For each display day, the band is the min / max / average of the same calendar month-day across the five years strictly before that day's own year:

07 — Why LNG Send-Out Has No Band

Underground storage follows a repeatable seasonal shape (inject in summer, withdraw in winter), which is what makes a five-year band a meaningful "normal range". LNG send-out does not: it is driven by spot-arbitrage economics — inter-basin price spreads, freight, and terminal slot availability. A min/max/avg band over that series would dress noise up as a norm, so the LNG history endpoint publishes the real trend only.

VOLTSTACK METHODOLOGY GUIDE§5–7 · DERIVED MEASURES
08 — The Merit-Order Supply Stack

The merit-order curve is assembled from three measured inputs plus one clearly-labeled modeled layer:

InputSourceRole
Installed capacity per fuelENTSO-E A68 (yearly)Block widths, per reference year
Active deratesENTSO-E A77 + A80 outage messagesUnavailable MW, subtracted per fuel
Latest actual loadENTSO-E A65 (hourly)The demand line
Marginal costsModeled assumptionBlock heights — never a market quote

Reference-year rule

One reference year is chosen per country — the most recent calendar year any fuel reports capacity for — and only fuels with a row in that year enter the stack. Taking each fuel's own all-time-latest row independently would resurrect retired fleets (Germany's nuclear exit is the live example: a per-fuel-latest read shows a phantom ~4 GW nuclear block from pre-shutdown years). Excluding a retired fuel entirely is the honest behavior; carrying a stale figure forward is not.

Derates and the AND-gate

Available MW per fuel = installed − the sum of currently active derates (latest version per outage message), floored at zero. Derate collection covers all eight merit-order countries (DE, FR, ES, IT, NL, NO, SE, PL). The AND-gate still applies: a zero derate read only counts as real for a country the collector actually asks about — the stack is marked live only where capacity, derates and demand are all real, and falls back otherwise.

Modeled marginal costs

FuelEUR/MWhFuelEUR/MWh
wind0biomass25
solar0coal42
hydro5gas68
nuclear8oil120

Fixed short-run assumptions for ordering the stack, carried with a permanent "costs: modeled" label in the terminal regardless of live status. The indicated clearing price is the modeled cost of the marginal fuel where cumulative availability meets demand — an illustration of the stack, not a price forecast. Fuels are eight buckets: ENTSO-E reports one MW figure per production type, so gas cannot be split into CCGT/OCGT cost tiers on real data.

09 — Caching & Freshness

Server-side cache TTLs are matched to each upstream's true publication cadence — polling faster than the TTL returns the cached value with cached: true, and meta.dataAgeSeconds keeps counting from the real ingestion timestamp either way. A cache fill is never reported as data age: the age /status prints is measured from meta.dataAsOf, as §02 describes. TTLs per domain are tabulated in the API Specification §7.

Degree days: HDD/CDD are computed at read time from the persisted forecast against a base of 18°C (EU) / 65°F (US) by default. An optional per-request base (°C, clamped 5–30) re-bases both; the base joins the cache key so different bases never share a cached value.

10 — Simulated Data

Where a real input does not exist yet, the terminal falls back to simulation — and says so. The Data Sources panel groups every connector by honesty tier (Live · Awaiting Access · Simulated · Enterprise, plus an Available slot for tenant-configured custom REST integrations) so the first question it answers is what is real. A payload is either entirely real or a declared fallback; measured and simulated values are never blended inside one figure.

Methodology as at 2026-10-01. v1.7 · 2026-10-01: Publisher details: Voltstack Limited, Dublin, Ireland. No derivation or model assumption changed. v1.6 · 2026-09-28: the point-in-time capture section no longer says German redispatch measures and GB balancing acceptances retain every version with a publication timestamp since 2026-07-14, since neither source gives one. It points each at the date /coverage shows for it, says what version 1 of each carries as published_at (the measure's own window start, or Elexon's own acceptance time), and says that a GB acceptance's matched price is our derivation, corrected in place rather than versioned. §04 now states how the GB balancing accepted prices are derived: each acceptance segment weighted, in every half-hour it delivers in, by the MWh it delivers inside that half-hour, with no figure for a side that has no energy behind it, and says that acceptance segments for a day's last periods made after its last pass that end by midnight are not captured, and how a day's last periods are numbered on the 46-period day in March and the 50-period day in October. That derivation changed from the date /coverage shows for GB balancing acceptances: before it, a segment was weighted by its level, one spanning two periods counted twice in the period it starts, and a day's last periods took in the day before's segments that ran past midnight, placed by period number, so on the day after the March change into periods 45 and 46 and on the October change day into periods 47 and 48, and those earlier values may be restated by a repair. No model assumption changed. v1.5 · 2026-09-23: §02's point-in-time section records the price and fundamentals series now captured on change, the as_of read over them and when its answer is final, and the data-age paragraphs state what dataAsOf and dataAgeSeconds describe on that read. No derivation or model assumption changed. v1.4 · 2026-09-23: §02 and §09 describe meta.dataAsOf, how /status holds each feed to its slowest series' cadence, and what meta.dataAgeSeconds does and does not measure; no derivation or model assumption changed. v1.3 · 2026-07-31: document version and date published as machine-readable meta tags (voltstack-doc-version, voltstack-doc-updated) and printed in the page header; the PDF edition is served alongside the HTML at /docs/. v1.2 — 2026-07-31: archive-depth table extended to the constraint & event layer, point-in-time capture section added, degree-day base documented, connector tier list completed. No derivation or model assumption changed in this revision; changes to any derivation or model assumption ship with a revision of this guide. © 2026 Voltstack Limited.
VOLTSTACK METHODOLOGY GUIDEdata@voltstack.energy