fixed libs
This commit is contained in:
parent
528be93804
commit
5cd24ade09
6 changed files with 29 additions and 420 deletions
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue