From 5cd24ade099036d827e2be104cb895599b14ca5f Mon Sep 17 00:00:00 2001 From: Bojan Kucera Date: Wed, 4 Jun 2025 22:56:03 -0400 Subject: [PATCH] fixed libs --- .../src/calculations/basic-calculations.ts | 11 - .../src/calculations/market-statistics.ts | 19 -- .../src/calculations/performance-metrics.ts | 57 +--- libs/utils/src/calculations/risk-metrics.ts | 300 +----------------- .../src/calculations/technical-indicators.ts | 25 -- .../src/calculations/volatility-models.ts | 37 --- 6 files changed, 29 insertions(+), 420 deletions(-) diff --git a/libs/utils/src/calculations/basic-calculations.ts b/libs/utils/src/calculations/basic-calculations.ts index 51c09fe..b1e88e2 100644 --- a/libs/utils/src/calculations/basic-calculations.ts +++ b/libs/utils/src/calculations/basic-calculations.ts @@ -339,17 +339,6 @@ export function capitalAssetPricingModel( return riskFreeRate + beta * marketRiskPremium; } -/** - * Calculate Treynor ratio - */ -export function treynorRatio( - portfolioReturn: number, - riskFreeRate: number, - beta: number -): number { - return (portfolioReturn - riskFreeRate) / beta; -} - /** * Calculate hurdle rate */ diff --git a/libs/utils/src/calculations/market-statistics.ts b/libs/utils/src/calculations/market-statistics.ts index 5bed776..52a2843 100644 --- a/libs/utils/src/calculations/market-statistics.ts +++ b/libs/utils/src/calculations/market-statistics.ts @@ -812,25 +812,6 @@ export function amihudIlliquidityHFT( return validTrades > 0 ? illiquiditySum / validTrades : 0; } -/** - * Parkinson Volatility - */ -export function parkinsonVolatility( - highPrices: number[], - lowPrices: number[] -): number { - if (highPrices.length !== lowPrices.length || highPrices.length < 2) return 0; - - let sumSquaredLogHL = 0; - for (let i = 0; i < highPrices.length; i++) { - const logHL = Math.log(highPrices[i] / lowPrices[i]); - sumSquaredLogHL += logHL * logHL; - } - - const parkinsonVariance = (1 / (4 * highPrices.length * Math.log(2))) * sumSquaredLogHL; - return Math.sqrt(parkinsonVariance); -} - /** * Garman-Klass Volatility */ diff --git a/libs/utils/src/calculations/performance-metrics.ts b/libs/utils/src/calculations/performance-metrics.ts index ceef4f6..5dab08d 100644 --- a/libs/utils/src/calculations/performance-metrics.ts +++ b/libs/utils/src/calculations/performance-metrics.ts @@ -3,7 +3,7 @@ * Comprehensive performance measurement tools for trading strategies and portfolios */ -import { PortfolioMetrics } from './index'; +import { PortfolioMetrics, ulcerIndex } from './index'; export interface TradePerformance { totalTrades: number; @@ -483,31 +483,6 @@ export function sterlingRatio(returns: number[], equityCurve: Array<{ value: num 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 @@ -598,25 +573,6 @@ export function tailRatio(returns: number[], tailPercent: number = 0.1): number 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 */ @@ -634,6 +590,17 @@ export function calculateRollingBeta(portfolioReturns: number[], marketReturns: return rollingBetas; } +/** + * 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 Rolling Alpha */ diff --git a/libs/utils/src/calculations/risk-metrics.ts b/libs/utils/src/calculations/risk-metrics.ts index 11e85f8..5b52100 100644 --- a/libs/utils/src/calculations/risk-metrics.ts +++ b/libs/utils/src/calculations/risk-metrics.ts @@ -3,7 +3,7 @@ * Comprehensive risk measurement tools for portfolio and trading analysis */ -import { RiskMetrics } from './index'; +import { RiskMetrics, treynorRatio } from './index'; /** * Calculate Value at Risk (VaR) using historical simulation @@ -106,52 +106,6 @@ export function sharpeRatio(returns: number[], riskFreeRate: number = 0): number return (mean - riskFreeRate) / stdDev; } -/** - * Calculate Sortino ratio - */ -export function sortinoRatio(returns: number[], targetReturn: number = 0): number { - if (returns.length === 0) return 0; - - const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; - const downsideDev = downsideDeviation(returns, targetReturn); - - if (downsideDev === 0) return 0; - - return (mean - targetReturn) / downsideDev; -} - -/** - * Calculate Calmar ratio - */ -export function calmarRatio(returns: number[], equityCurve: number[]): number { - if (returns.length === 0 || equityCurve.length === 0) return 0; - - const annualizedReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length * 252; // Assuming daily returns - const maxDD = maxDrawdown(equityCurve); - - if (maxDD === 0) return 0; - - return annualizedReturn / maxDD; -} - -/** - * Calculate Information Ratio - */ -export function informationRatio(portfolioReturns: number[], benchmarkReturns: number[]): number { - if (portfolioReturns.length !== benchmarkReturns.length || portfolioReturns.length === 0) { - return 0; - } - - const activeReturns = portfolioReturns.map((ret, i) => ret - benchmarkReturns[i]); - const mean = activeReturns.reduce((sum, ret) => sum + ret, 0) / activeReturns.length; - const trackingError = Math.sqrt( - activeReturns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (activeReturns.length - 1) - ); - - if (trackingError === 0) return 0; - - return mean / trackingError; -} /** * Calculate beta coefficient @@ -194,24 +148,6 @@ export function alpha( return portfolioMean - (riskFreeRate + portfolioBeta * (marketMean - riskFreeRate)); } -/** - * Calculate Treynor ratio - */ -export function treynorRatio( - portfolioReturns: number[], - marketReturns: number[], - riskFreeRate: number = 0 -): number { - if (portfolioReturns.length === 0) return 0; - - const portfolioMean = portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length; - const portfolioBeta = beta(portfolioReturns, marketReturns); - - if (portfolioBeta === 0) return 0; - - return (portfolioMean - riskFreeRate) / portfolioBeta; -} - /** * Calculate tracking error */ @@ -410,6 +346,22 @@ export function riskContribution( return contributions; } +/** + * 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 risk-adjusted return (RAR) */ @@ -421,221 +373,3 @@ export function riskAdjustedReturn( if (portfolioRisk === 0) return 0; return (portfolioReturn - riskFreeRate) / portfolioRisk; } - -/** - * Calculate Omega Ratio (probability-weighted ratio of gains vs losses) - */ -export function omegaRatio(returns: number[], threshold: number = 0): number { - if (returns.length === 0) return 0; - - let gainsSum = 0; - let lossesSum = 0; - - for (const ret of returns) { - const excessReturn = ret - threshold; - if (excessReturn > 0) { - gainsSum += excessReturn; - } else { - lossesSum += Math.abs(excessReturn); - } - } - - return lossesSum > 0 ? gainsSum / lossesSum : (gainsSum > 0 ? Infinity : 0); -} - -/** - * Calculate Upside Potential Ratio - */ -export function upsidePotentialRatio(returns: number[], threshold: number = 0): number { - if (returns.length === 0) return 0; - - let upsidePotentialSum = 0; - let downsideDeviationSum = 0; - - for (const ret of returns) { - const excessReturn = ret - threshold; - if (excessReturn > 0) { - upsidePotentialSum += excessReturn; - } else { - downsideDeviationSum += excessReturn * excessReturn; - } - } - - const downsideDeviation = Math.sqrt(downsideDeviationSum / returns.length); - const avgUpsidePotential = upsidePotentialSum / returns.length; - - return downsideDeviation > 0 ? avgUpsidePotential / downsideDeviation : 0; -} - -/** - * Calculate maximum drawdown duration - */ -export function drawdownDuration(equityCurve: number[]): { - maxDuration: number; - currentDuration: number; - avgDuration: number; -} { - if (equityCurve.length === 0) { - return { maxDuration: 0, currentDuration: 0, avgDuration: 0 }; - } - - let peak = equityCurve[0]; - let maxDuration = 0; - let currentDuration = 0; - const durations: number[] = []; - let inDrawdown = false; - let drawdownStart = 0; - - for (let i = 1; i < equityCurve.length; i++) { - if (equityCurve[i] > peak) { - if (inDrawdown) { - // End of drawdown - const duration = i - drawdownStart; - durations.push(duration); - maxDuration = Math.max(maxDuration, duration); - inDrawdown = false; - currentDuration = 0; - } - peak = equityCurve[i]; - } else { - if (!inDrawdown) { - // Start of drawdown - inDrawdown = true; - drawdownStart = i; - } - currentDuration = i - drawdownStart; - } - } - - // If still in drawdown at the end - if (inDrawdown) { - maxDuration = Math.max(maxDuration, currentDuration); - } - - const avgDuration = durations.length > 0 ? - durations.reduce((sum, dur) => sum + dur, 0) / durations.length : 0; - - return { - maxDuration, - currentDuration: inDrawdown ? currentDuration : 0, - avgDuration - }; -} - -/** - * Calculate CAPM expected return - * Uses the Capital Asset Pricing Model: E(R) = Rf + β(E(Rm) - Rf) - */ -export function capmExpectedReturn( - riskFreeRate: number, - marketReturn: number, - assetBeta: number -): number { - return riskFreeRate + assetBeta * (marketReturn - riskFreeRate); -} - -/** - * Calculate Jensen's Alpha - * Jensen's Alpha = Portfolio Return - CAPM Expected Return - */ -export function jensenAlpha( - portfolioReturn: number, - riskFreeRate: number, - marketReturn: number, - portfolioBeta: number -): number { - const expectedReturn = capmExpectedReturn(riskFreeRate, marketReturn, portfolioBeta); - return portfolioReturn - expectedReturn; -} - -/** - * Calculate Ulcer Index - */ -export function ulcerIndex(equityCurve: number[]): number { - if (equityCurve.length < 2) return 0; - - let sumOfSquaredDrawdowns = 0; - let peak = equityCurve[0]; - - for (let i = 1; i < equityCurve.length; i++) { - if (equityCurve[i] > peak) { - peak = equityCurve[i]; - } - const drawdown = Math.max(0, (peak - equityCurve[i]) / peak); - sumOfSquaredDrawdowns += drawdown * drawdown; - } - - return Math.sqrt(sumOfSquaredDrawdowns / equityCurve.length); -} - -/** - * Calculate Ulcer Performance Index (UPI) - */ -export function ulcerPerformanceIndex(returns: number[], equityCurve: number[], riskFreeRate: number = 0): number { - const annualizedReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length * 252; // Assuming daily returns - const ui = ulcerIndex(equityCurve); - - return ui !== 0 ? (annualizedReturn - riskFreeRate) / ui : 0; -} - -/** - * Calculate Rachev Ratio - */ -export function rachevRatio( - returns: number[], - confidenceLevel: number = 0.05 -): number { - if (returns.length === 0) return 0; - - const sortedReturns = [...returns].sort((a, b) => a - b); - const lossTailIndex = Math.floor(confidenceLevel * sortedReturns.length); - const gainTailIndex = Math.floor((1 - confidenceLevel) * sortedReturns.length); - - const expectedLoss = sortedReturns.slice(0, lossTailIndex) - .reduce((sum, ret) => sum + ret, 0) / lossTailIndex; - - const expectedGain = sortedReturns.slice(gainTailIndex) - .reduce((sum, ret) => sum + ret, 0) / (sortedReturns.length - gainTailIndex); - - return expectedGain > 0 && Math.abs(expectedLoss) > 0 ? expectedGain / Math.abs(expectedLoss) : 0; -} - -/** - * Calculate Conditional Sharpe Ratio - */ -export function conditionalSharpeRatio(returns: number[], threshold: number, riskFreeRate: number = 0): number { - const belowThresholdReturns = returns.filter(ret => ret <= threshold); - - if (belowThresholdReturns.length < 2) return 0; - - const mean = belowThresholdReturns.reduce((sum, ret) => sum + ret, 0) / belowThresholdReturns.length; - const variance = belowThresholdReturns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (belowThresholdReturns.length - 1); - const stdDev = Math.sqrt(variance); - - return stdDev !== 0 ? (mean - riskFreeRate) / stdDev : 0; -} - -/** - * Calculate Adjusted Sharpe Ratio - */ -export function adjustedSharpeRatio(returns: number[], riskFreeRate: number = 0): number { - const sr = sharpeRatio(returns, riskFreeRate); - const sk = skewness(returns); - const kurt = kurtosis(returns); - - return sr * (1 + (sk / 6) * sr - ((kurt - 3) / 24) * sr * sr); -} - -/** - * Calculate Bernardo-Ledoit Ratio - */ -export function bernardoLedoitRatio(returns: number[], riskFreeRate: number = 0): number { - const excessReturns = returns.map(ret => ret - riskFreeRate); - const positiveReturns = excessReturns.filter(ret => ret > 0); - const negativeReturns = excessReturns.filter(ret => ret < 0); - - const upsideMean = positiveReturns.length > 0 ? positiveReturns.reduce((sum, ret) => sum + ret, 0) / positiveReturns.length : 0; - const downsideMean = negativeReturns.length > 0 ? negativeReturns.reduce((sum, ret) => sum + Math.abs(ret), 0) / negativeReturns.length : 0; - - return downsideMean !== 0 ? upsideMean / downsideMean : 0; -} \ No newline at end of file diff --git a/libs/utils/src/calculations/technical-indicators.ts b/libs/utils/src/calculations/technical-indicators.ts index 5a70ceb..47638f6 100644 --- a/libs/utils/src/calculations/technical-indicators.ts +++ b/libs/utils/src/calculations/technical-indicators.ts @@ -1537,31 +1537,6 @@ export function twiggsMoneyFlow(ohlcv: OHLCVData[]): number[] { return twiggsMoneyFlowValues; } -/** - * Ulcer Index - * Measures downside risk - */ -export function ulcerIndex(prices: number[], period: number = 14): number[] { - const ulcerIndexValues: number[] = []; - - for (let i = period; i < prices.length; i++) { - let sumOfSquaredPercentDrawdowns = 0; - for (let j = i - period + 1; j <= i; j++) { - let highestPrice = prices[i - period]; - for (let k = i - period + 1; k <= j; k++) { - if (prices[k] > highestPrice) { - highestPrice = prices[k]; - } - } - const percentDrawdown = (prices[j] - highestPrice) / highestPrice * 100; - sumOfSquaredPercentDrawdowns += Math.pow(percentDrawdown, 2); - } - const ulcerIndexValue = Math.sqrt(sumOfSquaredPercentDrawdowns / period); - ulcerIndexValues.push(ulcerIndexValue); - } - - return ulcerIndexValues; -} /** * Relative Strength diff --git a/libs/utils/src/calculations/volatility-models.ts b/libs/utils/src/calculations/volatility-models.ts index 70a8000..e63ee9b 100644 --- a/libs/utils/src/calculations/volatility-models.ts +++ b/libs/utils/src/calculations/volatility-models.ts @@ -535,43 +535,6 @@ export function parkinsonVolatility( return Math.sqrt((sum / (ohlcv.length - 1)) * annualizationFactor); } -/** - * Calculate Implied Volatility using Black-Scholes model (simplified) - */ -export function calculateImpliedVolatility( - optionPrice: number, - spotPrice: number, - strikePrice: number, - timeToExpiry: number, - riskFreeRate: number, - optionType: 'call' | 'put', - maxIterations: number = 100, - tolerance: number = 1e-6 -): number { - // Bisection method for implied volatility calculation - let low = 0.01; - let high = 5.0; - let impliedVol = 0.0; - - for (let i = 0; i < maxIterations; i++) { - impliedVol = (low + high) / 2; - const modelPrice = blackScholes(spotPrice, strikePrice, timeToExpiry, impliedVol, riskFreeRate, optionType); - const diff = optionPrice - modelPrice; - - if (Math.abs(diff) < tolerance) { - return impliedVol; - } - - if (diff > 0) { - low = impliedVol; - } else { - high = impliedVol; - } - } - - return impliedVol; // Return best estimate if no convergence -} - /** * Black-Scholes option pricing model */