Methodology

This is a static research artifact. Source observations are validated and aggregated before they reach the browser; no page fetches a provider directly and no browser credential is required.

Scope

The study covers one market, Germany-Luxembourg (DE-LU): roughly seven and a half years of price, load and generation history, which is what a walk-forward benchmark and a multi-year storage valuation need.

Data Provider Committed coverage Use
Price, load, generation Energy-Charts From 2018-12-31, 94 monthly partitions The market study, the forecast and the battery valuation
Day-ahead load/wind/solar forecasts Energy-Charts From 2019-01-05 Labelled ablation, an upper bound
FCR and aFRR capacity auction results regelleistung.net From 2020-11-03, the first day both clear daily in four-hour blocks The revenue-stack comparison on the storage page

The store refreshes monthly; the prospective ledger runs daily and separately.

Two spreads, and why both are reported

The market view turns on a distinction that is easy to lose:

A day is complete at 23 observed hours, which is what the spring clock change leaves. Partial days are dropped rather than scaled, because a day the provider covered until noon has a genuinely smaller range and averaging it in would report a falling spread that is really a reporting gap.

Seasonality, and what a partial year costs. The within-day range is seasonal: averaged over 2019–2025 it runs about 64–70 EUR/MWh in January and February against 113–134 in August and September, because a solar-shaped day has a deeper midday trough in summer. The fourth quarter is wide too, so a year that ends in September is not consistently above its full-year average: January to September ran between −4.4% (2020) and +10.6% (2025) of the full year, with 2021 an outlier at −36% because the gas spike landed in its fourth quarter. Treat the current partial year's range as uncertain by roughly that much. Reproduce with gpa.metrics.price.intraday_spread(period="day") over the committed store.

Both series are published as a percentage of the same year's own average price. That normalisation is what separates a price-level shock from a change in daily shape; without it, 2022 dominates every chart and the structural change since is invisible. The hourly shape table applies the same normalisation to the mean price of each local clock hour.

Sources, lineage and revisions

No page fetches a provider directly, and no source in this project requires a credential. Each adapter is documented here because provenance, publication lag and revision behaviour change how far a result can be pushed.

Energy-Charts (api.energy-charts.info) is published by the Fraunhofer Institute for Solar Energy Systems ISE under CC BY 4.0. It republishes ENTSO-E and SMARD figures through an open API, so the underlying numbers are the ones the system operators publish. Four endpoints are used — /price for day-ahead prices, /public_power for load and generation by fuel, /public_power_forecast for the day-ahead fundamentals, which feed only the labelled ablation below, and /installed_power for installed capacity by technology together with the government's own 2030 targets. Two provider behaviours are handled explicitly rather than assumed: the end parameter is inclusive, and the German series changed resolution without notice, so interval length is measured from the returned timestamps instead of being hardcoded.

Every row in the committed store records Energy-Charts as its source. The retrospective archive and the prospective run read the same provider; what separates them is the publication vintage each can claim.

Revisions. Stored observations are the providers' latest revisions, not publication-time snapshots. Lagging every fundamental prevents a delivery-day value from entering a forecast, but it cannot prove that the exact stored revision was the one visible at the historical gate. This is the single most important limitation on the retrospective result, and it is restated wherever a figure depends on it. A prospective run does not share it, because it records its own fetch time.

Time and units

Every source timestamp is stored as a UTC-aware interval start and interpreted through the zone's market timezone before grouping by day, month or local hour. This prevents UTC day boundaries and daylight-saving changes from moving energy between trading days.

The European price history handles the transition from hourly to quarter-hourly products on a per-window basis. The published forecast remains an hourly analytical benchmark so it does not pretend to predict every traded quarter-hour.

Forecast protocol

The target is the DE-LU day-ahead price represented as a local clock-hour average. The information set is restricted to what is available before the day-ahead gate:

The retrospective panel does not substitute realised delivery-day demand or generation forecasts. Every model is refit in an expanding walk-forward loop, with dates strictly before its prediction date. The benchmark compares naive baselines, per-hour Ridge and pooled LightGBM. Hyperparameters are chosen on a validation period before the evaluation period.

The scorecard reports MAE, RMSE, bias, pinball loss and empirical interval coverage. Results are also broken down by market block, price regime, calendar year and hour so aggregate skill cannot hide a weak operating regime.

Day-ahead load/wind/solar forecasts (Energy-Charts) are backfilled from 2019-01-05 for DE-LU, but are not part of the published information set above. The provider exposes no publication timestamp, so every backfilled row is treated as public at its own D-1 noon gate. An ablation adding these features is shown as a labelled diagnostic on the Forecasting page, not folded into the published baseline.

That noon vintage is optimistic, and measurably so. Commission Regulation (EU) 543/2013 requires day-ahead wind and solar forecasts by 18:00 Brussels time on D-1, six hours after the gate (Article 14(1)(d)); only the load forecast is due before it (Article 6(1)(b)). A scheduled probe logged how much of the next delivery day Energy-Charts served, every three to five hours from 23 to 28 September 2026. On each of the five delivery days it covered, wind and solar were still missing at a check made between 2 h 10 min and 4 h 44 min after the gate. The load forecast appeared between a check before the gate and one after it, so its side of the gate is unresolved. The ablation's gain is therefore an upper bound for a forecast read from this source. A prospective Ridge arm with these features, ridge_da, found nothing to read in any of its 11 pre-gate runs and was retired with the probe on 28 September 2026.

See the complete result on the Forecasting page.

Capacity and market structure

The Battery page also correlates DE-LU price-shape metrics (capture rate, on/off-peak spread, negative-price frequency) against Germany's installed solar, wind and battery-storage capacity from Energy-Charts' /installed_power endpoint, including the government's own EEG 2023/WindSeeG 2030 targets. This is a country series (Germany), not a DE-LU bidding-zone series; Luxembourg's small share of DE-LU capacity is not in this data and is not estimated. Every correlation is annual with roughly seven complete-year points, excludes the current partial year using the underlying data's own last observed interval (not the day the site happens to be rebuilt), and is reported with its exact fitted range: this is a real, small-sample co-movement, not a fitted causal model, and none of it identifies whether battery-fleet growth is separately compressing this project's own arbitrage margin. One figure on that page, a BloombergNEF battery-price survey, is an external citation verified against its primary source rather than computed from this project's data.

Battery valuation

The battery study translates forecast error into an economic decision. The published base case is:

Assumption Value
Power 1 MW
Energy cases 1, 2 and 4 MWh
Round-trip efficiency 90%
Dispatch horizon Complete eligible local day
Daily cycling policy At most one charge-then-discharge episode (headline); a two-episode case is run separately
Initial and terminal SOC 0 MWh
Operating and degradation cost 0 in the base case; configurable

The optimizer uses forecast prices to choose a full-day schedule, then settles it against the observed price; there is no intraday re-optimization. The schedule is built by a backward dynamic program whose phase dimension counts episodes: charging after a discharge opens the next episode, and is refused once the day's allowance is spent. The headline allows one episode, which is the conservative reading of "one cycle a day"; the two-episode stress relaxes exactly that assumption and nothing else, and is reported beside the headline rather than replacing it.

Within a single episode, terminal SOC equal to initial SOC makes throughput exactly twice the peak SOC excursion, so bounding that peak enforces the per-episode cycle budget. That equivalence is per-episode: with several episodes the daily ceiling is the product of the per-episode budget and the episode count, which is why both bounds are declared separately. no_trade is the zero-value baseline, while perfect_foresight runs the same physical optimizer using realised prices only as an upper bound. Neither result is a trading recommendation.

The Battery page is a pre-computed historical backtest. The prospective ledger is evaluated separately after its delivery prices are known, against the three naive comparators issued at the same gate on the same inputs, so its incremental value is measured the same way as the retrospective one.

All three fixed naive models, Ridge and LightGBM share the same complete days. The strongest naive is ranked retrospectively over the sample, not selected daily as an oracle or promoted to a prospective policy. The battery page shows each fixed comparison, illustrative re-optimized costs, observed downside, incremental concentration and paired exploratory block-bootstrap intervals. Its fixed sensitivity protocol includes 85% efficiency, signal attenuation toward D-1, whole-day calendar downtime and a two-episode day. These are diagnostic assumptions, not a German asset calibration. Cost rates apply to charge plus discharge at the grid boundary; capital and fixed/lifetime costs remain outside the model. See the Battery page for exact definitions and source attribution.

Where the forecast earns. Each day is labelled before any model result is read: whether any hour cleared below zero, weekday or weekend, the quarter, and the quintile of "shape surprise", one minus the rank correlation between the best naive's hourly forecast and the realised prices. Surprise is judged after the fact, so it explains where the margin came from; it is not a signal a desk had at the gate. Groups sum to the total increment.

Forecast disagreement is the label a desk does have at the gate: how much more Ridge's schedule expected to earn than the best naive's, both valued at Ridge's forecast, in quintiles. The switch test kept the naive's schedule unless that expectation exceeded a threshold. Each day's threshold was the past decile (or never switching) that would have earned most on the days settled before its gate, after a one-year burn-in. Measuring disagreement instead by the rank correlation between the two hourly forecasts gave the same result.

Decision metrics. A day's cheapest and dearest forecast hours count as hits when within one hour of the realised ones. Negative hours are flagged by the point forecast below zero, or by the lower 10% quantile below zero; precision is the share of flags that were right, recall the share of negative hours flagged.

Quarter-hour value. From 1 October 2025, each day with 96 quarter-hours after a day with 96 is dispatched twice with identical physics: on its quarter-hour prices, and on each hour's average repeated across its quarters, which a flat hourly schedule settles at exactly. Both use a 1/64 MWh SOC grid, because on the hourly 0.25 MWh grid a quarter-hour at 1 MW could not move a single step. Clock- change days drop out. Perfect foresight is the ceiling; repeating the previous day's prices, at the same resolution, is the simple rule.

Revenue stack. FCR and aFRR capacity auction results come from the German TSOs' platform, one workbook per day and product, reading the German result only. A 1 MW battery is credited with one symmetric megawatt of FCR, or one megawatt of positive plus one of negative aFRR, for every four-hour block of the day. FCR clears at one uniform price; aFRR is pay-as-bid, so its average accepted price is what a typical accepted bid earned, not a price any bid was guaranteed. Activation energy, state-of-charge management and prequalification are not modelled, and neither is splitting a day between markets. The ex-ante rule commits each day on what is known before the balancing auctions close at 08:00 on D-1: the previous delivery day's balancing prices, and the margin the day-ahead schedule expects from its own forecast, whose inputs all predate that hour. The best market in hindsight is a ceiling, not a strategy.

Assumptions register

Every published figure rests on the assumptions below. The battery's physical parameters are in the table above; this register collects the rest, with the reason each one was chosen and what it costs the result.

Assumption Choice Rationale Consequence if wrong
Forecast gate 12:00 market time on D-1 The day-ahead auction's order book closes at midday for next-day delivery, so this is the last instant a bidder's information set is fixed A later gate would report hindsight as skill
Target resolution Local clock-hour, duration-weighted Day-ahead coupling moved to 15-minute market time units for delivery from 1 October 2025; the hourly figure is an analytical aggregate from then on The benchmark does not price a traded quarter-hour product
Information set Lagged prices, calendar features, residual load lagged ≥ 2 delivery days Stored revisions cannot certify publication-time vintages, so realised delivery-day fundamentals are excluded Reported skill is lower than a fundamentals-driven model would show; the ablation quantifies the gap
Fundamentals vintage Backfilled day-ahead forecasts are treated as public at their D-1 noon gate The archive exposes no publication timestamp Optimistic: the probe found wind and solar still missing two to nearly five hours after the gate on every day it covered, so the ablation's gain is an upper bound and the features stay out of the published baseline
Walk-forward protocol Expanding window, refit with dates strictly before each forecast day Mirrors how a model would actually be maintained in production A fixed split would hide regime-dependent decay
Hyperparameter selection Frozen on a validation window preceding the test period Selection inside the evaluation window reports a tuned fit as out-of-sample Published scores would be optimistically biased
Evaluation stance Retrospective development benchmark on already-inspected history The sample was examined during development Not an untouched holdout; a prospective ledger is still required
Common sample All five strategies scored on identical days and hours Prevents a model from winning by being evaluated on easier cells Comparisons would not be like-for-like
Missing data Left missing; incomplete intervals excluded, never imputed A provider gap is information, not a zero Fewer scored cells, but no invented observations
Costs Zero in the base case; illustrative non-zero cases rerun separately Rates are not calibrated German project estimates Margins are gross of asset-specific costs and of all capital costs
Daily cycling One charge-then-discharge episode in the headline The conservative reading of a one-cycle-a-day asset, and the binding constraint on almost every day in the sample Understates a real German battery, which cycles more than once; the two-episode stress measures by how much, and shows the simple rules earn almost all of the extra margin too
Revenue stack Day-ahead arbitrage, compared with FCR and aFRR capacity, one market per day Balancing capacity results are public; intraday prices are not, and activation energy needs data this project does not ingest Not a revenue estimate for a real battery: continuous intraday, mFRR and activation energy would add revenue, while prequalification, state-of-charge management, costs and bidding risk would reduce it
Intraday market Not modelled No free public source for German continuous intraday prices; EPEX data is licensed The study says nothing about intraday value or the day-ahead to intraday spread, which is the natural next step
Storage fleet composition Not observed Energy-Charts publishes installed battery power and energy as one aggregate with no residential/grid-scale split An average duration near 1.5 hours is consistent with a fleet of mostly home systems but does not establish it, so the market view does not rely on it. The Marktstammdatenregister would settle it and is outside this project's ingestion
Storage competition set Batteries and pumped hydro, both from the installed-capacity series Pumped hydro arbitrages the same daily spread and is the incumbent a battery-only view would miss Demand-side response, industrial flexibility and cross-border flexibility are not counted, so the competing fleet here is a lower bound
Scope Zonal, not nodal The day-ahead auction clears at bidding-zone level Congestion, basis and transmission constraints are outside the result

Quality controls

Validation happens at the source boundary with strict schemas for price, load and generation. The checks enforce canonical units, UTC timestamps, allowed resolutions, valid fuel labels, unique interval keys and plausible ranges.

The forecast and dispatch tests additionally check leakage boundaries, market local time, missing observations, SOC limits, power limits, terminal SOC, efficiency and cost treatment. gpa export --check regenerates the static tables in a temporary directory and compares them with the committed files, so the dashboard cannot silently drift from the Python analysis.

Reproduce

pip install -e ".[dev]"
gpa validate
gpa audit
gpa balancing            # FCR and aFRR results, incremental; slow on first run
gpa quarter-hour-study   # the 15-minute value study; minutes
gpa export
npm ci
npm run build

The prospective ledger lives under data/forecast_issues/ and grows by a bot commit on every scheduled run; gpa forecast-attempt report --start-date ... --end-date ... gives the verified daily denominator the ledger counter on the forecast page summarises. The retired publication-time probe's log is kept on the repository's separate probe-log branch.

The source repository contains the validated monthly Parquet store, forecast code, battery optimizer and test suite. The historical output is deliberately versioned so the figures shown on the site are reproducible.

References

Data providers, whose terms govern the underlying observations:

Market rules and structure:

Technical and cost references, used as stated external comparisons and never as calibration for this project's own figures:

Limitations

The forecast is retrospective until a separately recorded six-week prospective period is complete, and even then that period tests the process, not the value across seasons. The published information set uses lagged realised fundamentals, not operator forecasts: the public day-ahead wind and solar forecasts arrive after the gate, so they inform only the labelled ablation on the Forecasting page, as an upper bound. A desk's information set would add commercial forecasts issued before the auction, for which there is no public, credential-free source. Continuous intraday trading is not modelled, for want of a free public price source, and balancing is compared on capacity revenue only. The study is zonal, not nodal; congestion, basis and transmission constraints are outside scope.