Jaspers Hub

jaspers / ecb

pluginOfficialReviewed★ 0↓ 2

ECB Euro Reference Rates and Deposit Rate: Fetches ECB daily euro reference exchange rates against a chosen currency, and the ECB deposit facility rate history, from the ECB data portal. No key needed.

2.6 KB
1import { defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3
4
5// The European Central Bank's data portal. No key. Euro area reference rates and policy rates, in
6// SDMX-JSON, which puts its numbers in one object and their dates in another.
7
8const HOST = 'data-api.ecb.europa.eu'
9
10interface Sdmx {
11 dataSets: { series: Record<string, { observations: Record<string, (number | null)[]> }> }[]
12 structure: { dimensions: { observation: { id: string; values: { id: string; name?: string }[] }[] } }
13}
14
15/**
16 * SDMX-JSON as rows. The observations are keyed by position, and the positions index the time
17 * dimension's values, so the two are read together.
18 */
19async function read(ctx: { fetch: typeof fetch }, path: string, query: string): Promise<{ date: string; value: number }[]> {
20 const response = await ctx.fetch(`https://${HOST}/service/data/${path}?${query}&format=jsondata`)
21 if (!response.ok) {
22 if (response.status === 404) throw new Error('The ECB has no series at that key.')
23 throw new Error(`The ECB answered ${response.status}.`)
24 }
25 const body = (await response.json()) as Sdmx
26 const dates = body.structure.dimensions.observation[0]?.values.map((one) => one.id) ?? []
27 const series = Object.values(body.dataSets[0]?.series ?? {})[0]
28 if (!series) throw new Error('The ECB answered with no series.')
29 return Object.entries(series.observations)
30 .map(([at, values]) => ({ date: dates[Number(at)] ?? at, value: values[0] }))
31 .filter((one): one is { date: string; value: number } => typeof one.value === 'number')
32 .sort((a, b) => a.date.localeCompare(b.date))
33}
34
35export const fx = defineSource({
36 description:
37 'The ECB euro reference rate against one currency, daily: how many units of that currency one euro buys. currency is a three letter code like USD, GBP, JPY, or CHF.',
38 hosts: [HOST],
39 input: z.object({
40 currency: z.string().default('USD').describe('A three letter currency code.'),
41 days: z.number().int().min(1).max(10000).default(250),
42 }),
43 async run({ currency, days }, ctx) {
44 const code = currency.trim().toUpperCase()
45 const rows = await read(ctx, `EXR/D.${code}.EUR.SP00.A`, `lastNObservations=${days}`)
46 return { pair: `EUR/${code}`, rows: rows.map((one) => ({ date: one.date, pair: `EUR/${code}`, rate: one.value })) }
47 },
48})
49
50export const policyRate = defineSource({
51 description: 'The ECB deposit facility rate, the euro area policy rate, every time it changed.',
52 hosts: [HOST],
53 input: z.object({ days: z.number().int().min(1).max(10000).default(400) }),
54 async run({ days }, ctx) {
55 const rows = await read(ctx, 'FM/D.U2.EUR.4F.KR.DFR.LEV', `lastNObservations=${days}`)
56 return { rows: rows.map((one) => ({ date: one.date, rate: one.value })) }
57 },
58})