Skip to main content
Glama
Ademscodeisnotsobad

Quant Companion MCP

simulateWithLocalVol.ts6.5 kB
/** * MCP Tool: simulate_price_with_local_vol * * Monte Carlo simulation using local volatility from the vol surface. * Captures skew and term structure effects for more realistic tail risk. */ import { z } from "zod"; import { simulateWithLocalVol, computeVolSurface, type QuantError, } from "@quant-companion/core"; import { getDefaultProvider } from "../marketData"; import { createError, LIMITS } from "../errors"; export const simulateWithLocalVolSchema = z.object({ symbol: z.string().describe("Stock/ETF ticker symbol (e.g., AAPL, SPY, NVDA)"), timeHorizonYears: z .number() .min(0.01) .max(2) .describe("Time horizon in years (e.g., 0.25 for 3 months)"), rate: z .number() .default(0.05) .describe("Risk-free rate (annualized decimal, default: 0.05)"), dividendYield: z .number() .default(0) .describe("Dividend yield (annualized decimal, default: 0)"), paths: z .number() .min(1000) .max(LIMITS.MAX_PATHS) .default(LIMITS.DEFAULT_PATHS) .describe(`Number of simulation paths (default: ${LIMITS.DEFAULT_PATHS}, max: ${LIMITS.MAX_PATHS})`), steps: z .number() .min(10) .max(252) .default(50) .describe("Number of time steps (default: 50)"), targetPrice: z .number() .optional() .describe("Optional target price to calculate probability of reaching"), }); export type SimulateWithLocalVolInput = z.infer<typeof simulateWithLocalVolSchema>; export interface SimulateWithLocalVolOutput { symbol: string; spot: number; timeHorizonYears: number; distribution: { mean: number; median: number; stdDev: number; percentile5: number; percentile25: number; percentile75: number; percentile95: number; min: number; max: number; }; targetProbability?: number; riskMetrics: { var95: number; var99: number; expectedShortfall95: number; }; avgLocalVol: number; params: { paths: number; steps: number; }; interpretation: { expectedReturn: string; tailRisk: "low" | "moderate" | "high" | "extreme"; distributionShape: "symmetric" | "left_skewed" | "right_skewed"; }; } export const simulateWithLocalVolDefinition = { name: "simulate_price_with_local_vol", description: `Run Monte Carlo simulation using local volatility from the vol surface. Unlike constant-vol GBM, this captures: - Volatility skew (puts more expensive → fatter left tail) - Term structure (vol changes over time) - More realistic tail risk estimates Returns: - Price distribution statistics - VaR and Expected Shortfall - Target price probability (if specified) - Distribution shape interpretation Use this for: - Realistic tail risk analysis - Skew-adjusted probability estimates - Stress testing with market-implied dynamics`, inputSchema: { type: "object" as const, properties: { symbol: { type: "string", description: "Stock/ETF ticker symbol" }, timeHorizonYears: { type: "number", description: "Time horizon in years (e.g., 0.25 for 3 months)", }, rate: { type: "number", description: "Risk-free rate (default: 0.05)", default: 0.05, }, dividendYield: { type: "number", description: "Dividend yield (default: 0)", default: 0, }, paths: { type: "number", description: `Simulation paths (default: ${LIMITS.DEFAULT_PATHS})`, default: LIMITS.DEFAULT_PATHS, }, steps: { type: "number", description: "Time steps (default: 50)", default: 50, }, targetPrice: { type: "number", description: "Optional target price for probability calculation", }, }, required: ["symbol", "timeHorizonYears"], }, }; function interpretResults( spot: number, result: Awaited<ReturnType<typeof simulateWithLocalVol>> ): SimulateWithLocalVolOutput["interpretation"] { const { distribution, riskMetrics } = result; // Expected return const expectedReturn = ((distribution.mean - spot) / spot) * 100; const expectedReturnStr = expectedReturn >= 0 ? `+${expectedReturn.toFixed(1)}%` : `${expectedReturn.toFixed(1)}%`; // Tail risk assessment based on ES95 let tailRisk: "low" | "moderate" | "high" | "extreme" = "moderate"; if (riskMetrics.expectedShortfall95 < 0.1) tailRisk = "low"; else if (riskMetrics.expectedShortfall95 > 0.3) tailRisk = "extreme"; else if (riskMetrics.expectedShortfall95 > 0.2) tailRisk = "high"; // Distribution shape based on mean vs median let distributionShape: "symmetric" | "left_skewed" | "right_skewed" = "symmetric"; const skewRatio = distribution.mean / distribution.median; if (skewRatio < 0.98) distributionShape = "left_skewed"; else if (skewRatio > 1.02) distributionShape = "right_skewed"; return { expectedReturn: expectedReturnStr, tailRisk, distributionShape, }; } export async function simulateWithLocalVolTool( input: SimulateWithLocalVolInput ): Promise<SimulateWithLocalVolOutput | { error: QuantError }> { const provider = getDefaultProvider(); try { // Fetch options chain const chain = await provider.getOptionsChain({ symbol: input.symbol.toUpperCase(), }); // Compute vol surface const surface = computeVolSurface({ chain, maxExpirations: 8, minOpenInterest: 10, }); // Run local vol simulation const result = simulateWithLocalVol({ spot: chain.underlyingPrice, rate: input.rate, dividendYield: input.dividendYield, timeHorizonYears: input.timeHorizonYears, surface, paths: input.paths, steps: input.steps, targetPrice: input.targetPrice, }); const interpretation = interpretResults(chain.underlyingPrice, result); return { symbol: input.symbol.toUpperCase(), spot: chain.underlyingPrice, timeHorizonYears: input.timeHorizonYears, distribution: result.distribution, targetProbability: result.targetProbability, riskMetrics: result.riskMetrics, avgLocalVol: result.avgLocalVol, params: { paths: result.params.paths, steps: result.params.steps, }, interpretation, }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { error: createError("COMPUTATION_ERROR", `Local vol simulation failed: ${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