"use client"; import { useMemo } from "react"; import { bsPrice, bsGreeks, bsImpliedVol } from "@/lib/blackScholes"; import type { OptionInputs } from "./types"; function fmt(n: number, d = 2): string { return isNaN(n) || !isFinite(n) ? "—" : n.toFixed(d); } function fmtPct(n: number, d = 1): string { return isNaN(n) || !isFinite(n) ? "—" : (n * 100).toFixed(d) + "%"; } function pctColor(n: number): string { if (isNaN(n) || !isFinite(n)) return ""; return n >= 0 ? "pos" : "neg"; } interface SliderProps { glyph: string; label: string; value: number; min: number; max: number; step: number; unit?: string; displayScale?: number; meta?: string; onReset?: () => void; onChange: (v: number) => void; className?: string; } function Slider({ glyph, value, min, max, step, unit, displayScale = 1, meta, onReset, onChange, className }: SliderProps) { const displayed = value * displayScale; return (
{glyph} onChange(parseFloat(e.target.value) / displayScale)} />
{ const v = parseFloat(e.target.value) / displayScale; if (!isNaN(v)) onChange(Math.max(min, Math.min(max, v))); }} /> {unit && {unit}}
{(meta || onReset) && (
{meta && {meta}} {onReset && }
)}
); } interface PricerProps { inputs: OptionInputs; spot: number; onChange: (partial: Partial) => void; } export function Pricer({ inputs, spot, onChange }: PricerProps) { const { S, K, T, r, q, sigma, type } = inputs; const fair = useMemo(() => bsPrice(S, K, T, r, q, sigma, type), [S, K, T, r, q, sigma, type]); const g = useMemo(() => bsGreeks(S, K, T, r, q, sigma, type), [S, K, T, r, q, sigma, type]); const mid = bsPrice(S, K, T, r, q, bsImpliedVol(S, K, T, r, q, fair, type), type); const iv = bsImpliedVol(S, K, T, r, q, fair, type); const ivPct = isNaN(iv) ? 0.5 : Math.min(1, Math.max(0, (iv - 0.04) / (1.5 - 0.04))); const sigmaPct = Math.min(1, Math.max(0, (sigma - 0.04) / (1.5 - 0.04))); const dte = Math.round(T * 365); return (

Pricer

onChange({ S: spot })} onChange={v => onChange({ S: v })} /> onChange({ K: v })} /> onChange({ T: v })} /> onChange({ r: v })} /> onChange({ sigma: v })} /> onChange({ q: v })} />
Fair Value
$ {fmt(fair)}
Δ {fmt(g.delta, 4)}
Market Mid
${isNaN(mid) ? '—' : mid.toFixed(2)}
IV
{fmtPct(sigma)}
Δ {fmt(g.delta, 4)} Delta
Γ {fmt(g.gamma, 4)} Gamma
ν {fmt(g.vega, 4)} Vega
Θ {fmt(g.theta, 4)} Theta/d
ρ {fmt(g.rho, 4)} Rho
); } interface SolvePanelProps { inputs: OptionInputs; } export function SolvePanel({ inputs }: SolvePanelProps) { const { S, K, T, r, q, sigma, type } = inputs; const fair = useMemo(() => bsPrice(S, K, T, r, q, sigma, type), [S, K, T, r, q, sigma, type]); const iv = useMemo(() => bsImpliedVol(S, K, T, r, q, fair, type), [S, K, T, r, q, fair, type]); const intrinsic = type === 'C' ? Math.max(S - K, 0) : Math.max(K - S, 0); const extrinsic = Math.max(0, fair - intrinsic); const breakeven = type === 'C' ? K + fair : K - fair; return (
Analytics
σ Implied Vol {isNaN(iv) ? '—' : (iv * 100).toFixed(2) + '%'}
Breakeven ${isNaN(breakeven) ? '—' : breakeven.toFixed(2)}
Intrinsic ${fmt(intrinsic)}
Extrinsic ${fmt(extrinsic)}
); }