CI / Test & Type-Check (push) Canceled after 0s
Snapshot of in-progress module work across multiple slices: - Dealer Flow: dealerExposureEngine, dealerMapService, dealerMapExplain, dealerMapIntegrity, dealerMapReplay, dealerStudyEngine, hanStyleLevels - Mirror Portfolio (M21): fundRepository, captureIngest, mirrorAlertProducers, fund holdings strip, live book, position capture ingest - Options: BSM, NormalizedOptionSurface types, OptionsChainRouter, ConvexityGate, option legs panel - Alert producers: vixLevel, rotation, thesis, unlock, portfolioRisk, mirror (fund_capture, fund_13f, mirror_diff) - FINRA short interest adapter + queue integration - SEC company tickers adapter + ingest (symbol search index seed) - Vendor gate (rate-limit-first data plane, ADR-0009) - CUSIP registry, reverse 13F refresh, stock float service - LRU cache, portfolio backtest engine - Frontend: dealer-flow, funds, journal, lab, monitor, plan, portfolio, reports, screener, strategies, theses, guided-start, exits, more pages - Volume profile, workspace profile, visibility-aware poll - ADRs 0010 (mirror math not advice), 0011 (symbol search index) - VENDOR_INTEGRATIONS.md, END_USER_TEST.md - .gitignore: exclude DBs, .DS_Store, local config, agent scratch
151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
// 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<SourceKind, SourceFetch>([['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<number, { v: number; label: string }> = {
|
|
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);
|