fixed libs

This commit is contained in:
Bojan Kucera 2025-06-04 22:56:03 -04:00
parent 528be93804
commit 5cd24ade09
6 changed files with 29 additions and 420 deletions

View file

@ -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
*/

View file

@ -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
*/

View file

@ -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
*/

View file

@ -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;
}

View file

@ -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

View file

@ -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
*/