"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 (
);
}
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)}
Δ
{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)}
);
}