institutional-trader/backend/src/factorlab/snapshotter.js

57 lines
1.7 KiB
JavaScript

import fs from 'fs';
import path from 'path';
import { pullRawRecord } from './ingest.js';
import { getUniverse } from '../services/stockUniverseService.js';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SNAPSHOT_FILE = path.join(__dirname, '../../../data/snapshots/fundamentals_snapshot.jsonl');
/**
* Monthly cron script to snapshot current point-in-time fundamentals.
* Append-only. Immutable.
*/
export async function runSnapshot() {
const snapshot_date = new Date().toISOString().split('T')[0];
console.log(`Starting PIT Snapshot for ${snapshot_date}`);
// Ensure directory exists
const dir = path.dirname(SNAPSHOT_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const universe = getUniverse();
let successCount = 0;
for (const stock of universe) {
if (stock.isETF) continue; // Skip ETFs for fundamental factors
const symbol = stock.symbol;
try {
const data = await pullRawRecord(symbol, snapshot_date);
if (!data) {
console.warn(`[SNAPSHOT] No data for ${symbol}`);
continue;
}
// Add index membership flag to the raw record
data.in_universe = true;
// Append-only rule: We never overwrite.
fs.appendFileSync(SNAPSHOT_FILE, JSON.stringify(data) + '\n');
successCount++;
} catch (e) {
console.error(`[SNAPSHOT] Failed for ${symbol}: ${e.message}`);
}
}
console.log(`Snapshot complete. Safely appended ${successCount} records to ${SNAPSHOT_FILE}.`);
}
// Allow running directly
if (process.argv[1] === __filename) {
runSnapshot().catch(console.error);
}