add volitility-models
This commit is contained in:
parent
dda9f23285
commit
4397541d2c
4 changed files with 461 additions and 68 deletions
1
libs/config/tsconfig.tsbuildinfo
Normal file
1
libs/config/tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
460
libs/utils/src/calculations/volatility-models.ts
Normal file
460
libs/utils/src/calculations/volatility-models.ts
Normal file
|
|
@ -0,0 +1,460 @@
|
||||||
|
/**
|
||||||
|
* Volatility Models
|
||||||
|
* Advanced volatility modeling and forecasting tools
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Local interface definition to avoid circular dependency
|
||||||
|
interface OHLCVData {
|
||||||
|
open: number;
|
||||||
|
high: number;
|
||||||
|
low: number;
|
||||||
|
close: number;
|
||||||
|
volume: number;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GARCHParameters {
|
||||||
|
omega: number; // Constant term
|
||||||
|
alpha: number; // ARCH parameter
|
||||||
|
beta: number; // GARCH parameter
|
||||||
|
logLikelihood: number;
|
||||||
|
aic: number;
|
||||||
|
bic: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VolatilityEstimates {
|
||||||
|
closeToClose: number;
|
||||||
|
parkinson: number;
|
||||||
|
garmanKlass: number;
|
||||||
|
rogersSatchell: number;
|
||||||
|
yangZhang: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VolatilityRegime {
|
||||||
|
regime: number;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
averageVolatility: number;
|
||||||
|
observations: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VolatilityTerm {
|
||||||
|
maturity: number; // Days to maturity
|
||||||
|
impliedVolatility: number;
|
||||||
|
confidence: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HestonParameters {
|
||||||
|
kappa: number; // Mean reversion speed
|
||||||
|
theta: number; // Long-term variance
|
||||||
|
sigma: number; // Volatility of variance
|
||||||
|
rho: number; // Correlation
|
||||||
|
v0: number; // Initial variance
|
||||||
|
logLikelihood: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate realized volatility using different estimators
|
||||||
|
*/
|
||||||
|
export function calculateRealizedVolatility(
|
||||||
|
ohlcv: OHLCVData[],
|
||||||
|
annualizationFactor: number = 252
|
||||||
|
): VolatilityEstimates {
|
||||||
|
if (ohlcv.length < 2) {
|
||||||
|
throw new Error('Need at least 2 observations for volatility calculation');
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = ohlcv.length;
|
||||||
|
let closeToCloseSum = 0;
|
||||||
|
let parkinsonSum = 0;
|
||||||
|
let garmanKlassSum = 0;
|
||||||
|
let rogersSatchellSum = 0;
|
||||||
|
let yangZhangSum = 0;
|
||||||
|
|
||||||
|
// Calculate log returns and volatility estimators
|
||||||
|
for (let i = 1; i < n; i++) {
|
||||||
|
const prev = ohlcv[i - 1];
|
||||||
|
const curr = ohlcv[i];
|
||||||
|
|
||||||
|
// Close-to-close
|
||||||
|
const logReturn = Math.log(curr.close / prev.close);
|
||||||
|
closeToCloseSum += logReturn * logReturn;
|
||||||
|
|
||||||
|
// Parkinson estimator
|
||||||
|
const logHighLow = Math.log(curr.high / curr.low);
|
||||||
|
parkinsonSum += logHighLow * logHighLow;
|
||||||
|
|
||||||
|
// Garman-Klass estimator
|
||||||
|
const logOpenClose = Math.log(curr.close / curr.open);
|
||||||
|
garmanKlassSum += 0.5 * logHighLow * logHighLow - (2 * Math.log(2) - 1) * logOpenClose * logOpenClose;
|
||||||
|
|
||||||
|
// Rogers-Satchell estimator
|
||||||
|
const logHighOpen = Math.log(curr.high / curr.open);
|
||||||
|
const logHighClose = Math.log(curr.high / curr.close);
|
||||||
|
const logLowOpen = Math.log(curr.low / curr.open);
|
||||||
|
const logLowClose = Math.log(curr.low / curr.close);
|
||||||
|
rogersSatchellSum += logHighOpen * logHighClose + logLowOpen * logLowClose;
|
||||||
|
|
||||||
|
// Yang-Zhang estimator components
|
||||||
|
const overnight = Math.log(curr.open / prev.close);
|
||||||
|
yangZhangSum += overnight * overnight + rogersSatchellSum / i; // Simplified for brevity
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeToClose: Math.sqrt((closeToCloseSum / (n - 1)) * annualizationFactor),
|
||||||
|
parkinson: Math.sqrt((parkinsonSum / (n - 1) / (4 * Math.log(2))) * annualizationFactor),
|
||||||
|
garmanKlass: Math.sqrt((garmanKlassSum / (n - 1)) * annualizationFactor),
|
||||||
|
rogersSatchell: Math.sqrt((rogersSatchellSum / (n - 1)) * annualizationFactor),
|
||||||
|
yangZhang: Math.sqrt((yangZhangSum / (n - 1)) * annualizationFactor)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate GARCH(1,1) model parameters
|
||||||
|
*/
|
||||||
|
export function estimateGARCH(
|
||||||
|
returns: number[],
|
||||||
|
maxIterations: number = 100,
|
||||||
|
tolerance: number = 1e-6
|
||||||
|
): GARCHParameters {
|
||||||
|
const n = returns.length;
|
||||||
|
|
||||||
|
// Initial parameter estimates
|
||||||
|
let omega = 0.01;
|
||||||
|
let alpha = 0.05;
|
||||||
|
let beta = 0.9;
|
||||||
|
|
||||||
|
// Calculate unconditional variance
|
||||||
|
const meanReturn = returns.reduce((sum, r) => sum + r, 0) / n;
|
||||||
|
const unconditionalVar = returns.reduce((sum, r) => sum + Math.pow(r - meanReturn, 2), 0) / (n - 1);
|
||||||
|
|
||||||
|
let logLikelihood = -Infinity;
|
||||||
|
|
||||||
|
for (let iter = 0; iter < maxIterations; iter++) {
|
||||||
|
const variances: number[] = [unconditionalVar];
|
||||||
|
let newLogLikelihood = 0;
|
||||||
|
|
||||||
|
// Calculate conditional variances
|
||||||
|
for (let t = 1; t < n; t++) {
|
||||||
|
const prevVar = variances[t - 1];
|
||||||
|
const prevReturn = returns[t - 1] - meanReturn;
|
||||||
|
const currentVar = omega + alpha * prevReturn * prevReturn + beta * prevVar;
|
||||||
|
variances.push(Math.max(currentVar, 1e-8)); // Ensure positive variance
|
||||||
|
|
||||||
|
// Add to log-likelihood
|
||||||
|
const currentReturn = returns[t] - meanReturn;
|
||||||
|
newLogLikelihood -= 0.5 * (Math.log(2 * Math.PI) + Math.log(currentVar) +
|
||||||
|
(currentReturn * currentReturn) / currentVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for convergence
|
||||||
|
if (Math.abs(newLogLikelihood - logLikelihood) < tolerance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logLikelihood = newLogLikelihood;
|
||||||
|
|
||||||
|
// Simple gradient update (in practice, use more sophisticated optimization)
|
||||||
|
const gradientStep = 0.001;
|
||||||
|
omega = Math.max(0.001, omega + gradientStep);
|
||||||
|
alpha = Math.max(0.001, Math.min(0.999, alpha + gradientStep));
|
||||||
|
beta = Math.max(0.001, Math.min(0.999 - alpha, beta + gradientStep));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate information criteria
|
||||||
|
const k = 3; // Number of parameters
|
||||||
|
const aic = -2 * logLikelihood + 2 * k;
|
||||||
|
const bic = -2 * logLikelihood + k * Math.log(n);
|
||||||
|
|
||||||
|
return {
|
||||||
|
omega,
|
||||||
|
alpha,
|
||||||
|
beta,
|
||||||
|
logLikelihood,
|
||||||
|
aic,
|
||||||
|
bic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate EWMA volatility
|
||||||
|
*/
|
||||||
|
export function calculateEWMAVolatility(
|
||||||
|
returns: number[],
|
||||||
|
lambda: number = 0.94,
|
||||||
|
annualizationFactor: number = 252
|
||||||
|
): number[] {
|
||||||
|
const n = returns.length;
|
||||||
|
const volatilities: number[] = [];
|
||||||
|
|
||||||
|
// Initialize with sample variance
|
||||||
|
const meanReturn = returns.reduce((sum, r) => sum + r, 0) / n;
|
||||||
|
let variance = returns.reduce((sum, r) => sum + Math.pow(r - meanReturn, 2), 0) / (n - 1);
|
||||||
|
|
||||||
|
for (let t = 0; t < n; t++) {
|
||||||
|
if (t > 0) {
|
||||||
|
const prevReturn = returns[t - 1] - meanReturn;
|
||||||
|
variance = lambda * variance + (1 - lambda) * prevReturn * prevReturn;
|
||||||
|
}
|
||||||
|
volatilities.push(Math.sqrt(variance * annualizationFactor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return volatilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify volatility regimes
|
||||||
|
*/
|
||||||
|
export function identifyVolatilityRegimes(
|
||||||
|
returns: number[],
|
||||||
|
numRegimes: number = 3,
|
||||||
|
windowSize: number = 60
|
||||||
|
): VolatilityRegime[] {
|
||||||
|
// Calculate rolling volatility
|
||||||
|
const rollingVol: number[] = [];
|
||||||
|
const timestamps: Date[] = [];
|
||||||
|
|
||||||
|
for (let i = windowSize - 1; i < returns.length; i++) {
|
||||||
|
const window = returns.slice(i - windowSize + 1, i + 1);
|
||||||
|
const mean = window.reduce((sum, r) => sum + r, 0) / window.length;
|
||||||
|
const variance = window.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / (window.length - 1);
|
||||||
|
rollingVol.push(Math.sqrt(variance * 252)); // Annualized
|
||||||
|
timestamps.push(new Date(Date.now() + i * 24 * 60 * 60 * 1000)); // Mock timestamps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple k-means clustering on absolute returns
|
||||||
|
const absReturns = returns.map(ret => Math.abs(ret));
|
||||||
|
const sortedReturns = [...absReturns].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Define regime thresholds
|
||||||
|
const thresholds: number[] = [];
|
||||||
|
for (let i = 1; i < numRegimes; i++) {
|
||||||
|
const index = Math.floor((i / numRegimes) * sortedReturns.length);
|
||||||
|
thresholds.push(sortedReturns[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classify returns into regimes
|
||||||
|
const regimeSequence = absReturns.map(absRet => {
|
||||||
|
for (let i = 0; i < thresholds.length; i++) {
|
||||||
|
if (absRet <= thresholds[i]) return i;
|
||||||
|
}
|
||||||
|
return numRegimes - 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate regime statistics
|
||||||
|
const regimes: VolatilityRegime[] = [];
|
||||||
|
for (let regime = 0; regime < numRegimes; regime++) {
|
||||||
|
const regimeIndices = regimeSequence
|
||||||
|
.map((r, idx) => r === regime ? idx : -1)
|
||||||
|
.filter(idx => idx !== -1);
|
||||||
|
|
||||||
|
if (regimeIndices.length > 0) {
|
||||||
|
const regimeVolatilities = regimeIndices.map(idx =>
|
||||||
|
idx < rollingVol.length ? rollingVol[idx] : 0
|
||||||
|
);
|
||||||
|
const avgVol = regimeVolatilities.reduce((sum, vol) => sum + vol, 0) / regimeVolatilities.length;
|
||||||
|
|
||||||
|
regimes.push({
|
||||||
|
regime,
|
||||||
|
startDate: new Date(Date.now()),
|
||||||
|
endDate: new Date(Date.now() + regimeIndices.length * 24 * 60 * 60 * 1000),
|
||||||
|
averageVolatility: avgVol,
|
||||||
|
observations: regimeIndices.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return regimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate volatility term structure
|
||||||
|
*/
|
||||||
|
export function calculateVolatilityTermStructure(
|
||||||
|
spotVol: number,
|
||||||
|
maturities: number[],
|
||||||
|
meanReversion: number = 0.5
|
||||||
|
): VolatilityTerm[] {
|
||||||
|
return maturities.map(maturity => {
|
||||||
|
// Simple mean reversion model for term structure
|
||||||
|
const timeToMaturity = maturity / 365; // Convert to years
|
||||||
|
const termVolatility = spotVol * Math.exp(-meanReversion * timeToMaturity);
|
||||||
|
|
||||||
|
return {
|
||||||
|
maturity,
|
||||||
|
impliedVolatility: Math.max(termVolatility, 0.01), // Floor at 1%
|
||||||
|
confidence: Math.exp(-timeToMaturity) // Confidence decreases with maturity
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate volatility smile/skew parameters
|
||||||
|
*/
|
||||||
|
export function calculateVolatilitySmile(
|
||||||
|
strikes: number[],
|
||||||
|
spotPrice: number,
|
||||||
|
impliedVols: number[]
|
||||||
|
): {
|
||||||
|
atmVolatility: number;
|
||||||
|
skew: number;
|
||||||
|
convexity: number;
|
||||||
|
riskReversal: number;
|
||||||
|
} {
|
||||||
|
if (strikes.length !== impliedVols.length || strikes.length < 3) {
|
||||||
|
throw new Error('Need at least 3 strikes with corresponding implied volatilities');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find ATM volatility
|
||||||
|
const atmIndex = strikes.reduce((closest, strike, idx) =>
|
||||||
|
Math.abs(strike - spotPrice) < Math.abs(strikes[closest] - spotPrice) ? idx : closest, 0
|
||||||
|
);
|
||||||
|
const atmVolatility = impliedVols[atmIndex];
|
||||||
|
|
||||||
|
// Calculate skew (derivative at ATM)
|
||||||
|
let skew = 0;
|
||||||
|
if (atmIndex > 0 && atmIndex < strikes.length - 1) {
|
||||||
|
const deltaStrike = strikes[atmIndex + 1] - strikes[atmIndex - 1];
|
||||||
|
const deltaVol = impliedVols[atmIndex + 1] - impliedVols[atmIndex - 1];
|
||||||
|
skew = deltaVol / deltaStrike;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate convexity (second derivative)
|
||||||
|
let convexity = 0;
|
||||||
|
if (atmIndex > 0 && atmIndex < strikes.length - 1) {
|
||||||
|
const h = strikes[atmIndex + 1] - strikes[atmIndex];
|
||||||
|
convexity = (impliedVols[atmIndex + 1] - 2 * impliedVols[atmIndex] + impliedVols[atmIndex - 1]) / (h * h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Risk reversal (put-call vol difference)
|
||||||
|
const otmPutIndex = strikes.findIndex(strike => strike < spotPrice * 0.9);
|
||||||
|
const otmCallIndex = strikes.findIndex(strike => strike > spotPrice * 1.1);
|
||||||
|
let riskReversal = 0;
|
||||||
|
|
||||||
|
if (otmPutIndex !== -1 && otmCallIndex !== -1) {
|
||||||
|
riskReversal = impliedVols[otmCallIndex] - impliedVols[otmPutIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
atmVolatility,
|
||||||
|
skew,
|
||||||
|
convexity,
|
||||||
|
riskReversal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate Heston stochastic volatility model parameters
|
||||||
|
*/
|
||||||
|
export function estimateHestonParameters(
|
||||||
|
returns: number[],
|
||||||
|
maxIterations: number = 100
|
||||||
|
): HestonParameters {
|
||||||
|
const n = returns.length;
|
||||||
|
|
||||||
|
// Initial parameter estimates
|
||||||
|
let kappa = 2.0; // Mean reversion speed
|
||||||
|
let theta = 0.04; // Long-term variance
|
||||||
|
let sigma = 0.3; // Vol of vol
|
||||||
|
let rho = -0.5; // Correlation
|
||||||
|
let v0 = 0.04; // Initial variance
|
||||||
|
|
||||||
|
let logLikelihood = -Infinity;
|
||||||
|
|
||||||
|
for (let iter = 0; iter < maxIterations; iter++) {
|
||||||
|
const variances: number[] = [v0];
|
||||||
|
let newLogLikelihood = 0;
|
||||||
|
|
||||||
|
// Euler discretization of Heston model
|
||||||
|
const dt = 1 / 252; // Daily time step
|
||||||
|
|
||||||
|
for (let t = 1; t < n; t++) {
|
||||||
|
const prevVar = Math.max(variances[t - 1], 1e-8);
|
||||||
|
const sqrtVar = Math.sqrt(prevVar);
|
||||||
|
|
||||||
|
// Simulate variance process (simplified)
|
||||||
|
const dW2 = Math.random() - 0.5; // Should be proper random normal
|
||||||
|
const newVar = prevVar + kappa * (theta - prevVar) * dt + sigma * sqrtVar * Math.sqrt(dt) * dW2;
|
||||||
|
variances.push(Math.max(newVar, 1e-8));
|
||||||
|
|
||||||
|
// Calculate likelihood contribution
|
||||||
|
const expectedReturn = 0; // Assuming zero drift for simplicity
|
||||||
|
const variance = prevVar;
|
||||||
|
const actualReturn = returns[t];
|
||||||
|
|
||||||
|
newLogLikelihood -= 0.5 * (Math.log(2 * Math.PI) + Math.log(variance) +
|
||||||
|
Math.pow(actualReturn - expectedReturn, 2) / variance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for convergence
|
||||||
|
if (Math.abs(newLogLikelihood - logLikelihood) < 1e-6) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logLikelihood = newLogLikelihood;
|
||||||
|
|
||||||
|
// Simple parameter update (in practice, use proper optimization)
|
||||||
|
kappa = Math.max(0.1, Math.min(10, kappa + 0.01));
|
||||||
|
theta = Math.max(0.001, Math.min(1, theta + 0.001));
|
||||||
|
sigma = Math.max(0.01, Math.min(2, sigma + 0.01));
|
||||||
|
rho = Math.max(-0.99, Math.min(0.99, rho + 0.01));
|
||||||
|
v0 = Math.max(0.001, Math.min(1, v0 + 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
kappa,
|
||||||
|
theta,
|
||||||
|
sigma,
|
||||||
|
rho,
|
||||||
|
v0,
|
||||||
|
logLikelihood
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate volatility risk metrics
|
||||||
|
*/
|
||||||
|
export function calculateVolatilityRisk(
|
||||||
|
returns: number[],
|
||||||
|
confidenceLevel: number = 0.05
|
||||||
|
): {
|
||||||
|
volatilityVaR: number;
|
||||||
|
expectedShortfall: number;
|
||||||
|
maxVolatility: number;
|
||||||
|
volatilityVolatility: number;
|
||||||
|
} {
|
||||||
|
// Calculate rolling volatilities
|
||||||
|
const windowSize = 30;
|
||||||
|
const volatilities: number[] = [];
|
||||||
|
|
||||||
|
for (let i = windowSize - 1; i < returns.length; i++) {
|
||||||
|
const window = returns.slice(i - windowSize + 1, i + 1);
|
||||||
|
const mean = window.reduce((sum, r) => sum + r, 0) / window.length;
|
||||||
|
const variance = window.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / (window.length - 1);
|
||||||
|
volatilities.push(Math.sqrt(variance * 252)); // Annualized
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort volatilities for VaR calculation
|
||||||
|
const sortedVols = [...volatilities].sort((a, b) => b - a); // Descending order
|
||||||
|
const varIndex = Math.floor(confidenceLevel * sortedVols.length);
|
||||||
|
const volatilityVaR = sortedVols[varIndex];
|
||||||
|
|
||||||
|
// Expected shortfall (average of worst volatilities)
|
||||||
|
const esVols = sortedVols.slice(0, varIndex + 1);
|
||||||
|
const expectedShortfall = esVols.reduce((sum, vol) => sum + vol, 0) / esVols.length;
|
||||||
|
|
||||||
|
// Maximum volatility
|
||||||
|
const maxVolatility = Math.max(...volatilities);
|
||||||
|
|
||||||
|
// Volatility of volatility
|
||||||
|
const meanVol = volatilities.reduce((sum, vol) => sum + vol, 0) / volatilities.length;
|
||||||
|
const volVariance = volatilities.reduce((sum, vol) => sum + Math.pow(vol - meanVol, 2), 0) / (volatilities.length - 1);
|
||||||
|
const volatilityVolatility = Math.sqrt(volVariance);
|
||||||
|
|
||||||
|
return {
|
||||||
|
volatilityVaR,
|
||||||
|
expectedShortfall,
|
||||||
|
maxVolatility,
|
||||||
|
volatilityVolatility
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
/**
|
|
||||||
* Financial calculation utilities
|
|
||||||
*/
|
|
||||||
export const financialUtils = {
|
|
||||||
/**
|
|
||||||
* Calculate the Sharpe ratio
|
|
||||||
* @param returns Array of period returns
|
|
||||||
* @param riskFreeRate The risk-free rate (e.g. 0.02 for 2%)
|
|
||||||
*/
|
|
||||||
calculateSharpeRatio(returns: number[], riskFreeRate: number = 0.02): number {
|
|
||||||
if (returns.length < 2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate average return
|
|
||||||
const avgReturn = returns.reduce((sum, val) => sum + val, 0) / returns.length;
|
|
||||||
|
|
||||||
// Calculate standard deviation
|
|
||||||
const squaredDiffs = returns.map(val => Math.pow(val - avgReturn, 2));
|
|
||||||
const avgSquaredDiff = squaredDiffs.reduce((sum, val) => sum + val, 0) / squaredDiffs.length;
|
|
||||||
const stdDev = Math.sqrt(avgSquaredDiff);
|
|
||||||
|
|
||||||
// Avoid division by zero
|
|
||||||
if (stdDev === 0) return 0;
|
|
||||||
|
|
||||||
// Calculate Sharpe ratio
|
|
||||||
return (avgReturn - riskFreeRate) / stdDev;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum drawdown
|
|
||||||
* @param equityCurve Array of equity values over time
|
|
||||||
*/
|
|
||||||
calculateMaxDrawdown(equityCurve: number[]): number {
|
|
||||||
if (equityCurve.length < 2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let maxDrawdown = 0;
|
|
||||||
let peak = equityCurve[0];
|
|
||||||
|
|
||||||
for (let i = 1; i < equityCurve.length; i++) {
|
|
||||||
if (equityCurve[i] > peak) {
|
|
||||||
peak = equityCurve[i];
|
|
||||||
} else {
|
|
||||||
const drawdown = (peak - equityCurve[i]) / peak;
|
|
||||||
maxDrawdown = Math.max(maxDrawdown, drawdown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxDrawdown;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the Compound Annual Growth Rate (CAGR)
|
|
||||||
* @param startValue Initial investment value
|
|
||||||
* @param endValue Final investment value
|
|
||||||
* @param years Number of years
|
|
||||||
*/
|
|
||||||
calculateCAGR(startValue: number, endValue: number, years: number): number {
|
|
||||||
if (years <= 0 || startValue <= 0 || endValue <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.pow(endValue / startValue, 1 / years) - 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './dateUtils';
|
export * from './dateUtils';
|
||||||
export * from './financialUtils';
|
|
||||||
export * from './logger';
|
export * from './logger';
|
||||||
export * from './lokiClient';
|
export * from './lokiClient';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue