This commit is contained in:
Boki 2025-06-11 10:38:05 -04:00
parent 597c6efc9b
commit 8b5e06954a
26 changed files with 532 additions and 186 deletions

View file

@ -68,7 +68,9 @@ export class PerformanceAnalyzer {
}
private calculateReturns(_period: 'daily' | 'weekly' | 'monthly'): number[] {
if (this.snapshots.length < 2) {return [];}
if (this.snapshots.length < 2) {
return [];
}
const returns: number[] = [];
@ -83,7 +85,9 @@ export class PerformanceAnalyzer {
}
private calculateTotalReturn(): number {
if (this.snapshots.length < 2) {return 0;}
if (this.snapshots.length < 2) {
return 0;
}
const firstValue = this.snapshots[0].totalValue;
const lastValue = this.snapshots[this.snapshots.length - 1].totalValue;
@ -92,14 +96,18 @@ export class PerformanceAnalyzer {
}
private calculateAnnualizedReturn(returns: number[]): number {
if (returns.length === 0) {return 0;}
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;}
if (returns.length === 0) {
return 0;
}
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance =
@ -109,19 +117,25 @@ export class PerformanceAnalyzer {
}
private calculateSharpeRatio(returns: number[], riskFreeRate: number): number {
if (returns.length === 0) {return 0;}
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;}
if (volatility === 0) {
return 0;
}
return (annualizedReturn - riskFreeRate) / volatility;
}
private calculateMaxDrawdown(): number {
if (this.snapshots.length === 0) {return 0;}
if (this.snapshots.length === 0) {
return 0;
}
let maxDrawdown = 0;
let peak = this.snapshots[0].totalValue;
@ -139,7 +153,9 @@ export class PerformanceAnalyzer {
}
private calculateBeta(returns: number[]): number {
if (returns.length === 0 || this.benchmarkReturns.length === 0) {return 1.0;}
if (returns.length === 0 || this.benchmarkReturns.length === 0) {
return 1.0;
}
// Simple beta calculation - would need actual benchmark data
return 1.0; // Placeholder
@ -157,7 +173,9 @@ export class PerformanceAnalyzer {
const annualizedReturn = this.calculateAnnualizedReturn(returns);
const maxDrawdown = this.calculateMaxDrawdown();
if (maxDrawdown === 0) {return 0;}
if (maxDrawdown === 0) {
return 0;
}
return annualizedReturn / maxDrawdown;
}
@ -166,16 +184,22 @@ export class PerformanceAnalyzer {
const annualizedReturn = this.calculateAnnualizedReturn(returns);
const downsideDeviation = this.calculateDownsideDeviation(returns);
if (downsideDeviation === 0) {return 0;}
if (downsideDeviation === 0) {
return 0;
}
return (annualizedReturn - riskFreeRate) / downsideDeviation;
}
private calculateDownsideDeviation(returns: number[]): number {
if (returns.length === 0) {return 0;}
if (returns.length === 0) {
return 0;
}
const negativeReturns = returns.filter(ret => ret < 0);
if (negativeReturns.length === 0) {return 0;}
if (negativeReturns.length === 0) {
return 0;
}
const avgNegativeReturn =
negativeReturns.reduce((sum, ret) => sum + ret, 0) / negativeReturns.length;
@ -187,7 +211,9 @@ export class PerformanceAnalyzer {
}
private calculateVaR(returns: number[], confidence: number): number {
if (returns.length === 0) {return 0;}
if (returns.length === 0) {
return 0;
}
const sortedReturns = returns.slice().sort((a, b) => a - b);
const index = Math.floor((1 - confidence) * sortedReturns.length);
@ -196,13 +222,17 @@ export class PerformanceAnalyzer {
}
private calculateCVaR(returns: number[], confidence: number): number {
if (returns.length === 0) {return 0;}
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;}
if (tailReturns.length === 0) {
return 0;
}
const avgTailReturn = tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length;
return -avgTailReturn; // Return as positive value