53 lines
2.1 KiB
JavaScript
53 lines
2.1 KiB
JavaScript
/**
|
|
* FRED (Federal Reserve Economic Data) CSV fetcher via curl.exe
|
|
*
|
|
* Why curl.exe instead of axios:
|
|
* Node.js axios → FRED's Akamai CDN times out on SP500, DGS2, DGS10 series.
|
|
* curl.exe → same URL returns data in ~1s.
|
|
*
|
|
* Why date range params (cosd/coed):
|
|
* Full DGS2/DGS10 datasets (daily from 1962) are huge → Akamai times out.
|
|
* Limiting to recent years solves this. All calculations only need ≤3 years.
|
|
*
|
|
* Root cause: Akamai CDN differentiates by TLS fingerprint AND URL pattern.
|
|
*/
|
|
|
|
const { execFile } = require('child_process')
|
|
|
|
const FRED_BASE = 'https://fred.stlouisfed.org/graph/fredgraph.csv'
|
|
|
|
/**
|
|
* Fetch FRED CSV data using curl.exe with a date range limit
|
|
* @param {string} seriesId - FRED series ID (e.g. 'SP500', 'FEDFUNDS')
|
|
* @param {object} [opts]
|
|
* @param {number} [opts.timeout=20] - curl --max-time seconds
|
|
* @param {number} [opts.lookbackYears=3] - years to look back from today
|
|
* @param {string} [opts.coed] - custom observation end date YYYY-MM-DD (default today)
|
|
* @param {string} [opts.cosd] - custom observation start date YYYY-MM-DD (default coed - lookbackYears)
|
|
* @returns {Promise<string>} raw CSV text
|
|
*/
|
|
const fetchFredCurl = (seriesId, opts = {}) => {
|
|
const timeout = opts.timeout || 20
|
|
const lookbackYears = opts.lookbackYears || 3
|
|
const now = new Date()
|
|
const coed = opts.coed || now.toISOString().slice(0, 10)
|
|
const cosd = opts.cosd || (new Date(now.getTime() - lookbackYears * 365 * 24 * 60 * 60 * 1000)).toISOString().slice(0, 10)
|
|
const url = `${FRED_BASE}?id=${encodeURIComponent(seriesId)}&cosd=${cosd}&coed=${coed}`
|
|
|
|
return new Promise((resolve, reject) => {
|
|
execFile('curl.exe', ['-s', '--max-time', String(timeout), url], {
|
|
encoding: 'utf8',
|
|
timeout: (timeout + 10) * 1000,
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
windowsHide: true
|
|
}, (err, stdout, stderr) => {
|
|
if (err) {
|
|
const msg = stderr ? stderr.trim() : err.message
|
|
return reject(new Error(`curl failed for ${seriesId}: ${msg}`))
|
|
}
|
|
resolve(stdout)
|
|
})
|
|
})
|
|
}
|
|
|
|
module.exports = { fetchFredCurl } |