Skip to main content
Glama
Ademscodeisnotsobad

Quant Companion MCP

risk.ts5.35 kB
/** * Risk metrics calculations */ import { RiskMetrics, VaRParams, VaRResult } from "./types"; import { simpleReturns, mean, stdDev, TRADING_DAYS_PER_YEAR } from "./utils"; /** * Compute comprehensive risk metrics from an equity curve * * @param equityCurve - Array of portfolio values over time (daily) * @param riskFreeRate - Annual risk-free rate (e.g., 0.02 for 2%) * @returns Risk metrics including Sharpe, Sortino, max drawdown */ export function computeRiskMetrics( equityCurve: number[], riskFreeRate: number = 0 ): RiskMetrics { if (equityCurve.length < 2) { throw new Error("Need at least 2 data points for risk metrics"); } const returns = simpleReturns(equityCurve); const n = equityCurve.length; // Total return const totalReturn = equityCurve[n - 1] / equityCurve[0] - 1; // Daily metrics const avgDailyReturn = mean(returns); const dailyVol = stdDev(returns, true); // Annualized metrics const annualizedReturn = Math.pow(1 + avgDailyReturn, TRADING_DAYS_PER_YEAR) - 1; const annualizedVol = dailyVol * Math.sqrt(TRADING_DAYS_PER_YEAR); // Sharpe ratio let sharpe: number | null = null; if (annualizedVol > 0) { sharpe = (annualizedReturn - riskFreeRate) / annualizedVol; } // Sortino ratio (downside deviation) const negativeReturns = returns.filter((r) => r < 0); let sortino: number | null = null; if (negativeReturns.length > 0) { // Downside deviation: std dev of negative returns (treating target as 0) const downsideSum = negativeReturns.reduce((sum, r) => sum + r * r, 0); const downsideVol = Math.sqrt(downsideSum / returns.length); const annualizedDownsideVol = downsideVol * Math.sqrt(TRADING_DAYS_PER_YEAR); if (annualizedDownsideVol > 0) { sortino = (annualizedReturn - riskFreeRate) / annualizedDownsideVol; } } // Maximum drawdown const maxDrawdown = computeMaxDrawdown(equityCurve); // Sample size info const sampleSize = returns.length; const isReliable = sampleSize >= 30; // Statistical rule of thumb return { totalReturn, annualizedReturn, annualizedVol, sharpe, sortino, maxDrawdown, sampleSize, isReliable, }; } /** * Compute maximum drawdown from peak to trough * * @param equityCurve - Array of portfolio values * @returns Maximum drawdown as positive percentage (e.g., 0.2 = 20%) */ export function computeMaxDrawdown(equityCurve: number[]): number { if (equityCurve.length < 2) return 0; let maxDrawdown = 0; let peak = equityCurve[0]; for (const value of equityCurve) { if (value > peak) { peak = value; } const drawdown = (peak - value) / peak; if (drawdown > maxDrawdown) { maxDrawdown = drawdown; } } return maxDrawdown; } /** * Compute drawdown series * * @param equityCurve - Array of portfolio values * @returns Array of drawdown values at each point */ export function computeDrawdownSeries(equityCurve: number[]): number[] { const drawdowns: number[] = []; let peak = equityCurve[0]; for (const value of equityCurve) { if (value > peak) { peak = value; } drawdowns.push((peak - value) / peak); } return drawdowns; } /** * Compute Historical Value at Risk (VaR) * * @param params - VaR parameters (returns array and confidence level) * @returns VaR and Expected Shortfall */ export function computeHistoricalVaR(params: VaRParams): VaRResult { const { returns, confidence } = params; if (returns.length === 0) { throw new Error("Returns array cannot be empty"); } if (confidence <= 0 || confidence >= 1) { throw new Error("Confidence must be between 0 and 1"); } // Sort returns ascending (worst to best) const sorted = [...returns].sort((a, b) => a - b); // VaR is the quantile at (1 - confidence) // e.g., 95% confidence means we look at the 5th percentile const index = Math.floor(sorted.length * (1 - confidence)); const varValue = -sorted[index]; // Return as positive loss // Expected Shortfall: average of returns worse than VaR const tailReturns = sorted.slice(0, index + 1); const expectedShortfall = tailReturns.length > 0 ? -mean(tailReturns) : varValue; return { var: varValue, expectedShortfall, }; } /** * Compute parametric (Gaussian) VaR * * @param meanReturn - Mean return (daily) * @param stdReturn - Standard deviation of returns (daily) * @param confidence - Confidence level (e.g., 0.95) * @returns VaR estimate */ export function computeParametricVaR( meanReturn: number, stdReturn: number, confidence: number ): number { // Z-score for given confidence (using normal approximation) // For 95%: ~1.645, for 99%: ~2.326 const zScores: Record<number, number> = { 0.9: 1.282, 0.95: 1.645, 0.99: 2.326, 0.995: 2.576, 0.999: 3.09, }; // Interpolate or use closest const z = zScores[confidence] ?? 1.645; return -(meanReturn - z * stdReturn); } /** * Compute Calmar ratio (CAGR / Max Drawdown) * * @param annualizedReturn - Annualized return * @param maxDrawdown - Maximum drawdown * @returns Calmar ratio (null if drawdown is 0) */ export function computeCalmarRatio( annualizedReturn: number, maxDrawdown: number ): number | null { if (maxDrawdown === 0) return null; return annualizedReturn / maxDrawdown; }

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