export type OptionType = 'C' | 'P'; export interface Greeks { delta: number; gamma: number; vega: number; theta: number; rho: number; } function erf(x: number): number { const sign = x < 0 ? -1 : 1; x = Math.abs(x); const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741, a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911; const t = 1.0 / (1.0 + p * x); const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x); return sign * y; } function normCdf(x: number): number { return 0.5 * (1 + erf(x / Math.SQRT2)); } function normPdf(x: number): number { return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI); } function d1d2(S: number, K: number, T: number, r: number, q: number, sigma: number): [number, number] { const vt = sigma * Math.sqrt(T); const d1 = (Math.log(S / K) + (r - q + 0.5 * sigma * sigma) * T) / vt; return [d1, d1 - vt]; } export function bsPrice(S: number, K: number, T: number, r: number, q: number, sigma: number, type: OptionType): number { if (T <= 0 || sigma <= 0) return type === 'C' ? Math.max(S - K, 0) : Math.max(K - S, 0); const [d1, d2] = d1d2(S, K, T, r, q, sigma); const eqt = Math.exp(-q * T), ert = Math.exp(-r * T); if (type === 'C') return S * eqt * normCdf(d1) - K * ert * normCdf(d2); return K * ert * normCdf(-d2) - S * eqt * normCdf(-d1); } export function bsGreeks(S: number, K: number, T: number, r: number, q: number, sigma: number, type: OptionType): Greeks { const safeT = Math.max(T, 1e-6), safeS = Math.max(sigma, 1e-6); const [d1, d2] = d1d2(S, K, safeT, r, q, safeS); const eqt = Math.exp(-q * safeT), ert = Math.exp(-r * safeT); const pdf1 = normPdf(d1); const delta = type === 'C' ? eqt * normCdf(d1) : -eqt * normCdf(-d1); const gamma = eqt * pdf1 / (S * safeS * Math.sqrt(safeT)); const vega = S * eqt * pdf1 * Math.sqrt(safeT) * 0.01; const thetaYr = type === 'C' ? (-S * pdf1 * safeS * eqt / (2 * Math.sqrt(safeT)) - r * K * ert * normCdf(d2) + q * S * eqt * normCdf(d1)) : (-S * pdf1 * safeS * eqt / (2 * Math.sqrt(safeT)) + r * K * ert * normCdf(-d2) - q * S * eqt * normCdf(-d1)); const theta = thetaYr / 365; const rho = type === 'C' ? K * safeT * ert * normCdf(d2) * 0.01 : -K * safeT * ert * normCdf(-d2) * 0.01; return { delta, gamma, vega, theta, rho }; } export function bsImpliedVol(S: number, K: number, T: number, r: number, q: number, mktPrice: number, type: OptionType): number { if (T <= 0) return NaN; let lo = 0.0001, hi = 5.0; if (mktPrice < bsPrice(S, K, T, r, q, lo, type) || mktPrice > bsPrice(S, K, T, r, q, hi, type)) return NaN; for (let i = 0; i < 100; i++) { const mid = (lo + hi) / 2; const pMid = bsPrice(S, K, T, r, q, mid, type); if (Math.abs(pMid - mktPrice) < 1e-6) return mid; if (pMid < mktPrice) lo = mid; else hi = mid; } return (lo + hi) / 2; } export function bsSynthIV(S: number, K: number, T: number, atmSigma: number): number { const logM = Math.log(K / S); const t = Math.max(T, 1 / 365); const termLift = 0.014 * (Math.sqrt(t) - Math.sqrt(30 / 365)); const skew = -0.085 * logM / Math.sqrt(t * 4); const smile = 0.32 * (logM * logM) / Math.sqrt(t); return Math.max(0.04, Math.min(1.5, atmSigma + termLift + skew + smile)); }