/**
* MCP Tool: summarize_vol_regime
*
* Multi-step analysis tool that summarizes the volatility regime for a symbol.
* Fetches options chain, computes vol surface, and generates a human-readable report.
*/
import { z } from "zod";
import {
computeVolSurface,
computeHistoricalVol,
type QuantError,
} from "@quant-companion/core";
import { getDefaultProvider } from "../marketData";
import { createError } from "../errors";
export const summarizeVolRegimeSchema = z.object({
symbol: z.string().describe("Stock/ETF ticker symbol (e.g., AAPL, SPY, NVDA)"),
historicalWindow: z
.number()
.min(5)
.max(252)
.default(30)
.describe("Window for historical vol calculation (default: 30 days)"),
});
export type SummarizeVolRegimeInput = z.infer<typeof summarizeVolRegimeSchema>;
export interface SummarizeVolRegimeOutput {
symbol: string;
spot: number;
asOf: string;
/** Historical (realized) volatility */
historicalVol: {
window: number;
value: number;
};
/** Implied volatility summary */
impliedVol: {
atmNearTerm: number;
atmLongTerm: number;
avgAtm: number;
};
/** Vol premium/discount */
volPremium: {
value: number;
interpretation: "expensive" | "cheap" | "fair";
};
/** Term structure */
termStructure: {
shape: "contango" | "backwardation" | "flat";
slope: number | null;
interpretation: string;
};
/** Skew summary */
skew: {
avgRiskReversal: number | null;
direction: "put" | "call" | "neutral";
interpretation: string;
};
/** Overall regime classification */
regime: {
level: "low_vol" | "normal" | "elevated" | "crisis";
description: string;
};
/** Human-readable summary */
summary: string;
/** Actionable insights */
insights: string[];
}
export const summarizeVolRegimeDefinition = {
name: "summarize_vol_regime",
description: `Generate a comprehensive volatility regime summary for a symbol.
This multi-step tool:
1. Fetches current price and options chain
2. Computes historical volatility
3. Builds vol surface from options data
4. Analyzes term structure and skew
5. Generates human-readable report with insights
Returns:
- Historical vs implied vol comparison
- Term structure shape and interpretation
- Skew analysis
- Overall regime classification
- Actionable trading insights
Use this for:
- Quick volatility regime assessment
- Comparing implied vs realized vol
- Generating volatility reports`,
inputSchema: {
type: "object" as const,
properties: {
symbol: { type: "string", description: "Stock/ETF ticker symbol" },
historicalWindow: {
type: "number",
description: "Historical vol window in days (default: 30)",
default: 30,
},
},
required: ["symbol"],
},
};
export async function summarizeVolRegime(
input: SummarizeVolRegimeInput
): Promise<SummarizeVolRegimeOutput | { error: QuantError }> {
const provider = getDefaultProvider();
const symbol = input.symbol.toUpperCase();
try {
// Step 1: Fetch options chain
const chain = await provider.getOptionsChain({ symbol });
const spot = chain.underlyingPrice;
// Step 2: Compute historical volatility
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - input.historicalWindow - 10); // Extra buffer
const historical = await provider.getHistoricalOHLCV({
symbol,
start,
end,
interval: "1d",
});
const closes = historical.map((c) => c.close);
const hvResult = computeHistoricalVol(closes, input.historicalWindow);
// Step 3: Compute vol surface
const surface = computeVolSurface({
chain,
maxExpirations: 8,
minOpenInterest: 10,
});
// Step 4: Analyze
const nearTermSmile = surface.smiles[0];
const longTermSmile = surface.smiles[surface.smiles.length - 1];
const atmNearTerm = nearTermSmile?.atmIv ?? surface.stats.avgAtmIv;
const atmLongTerm = longTermSmile?.atmIv ?? surface.stats.avgAtmIv;
const avgAtmIv = surface.stats.avgAtmIv;
// Vol premium (IV vs HV)
const volPremiumValue = avgAtmIv - hvResult.volatility;
let volPremiumInterpretation: "expensive" | "cheap" | "fair" = "fair";
if (volPremiumValue > 0.05) volPremiumInterpretation = "expensive";
else if (volPremiumValue < -0.03) volPremiumInterpretation = "cheap";
// Term structure
let termShape: "contango" | "backwardation" | "flat" = "flat";
if (surface.stats.termSlope !== null) {
if (surface.stats.termSlope > 0.03) termShape = "contango";
else if (surface.stats.termSlope < -0.03) termShape = "backwardation";
}
const termInterpretation =
termShape === "contango"
? "Longer-dated options trading at higher IV; typical calm market structure"
: termShape === "backwardation"
? "Near-term IV elevated above long-term; suggests near-term event risk or stress"
: "Flat term structure; no significant time-based IV differences";
// Skew
const rrValues = surface.smiles
.map((s) => s.skew.riskReversal25d)
.filter((v): v is number => v !== null);
const avgRR = rrValues.length > 0 ? rrValues.reduce((a, b) => a + b, 0) / rrValues.length : null;
let skewDirection: "put" | "call" | "neutral" = "neutral";
if (avgRR !== null) {
if (avgRR > 0.02) skewDirection = "put";
else if (avgRR < -0.02) skewDirection = "call";
}
const skewInterpretation =
skewDirection === "put"
? "Puts trading at premium to calls; market hedging downside risk"
: skewDirection === "call"
? "Calls trading at premium to puts; bullish sentiment or upside hedging"
: "Balanced put-call pricing; no strong directional bias";
// Regime classification
let regimeLevel: "low_vol" | "normal" | "elevated" | "crisis" = "normal";
if (avgAtmIv < 0.15) regimeLevel = "low_vol";
else if (avgAtmIv > 0.40) regimeLevel = "crisis";
else if (avgAtmIv > 0.25) regimeLevel = "elevated";
const regimeDescription =
regimeLevel === "low_vol"
? "Low volatility environment; options are cheap, consider buying premium"
: regimeLevel === "crisis"
? "Crisis-level volatility; high uncertainty, consider hedging"
: regimeLevel === "elevated"
? "Elevated volatility; increased uncertainty, premium selling may be attractive"
: "Normal volatility regime; balanced market conditions";
// Generate summary
const summary = `${symbol} is currently in a ${regimeLevel.replace("_", " ")} regime with ATM IV at ${(avgAtmIv * 100).toFixed(1)}% vs ${(hvResult.volatility * 100).toFixed(1)}% realized vol. Options are ${volPremiumInterpretation} relative to historical. Term structure is in ${termShape} with ${skewDirection} skew.`;
// Generate insights
const insights: string[] = [];
if (volPremiumInterpretation === "expensive") {
insights.push("Consider selling premium (credit spreads, iron condors) given elevated IV");
} else if (volPremiumInterpretation === "cheap") {
insights.push("Options appear cheap; consider buying premium for directional bets or hedges");
}
if (termShape === "backwardation") {
insights.push("Near-term event risk priced in; calendar spreads may benefit from term structure normalization");
}
if (skewDirection === "put") {
insights.push("Put skew elevated; put credit spreads or put ratio spreads may offer value");
} else if (skewDirection === "call") {
insights.push("Call skew elevated; covered calls or call spreads may be attractive");
}
if (regimeLevel === "low_vol") {
insights.push("Low vol environment favors long gamma strategies (straddles, strangles)");
} else if (regimeLevel === "elevated" || regimeLevel === "crisis") {
insights.push("High vol favors short gamma strategies, but manage risk carefully");
}
return {
symbol,
spot,
asOf: chain.asOf,
historicalVol: {
window: input.historicalWindow,
value: hvResult.volatility,
},
impliedVol: {
atmNearTerm,
atmLongTerm,
avgAtm: avgAtmIv,
},
volPremium: {
value: volPremiumValue,
interpretation: volPremiumInterpretation,
},
termStructure: {
shape: termShape,
slope: surface.stats.termSlope,
interpretation: termInterpretation,
},
skew: {
avgRiskReversal: avgRR,
direction: skewDirection,
interpretation: skewInterpretation,
},
regime: {
level: regimeLevel,
description: regimeDescription,
},
summary,
insights,
};
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return {
error: createError("COMPUTATION_ERROR", `Failed to summarize vol regime: ${message}`),
};
}
}