Jaspers Hub

jaspers / fed

pluginOfficialReviewed★ 0↓ 9

Fed Treasury Yield Curve (H.15): Gets the daily constant maturity Treasury yield curve, from 1 month to 30 years, out of the Federal Reserve's H.15 CSV download. Adds a 10y-2y spread. No key needed.

2.9 KB
1import { defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3
4
5// The Federal Reserve Board's own data download, which answers CSV without a key. FRED is the
6// friendlier way to the same numbers and wants a free key; this does not, so the yield curve is
7// here for nothing.
8
9const HOST = 'www.federalreserve.gov'
10/** H.15, the daily selected interest rates release: the constant maturity Treasury curve. */
11const H15 = 'bf17364827e38702b42a58cf8eaa3f78'
12/** The maturities in the order the release gives them, which is the order a curve is read in. */
13const TENORS = ['1m', '3m', '6m', '1y', '2y', '3y', '5y', '7y', '10y', '20y', '30y']
14
15/** A CSV line, honouring the quotes the Fed writes its headings in. */
16function cells(line: string): string[] {
17 const out: string[] = []
18 let current = ''
19 let quoted = false
20 for (let i = 0; i < line.length; i++) {
21 const char = line[i]!
22 if (quoted) {
23 if (char === '"' && line[i + 1] === '"') { current += '"'; i++ }
24 else if (char === '"') quoted = false
25 else current += char
26 } else if (char === '"') quoted = true
27 else if (char === ',') { out.push(current); current = '' }
28 else current += char
29 }
30 out.push(current)
31 return out
32}
33
34export const yields = defineSource({
35 description:
36 'The US Treasury yield curve, daily, from the Fed\'s own H.15 release: the constant maturity yield at each tenor from one month to thirty years. One row per day, one column per tenor, so a row is a curve and a column is a series.',
37 hosts: [HOST],
38 input: z.object({ days: z.number().int().min(1).max(5000).default(90).describe('Trading days to read, newest last.') }),
39 async run({ days }, ctx) {
40 const url = `https://${HOST}/datadownload/Output.aspx?rel=H15&series=${H15}&lastobs=${days}&filetype=csv&label=include&layout=seriescolumn`
41 const response = await ctx.fetch(url, { headers: { 'User-Agent': 'Jaspers Terminal (https://jsprai.com)' } })
42 if (!response.ok) throw new Error(`The Federal Reserve answered ${response.status}.`)
43 const lines = (await response.text()).split(/\r?\n/).filter((line) => line.trim() !== '')
44 // Five heading rows describe the series; the data starts where a line begins with a date.
45 const start = lines.findIndex((line) => /^\d{4}-\d{2}-\d{2},/.test(line))
46 if (start === -1) throw new Error('The Fed answered in a shape this does not know how to read.')
47 const rows = lines.slice(start).map((line) => {
48 const parts = cells(line)
49 const row: Record<string, unknown> = { date: parts[0] }
50 TENORS.forEach((tenor, index) => {
51 const value = parts[index + 1]
52 // The release writes ND on a day a rate was not published.
53 row[tenor] = value && value !== 'ND' && Number.isFinite(Number(value)) ? Number(value) : null
54 })
55 // What everyone actually watches.
56 row['10y-2y'] = typeof row['10y'] === 'number' && typeof row['2y'] === 'number' ? Number((row['10y'] - row['2y']).toFixed(2)) : null
57 return row
58 })
59 return { tenors: TENORS, rows }
60 },
61})