Skip to main content
Glama
Ademscodeisnotsobad

Quant Companion MCP

impliedVol.ts5.23 kB
/** * Implied volatility solver using Newton-Raphson with bisection fallback */ import { BlackScholesParams, OptionType } from "./types"; import { priceBlackScholes } from "./blackScholes"; import { normalPDF } from "./utils"; const DEFAULT_MAX_ITERATIONS = 100; const DEFAULT_TOLERANCE = 1e-6; const VOL_LOWER_BOUND = 0.001; const VOL_UPPER_BOUND = 5.0; const DEFAULT_INITIAL_VOL = 0.2; /** * Compute vega for Newton-Raphson (derivative of price w.r.t. vol) */ function computeVega( spot: number, strike: number, rate: number, vol: number, timeToMaturity: number, dividendYield: number ): number { if (vol <= 0 || timeToMaturity <= 0) return 0; const sqrtT = Math.sqrt(timeToMaturity); const d1 = (Math.log(spot / strike) + (rate - dividendYield + 0.5 * vol * vol) * timeToMaturity) / (vol * sqrtT); const dfDiv = Math.exp(-dividendYield * timeToMaturity); return spot * dfDiv * sqrtT * normalPDF(d1); } export interface ImpliedVolOptions { maxIterations?: number; tolerance?: number; initialGuess?: number; } /** * Compute implied volatility from an observed option price * * Uses Newton-Raphson with bisection fallback for robustness. * * @param observedPrice - The market price of the option * @param params - Black-Scholes parameters (excluding vol) * @param options - Solver options * @returns The implied volatility * @throws Error if solver fails to converge */ export function impliedVol( observedPrice: number, params: Omit<BlackScholesParams, "vol">, options: ImpliedVolOptions = {} ): number { const { spot, strike, rate, timeToMaturity, dividendYield = 0, optionType, } = params; const { maxIterations = DEFAULT_MAX_ITERATIONS, tolerance = DEFAULT_TOLERANCE, initialGuess = DEFAULT_INITIAL_VOL, } = options; // Validate inputs if (observedPrice < 0) { throw new Error("observedPrice cannot be negative"); } if (timeToMaturity <= 0) { // At expiry, price is intrinsic value const intrinsic = optionType === "call" ? Math.max(spot - strike, 0) : Math.max(strike - spot, 0); if (Math.abs(observedPrice - intrinsic) < tolerance) { return 0; // Any vol is consistent at expiry } throw new Error("At expiry, price must equal intrinsic value"); } // Check for arbitrage bounds const df = Math.exp(-rate * timeToMaturity); const dfDiv = Math.exp(-dividendYield * timeToMaturity); if (optionType === "call") { const lowerBound = Math.max(spot * dfDiv - strike * df, 0); const upperBound = spot * dfDiv; if (observedPrice < lowerBound - tolerance) { throw new Error( `Price ${observedPrice} is below intrinsic value ${lowerBound}` ); } if (observedPrice > upperBound + tolerance) { throw new Error( `Price ${observedPrice} exceeds theoretical maximum ${upperBound}` ); } } else { const lowerBound = Math.max(strike * df - spot * dfDiv, 0); const upperBound = strike * df; if (observedPrice < lowerBound - tolerance) { throw new Error( `Price ${observedPrice} is below intrinsic value ${lowerBound}` ); } if (observedPrice > upperBound + tolerance) { throw new Error( `Price ${observedPrice} exceeds theoretical maximum ${upperBound}` ); } } // Try Newton-Raphson first let vol = initialGuess; let converged = false; for (let i = 0; i < maxIterations; i++) { const result = priceBlackScholes({ ...params, vol }); const price = result.price; const diff = price - observedPrice; if (Math.abs(diff) < tolerance) { converged = true; break; } const vega = computeVega( spot, strike, rate, vol, timeToMaturity, dividendYield ); if (vega < 1e-10) { // Vega too small, switch to bisection break; } const newVol = vol - diff / vega; // Ensure vol stays within bounds vol = Math.max(VOL_LOWER_BOUND, Math.min(VOL_UPPER_BOUND, newVol)); } if (converged) { return vol; } // Fallback to bisection let lower = VOL_LOWER_BOUND; let upper = VOL_UPPER_BOUND; // Check that solution is bracketed const priceLower = priceBlackScholes({ ...params, vol: lower }).price; const priceUpper = priceBlackScholes({ ...params, vol: upper }).price; if (observedPrice < priceLower) { throw new Error( `Cannot find IV: price ${observedPrice} is below minimum possible at vol=${lower}` ); } if (observedPrice > priceUpper) { throw new Error( `Cannot find IV: price ${observedPrice} is above maximum possible at vol=${upper}` ); } for (let i = 0; i < maxIterations; i++) { const mid = (lower + upper) / 2; const midPrice = priceBlackScholes({ ...params, vol: mid }).price; if (Math.abs(midPrice - observedPrice) < tolerance) { return mid; } if (midPrice < observedPrice) { lower = mid; } else { upper = mid; } if (upper - lower < tolerance) { return mid; } } throw new Error( `Implied volatility solver did not converge after ${maxIterations} iterations` ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ademscodeisnotsobad/Quant-Companion-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server