#!/usr/bin/env npx tsx /** * CLI: backfill stock_float table with sharesOutstanding / floatShares from Yahoo Finance. * * Usage (from app/server): * npx tsx scripts/backfill-stock-float.ts * npx tsx scripts/backfill-stock-float.ts --symbol IREN # single symbol * npx tsx scripts/backfill-stock-float.ts --all # all symbols in DB * * Reads tracked fund positions and 13F filings to find symbols needing float data. * Respects vendor rate limits; skips entries fetched within the last 24h. */ import { DatabaseSync } from 'node:sqlite'; import { resolve, dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { fetchAndPersistFloat, refreshAllStockFloats } from '../src/services/stockFloatService.ts'; const __dirname = dirname(fileURLToPath(import.meta.url)); const SERVER_ROOT = join(__dirname, '..'); const DB_PATH = resolve(SERVER_ROOT, 'data', 'investor-flow.db'); function arg(name: string): string | undefined { const i = process.argv.indexOf(name); if (i < 0 || i + 1 >= process.argv.length) return undefined; return process.argv[i + 1]; } async function main() { const symbolArg = arg('--symbol'); const forceFlag = arg('--force') === 'true' || arg('--force') === ''; console.log('Backfilling stock float data from Yahoo Finance...'); console.log(`DB: ${DB_PATH}`); if (symbolArg) console.log(`Symbol filter: ${symbolArg.toUpperCase()}`); if (forceFlag && !symbolArg) console.log('Mode: force refresh all symbols'); const db = new DatabaseSync(DB_PATH); try { let result; if (symbolArg) { // Single symbol backfill. console.log(`Fetching float for ${symbolArg.toUpperCase()}...`); await fetchAndPersistFloat(db, symbolArg.toUpperCase()); const row = db.prepare( `SELECT shares_outstanding, float_shares FROM stock_float WHERE symbol = ? ORDER BY as_of DESC LIMIT 1` ).get(symbolArg.toUpperCase()) as { shares_outstanding: number | null; float_shares: number | null } | undefined; console.log(` sharesOutstanding: ${row?.shares_outstanding ?? 'null'}`); console.log(` floatShares: ${row?.float_shares ?? 'null'}`); result = { refreshed: 1, skipped: 0, errors: [] }; } else if (forceFlag) { // Force refresh all symbols regardless of TTL. result = await refreshAllStockFloats(db, { forceRefresh: true }); } else { // Default: refresh stale entries only. result = await refreshAllStockFloats(db); } console.log('\nDone.'); console.log(` Refreshed: ${result.refreshed}`); console.log(` Skipped (fresh): ${result.skipped}`); if (result.errors.length > 0) { console.log(`\nErrors (${result.errors.length}):`); for (const err of result.errors.slice(0, 10)) console.log(` - ${err}`); if (result.errors.length > 10) console.log(` ... and ${result.errors.length - 10} more`); } } finally { db.close(); } } main().catch((err) => { console.error('Backfill failed:', err); process.exit(1); });