added more functions

This commit is contained in:
Bojan Kucera 2025-06-04 20:01:39 -04:00
parent a1c82ae0b8
commit cca9ac03dd
8 changed files with 1563 additions and 2 deletions

View file

@ -463,6 +463,233 @@ export function calculateStrategyMetrics(
};
}
/**
* Calculate Calmar Ratio
*/
export function calmarRatio(returns: number[], equityCurve: Array<{ value: number; date: Date }>, riskFreeRate: number = 0): number {
const maxDrawdown = analyzeDrawdowns(equityCurve).maxDrawdown;
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
return maxDrawdown === 0 ? 0 : (avgReturn - riskFreeRate) / maxDrawdown;
}
/**
* Calculate Sterling Ratio
*/
export function sterlingRatio(returns: number[], equityCurve: Array<{ value: number; date: Date }>, riskFreeRate: number = 0): number {
const averageDrawdown = analyzeDrawdowns(equityCurve).averageDrawdown;
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
return averageDrawdown === 0 ? 0 : (avgReturn - riskFreeRate) / averageDrawdown;
}
/**
* Calculate Ulcer Index
*/
export function ulcerIndex(equityCurve: Array<{ value: number; date: Date }>): number {
let sumSquaredDrawdown = 0;
let peak = equityCurve[0].value;
for (const point of equityCurve) {
peak = Math.max(peak, point.value);
const drawdownPercent = (peak - point.value) / peak * 100;
sumSquaredDrawdown += drawdownPercent * drawdownPercent;
}
return Math.sqrt(sumSquaredDrawdown / equityCurve.length);
}
/**
* Calculate Ulcer Performance Index (UPI)
*/
export function ulcerPerformanceIndex(returns: number[], equityCurve: Array<{ value: number; date: Date }>, riskFreeRate: number = 0): number {
const ui = ulcerIndex(equityCurve);
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
return ui === 0 ? 0 : (avgReturn - riskFreeRate) / ui;
}
/**
* Calculate Information Ratio
*/
export function informationRatio(portfolioReturns: number[], benchmarkReturns: number[]): number {
if (portfolioReturns.length !== benchmarkReturns.length) {
throw new Error("Portfolio and benchmark returns must have the same length.");
}
const excessReturns = portfolioReturns.map((portfolioReturn, index) => portfolioReturn - benchmarkReturns[index]);
const trackingError = calculateVolatility(excessReturns);
const avgExcessReturn = excessReturns.reduce((sum, ret) => sum + ret, 0) / excessReturns.length;
return trackingError === 0 ? 0 : avgExcessReturn / trackingError;
}
/**
* Calculate Treynor Ratio
*/
export function treynorRatio(portfolioReturns: number[], marketReturns: number[], riskFreeRate: number): number {
const beta = calculateBeta(portfolioReturns, marketReturns);
const avgPortfolioReturn = portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
return beta === 0 ? 0 : (avgPortfolioReturn - riskFreeRate) / beta;
}
/**
* Calculate Jensen's Alpha (same as Alpha, but included for clarity)
*/
export function jensensAlpha(portfolioReturns: number[], marketReturns: number[], riskFreeRate: number): number {
return calculateAlpha(portfolioReturns, marketReturns, riskFreeRate);
}
/**
* Calculate Capture Ratio (Up Capture and Down Capture)
*/
export function captureRatio(portfolioReturns: number[], benchmarkReturns: number[]): { upCaptureRatio: number; downCaptureRatio: number } {
let upCapture = 0;
let downCapture = 0;
let upMarketPeriods = 0;
let downMarketPeriods = 0;
for (let i = 0; i < portfolioReturns.length; i++) {
if (benchmarkReturns[i] > 0) {
upCapture += portfolioReturns[i];
upMarketPeriods++;
} else if (benchmarkReturns[i] < 0) {
downCapture += portfolioReturns[i];
downMarketPeriods++;
}
}
const upCaptureRatio = upMarketPeriods > 0 ? (upCapture / upMarketPeriods) / (benchmarkReturns.filter(r => r > 0).reduce((sum, r) => sum + r, 0) / upMarketPeriods) : 0;
const downCaptureRatio = downMarketPeriods > 0 ? (downCapture / downMarketPeriods) / (benchmarkReturns.filter(r => r < 0).reduce((sum, r) => sum + r, 0) / downMarketPeriods) : 0;
return { upCaptureRatio, downCaptureRatio };
}
/**
* Calculate Sortino Ratio
*/
export function sortinoRatio(returns: number[], riskFreeRate: number = 0): number {
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const downsideReturns = returns.filter(ret => ret < riskFreeRate);
const downsideDeviation = Math.sqrt(
downsideReturns.reduce((sum, ret) => sum + Math.pow(ret - riskFreeRate, 2), 0) / returns.length
);
return downsideDeviation === 0 ? 0 : (avgReturn - riskFreeRate) / downsideDeviation;
}
/**
* Calculate Tail Ratio
*/
export function tailRatio(returns: number[], tailPercent: number = 0.1): number {
const numReturns = returns.length;
const tailSize = Math.floor(numReturns * tailPercent);
if (tailSize === 0) return 0;
const sortedReturns = [...returns].sort((a, b) => a - b);
const worstTail = sortedReturns.slice(0, tailSize);
const bestTail = sortedReturns.slice(numReturns - tailSize);
const avgWorst = worstTail.reduce((sum, ret) => sum + ret, 0) / tailSize;
const avgBest = bestTail.reduce((sum, ret) => sum + ret, 0) / tailSize;
return avgWorst === 0 ? 0 : avgBest / Math.abs(avgWorst);
}
/**
* Calculate Value at Risk (VaR)
*/
export function valueAtRisk(returns: number[], confidenceLevel: number = 0.05): number {
const sortedReturns = [...returns].sort((a, b) => a - b);
const varIndex = Math.floor(confidenceLevel * returns.length);
return sortedReturns[varIndex];
}
/**
* Calculate Conditional Value at Risk (CVaR) / Expected Shortfall
*/
export function conditionalValueAtRisk(returns: number[], confidenceLevel: number = 0.05): number {
const sortedReturns = [...returns].sort((a, b) => a - b);
const varIndex = Math.floor(confidenceLevel * returns.length);
const tailReturns = sortedReturns.slice(0, varIndex + 1);
return tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length;
}
/**
* Calculate Rolling Beta
*/
export function calculateRollingBeta(portfolioReturns: number[], marketReturns: number[], windowSize: number): number[] {
if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize) return [];
const rollingBetas: number[] = [];
for (let i = windowSize; i <= portfolioReturns.length; i++) {
const portfolioWindow = portfolioReturns.slice(i - windowSize, i);
const marketWindow = marketReturns.slice(i - windowSize, i);
rollingBetas.push(calculateBeta(portfolioWindow, marketWindow));
}
return rollingBetas;
}
/**
* Calculate Rolling Alpha
*/
export function calculateRollingAlpha(portfolioReturns: number[], marketReturns: number[], riskFreeRate: number, windowSize: number): number[] {
if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize) return [];
const rollingAlphas: number[] = [];
for (let i = windowSize; i <= portfolioReturns.length; i++) {
const portfolioWindow = portfolioReturns.slice(i - windowSize, i);
const marketWindow = marketReturns.slice(i - windowSize, i);
rollingAlphas.push(calculateAlpha(portfolioWindow, marketWindow, riskFreeRate));
}
return rollingAlphas;
}
/**
* Calculate Time Weighted Rate of Return (TWRR)
*/
export function timeWeightedRateOfReturn(cashFlows: Array<{ amount: number; date: Date; value: number }>): number {
let totalReturn = 1;
let previousValue = cashFlows[0].value;
for (let i = 1; i < cashFlows.length; i++) {
const current = cashFlows[i];
const periodReturn = (current.value - previousValue - current.amount) / (previousValue + current.amount);
totalReturn *= (1 + periodReturn);
previousValue = current.value;
}
return totalReturn - 1;
}
/**
* Calculate Money Weighted Rate of Return (MWRR) - Approximation using IRR
*/
export function moneyWeightedRateOfReturn(cashFlows: Array<{ amount: number; date: Date; value: number }>): number {
// Approximate MWRR using Internal Rate of Return (IRR)
// This requires a numerical method or library for accurate IRR calculation
// This is a simplified example and may not be accurate for all cases
let totalCashFlow = 0;
let totalWeightedCashFlow = 0;
const startDate = cashFlows[0].date.getTime();
for (const cf of cashFlows) {
const timeDiff = (cf.date.getTime() - startDate) / (1000 * 60 * 60 * 24 * 365); // Years
totalCashFlow += cf.amount;
totalWeightedCashFlow += cf.amount * timeDiff;
}
// Simplified approximation: MWRR ≈ totalCashFlow / totalWeightedCashFlow - 1
return totalCashFlow / totalWeightedCashFlow - 1;
}
// Helper functions
function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0): number {