// One-shot: refresh IREN options surface, rebuild dealer map, diff vs Heatseeker snapshot. import { db as openDb } from '../src/db/client.ts'; import { createCacheRepository, type SourceKind } from '../src/cache/CacheRepository.ts'; import { AdapterQueue } from '../src/queue/AdapterQueue.ts'; import { composeYFinanceWithOptions } from '../src/options/OptionsChainRouter.ts'; import { YFinanceAdapter } from '../src/adapters/YFinanceAdapter.ts'; import { OptionsAdapter } from '../src/adapters/OptionsAdapter.ts'; import type { SourceFetch } from '../src/adapters/SourceAdapter.ts'; import { clearVendorRateLimit } from '../src/services/vendorGate.ts'; import { getDealerMap, recomputeDealerMapFromCacheDetailed, scheduleDealerMapInputs, } from '../src/analysis/dealerMapService.ts'; const database = openDb(); clearVendorRateLimit('yfinance'); for (const k of database .prepare("SELECT key FROM queue_state WHERE key LIKE 'source_cooldown%yfinance%' OR key LIKE 'source_cooldown:yfinance%'") .all() as Array<{ key: string }>) { database.prepare('DELETE FROM queue_state WHERE key=?').run(k.key); } const yfinanceAdapter = composeYFinanceWithOptions( new YFinanceAdapter({ db: database }) as unknown as SourceFetch, new OptionsAdapter() as unknown as SourceFetch, ); const adapters = new Map([['yfinance' as const, yfinanceAdapter]]); const queue = new AdapterQueue({ db: database, adapters, rateLimitMs: { yfinance: 0 } }); const cache = createCacheRepository({ db: database, scheduler: queue }); queue.cache = cache; const deps = { db: database, cache, queue }; console.log('1) schedule quote + chains for IREN...'); await scheduleDealerMapInputs(deps, 'IREN', 6); console.log('2) drain until chains land (max ~120s)...'); const t0 = Date.now(); let rounds = 0; while (Date.now() - t0 < 120_000) { await queue.drain(); rounds += 1; const chains = database .prepare( `SELECT expiry, COUNT(*) n, MAX(ts) ts FROM options_chains WHERE symbol='IREN' AND expiry >= date('now') GROUP BY expiry ORDER BY expiry LIMIT 8`, ) .all() as Array<{ expiry: string; n: number; ts: string }>; const quote = database.prepare(`SELECT observed_at FROM kv_cache WHERE key='yfinance:quote:IREN'`).get() as | { observed_at: string } | undefined; if (rounds % 4 === 0) { console.log( ' round', rounds, 'expiries', chains.map((c) => `${c.expiry}:${c.n}`).join(','), 'quote', quote?.observed_at?.slice(0, 19), ); } const fresh = chains.filter((c) => c.ts && Date.now() - Date.parse(c.ts) < 45 * 60_000); if (fresh.length >= 4 && quote && Date.now() - Date.parse(quote.observed_at) < 45 * 60_000) break; await new Promise((r) => setTimeout(r, 1200)); } const detail = recomputeDealerMapFromCacheDetailed(database, 'IREN', { maxExpiries: 6, write: true }); console.log('3) recompute', { asOf: detail.map?.asOf, spot: detail.map?.spot, king: detail.map?.levels?.king, keptPrior: detail.keptPriorMap, msg: detail.message, integrity: detail.integrity?.status, }); const result = await getDealerMap(deps, 'IREN', { forceSchedule: true, maxExpiries: 6 }); const m = result.map ?? detail.map; if (!m) { console.error('NO MAP after rebuild'); process.exit(1); } const fmt = (n: number) => { const k = n / 1000; if (Math.abs(k) >= 1000) return `$${(k / 1000).toFixed(1)}M`; return `$${k.toFixed(1)}K`; }; // Heatseeker LIVE ~03:45:52 PM, IREN 40.41, GEX 0DTE column from screenshot const hs0: Record = { 48: { v: 742_400, label: '+742.4K' }, 45: { v: 231_800, label: '+231.8K' }, 43: { v: 284_600, label: '+284.6K' }, 42: { v: -455_900, label: '-455.9K' }, 41.5: { v: 288_300, label: '+288.3K' }, 41: { v: -605_900, label: '-605.9K' }, 40.5: { v: 3_192_100, label: '+3.19M' }, 40: { v: -5_735_300, label: '-5.74M ★' }, 39.5: { v: 2_021_300, label: '+2.02M' }, 39: { v: -37_200, label: '-37.2K' }, 38: { v: 1_502_600, label: '+1.50M' }, 37.5: { v: 567_400, label: '+567K' }, 37: { v: 638_000, label: '+638K' }, 34: { v: -1_402_300, label: '-1.40M' }, }; console.log('\n=== LIVE rebuild vs HS 0DTE (HS @40.41 ~3:45pm ET) ==='); console.log('OUR spot', m.spot, 'asOf', m.asOf, 'status', result.status); console.log( 'strike'.padStart(6), 'HS'.padStart(12), 'OUR_0DTE'.padStart(12), 'ratio'.padStart(8), 'sign', ); let ok = 0; let flip = 0; for (const s of Object.keys(hs0) .map(Number) .sort((a, b) => b - a)) { const cell = m.cells.find((c) => c.strike === s && c.expiry === '2026-08-07'); const our = cell?.gex ?? null; const hs = hs0[s]; let ratio = '—'; let sign = '—'; if (our != null && Math.abs(hs.v) > 0) { ratio = (our / hs.v).toFixed(2); if ((our < 0) === (hs.v < 0)) ok += 1; else flip += 1; sign = (our < 0) === (hs.v < 0) ? 'OK' : 'FLIP'; } console.log( String(s).padStart(6), hs.label.padStart(12), (our == null ? 'miss' : fmt(our)).padStart(12), ratio.padStart(8), sign, ); } console.log(`\nsign match ${ok} / flip ${flip}`); console.log('king', m.levels?.king); console.log('flip/walls', { flip: m.levels?.flip, callWall: m.levels?.callWall, putWall: m.levels?.putWall }); console.log('quality', m.quality); const c35 = m.cells.find((c) => c.strike === 35 && c.expiry === '2026-08-21'); const c34 = m.cells.find((c) => c.strike === 34 && c.expiry === '2026-08-21'); console.log('Aug21 35', c35 ? fmt(c35.gex) : 'miss', 'HS~+3.3M'); console.log('Aug21 34', c34 ? fmt(c34.gex) : 'miss', 'HS~+2.9M'); console.log('\nDone.'); process.exit(0);