added more functions
This commit is contained in:
parent
a1c82ae0b8
commit
cca9ac03dd
8 changed files with 1563 additions and 2 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue