/** * 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} 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 }