running prettier for cleanup
This commit is contained in:
parent
fe7733aeb5
commit
d85cd58acd
151 changed files with 29158 additions and 27966 deletions
|
|
@ -1,204 +1,210 @@
|
|||
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<string, Record<string, number>>;
|
||||
}
|
||||
|
||||
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.10; // 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
|
||||
}
|
||||
}
|
||||
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<string, Record<string, number>>;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue