"use client"; import { useMemo } from "react"; import { bsPrice, bsSynthIV } from "@/lib/blackScholes"; import type { Expiry, OptionType } from "./types"; interface PolarSmileProps { S: number; r: number; q: number; atmSigma: number; K: number; T: number; type: OptionType; expiries: Expiry[]; selectedExpiryIdx: number; } export function PolarSmile({ S, r, q, atmSigma, K, T, type, expiries, selectedExpiryIdx }: PolarSmileProps) { const SIZE = 680; const cx = SIZE / 2, cy = SIZE / 2; const maxR = SIZE * 0.42; const ivMin = 0.04, ivMax = 0.8; const ivRings = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]; const kMin = S * 0.82, kMax = S * 1.18; const nSpokes = 13; const strikes = useMemo(() => { const arr: number[] = []; for (let i = 0; i < nSpokes; i++) { arr.push(kMin + (kMax - kMin) * i / (nSpokes - 1)); } return arr; }, [kMin, kMax]); function strikeToAngle(strike: number): number { const norm = (strike - kMin) / (kMax - kMin); return -Math.PI / 2 + (norm - 0.5) * Math.PI * 1.5; } function ivToR(iv: number): number { return maxR * Math.max(0, Math.min(1, (iv - ivMin) / (ivMax - ivMin))); } function polarToXY(angle: number, radius: number): [number, number] { return [cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]; } const fairVal = useMemo(() => bsPrice(S, K, T, r, q, atmSigma, type), [S, K, T, r, q, atmSigma, type]); const curves = useMemo(() => { function _strikeToAngle(strike: number): number { const norm = (strike - kMin) / (kMax - kMin); return -Math.PI / 2 + (norm - 0.5) * Math.PI * 1.5; } function _ivToR(iv: number): number { return maxR * Math.max(0, Math.min(1, (iv - ivMin) / (ivMax - ivMin))); } function _polarToXY(angle: number, radius: number): [number, number] { return [cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]; } return expiries.map(e => { return strikes.map(strike => { const iv = bsSynthIV(S, strike, e.T, atmSigma); const angle = _strikeToAngle(strike); const radius = _ivToR(iv); return _polarToXY(angle, radius); }); }); }, [S, atmSigma, expiries, strikes, kMin, kMax, maxR, ivMin, ivMax, cx, cy]); function curvePath(pts: [number, number][]): string { return pts.map(([x, y], i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${y.toFixed(1)}`).join(' '); } const selCurve = curves[selectedExpiryIdx]; const kAngle = strikeToAngle(K); const kIv = bsSynthIV(S, K, T, atmSigma); const kR = ivToR(kIv); const [dotX, dotY] = polarToXY(kAngle, kR); const atmAngle = -Math.PI / 2; const eyeR = 28; const legendColors = [ 'var(--fg-4)', 'var(--fg-3)', 'var(--fg-2)', 'var(--fg-2)', 'var(--brass)', 'var(--brass-bright)' ]; return (