"use client"; import { useMemo } from "react"; import { bsPrice, bsGreeks, bsSynthIV } from "@/lib/blackScholes"; import type { ChainRow, OptionType } from "./types"; function hashRand(seed: number): number { const x = Math.sin(seed) * 10000; return x - Math.floor(x); } export function buildChain(S: number, T: number, r: number, q: number, atmSigma: number, expirySeed: number): ChainRow[] { const rawMin = Math.round(S * 0.85 / 5) * 5; const rawMax = Math.round(S * 1.20 / 5) * 5; const rows: ChainRow[] = []; for (let K = rawMin; K <= rawMax; K += 5) { const iv = bsSynthIV(S, K, T, atmSigma); const cMid = bsPrice(S, K, T, r, q, iv, 'C'); const pMid = bsPrice(S, K, T, r, q, iv, 'P'); const cG = bsGreeks(S, K, T, r, q, iv, 'C'); const pG = bsGreeks(S, K, T, r, q, iv, 'P'); const seed = expirySeed * 1000 + K; const cOi = Math.round(hashRand(seed + 1) * 50000 + 1000); const pOi = Math.round(hashRand(seed + 2) * 40000 + 800); const cVol = Math.round(hashRand(seed + 3) * 20000 + 200); const pVol = Math.round(hashRand(seed + 4) * 15000 + 150); rows.push({ K, cMid, pMid, cIv: iv, pIv: iv, cDelta: cG.delta, pDelta: pG.delta, cOi, pOi, cVol, pVol, }); } return rows; } export function findAtmStrike(strikes: number[], S: number): number { return strikes.reduce((prev, curr) => Math.abs(curr - S) < Math.abs(prev - S) ? curr : prev, strikes[0]); } function fmtOi(n: number): string { if (n >= 1000) return (n / 1000).toFixed(1) + 'k'; return n.toString(); } interface ChainTableProps { rows: ChainRow[]; atmStrike: number; selectedK: number; type: OptionType; onPick: (K: number) => void; compact?: boolean; } export function ChainTable({ rows, atmStrike, selectedK, type, onPick, compact = false }: ChainTableProps) { return (

Option Chain

{compact ? 'Compact' : 'Full'} · {rows.length} strikes
{!compact && } {compact && } {!compact && } {compact && } {!compact ? ( ) : ( )} {rows.map((row) => { const isAtm = row.K === atmStrike; const isSel = row.K === selectedK; const cItm = type === 'C' ? row.K < atmStrike : row.K > atmStrike; const pItm = type === 'P' ? row.K < atmStrike : row.K > atmStrike; return ( onPick(row.K)} > {!compact && } {!compact && } ); })}
— calls —— calls —strike— puts —— puts —
OI IV last Δ K Δ last IV OI
IV last Δ K Δ last IV
{fmtOi(row.cOi)}{(row.cIv * 100).toFixed(1)}% {row.cMid.toFixed(2)} {row.cDelta.toFixed(2)} {row.K.toFixed(0)} {row.pDelta.toFixed(2)} {row.pMid.toFixed(2)} {(row.pIv * 100).toFixed(1)}%{fmtOi(row.pOi)}
); } interface OptionsChainProps { S: number; T: number; r: number; q: number; atmSigma: number; expirySeed: number; selectedK: number; type: OptionType; onPick: (K: number) => void; compact?: boolean; } export function OptionsChain({ S, T, r, q, atmSigma, expirySeed, selectedK, type, onPick, compact }: OptionsChainProps) { const rows = useMemo(() => buildChain(S, T, r, q, atmSigma, expirySeed), [S, T, r, q, atmSigma, expirySeed]); const atmStrike = useMemo(() => findAtmStrike(rows.map(rw => rw.K), S), [rows, S]); return ( ); }