UNPKG

quantitivecalc

Version:

A TypeScript library providing advanced quantitative finance functions for risk analysis, performance metrics, and technical indicators. (Currently in development)

251 lines (250 loc) 11.3 kB
"use strict"; /** * Risk and Volatility Analysis Utilities * * Functions: * - calculateVolatility: Calculates rolling volatility (standard deviation) of returns, with optional annualization. * - calculateMaxDrawdown: Calculates the maximum drawdown over time for a value series. * - calculateSharpeRatio: Calculates the rolling Sharpe ratio for a series of returns. * - calculateVaR: Value at Risk calculation (parametric, historical, Monte Carlo methods) * - calculateBeta: Stock's correlation with market benchmark * * All functions operate on arrays of objects (list of dicts) and allow you to specify source/result columns. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateVolatility = calculateVolatility; exports.calculateMaxDrawdown = calculateMaxDrawdown; exports.calculateSharpeRatio = calculateSharpeRatio; exports.calculateVaR = calculateVaR; exports.calculateBeta = calculateBeta; function calculateVolatility(data, returnsColumn, resultColumn, windowSize = 20, annualize = true) { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); for (let i = 0; i < result.length; i++) { if (i < windowSize - 1) { result[i][resultColumn] = null; } else { // Get returns for the window const windowReturns = []; for (let j = i - windowSize + 1; j <= i; j++) { const returnValue = result[j][returnsColumn]; if (typeof returnValue === 'number' && !isNaN(returnValue)) { windowReturns.push(returnValue); } } if (windowReturns.length > 1) { // Calculate standard deviation const mean = windowReturns.reduce((sum, val) => sum + val, 0) / windowReturns.length; const variance = windowReturns.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (windowReturns.length - 1); let volatility = Math.sqrt(variance); // Annualize if requested (assuming daily data) if (annualize) { volatility = volatility * Math.sqrt(252); // 252 trading days per year } result[i][resultColumn] = volatility; } else { result[i][resultColumn] = null; } } } return result; } function calculateMaxDrawdown(data, sourceColumn, resultColumn = 'maxDrawdown') { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); let peak = -Infinity; let maxDrawdown = 0; for (let i = 0; i < result.length; i++) { const value = result[i][sourceColumn]; if (typeof value === 'number' && !isNaN(value)) { // Update peak if current value is higher if (value > peak) { peak = value; } // Calculate current drawdown const currentDrawdown = (peak - value) / peak; // Update max drawdown if current is larger if (currentDrawdown > maxDrawdown) { maxDrawdown = currentDrawdown; } result[i][resultColumn] = maxDrawdown; } else { result[i][resultColumn] = i > 0 ? result[i - 1][resultColumn] : 0; } } return result; } function calculateSharpeRatio(data, returnsColumn, resultColumn, windowSize = 252, // 1 year for daily data riskFreeRate = 0.02 // 2% annual risk-free rate ) { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); const dailyRiskFreeRate = riskFreeRate / 252; // Convert annual to daily for (let i = 0; i < result.length; i++) { if (i < windowSize - 1) { result[i][resultColumn] = null; } else { // Get returns for the window const windowReturns = []; for (let j = i - windowSize + 1; j <= i; j++) { const returnValue = result[j][returnsColumn]; if (typeof returnValue === 'number' && !isNaN(returnValue)) { windowReturns.push(returnValue); } } if (windowReturns.length > 1) { // Calculate excess returns (returns - risk-free rate) const excessReturns = windowReturns.map(r => r - dailyRiskFreeRate); // Calculate mean and standard deviation of excess returns const meanExcessReturn = excessReturns.reduce((sum, val) => sum + val, 0) / excessReturns.length; const variance = excessReturns.reduce((sum, val) => sum + Math.pow(val - meanExcessReturn, 2), 0) / (excessReturns.length - 1); const stdDev = Math.sqrt(variance); // Sharpe ratio = mean excess return / standard deviation // Annualize by multiplying by sqrt(252) const sharpeRatio = stdDev > 0 ? (meanExcessReturn / stdDev) * Math.sqrt(252) : 0; result[i][resultColumn] = sharpeRatio; } else { result[i][resultColumn] = null; } } } return result; } function calculateVaR(data, returnsColumn, resultColumn, confidenceLevel = 0.05, // 5% VaR (95% confidence) windowSize = 252, method = 'historical') { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); for (let i = 0; i < result.length; i++) { if (i < windowSize - 1) { result[i][resultColumn] = null; } else { // Get returns for the window const windowReturns = []; for (let j = i - windowSize + 1; j <= i; j++) { const returnValue = result[j][returnsColumn]; if (typeof returnValue === 'number' && !isNaN(returnValue)) { windowReturns.push(returnValue); } } if (windowReturns.length > 10) { let var95; switch (method) { case 'parametric': // Assume normal distribution const mean = windowReturns.reduce((sum, val) => sum + val, 0) / windowReturns.length; const variance = windowReturns.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (windowReturns.length - 1); const stdDev = Math.sqrt(variance); // Z-score for confidence level (e.g., -1.645 for 5% VaR) const zScore = getZScore(confidenceLevel); var95 = mean + zScore * stdDev; break; case 'historical': // Sort returns and take percentile const sortedReturns = [...windowReturns].sort((a, b) => a - b); const index = Math.floor(confidenceLevel * sortedReturns.length); var95 = sortedReturns[index]; break; case 'monteCarlo': // Simple Monte Carlo simulation const mcMean = windowReturns.reduce((sum, val) => sum + val, 0) / windowReturns.length; const mcVariance = windowReturns.reduce((sum, val) => sum + Math.pow(val - mcMean, 2), 0) / (windowReturns.length - 1); const mcStdDev = Math.sqrt(mcVariance); // Generate random scenarios const simulations = 10000; const scenarios = []; for (let k = 0; k < simulations; k++) { // Box-Muller transform for normal random numbers const u1 = Math.random(); const u2 = Math.random(); const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); scenarios.push(mcMean + z * mcStdDev); } scenarios.sort((a, b) => a - b); const mcIndex = Math.floor(confidenceLevel * scenarios.length); var95 = scenarios[mcIndex]; break; } result[i][resultColumn] = var95; } else { result[i][resultColumn] = null; } } } return result; } function calculateBeta(data, assetReturnsColumn, benchmarkReturnsColumn, resultColumn, windowSize = 252 // 1 year for daily data ) { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); for (let i = 0; i < result.length; i++) { if (i < windowSize - 1) { result[i][resultColumn] = null; } else { const assetReturns = []; const benchmarkReturns = []; // Collect returns for the window for (let j = i - windowSize + 1; j <= i; j++) { const assetReturn = result[j][assetReturnsColumn]; const benchmarkReturn = result[j][benchmarkReturnsColumn]; if (typeof assetReturn === 'number' && !isNaN(assetReturn) && typeof benchmarkReturn === 'number' && !isNaN(benchmarkReturn)) { assetReturns.push(assetReturn); benchmarkReturns.push(benchmarkReturn); } } if (assetReturns.length > 10) { // Calculate beta using covariance / variance formula const n = assetReturns.length; const assetMean = assetReturns.reduce((sum, val) => sum + val, 0) / n; const benchmarkMean = benchmarkReturns.reduce((sum, val) => sum + val, 0) / n; // Calculate covariance let covariance = 0; for (let k = 0; k < n; k++) { covariance += (assetReturns[k] - assetMean) * (benchmarkReturns[k] - benchmarkMean); } covariance = covariance / (n - 1); // Calculate benchmark variance let benchmarkVariance = 0; for (let k = 0; k < n; k++) { benchmarkVariance += Math.pow(benchmarkReturns[k] - benchmarkMean, 2); } benchmarkVariance = benchmarkVariance / (n - 1); // Beta = Covariance(asset, benchmark) / Variance(benchmark) const beta = benchmarkVariance > 0 ? covariance / benchmarkVariance : 0; result[i][resultColumn] = beta; } else { result[i][resultColumn] = null; } } } return result; } // Helper function to get Z-score for confidence levels function getZScore(confidenceLevel) { // Common confidence levels and their Z-scores const zScores = { 0.01: -2.326, // 99% confidence 0.05: -1.645, // 95% confidence 0.10: -1.282, // 90% confidence }; return zScores[confidenceLevel] || -1.645; // Default to 95% confidence }