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
JavaScript
;
/**
* 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
}