/**
* Historical Volatility Indicator
* Measures the standard deviation of price returns over a period
*/
export function calculateHistoricalVolatility(closes, period = 20, annualizationFactor = 365 // Assuming daily data, annualize to yearly
) {
// Minimum 5 data points required
if (closes.length < 5) {
return {
volatility: null,
annualizedVolatility: null,
volatilityLevel: null,
};
}
// Use adaptive period
const effectivePeriod = Math.min(period, closes.length - 1);
// Calculate logarithmic returns: ln(close_t / close_t-1)
const returns = [];
for (let i = 1; i < closes.length; i++) {
const returnValue = Math.log(closes[i] / closes[i - 1]);
// Avoid infinite values from zero prices
if (isFinite(returnValue)) {
returns.push(returnValue);
}
}
if (returns.length < 2) {
return {
volatility: null,
annualizedVolatility: null,
volatilityLevel: null,
};
}
// Use the most recent 'effectivePeriod' returns
const usePeriod = Math.min(effectivePeriod, returns.length);
const recentReturns = returns.slice(-usePeriod);
// Calculate mean of returns
const meanReturn = recentReturns.reduce((sum, ret) => sum + ret, 0) / usePeriod;
// Calculate variance
const variance = recentReturns.reduce((sum, ret) => {
return sum + Math.pow(ret - meanReturn, 2);
}, 0) / usePeriod;
// Calculate standard deviation (volatility)
const volatility = Math.sqrt(variance);
// Convert to percentage
const volatilityPercent = volatility * 100;
// Annualize volatility (assuming daily data)
const annualizedVolatility = volatilityPercent * Math.sqrt(annualizationFactor);
// Determine volatility level
let volatilityLevel = null;
if (annualizedVolatility < 15)
volatilityLevel = 'low';
else if (annualizedVolatility < 25)
volatilityLevel = 'normal';
else if (annualizedVolatility < 40)
volatilityLevel = 'high';
else
volatilityLevel = 'extreme';
return {
volatility: volatilityPercent,
annualizedVolatility,
volatilityLevel,
};
}