ED.RANGE
Generic time-series access for any of 120+ metrics — average value or total energy.
Signature
ED.RANGE(metric, [start], [end], [agg], [zone], [headers], [noDate], [method], [tz], [unit])Description
Generic time-series accessor for any metric in the catalog. Output is a spill array with auto-formatted period and value columns. Use ED.METRICS() to discover available metrics.
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.
**Granularity floor by metric family:** energy_prices (dayahead, pvpc, balancing) and energy_volumes (gen_*, demand_*, etc.) support all agg values down to native (0/RAW, "5min", "15min"). Calendar-based metrics — commodities (brent, eua_co2, gold, …), futures (futures_yr, …) and capture prices (capture_*) — are stored at daily granularity in calendar tables; aggregations finer than daily (agg=0/RAW, "5min", "15min", "30min", or agg=1/H) are silently promoted to daily rather than rejected. **C2 liquidation (c2_*) is bucketed by calendar date here too, but its rows are NOT daily**: the settlement files are hourly (c2_rrtt_price, c2_rrtt_cost) or quarter-hourly (the other eight), and each day is reduced with the metric's natural aggregate — AVG for prices, SUM for energies and costs. Sub-daily c2_* reads are NOT offered on any surface: the liquidation hour index is 0-based over most of the history and 1-based recently, so an hour-addressed lookup could not be trusted. Daily and coarser buckets are unaffected.
**Unit (unit parameter):** by default each bucket holds the AVERAGE of the stored values. For metrics whose stored unit is MW (generation, demand, balancing band capacity, cross-border flows) you can pass unit="MWh" to convert to total energy delivered in each bucket — multiplied by the metric's native sample interval (5 min for ESIOS Spain gen/demand, 15 min for balancing, 60 min for ENTSO-E). Calling it on a price (EUR/MWh) or already-MWh metric returns a 400 error.
Pass tz to switch the timezone interpretation: 0/madrid (default, DST-aware), 1/cet (UTC+1 fixed — 8760 hourly buckets/year), or 2/utc. See /docs/concepts for why this matters around the spring/autumn DST transitions. The method argument is a no-op for everything except capture_* metrics, where 0=realized and 1=PBF.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| metric* | string | — | Metric name (see ED.METRICS). |
| 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 | 0-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. |
| zone | string | "ES" | Zone (electricity metrics only). Iberian "ES"/"PT", EU/external day-ahead zones, and the SENP islands "ES_CAN"/"ES_BAL" (demand + generation) / "ES_CEU"/"ES_MEL" (PVPC + thermal generation gen_diesel/gen_gas_turbine). The dispatch_price metric uses the per-subsystem SENP zones ("ES_CAN_GC"/"ES_CAN_TF"/"ES_CAN_GO"/"ES_CAN_HI"/"ES_CAN_PA"/"ES_CAN_LZFV" + "ES_BAL"/"ES_CEU"/"ES_MEL"). OMIP futures/forwards/swaps accept "ES" only. |
| headers | 0 | 1 | 0 | Set 1 for header row. |
| noDate | 0 | 1 | 0 | Set 1 to omit the period column. |
| method | 0 | 1 | 0 | capture_* metrics: 0=realized, 1=PBF. Ignored for other metrics. |
| tz | 0 | 1 | 2 | "madrid" | Timezone interpretation. |
| unit | "MW" | "MWh" | "MW" | For MW-stored metrics: "MW" returns AVG (default). "MWh" converts to total energy via SUM × native_sample_minutes/60. |
* Required.
Returns
Spill array — [period, value] rows with format inferred from the metric's unit (or MWh if unit="MWh").
Examples
=ED.RANGE("eua_co2", "2020-01-01",, 3)— CO2 monthly since 2020=ED.RANGE("dayahead", "2025-01-01", "2025-12-31", 1, "PT", 1)— PT hourly day-ahead with headers=ED.RANGE("demand_real", "2025-07-01", "2025-07-01", 0)— Real demand 5-min for one day (MW)=ED.RANGE("demand_real", "2025-01-01",, 6, , , , , , "MWh")— Real demand annual energy (MWh) — total electricity consumed=ED.RANGE("gen_total", "2025-01-01", "2025-12-31", 6, , , , , , "MWh")— Total domestic generation 2025 (MWh) — virtual aggregate, T.Real real-time=ED.RANGE("gen_total_measured", "2025-01-01", "2025-12-31", 6)— Same total but from REE\'s official measured series (settled, MWh)=ED.RANGE("gen_renewable", "2025-01-01", "2025-12-31", 3, , , , , , "MWh")— Renewable generation monthly (MWh) — solar+wind+hydro+biomass+…=ED.RANGE("gen_fossil", "2025-01-01", "2025-12-31", 6, , , , , , "MWh")— Fossil-fuel generation 2025 — for emissions analysis=ED.RANGE("afrr_energy_up", "2025-01-01",, 3)— aFRR upward energy activated, monthly. Already MWh-stored — SUM aggregation auto-applied.=ED.RANGE("gen_diesel", "2025-01-01",, 3, "ES_CAN")— Canarias diesel-engine generation, monthly (SENP island-only tech)=ED.RANGE("dispatch_price", "2025-01-01",, 3, "ES_CAN_TF")— Tenerife SENP dispatch marginal cost, monthly (Sphgenf, EUR/MWh)=ED.RANGE("dispatch_demand_price", "2025-01-01",, 3, "ES_CAN")— Canarias SENP demand acquisition price, monthly (Sphdem, per-archipelago, EUR/MWh)=ED.RANGE("brent", "2010-01-01",, 6)— Brent annual since 2010=ED.RANGE("futures_yr", "2015-01-01",, 2)— OMIP Cal+1 front-contract daily history since 2015 (same contract ED.GET returns)=ED.RANGE("forwards_m", "2015-01-01",, 3)— OMIP Base forward front-month, monthly average — one of the 14 metrics that used to come back emptyNotes
- Virtual gen aggregates (gen_total, gen_renewable, gen_thermal, gen_fossil) are computed on-the-fly: per civil hour the API averages the constituent gen_* metrics, then sums those over your bucket. They behave like any other MW metric — both unit=MW (default) and unit=MWh work.
- gen_total constituents (ES peninsular): solar_pv, solar_thermal, wind, nuclear, hydro, combined_cycle, coal, cogeneration, biomass, fuel_gas, pumped_storage, plus ENTSO-E catch-alls (oil, geothermal, other_renewable, waste, other) for non-Iberian zones. With this set the totals match REE published peninsular generation within ~1-2%.
- gen_total excludes gen_exchanges (interconnections, not domestic production) and gen_pumped_consumption (charging load, not generated energy). To include imports add gen_exchanges manually: =ED.RANGE("gen_total", …) + ED.RANGE("gen_exchanges", …).
- gen_thermal includes nuclear + cogeneration; gen_fossil excludes nuclear. Use gen_fossil for emissions analysis.
- **Authoritative alternative —
gen_total_measured**: REE's official post-settlement total (ESIOS id 10043, sum of per-province measured generation). Hourly, ~2-3 day lag, already in MWh. Use this when your output needs to match REE's published reports to the decimal. The virtualgen_totalis the same concept built from real-time T.Real indicators — slightly different numbers (1-2%) but available with no settlement lag. - **Auto-SUM for MWh-stored metrics**: when a metric is already stored as energy (MWh) — e.g.
gen_total_measured,intraday_s*_energy,afrr_energy_up/down,mfrr_*_assign_*— bucket aggregation uses SUM instead of AVG. Asking forunit="MWh"on these is a no-op (already energy). Asking forunit="MW"is silently ignored (energy can't be converted back to power without knowing the time span). - Granularity floor: time-series metrics (energy_prices / energy_volumes / virtual gen_*) accept all
aggvalues; calendar metrics silently promote sub-daily aggregations to daily. For commodities, futures andcapture_*that is because they really are one row per day. **c2_*is different**: the C2 liquidation files are hourly (c2_rrtt_price,c2_rrtt_cost) or quarter-hourly (the other eight) —ED.RANGEstill buckets them by calendar date, reducing each day with AVG for prices and SUM for energies/costs, so a dailyc2_rrtt_costfigure is the day's TOTAL over its 24 hourly rows. There is deliberately no sub-dailyc2_*read (andED.GETreturns empty for these metrics): the liquidation hour index is 0-based for most historical dates and 1-based for recent ones, so a per-hour value could not be trusted. Date bucketing is immune to that. - **SENP islands (ADR-026)**:
ES_CAN/ES_BALcarry real demand + generation by tech (archipelago aggregate, incl. island-onlygen_diesel/gen_gas_turbine/gen_steam, plus for Balearesgen_other_renewables/gen_auxiliary/gen_exchanges(Balearic link) /gen_waste);ES_CEU/ES_MELcarry PVPC plus thermal generation (gen_diesel/gen_gas_turbine). No island day-ahead. Requesting an unpopulated (metric, island) pair returns a 400 listing what IS available for that zone rather than an empty result. - **SENP dispatch cost (
dispatch_price)**: hourly marginal cost of the island merit-order dispatch (Sphgenf, art. 67.f RD 738/2015) — a settlement cost, not a market price. Per-subsystem:ES_CAN_GC/ES_CAN_TF/ES_CAN_GO/ES_CAN_HI/ES_CAN_PA/ES_CAN_LZFV(Canarias) +ES_BAL/ES_CEU/ES_MEL. History from 2017-04 (ES_BALfrom 2018-12);ES_CAN_TF/ES_CAN_GOconsolidate at the monthly settlement (~M+1), the rest refresh intra-month (~D+2). NoES_CANaggregate and no peninsularESseries. - **SENP demand price (
dispatch_demand_price)**: the demand-side companion (Sphdem settlement, RD 738/2015) — a settlement cost, not a market price. PER-ARCHIPELAGO, so the aggregateES_CANIS valid:ES_CAN/ES_BAL/ES_CEU/ES_MEL. Modern era only, from 2018-12 (monthly C2). Not on peninsularESnor on the per-subsystemES_CAN_*zones. - **⚠ OMIP futures/forwards/swaps — the numbers changed, and 14 metrics started working.** Applies to the 18 OMIP metrics (
futures_yr/q/m/ppa,futures_solar_*,futures_peak_*,forwards_*,swaps_*). **What it returns now:** one point per trading day, holding the settlement of the FRONT contract — the listed contract with the nearest delivery — for that instrument and tenor. That is the same contractED.GEThas always returned, so the two functions finally agree. **What it returned before:** the average of the ENTIRE forward curve, blended across all five OMIP instruments (Base + Solar + Peak + Forwards + Swaps), because the query filtered by contract type but not by instrument. On 2026-07-24futures_yrreturned **43.62** that way; the correct front-contract figure is **70.90**. If you have those older numbers in a workbook, expect a visible step up when the cells refresh — the old series was wrong, not merely different. **Also fixed:** the 14 non-Base metrics (forwards_*,swaps_*,futures_peak_*,futures_solar_*) used to return an empty series — and0when asked for a total; they now return their full history: from 2015-01-01 for forwards/swaps/peak, 2018-09-04 for solar, 2022-07-12 for the PPA curves. **Expect roll-over steps:** a front-contract series jumps when the front contract changes (each month/quarter/year), which is inherent to the convention, not a data error. **Zone"ES"only** — any other zone is now an explicit error rather than a silent Spanish number. **swaps_ppastays empty on purpose**: OMIP has never listed an SWB PPA contract. - **FTB, FWB and SWB carry the same price for the same CONTRACT — by clearing-house rule.** Seeing
futures_yr=forwards_yr=swaps_yr(and the same for_q/_m) is expected, not a bug: OMIClear assigns the same settlement reference price to every power derivative sharing an underlying asset, and the Base futures / forward / swap contracts all shareSPEL BASE. They differ in contract type, cash-vs-physical delivery, open interest and traded volume — not in price. Checked contract by contract on every shared contract-day since 2015: no discrepancies. **But the SERIES are not guaranteed to match.** Each metric follows the front contract of its own instrument, and the instruments do not delist on the same calendar: OMIP drops an expiring month from the forwards board before it drops it from the futures board, and on those days the two metrics are quoting different deliveries. In practiceforwards_mdiffers fromfutures_mon about 17 trading days a year (173 of 2,694 since 2015) — most recently 2026-06-30, 83.30 vs 90.00 EUR/MWh, because the forwards front had already rolled to Aug-26 while the futures front was still Jul-26. The yearly and quarterly tenors, and all theswaps_*, have never diverged. If you need a like-for-like comparison, compare a specific delivery rather than the two front series.futures_peak_*(SPEL PEAK) andfutures_solar_*(SPEL SOLAR) have a different underlying asset, so the rule does not apply to them and their prices genuinely differ. - **A total (
agg=7) over a window with no data returns #N/A, not 0.** Previously an empty window produced0— a perfectly plausible EUR/MWh figure and impossible to tell apart from a real one. This affects every metric family, not just OMIP. If you were relying on the old0(e.g. in a SUM), wrap the call:=IFNA(ED.RANGE(...), 0).