Skip to content

Beyond day-ahead: intraday, margins, and offshore outages

The headline wrappers (day_ahead_prices, actual_total_load, cross_border_physical_flows) cover most everyday workflows, but ENTSO-E publishes a long tail of more specialised documents. This page introduces four wrappers that cover those specialised documents:

WrapperWhat it returns
intraday_pricesContinuous-intraday / SIDC IDA auction clearing prices (12.1.D, A07)
intraday_wind_solar_forecastWind/solar forecast republished as intraday auctions clear (14.1.D, A40)
year_ahead_forecast_marginGeneration-adequacy margin one year out (8.1, A70/A33)
unavailability_of_offshore_gridOutage notices for offshore-grid infrastructure (10.1.C, A79)

Setup

julia
using ENTSOE
using Dates

include(joinpath(pkgdir(ENTSOE), "test", "_brokenrecord_helpers.jl"))
const BR = _load_brokenrecord()
client = ENTSOEClient("PLAYBACK")

Intraday wind & solar forecast

When the day-ahead forecast looks wrong overnight (a storm front shifts, clouds break unexpectedly), TSOs republish their wind/solar forecast on the intraday horizon. intraday_wind_solar_forecast exposes the latest published forecast — same wire endpoint as the day-ahead variant, but with the intraday process type:

julia
forecast = BR.playback("generation_141d_intraday_wind_solar_forecast_NL.yml") do
    intraday_wind_solar_forecast(
        client, EIC.NL,
        DateTime("2024-09-01T22:00"), DateTime("2024-09-02T22:00"),
    )
end
forecast[1]
(time = Dates.DateTime("2024-09-01T22:00:00"), psr_type = "B16", value = 0.0)

Pull out one technology — PsrType.SOLAR resolves to the IEC code "B16":

julia
solar = forecast[forecast.psr_type .== PsrType.SOLAR]
length(solar), solar[1]
(96, (time = Dates.DateTime("2024-09-01T22:00:00"), psr_type = "B16", value = 0.0))

For server-side filtering (useful when the response is large), pass the same constant to the wrapper's psr_type kwarg:

julia
intraday_wind_solar_forecast(client, EIC.NL, t1, t2;
    psr_type = PsrType.SOLAR)

Intraday prices

Intraday cleared prices ride on the same 12.1.D endpoint as day-ahead, distinguished by the contract-marketagreement type (A07 vs A01). The wrapper takes an optional sequence=1/2/3 kwarg to pick a specific IDA auction; omit it to receive every published sequence in one call.

ENTSO-E publishes A07 patchily — many zones don't expose intraday results on a given day, and the body comes back as an ENTSOEAcknowledgement with reason_code = "999". DE_LU on 2024-09-01 is one such case, and the wrapper surfaces the empty document as a typed exception rather than crashing the parser:

julia
reason_code = try
    BR.playback("market_121d_intraday_prices_DE_LU.yml") do
        intraday_prices(
            client, EIC.DE_LU,
            DateTime("2024-09-01T22:00"), DateTime("2024-09-02T22:00"),
        )
    end
    nothing
catch err
    err isa ENTSOEAcknowledgement || rethrow()
    err.reason_code
end
"999"

reason_code == "999" is ENTSO-E's "no matching data" code — wrap intraday calls in try/catch whenever you sweep across zones or dates that may not all have IDA results.

Year-ahead forecast margin

year_ahead_forecast_margin answers the adequacy question one year out: how much surplus capacity is forecast to exist over forecast peak load? One row per published period — typically a single annual snapshot in MW.

julia
margin = BR.playback("load_81_year_ahead_forecast_margin_BE.yml") do
    year_ahead_forecast_margin(
        client, EIC.BE,
        DateTime("2023-12-31T23:00"), DateTime("2024-12-31T23:00"),
    )
end
margin[1]
(time = Dates.DateTime("2023-12-31T23:00:00"), value = 970.0)

Distinct from year_ahead_load_forecast — that's the forecast of demand alone (document A65); this is the difference between forecasted available capacity and forecasted peak load (document A70).

Offshore-grid outages

The offshore-grid outage notices live on a separate endpoint from onshore transmission outages (10.1.C vs 10.1.A/B) and a different document type (A79 vs A78). The wrapper takes a single bidding_zone rather than the in_Domain/out_Domain pair the onshore endpoint requires.

The response is an application/zip bundle (one XML notice per event), so the cassette is stored as BSON — YAML can't byte-stably round-trip binary bodies. We flip BrokenRecord's extension before loading:

julia
BR.configure!(; extension = "bson")
outages = BR.playback("outages_101c_unavailability_offshore_grid_DE_LU.bson") do
    unavailability_of_offshore_grid(
        client, EIC.DE_LU,
        DateTime("2024-01-01"), DateTime("2024-04-01"),
    )
end
length(outages), propertynames(outages)
(27, (:start, :stop, :business_type, :resource_name, :resource_mrid, :psr_type, :nominal_mw))

Sample row:

julia
outages[1]
(start = Dates.DateTime("2023-12-30T20:53:00"), stop = Dates.DateTime("2024-01-31T17:58:00"), business_type = "A54", resource_name = "TTG/500/BUETTEL-HELWIN ALPHA/DC501", resource_mrid = "11TD2L000000268M", psr_type = "B22", nominal_mw = 576.0)

Same parse_unavailability shape used by the onshore wrappers (unavailability_of_generation_units, ..._production_units, ..._transmission_infrastructure), so existing analyses port across unchanged. Filter to unplanned outages with the BusinessType constant:

julia
unplanned = outages[outages.business_type .== BusinessType.UNPLANNED_OUTAGE]
length(unplanned)
27

EIC codes from plain strings

EIC is also callable with a plain country-code string, so zones can be constructed dynamically without field access:

julia
EIC("NL") == EIC.NL, EIC("DE_LU") == EIC.DE_LU
(true, true)

This matters when you're iterating over a list of country-code strings (e.g. from a config file or another data source) — no need to hand-write a Dict("NL" => EIC.NL, "BE" => EIC.BE, …) lookup.