57 lines
1.7 KiB
JavaScript
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);
|
|
}
|