85 lines
2.3 KiB
TypeScript
85 lines
2.3 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|||
|
|
/**
|
||
|
|
* CLI: backtest GEX/VEX integrity by replaying chains as-of a timestamp.
|
||
|
|
*
|
||
|
|
* npx tsx scripts/dealer-map-replay.ts --symbol IREN --as-of 2026-08-07T13:14:00.000Z
|
||
|
|
* npx tsx scripts/dealer-map-replay.ts --symbol IREN --from-snapshots --days 7
|
||
|
|
*
|
||
|
|
* Exit 1 if integrity incomplete or snapshot drift too large.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { DatabaseSync } from 'node:sqlite';
|
||
|
|
import { resolve } from 'node:path';
|
||
|
|
import { replayDealerMapAt, replayFromSnapshots } from '../src/analysis/dealerMapReplay.ts';
|
||
|
|
|
||
|
|
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];
|
||
|
|
}
|
||
|
|
|
||
|
|
function has(name: string): boolean {
|
||
|
|
return process.argv.includes(name);
|
||
|
|
}
|
||
|
|
|
||
|
|
const symbol = (arg('--symbol') ?? 'IREN').toUpperCase();
|
||
|
|
const asOf = arg('--as-of');
|
||
|
|
const days = Number(arg('--days') ?? '7');
|
||
|
|
const fromSnapshots = has('--from-snapshots');
|
||
|
|
|
||
|
|
const dbPath =
|
||
|
|
arg('--db') ??
|
||
|
|
resolve(process.cwd(), 'data/investor-flow.db');
|
||
|
|
|
||
|
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
||
|
|
|
||
|
|
function printResult(r: ReturnType<typeof replayDealerMapAt>) {
|
||
|
|
console.log('---');
|
||
|
|
console.log(r.symbol, r.asOf);
|
||
|
|
console.log(' ok:', r.ok, '|', r.integrity.status);
|
||
|
|
console.log(' ', r.summary);
|
||
|
|
if (r.map?.levels.king) {
|
||
|
|
console.log(
|
||
|
|
' king:',
|
||
|
|
r.map.levels.king.strike,
|
||
|
|
r.map.levels.king.expiry,
|
||
|
|
`gex=${(r.map.levels.king.gex / 1000).toFixed(1)}K`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
console.log(
|
||
|
|
' metrics:',
|
||
|
|
JSON.stringify(r.integrity.metrics),
|
||
|
|
);
|
||
|
|
const fails = r.integrity.checks.filter((c) => !c.ok);
|
||
|
|
if (fails.length) {
|
||
|
|
console.log(' failed checks:');
|
||
|
|
for (const c of fails) {
|
||
|
|
console.log(` [${c.level}] ${c.id}: ${c.detail}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let anyFail = false;
|
||
|
|
|
||
|
|
if (fromSnapshots) {
|
||
|
|
const results = replayFromSnapshots(db, symbol, days);
|
||
|
|
if (results.length === 0) {
|
||
|
|
console.error('No snapshots for', symbol);
|
||
|
|
process.exit(2);
|
||
|
|
}
|
||
|
|
for (const r of results) {
|
||
|
|
printResult(r);
|
||
|
|
if (!r.ok) anyFail = true;
|
||
|
|
}
|
||
|
|
} else if (asOf) {
|
||
|
|
const r = replayDealerMapAt(db, symbol, asOf);
|
||
|
|
printResult(r);
|
||
|
|
if (!r.ok) anyFail = true;
|
||
|
|
} else {
|
||
|
|
console.error('Usage: --symbol SYM --as-of ISO | --from-snapshots [--days N] [--db path]');
|
||
|
|
process.exit(2);
|
||
|
|
}
|
||
|
|
|
||
|
|
db.close();
|
||
|
|
process.exit(anyFail ? 1 : 0);
|