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).
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:
| State | Meaning | How it is declared |
|---|---|---|
| Measured | Real upstream data, passed through with unit conversion and aggregation only. | live: true; upstream named in meta.upstream. |
| Derived | Computed 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. |
| Modeled | An 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.
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.
| Series | Upstream | Archive depth |
|---|---|---|
| Day-ahead prices (11 zones) | ENTSO-E A44 | ~2 years, extended daily |
| Generation by fuel (8 countries) | ENTSO-E A75 | Rolling, extended hourly |
| Installed capacity / outages / load | ENTSO-E A68 · A77+A80 · A65 | Yearly · event-driven · hourly |
| EU gas storage | GIE AGSI+ | From 2015-01-01 |
| EU LNG send-out & fullness | GIE ALSI+ | From 2015-01-01 |
| Commodity benchmarks (5 series) | FRED | From 2015-01-01 |
| GB N2EX day-ahead auction | Nord Pool N2EX portal | From 2026-05-18, accrues daily |
| Core FBMC MaxBex | JAO Publication Tool | 365-day backfill, extended daily |
| German redispatch events | netztransparenz.de | From 2024-07 |
| German settled imbalance (AEP) | netztransparenz.de | From 2022 |
| Norwegian reservoir fill | NVE Magasinstatistikk | From 1995, weekly |
| EUA primary auctions | EEX (licensed) | From 2020, auction-day cadence |
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.
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.
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.
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:
meta.completeness — arrived points ÷ expected points for the window at the inferred resolution, in [0, 1].meta.gaps — explicit timestamp ranges where expected points are missing, rather than a silently thinner series.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.
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.
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.
null until enough real changes exist before that display day — rendered as a break in
the line, never a fabricated early reading from a too-short sample.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:
null for that one day — an honest gap, not an approximation borrowed from Feb 28 or
Mar 1.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.
The merit-order curve is assembled from three measured inputs plus one clearly-labeled modeled layer:
| Input | Source | Role |
|---|---|---|
| Installed capacity per fuel | ENTSO-E A68 (yearly) | Block widths, per reference year |
| Active derates | ENTSO-E A77 + A80 outage messages | Unavailable MW, subtracted per fuel |
| Latest actual load | ENTSO-E A65 (hourly) | The demand line |
| Marginal costs | Modeled assumption | Block heights — never a market quote |
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.
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.
| Fuel | EUR/MWh | Fuel | EUR/MWh |
|---|---|---|---|
| wind | 0 | biomass | 25 |
| solar | 0 | coal | 42 |
| hydro | 5 | gas | 68 |
| nuclear | 8 | oil | 120 |
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.
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.
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.
/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.