Skip to main content
Glama
Ademscodeisnotsobad

Quant Companion MCP

getVolSurface.ts5.38 kB
/** * MCP Tool: get_vol_surface * * Compute volatility surface (IV vs Strike vs Maturity) from options chain. */ import { z } from "zod"; import { computeVolSurface, type VolSurface, type QuantError } from "@quant-companion/core"; import { getDefaultProvider } from "../marketData"; import { createError } from "../errors"; export const getVolSurfaceSchema = z.object({ symbol: z.string().describe("Stock/ETF ticker symbol (e.g., AAPL, SPY, NVDA)"), maxExpirations: z .number() .min(1) .max(20) .default(8) .describe("Maximum number of expirations to include (default: 8)"), minOpenInterest: z .number() .min(0) .default(10) .describe("Minimum open interest filter (default: 10)"), maxBidAskSpreadPct: z .number() .min(0) .max(1) .default(0.5) .describe("Maximum bid-ask spread as fraction of mid (default: 0.5)"), }); export type GetVolSurfaceInput = z.infer<typeof getVolSurfaceSchema>; export interface GetVolSurfaceOutput { symbol: string; spot: number; asOf: string; /** Term structure: ATM IV by maturity */ termStructure: Array<{ expiration: string; timeToMaturityYears: number; atmIv: number; }>; /** Surface statistics */ stats: { minIv: number; maxIv: number; avgAtmIv: number; termSlope: number | null; }; /** Smiles summary (full points omitted for brevity) */ smiles: Array<{ expiration: string; timeToMaturityYears: number; atmIv: number; skew: { riskReversal25d: number | null; butterfly25d: number | null; }; pointCount: number; }>; /** Interpretation */ interpretation: { termStructure: "contango" | "backwardation" | "flat"; avgSkew: "put" | "call" | "neutral"; regime: "low_vol" | "normal" | "elevated" | "crisis"; }; } export const getVolSurfaceDefinition = { name: "get_vol_surface", description: `Compute volatility surface (IV vs Strike vs Maturity) from options chain. Returns: - Term structure (ATM IV across expirations) - Surface statistics (min/max IV, average ATM IV, term slope) - Per-expiration smile summaries with skew metrics - Market interpretation (contango/backwardation, skew direction, vol regime) Use this to analyze: - Is vol term structure in contango or backwardation? - How does skew evolve across maturities? - What's the overall volatility regime?`, inputSchema: { type: "object" as const, properties: { symbol: { type: "string", description: "Stock/ETF ticker symbol", }, maxExpirations: { type: "number", description: "Maximum expirations to include (default: 8)", default: 8, }, minOpenInterest: { type: "number", description: "Minimum open interest filter (default: 10)", default: 10, }, maxBidAskSpreadPct: { type: "number", description: "Maximum bid-ask spread % (default: 0.5)", default: 0.5, }, }, required: ["symbol"], }, }; function interpretSurface(surface: VolSurface): GetVolSurfaceOutput["interpretation"] { const { stats, smiles } = surface; // Term structure interpretation let termStructure: "contango" | "backwardation" | "flat" = "flat"; if (stats.termSlope !== null) { if (stats.termSlope > 0.05) termStructure = "contango"; else if (stats.termSlope < -0.05) termStructure = "backwardation"; } // Average skew direction const skewValues = smiles .map((s) => s.skew.riskReversal25d) .filter((v): v is number => v !== null); let avgSkew: "put" | "call" | "neutral" = "neutral"; if (skewValues.length > 0) { const avgRR = skewValues.reduce((a, b) => a + b, 0) / skewValues.length; if (avgRR > 0.02) avgSkew = "put"; else if (avgRR < -0.02) avgSkew = "call"; } // Vol regime let regime: "low_vol" | "normal" | "elevated" | "crisis" = "normal"; if (stats.avgAtmIv < 0.15) regime = "low_vol"; else if (stats.avgAtmIv > 0.40) regime = "crisis"; else if (stats.avgAtmIv > 0.25) regime = "elevated"; return { termStructure, avgSkew, regime }; } export async function getVolSurface( input: GetVolSurfaceInput ): Promise<GetVolSurfaceOutput | { error: QuantError }> { const provider = getDefaultProvider(); try { const chain = await provider.getOptionsChain({ symbol: input.symbol.toUpperCase(), }); const surface = computeVolSurface({ chain, maxExpirations: input.maxExpirations, minOpenInterest: input.minOpenInterest, maxBidAskSpreadPct: input.maxBidAskSpreadPct, }); const interpretation = interpretSurface(surface); return { symbol: surface.symbol, spot: surface.spot, asOf: surface.asOf, termStructure: surface.termStructure, stats: surface.stats, smiles: surface.smiles.map((s) => ({ expiration: s.expiration, timeToMaturityYears: s.timeToMaturityYears, atmIv: s.atmIv, skew: { riskReversal25d: s.skew.riskReversal25d, butterfly25d: s.skew.butterfly25d, }, pointCount: s.points.length, })), interpretation, }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { error: createError("DATA_NOT_FOUND", `Failed to compute vol surface: ${message}`) }; } }

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