"use client"; import { useState, useEffect } from "react"; import { bsSynthIV } from "@/lib/blackScholes"; import type { TickerOverview } from "@/types/api"; import type { OptionInputs } from "./types"; import { EXPIRIES } from "./types"; import { Pricer, SolvePanel } from "./OptionsPricer"; import { OptionsChain } from "./OptionsChain"; import { SmileChart, TermStructure, GreekMini, Payoff } from "./OptionsCharts"; import { PolarSmile, IvHeatmap } from "./OptionsSurface"; interface OptionsPageProps { overview: TickerOverview | null; ticker: string; } export function OptionsPage({ overview, ticker }: OptionsPageProps) { const [view, setView] = useState<'terminal' | 'surface'>('terminal'); const [expiryIdx, setExpiryIdx] = useState(1); const spot = overview?.quote.price ?? 0; const chgAbs = overview?.quote.change ?? 0; const chgPct = overview?.quote.change_pct ?? 0; const r = 0.0425; const q = overview?.ratios.dividend_yield_ttm ?? 0; const atmSigma30 = 0.243; const sym = overview?.profile.symbol ?? ticker; const name = overview?.profile.name ?? ""; const expiry = EXPIRIES[expiryIdx]; const [inputs, setInputs] = useState(() => ({ S: spot || 100, K: spot ? Math.round(spot / 5) * 5 : 100, T: expiry.T, r, q, sigma: atmSigma30, type: 'C', })); useEffect(() => { if (spot > 0) { setInputs(prev => ({ ...prev, S: spot, K: Math.round(spot / 5) * 5, r, q, })); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [spot]); function patchInputs(partial: Partial) { setInputs(prev => ({ ...prev, ...partial })); } function selectExpiry(idx: number) { setExpiryIdx(idx); setInputs(prev => ({ ...prev, T: EXPIRIES[idx].T })); } const atmIv = bsSynthIV(inputs.S, inputs.S, expiry.T, atmSigma30); const d25C = 0.243 + 0.012; const d25P = 0.243 + 0.012; const rr25 = (d25C - d25P) * 100; const bf25 = ((d25C + d25P) / 2 - atmIv) * 100; const pcRatio = 0.87; if (!overview || spot === 0) { return (
Options

Select a ticker to view options

Load a ticker from the search bar to access the Black-Scholes pricer, option chain, vol surface, and greek visualizations.

); } const terminalView = (
patchInputs({ K })} />
{ const idx = EXPIRIES.findIndex(e => Math.abs(e.T - T) < 0.001); if (idx >= 0) selectExpiry(idx); }} />
); const surfaceView = (
{ selectExpiry(eIdx); patchInputs({ K }); }} />
patchInputs({ K })} compact />
); return (
Options · Black–Scholes
{sym} {name}
${spot.toFixed(2)} = 0 ? 'pos' : 'neg'}`}> {chgPct >= 0 ? '+' : ''}{chgAbs.toFixed(2)} · {chgPct >= 0 ? '+' : ''}{chgPct.toFixed(2)}%
Expiry
{EXPIRIES.map((e, idx) => ( ))}
ATM IV {(atmIv * 100).toFixed(1)}% {expiry.label}
25Δ RR = 0 ? 'gain' : 'loss'}`}>{rr25 >= 0 ? '+' : ''}{rr25.toFixed(2)}v call skew
25Δ BF {bf25 >= 0 ? '+' : ''}{bf25.toFixed(2)}v smile
P/C Ratio {pcRatio.toFixed(2)} put / call OI
Rate r {(r * 100).toFixed(2)}% risk-free
Div q {(q * 100).toFixed(2)}% yield
Contract {inputs.K.toFixed(0)} {inputs.type === 'C' ? 'Call' : 'Put'} · {expiry.label}
{view === 'terminal' ? terminalView : surfaceView}
); }