import { PortfolioSnapshot, Trade } from '../portfolio/portfolio-manager.ts'; export interface PerformanceMetrics { totalReturn: number; annualizedReturn: number; sharpeRatio: number; maxDrawdown: number; volatility: number; beta: number; alpha: number; calmarRatio: number; sortinoRatio: number; } export interface RiskMetrics { var95: number; // Value at Risk (95% confidence) cvar95: number; // Conditional Value at Risk maxDrawdown: number; downsideDeviation: number; correlationMatrix: Record>; } export class PerformanceAnalyzer { private snapshots: PortfolioSnapshot[] = []; private benchmarkReturns: number[] = []; // S&P 500 or other benchmark addSnapshot(snapshot: PortfolioSnapshot): void { this.snapshots.push(snapshot); // Keep only last 252 trading days (1 year) if (this.snapshots.length > 252) { this.snapshots = this.snapshots.slice(-252); } } calculatePerformanceMetrics( period: 'daily' | 'weekly' | 'monthly' = 'daily' ): PerformanceMetrics { if (this.snapshots.length < 2) { throw new Error('Need at least 2 snapshots to calculate performance'); } const returns = this.calculateReturns(period); const riskFreeRate = 0.02; // 2% annual risk-free rate return { totalReturn: this.calculateTotalReturn(), annualizedReturn: this.calculateAnnualizedReturn(returns), sharpeRatio: this.calculateSharpeRatio(returns, riskFreeRate), maxDrawdown: this.calculateMaxDrawdown(), volatility: this.calculateVolatility(returns), beta: this.calculateBeta(returns), alpha: this.calculateAlpha(returns, riskFreeRate), calmarRatio: this.calculateCalmarRatio(returns), sortinoRatio: this.calculateSortinoRatio(returns, riskFreeRate), }; } calculateRiskMetrics(): RiskMetrics { const returns = this.calculateReturns('daily'); return { var95: this.calculateVaR(returns, 0.95), cvar95: this.calculateCVaR(returns, 0.95), maxDrawdown: this.calculateMaxDrawdown(), downsideDeviation: this.calculateDownsideDeviation(returns), correlationMatrix: {}, // TODO: Implement correlation matrix }; } private calculateReturns(period: 'daily' | 'weekly' | 'monthly'): number[] { if (this.snapshots.length < 2) return []; const returns: number[] = []; for (let i = 1; i < this.snapshots.length; i++) { const currentValue = this.snapshots[i].totalValue; const previousValue = this.snapshots[i - 1].totalValue; const return_ = (currentValue - previousValue) / previousValue; returns.push(return_); } return returns; } private calculateTotalReturn(): number { if (this.snapshots.length < 2) return 0; const firstValue = this.snapshots[0].totalValue; const lastValue = this.snapshots[this.snapshots.length - 1].totalValue; return (lastValue - firstValue) / firstValue; } private calculateAnnualizedReturn(returns: number[]): number { if (returns.length === 0) return 0; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; return Math.pow(1 + avgReturn, 252) - 1; // 252 trading days per year } private calculateVolatility(returns: number[]): number { if (returns.length === 0) return 0; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - avgReturn, 2), 0) / returns.length; return Math.sqrt(variance * 252); // Annualized volatility } private calculateSharpeRatio(returns: number[], riskFreeRate: number): number { if (returns.length === 0) return 0; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const annualizedReturn = Math.pow(1 + avgReturn, 252) - 1; const volatility = this.calculateVolatility(returns); if (volatility === 0) return 0; return (annualizedReturn - riskFreeRate) / volatility; } private calculateMaxDrawdown(): number { if (this.snapshots.length === 0) return 0; let maxDrawdown = 0; let peak = this.snapshots[0].totalValue; for (const snapshot of this.snapshots) { if (snapshot.totalValue > peak) { peak = snapshot.totalValue; } const drawdown = (peak - snapshot.totalValue) / peak; maxDrawdown = Math.max(maxDrawdown, drawdown); } return maxDrawdown; } private calculateBeta(returns: number[]): number { if (returns.length === 0 || this.benchmarkReturns.length === 0) return 1.0; // Simple beta calculation - would need actual benchmark data return 1.0; // Placeholder } private calculateAlpha(returns: number[], riskFreeRate: number): number { const beta = this.calculateBeta(returns); const portfolioReturn = this.calculateAnnualizedReturn(returns); const benchmarkReturn = 0.1; // 10% benchmark return (placeholder) return portfolioReturn - (riskFreeRate + beta * (benchmarkReturn - riskFreeRate)); } private calculateCalmarRatio(returns: number[]): number { const annualizedReturn = this.calculateAnnualizedReturn(returns); const maxDrawdown = this.calculateMaxDrawdown(); if (maxDrawdown === 0) return 0; return annualizedReturn / maxDrawdown; } private calculateSortinoRatio(returns: number[], riskFreeRate: number): number { const annualizedReturn = this.calculateAnnualizedReturn(returns); const downsideDeviation = this.calculateDownsideDeviation(returns); if (downsideDeviation === 0) return 0; return (annualizedReturn - riskFreeRate) / downsideDeviation; } private calculateDownsideDeviation(returns: number[]): number { if (returns.length === 0) return 0; const negativeReturns = returns.filter(ret => ret < 0); if (negativeReturns.length === 0) return 0; const avgNegativeReturn = negativeReturns.reduce((sum, ret) => sum + ret, 0) / negativeReturns.length; const variance = negativeReturns.reduce((sum, ret) => sum + Math.pow(ret - avgNegativeReturn, 2), 0) / negativeReturns.length; return Math.sqrt(variance * 252); // Annualized } private calculateVaR(returns: number[], confidence: number): number { if (returns.length === 0) return 0; const sortedReturns = returns.slice().sort((a, b) => a - b); const index = Math.floor((1 - confidence) * sortedReturns.length); return -sortedReturns[index]; // Return as positive value } private calculateCVaR(returns: number[], confidence: number): number { if (returns.length === 0) return 0; const sortedReturns = returns.slice().sort((a, b) => a - b); const cutoffIndex = Math.floor((1 - confidence) * sortedReturns.length); const tailReturns = sortedReturns.slice(0, cutoffIndex + 1); if (tailReturns.length === 0) return 0; const avgTailReturn = tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length; return -avgTailReturn; // Return as positive value } }