From 25360aacb8aab46e7e579707eb9704759af9536d Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Wed, 20 May 2026 00:22:32 -0700 Subject: feat: implement options tab with Black-Scholes pricer and vol surface Adds a fully interactive options tab: Terminal view (3-column Bloomberg- style with pricer, chain, smile/term-structure/greek curves) and Surface view (polar smile dial + IV heatmap). Uses synthetic vol surface until a live yfinance chain endpoint is wired up. Co-Authored-By: Claude Sonnet 4.6 --- frontend/components/prism/options/OptionsPage.tsx | 292 ++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 frontend/components/prism/options/OptionsPage.tsx (limited to 'frontend/components/prism/options/OptionsPage.tsx') diff --git a/frontend/components/prism/options/OptionsPage.tsx b/frontend/components/prism/options/OptionsPage.tsx new file mode 100644 index 0000000..7fcf5c3 --- /dev/null +++ b/frontend/components/prism/options/OptionsPage.tsx @@ -0,0 +1,292 @@ +"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} +
+ ); +} -- cgit v1.3-2-g0d8e