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;
|
return riskFreeRate + beta * marketRiskPremium;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate Treynor ratio
|
|
||||||
*/
|
|
||||||
export function treynorRatio(
|
|
||||||
portfolioReturn: number,
|
|
||||||
riskFreeRate: number,
|
|
||||||
beta: number
|
|
||||||
): number {
|
|
||||||
return (portfolioReturn - riskFreeRate) / beta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate hurdle rate
|
* Calculate hurdle rate
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -812,25 +812,6 @@ export function amihudIlliquidityHFT(
|
||||||
return validTrades > 0 ? illiquiditySum / validTrades : 0;
|
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
|
* Garman-Klass Volatility
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Comprehensive performance measurement tools for trading strategies and portfolios
|
* Comprehensive performance measurement tools for trading strategies and portfolios
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PortfolioMetrics } from './index';
|
import { PortfolioMetrics, ulcerIndex } from './index';
|
||||||
|
|
||||||
export interface TradePerformance {
|
export interface TradePerformance {
|
||||||
totalTrades: number;
|
totalTrades: number;
|
||||||
|
|
@ -483,31 +483,6 @@ export function sterlingRatio(returns: number[], equityCurve: Array<{ value: num
|
||||||
return averageDrawdown === 0 ? 0 : (avgReturn - riskFreeRate) / averageDrawdown;
|
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
|
* 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);
|
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
|
* Calculate Rolling Beta
|
||||||
*/
|
*/
|
||||||
|
|
@ -634,6 +590,17 @@ export function calculateRollingBeta(portfolioReturns: number[], marketReturns:
|
||||||
return rollingBetas;
|
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
|
* Calculate Rolling Alpha
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Comprehensive risk measurement tools for portfolio and trading analysis
|
* 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
|
* 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;
|
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
|
* Calculate beta coefficient
|
||||||
|
|
@ -194,24 +148,6 @@ export function alpha(
|
||||||
return portfolioMean - (riskFreeRate + portfolioBeta * (marketMean - riskFreeRate));
|
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
|
* Calculate tracking error
|
||||||
*/
|
*/
|
||||||
|
|
@ -410,6 +346,22 @@ export function riskContribution(
|
||||||
return contributions;
|
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)
|
* Calculate risk-adjusted return (RAR)
|
||||||
*/
|
*/
|
||||||
|
|
@ -421,221 +373,3 @@ export function riskAdjustedReturn(
|
||||||
if (portfolioRisk === 0) return 0;
|
if (portfolioRisk === 0) return 0;
|
||||||
return (portfolioReturn - riskFreeRate) / portfolioRisk;
|
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;
|
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
|
* Relative Strength
|
||||||
|
|
|
||||||
|
|
@ -535,43 +535,6 @@ export function parkinsonVolatility(
|
||||||
return Math.sqrt((sum / (ohlcv.length - 1)) * annualizationFactor);
|
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
|
* Black-Scholes option pricing model
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue