ED.CURTAIL.RANGE
Renewable curtailment — economic (forecast vs PBF) or technical (PBF vs P48).
Signature
ED.CURTAIL.RANGE(tech, type, [start], [end], [agg], [unit], [zone], [headers])Description
Renewable curtailment time series. Two methodologies:
- **Economic curtailment** (type="economic"): forecast − Σ PBF. The fraction of forecast renewable production that did not clear the day-ahead market because bids were rejected at low/negative prices. Source: ESIOS forecast 10358 (combined wind+solar) minus the sum of OMIE PBF programs. Currently only tech="vre" (combined) is supported because the upstream forecast is bundled.
- **Technical curtailment** (type="technical", default): PBF − P48 (downward redispatch). The MWh that cleared MD but were curtailed by the operator's RRTT (Restricciones Técnicas) Phases 1 + 2 to keep the grid stable. Combined Phase 1 + Phase 2 — the source data does not split them.
Aggregation is controlled by agg: 0=native, 5min/10min/15min, 1=hourly, 2=daily (default), 3=monthly, 4=quarterly, 5=semiannual, 6=annual, 7=total.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| tech* | 1-3 | string | — | Technology: 1=solar_pv, 2=wind, 3=vre (combined wind+solar). For type="economic" only vre is allowed (forecast is bundled). |
| type | 0 | 1 | string | 1 ("technical") | 0=economic (forecast − Σ PBF, VRE only), 1=technical (PBF − P48, default). |
| start | date | — | Start. Defaults: omit end for today (Madrid, with a small overnight cutoff), omit start for 30 days before end. |
| end | date | — | End. Omit for today. |
| agg | 1-7 | string | 2 (daily) | Aggregation is controlled by agg: 0=native, 5min/10min/15min, 1=hourly, 2=daily (default), 3=monthly, 4=quarterly, 5=semiannual, 6=annual, 7=total. This function always aggregates to Madrid civil hour first, so 1/H is its finest grain — there is no 15-min output, and sub-hourly agg values collapse to the hour. Cadence is not a fixed property of these series: ESIOS publishes them hourly on some days and in 15-min slots on others (P48 went 15-min on 2022-05-24 and PBF on 2025-10-01, but PBF was hourly again for the whole of 2025-12-31), so each series' cadence is read from the rows it published that civil day, never from a date. An hour is summed into the bucket only if every series the calculation needs published that hour in full at its own cadence. The forecast is hourly throughout. |
| unit | "mwh" | "pct" | "mwh" | Output unit. "mwh" returns curtailed MWh per bucket. "pct" returns curtailment as % of the reference series (PBF for technical, forecast for economic). |
| zone | "ES" | "ES" | ES only in v1 (PT not yet wired). |
| headers | 0 | 1 | 0 | Set 1 for header row. |
* Required.
Valid values for `tech`
| Code | English alias | Description |
|---|---|---|
| 1 | solar_pv, solar, pv | Solar photovoltaic. Available for type="technical" only. |
| 2 | wind, eolica | Onshore wind. Available for type="technical" only. |
| 3 | vre, renewable, wind_solar | Combined wind + solar. Required for type="economic" because the upstream ESIOS forecast (10358) is bundled. |
The add-in accepts either the canonical Spanish code or the English alias — both are equivalent.
Returns
Spill array — [period, value] rows. Value is MWh by default, or % when unit="pct". A bucket in which no hour carried every series the calculation needs — complete, not merely present — comes back as an empty cell, never 0; with agg=7 (or "T"), which collapses the range to a single scalar, that same case returns #N/A. An hour where one of those series published only part of its slots is a gap in the same way: it leaves the numerator and the denominator instead of being compared against a full hour of the other series. In unit="pct" a bucket whose reference adds up to 0 over its covered hours is empty too — the ratio is undefined, not 0 %.
Examples
=ED.CURTAIL.RANGE("vre", "economic", "2026-01-01", "2026-04-30", 3, "mwh")— VRE economic curtailment, monthly MWh=ED.CURTAIL.RANGE("vre", "economic", "2026-01-01",, 7, "pct")— VRE economic curtailment, total % of forecast=ED.CURTAIL.RANGE(1, "technical", "2026-01-01", "2026-04-30", 7, "pct")— Solar technical curtailment, total % of PBF (~17.7%)=ED.CURTAIL.RANGE("wind", "technical", "2026-01-01",, 3)— Wind technical curtailment, monthly MWh (default unit)Notes
- **Limitation — economic mode is VRE-only**: the ESIOS forecast indicator (10358) is
forecast_renewable_d1, a bundled wind+solar series. Per-tech economic curtailment would need a per-tech forecast which is not published. Callingtype="economic"withtech="solar_pv"or"wind"returns 400. - **Limitation — technical mode mixes Phase 1 + Phase 2**: PBF − P48 captures all downward redispatch, but the source does not separate Fase 1 (constraints solved at scheduling) from Fase 2 (real-time). They are reported aggregated.
- **Not implemented in v1**:
type="technical_settled"(would use C2c2_rrtt_energy_down— system aggregate, no tech split) andunit="eur"(cost in EUR via redispatch prices). Both return 400 with a clear message. - **Gaps and coverage**: each bucket is computed only over the hours that carry every series the calculation needs, in full —
technicalneeds the PBF and the P48 of a technology,economicneeds the forecast and every PBF that published. *In full* means the series published every slot that hour has at its own cadence for that civil day: one row on a day it publishes hourly, four on a 15-min day, and twice as many in the repeated civil hour of the October DST change, which holds two UTC hours. An hour missing one of the required series — or holding only part of it — is dropped from the numerator and the denominator alike instead of being read as a zero (or as a fraction) of that series, and a bucket with no usable hour at all comes back empty (#N/Aforagg=7/"T"). Withtech="vre"each technology is judged on its own PBF/P48 pair and only the covered ones contribute, so a solar gap never travels inside a complete wind total. Three cases where this shows: ESIOS does not publish P48 solar for the night hours when production is ~nil, so those hours leave the solartechnicalfigure — the effect is ≈0.01 pp on 2026 year-to-date totals (measured 2026-09-05); ESIOS also publishes only some of the quarters of a night hour often enough that 203 hours since 2025-10 were reporting 2 718 MWh of curtailment that the PBF had scheduled but no complete P48 hour ever confirmed, up to 100 % of a single hour (2026-03-13 06:00 was 407.75 MWh / 99.4 %, and is empty now); and the 2025-04-28/29 blackout had no day-ahead match, so ineconomicthe 29th is a gap (the function used to report it as 100 % curtailment) and the April 2025 monthly figure moves from ~8.2 % to ~1.6 %. The REST response carries the per-bucket detail in acoveragefield,[bucket, hours_used, hours_seen]in the same order as the values — and with no header row of its own, so underheaders=1it isvalues[i+1]that pairs withcoverage[i]. - **Methodology disclaimer**: technical % can disagree with publicly reported figures by several pp depending on the denominator (Σ PBF vs gross gen vs theoretical generation without restrictions) and the phase split assumed. The HANDOFF for the 2026-05-05 sanity-check documents a 17.7% (this function) vs 13% (LinkedIn analyst) divergence traced to denominator choice.
- **Related**: see
ED.CURTAIL.ECONfor the strict OMIE bid-curve definition (offered ≤ marginal − cleared, per tech). It answers a different question — what MWh would have cleared at the marginal price but didn't — and is restricted to must-run renewables (solar/wind/vre).