ONS Series
UK macroeconomic series from the Office for National Statistics.
ons_series — the dataset name to pass to the Obscura API.
What one row means
One published data point for a single curated UK ONS macro series (CPI/CPIH/RPI inflation indices or labour-market rates) for one reference period — e.g. the CPI 12-month rate for May-2026, or the unemployment rate for the March-May 2026 rolling window.
One row per (series_id, period_end).
Point-in-time availability
Every Obscura dataset carries available_date: the calendar day the publisher made the row
available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the
same thing on every dataset in the catalog.
For ons_series: published: available_date is the point's OWN publication day, materialized by the collector from revised_at (raw months[].updateDate) on ONS's London clock — m20260702_000183 replaced the generated period_end + 80 with a plain column and backfilled every row. That one scalar lag was split between two ONS release calendars ~60 days apart, so it was wrong for both — and wrong in the direction that invents a FUTURE date: the June 2026 CPI point claimed 2026-08-20, a month after ONS actually published it (2026-07-22), which hid the freshest real data from any point-in-time filter. release_date cannot serve, being document-level and identical on every row of a series. The date is taken in Europe/London because ONS stamps updateDate at LOCAL MIDNIGHT (00:00Z under GMT, 23:00Z on the previous UTC day under BST), so a naive UTC ::date reads a day early for every release from late March to late October.
Refresh cadence
Obscura refreshes ons_series daily — the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.
Schema — 14 columns
The full public column list for ons_series, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/ons_series.
| Column | Type | Description |
|---|---|---|
| series_id | text · not null | ONS series id (CDID), e.g. `D7BT`. Half of the upsert conflict key together with period_end. |
| period_end | date · not null | Reference-period date for this data point, computed by point_date() from the JSON point's {year, month/quarter} (first day of the reporting month/quarter/year). Other half of the upsert conflict key with series_id. |
| value | double precision | The observation value for this series at this reference period; an index number (2015=100) or a percent depending on series. Nullable — a point whose value fails to parse is stored as NULL rather than dropped. |
| dataset | text · not null | Source dataset code (e.g. `MM23` for CPI/RPI/CPIH price indices, `LMS` for Labour Market Statistics); also forms part of the source fetch URL. |
| name | text · not null | Human-readable label for the series curated in-code, e.g. "CPI 12-month rate (%)"; the only field documenting the value's units/meaning since `value` itself is unitless. |
| freq | text · not null | Reporting frequency label for the series: `monthly` | `quarterly` | `annual`. |
| available_date | date | PUBLIC-availability date — the day ONS published THIS observation, and the only column to filter or join on for point-in-time work. A PLAIN column written by the collector (`tasks::ons`) as `revised_at` read on ONS's own Europe/London clock, not a generated one: the availability of an ONS point is a property of the point, and no table-wide formula over `period_end` can express two release calendars ~60 days apart (the previous `period_end + 80` was look-ahead for the labour-market series and dated the price series into the FUTURE). NULL when the source states no `updateDate` — honestly unknown, never manufactured from the reference period. Never the reference period itself. |
| revised_at | timestamp with time zone | Per-point publication/revision timestamp (raw `months[].updateDate`, ISO e.g. `2026-06-16T23:00:00.000Z`): when THIS observation was last published/revised. The true point-in-time / vintage marker per data point, and the column `available_date` is materialized from (converted to Europe/London, since the stamp is local midnight — the example above is 2026-06-17 in London). NULL if the point carries no updateDate. |
| unit | text | Canonical unit of measure for `value` (raw `description.unit`, e.g. `%` or blank for an index number). Document-level metadata identical for every point of the series; `value` itself is otherwise unitless. |
| pre_unit | text | Prefix unit applied before `value` (raw `description.preUnit`, e.g. a currency prefix `£`). Document-level; usually empty for the curated index/rate series. |
| source_title | text | Source's authoritative series title (raw `description.title`, e.g. `CPI ANNUAL RATE 00: ALL ITEMS 2015=100`). We substitute a hand-curated `name`; this is the canonical label and drift-detects renamed/re-based series. Document-level. |
| key_note | text | Short note describing what `value` measures (raw `description.keyNote`, e.g. `Change over 12 months`). Document-level; NULL when the source omits it. |
| release_date | timestamp with time zone | Latest actual release timestamp from the ONS release calendar (raw `description.releaseDate`, ISO). DOCUMENT-level, so it is the same value on every row of a series (verified in production: min = max for all six series) — a freshness signal for the series as a whole, and NOT a per-row availability date. The per-point equivalent is `revised_at`. |
| next_release | date | Next scheduled release date from the ONS release calendar (raw `description.nextRelease`, parsed from a `DD Month YYYY` string, e.g. `16 July 2026`). Document-level; NULL when unparseable or unannounced. |
Access ons_series
Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.
import obscura
client = obscura.Client("obs_live_…")
df = client.query(
dataset="ons_series",
symbols=["NVDA", "AAPL"],
start="2024-01-01",
)
Create a free account Browse all 95 datasets
Frequently asked questions
What is in the ons_series dataset?
UK macroeconomic series from the Office for National Statistics. One published data point for a single curated UK ONS macro series (CPI/CPIH/RPI inflation indices or labour-market rates) for one reference period — e.g. the CPI 12-month rate for May-2026, or the unemployment rate for the March-May 2026 rolling window.
How do I avoid look-ahead bias with ons_series?
Filter on ons_series.available_date, the day the publisher made the row public. For this dataset that date is derived as follows — published: available_date is the point's OWN publication day, materialized by the collector from revised_at (raw months[].updateDate) on ONS's London clock — m20260702_000183 replaced the generated period_end + 80 with a plain column and backfilled every row. That one scalar lag was split between two ONS release calendars ~60 days apart, so it was wrong for both — and wrong in the direction that invents a FUTURE date: the June 2026 CPI point claimed 2026-08-20, a month after ONS actually published it (2026-07-22), which hid the freshest real data from any point-in-time filter. release_date cannot serve, being document-level and identical on every row of a series. The date is taken in Europe/London because ONS stamps updateDate at LOCAL MIDNIGHT (00:00Z under GMT, 23:00Z on the previous UTC day under BST), so a naive UTC ::date reads a day early for every release from late March to late October. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.
In what formats can I get ons_series?
As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="ons_series". The column schema is public at https://api.obscura.trade/v1/catalog/ons_series.
Can I filter ons_series by company or symbol?
Yes. ons_series carries series_id, the column the API's symbols filter resolves against.
How often is ons_series updated?
Obscura refreshes ons_series on a daily schedule — that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.