meridianalgo-js
Version:
Advanced algorithmic trading library for Node.js & TypeScript with 100+ technical indicators, pattern recognition, and risk management tools.
391 lines • 16.5 kB
JavaScript
"use strict";
/**
* Volatility Technical Indicators
*
* This module provides volatility-based technical analysis indicators including
* Keltner Channels, Donchian Channels, Standard Deviation, and other volatility metrics.
*
* @fileoverview Volatility indicators for technical analysis
* @author MeridianAlgo
* @version 1.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VolatilityIndicators = exports.volatilityRatio = exports.yangZhangVolatility = exports.rogersSatchellVolatility = exports.garmanKlassVolatility = exports.parkinsonVolatility = exports.historicalVolatility = exports.averageDeviation = exports.variance = exports.standardDeviation = exports.keltnerChannels = void 0;
const indicators_1 = require("./indicators");
/**
* Keltner Channels
*
* Keltner Channels are volatility-based envelopes set above and below an exponential
* moving average. The channels are typically set 2 standard deviations away from
* the EMA, and the indicator is used to identify overbought and oversold conditions.
*
* @param high - Array of high prices
* @param low - Array of low prices
* @param close - Array of closing prices
* @param period - Number of periods for EMA calculation (default: 20)
* @param multiplier - Standard deviation multiplier (default: 2)
* @param atrPeriod - ATR period for calculation (default: 10)
* @returns Object containing upper, middle, and lower channel values
*
* @example
* ```typescript
* const high = [100, 101, 102, 103, 104];
* const low = [99, 100, 101, 102, 103];
* const close = [99.5, 100.5, 101.5, 102.5, 103.5];
* const keltner = VolatilityIndicators.keltnerChannels(high, low, close);
* ```
*/
function keltnerChannels(high, low, close, period = 20, multiplier = 2, atrPeriod = 10) {
if (high.length !== low.length || high.length !== close.length) {
throw new Error('High, low, and close arrays must have the same length');
}
if (high.length < period) {
return { upper: [], middle: [], lower: [] };
}
const middle = indicators_1.Indicators.ema(close, period);
const atr = indicators_1.Indicators.atr(high, low, close, atrPeriod);
const upper = [];
const lower = [];
for (let i = 0; i < middle.length; i++) {
const atrValue = atr[i] || 0;
upper.push(middle[i] + (multiplier * atrValue));
lower.push(middle[i] - (multiplier * atrValue));
}
// Pad the beginning with NaN to match input length
const padLength = close.length - upper.length;
const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr);
return {
upper: padArray(upper),
middle: padArray(middle),
lower: padArray(lower)
};
}
exports.keltnerChannels = keltnerChannels;
/**
* Standard Deviation
*
* Calculates the standard deviation of price data over a specified period.
* This is useful for measuring price volatility and creating volatility-based indicators.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 20)
* @returns Array of standard deviation values
*/
function standardDeviation(prices, period = 20) {
if (prices.length < period)
return [];
const stdDevValues = [];
for (let i = period - 1; i < prices.length; i++) {
const slice = prices.slice(i - period + 1, i + 1);
const mean = slice.reduce((sum, val) => sum + val, 0) / period;
const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period;
stdDevValues.push(Math.sqrt(variance));
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - stdDevValues.length;
return new Array(padLength).fill(NaN).concat(stdDevValues);
}
exports.standardDeviation = standardDeviation;
/**
* Variance
*
* Calculates the variance of price data over a specified period.
* Variance is the square of standard deviation and measures price dispersion.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 20)
* @returns Array of variance values
*/
function variance(prices, period = 20) {
if (prices.length < period)
return [];
const varianceValues = [];
for (let i = period - 1; i < prices.length; i++) {
const slice = prices.slice(i - period + 1, i + 1);
const mean = slice.reduce((sum, val) => sum + val, 0) / period;
const varValue = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period;
varianceValues.push(varValue);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - varianceValues.length;
return new Array(padLength).fill(NaN).concat(varianceValues);
}
exports.variance = variance;
/**
* Average Deviation
*
* Calculates the average deviation (mean absolute deviation) of price data
* over a specified period. This provides a measure of price volatility.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 20)
* @returns Array of average deviation values
*/
function averageDeviation(prices, period = 20) {
if (prices.length < period)
return [];
const avgDevValues = [];
for (let i = period - 1; i < prices.length; i++) {
const slice = prices.slice(i - period + 1, i + 1);
const mean = slice.reduce((sum, val) => sum + val, 0) / period;
const avgDev = slice.reduce((sum, val) => sum + Math.abs(val - mean), 0) / period;
avgDevValues.push(avgDev);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - avgDevValues.length;
return new Array(padLength).fill(NaN).concat(avgDevValues);
}
exports.averageDeviation = averageDeviation;
/**
* Historical Volatility
*
* Calculates the historical volatility of price data using the standard deviation
* of logarithmic returns over a specified period.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 20)
* @param annualized - Whether to annualize the volatility (default: true)
* @returns Array of historical volatility values
*/
function historicalVolatility(prices, period = 20, annualized = true) {
if (prices.length < period + 1)
return [];
const returns = [];
for (let i = 1; i < prices.length; i++) {
returns.push(Math.log(prices[i] / prices[i - 1]));
}
const volatilityValues = [];
for (let i = period - 1; i < returns.length; i++) {
const slice = returns.slice(i - period + 1, i + 1);
const mean = slice.reduce((sum, val) => sum + val, 0) / period;
const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (period - 1);
const stdDev = Math.sqrt(variance);
let volatility = stdDev;
if (annualized) {
// Annualize by multiplying by square root of trading periods per year (252)
volatility *= Math.sqrt(252);
}
volatilityValues.push(volatility);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - volatilityValues.length;
return new Array(padLength).fill(NaN).concat(volatilityValues);
}
exports.historicalVolatility = historicalVolatility;
/**
* Parkinson Volatility
*
* Parkinson volatility uses high and low prices to estimate volatility.
* It's more efficient than close-to-close volatility as it uses more information.
*
* @param high - Array of high prices
* @param low - Array of low prices
* @param period - Number of periods for calculation (default: 20)
* @param annualized - Whether to annualize the volatility (default: true)
* @returns Array of Parkinson volatility values
*/
function parkinsonVolatility(high, low, period = 20, annualized = true) {
if (high.length !== low.length) {
throw new Error('High and low arrays must have the same length');
}
if (high.length < period)
return [];
const volatilityValues = [];
for (let i = period - 1; i < high.length; i++) {
let sum = 0;
for (let j = i - period + 1; j <= i; j++) {
sum += Math.pow(Math.log(high[j] / low[j]), 2);
}
let volatility = Math.sqrt(sum / (4 * Math.log(2) * period));
if (annualized) {
volatility *= Math.sqrt(252);
}
volatilityValues.push(volatility);
}
// Pad the beginning with NaN to match input length
const padLength = high.length - volatilityValues.length;
return new Array(padLength).fill(NaN).concat(volatilityValues);
}
exports.parkinsonVolatility = parkinsonVolatility;
/**
* Garman-Klass Volatility
*
* Garman-Klass volatility uses open, high, low, and close prices to estimate volatility.
* It's more efficient than Parkinson volatility as it uses all OHLC data.
*
* @param open - Array of opening prices
* @param high - Array of high prices
* @param low - Array of low prices
* @param close - Array of closing prices
* @param period - Number of periods for calculation (default: 20)
* @param annualized - Whether to annualize the volatility (default: true)
* @returns Array of Garman-Klass volatility values
*/
function garmanKlassVolatility(open, high, low, close, period = 20, annualized = true) {
if (open.length !== high.length || open.length !== low.length || open.length !== close.length) {
throw new Error('Open, high, low, and close arrays must have the same length');
}
if (open.length < period)
return [];
const volatilityValues = [];
for (let i = period - 1; i < open.length; i++) {
let sum = 0;
for (let j = i - period + 1; j <= i; j++) {
const term1 = Math.pow(Math.log(high[j] / close[j]), 2) / 2;
const term2 = Math.pow(Math.log(low[j] / close[j]), 2) / 2;
const term3 = Math.pow(Math.log(close[j] / open[j]), 2);
sum += Math.max(0, term1 - term2 - term3);
}
let volatility = Math.sqrt(sum / period);
if (annualized) {
volatility *= Math.sqrt(252);
}
volatilityValues.push(volatility);
}
// Pad the beginning with NaN to match input length
const padLength = open.length - volatilityValues.length;
return new Array(padLength).fill(NaN).concat(volatilityValues);
}
exports.garmanKlassVolatility = garmanKlassVolatility;
/**
* Rogers-Satchell Volatility
*
* Rogers-Satchell volatility is another OHLC-based volatility estimator
* that doesn't require the assumption of zero drift.
*
* @param open - Array of opening prices
* @param high - Array of high prices
* @param low - Array of low prices
* @param close - Array of closing prices
* @param period - Number of periods for calculation (default: 20)
* @param annualized - Whether to annualize the volatility (default: true)
* @returns Array of Rogers-Satchell volatility values
*/
function rogersSatchellVolatility(open, high, low, close, period = 20, annualized = true) {
if (open.length !== high.length || open.length !== low.length || open.length !== close.length) {
throw new Error('Open, high, low, and close arrays must have the same length');
}
if (open.length < period)
return [];
const volatilityValues = [];
for (let i = period - 1; i < open.length; i++) {
let sum = 0;
for (let j = i - period + 1; j <= i; j++) {
const term1 = Math.log(high[j] / open[j]) * Math.log(high[j] / close[j]);
const term2 = Math.log(low[j] / open[j]) * Math.log(low[j] / close[j]);
sum += term1 + term2;
}
let volatility = Math.sqrt(sum / period);
if (annualized) {
volatility *= Math.sqrt(252);
}
volatilityValues.push(volatility);
}
// Pad the beginning with NaN to match input length
const padLength = open.length - volatilityValues.length;
return new Array(padLength).fill(NaN).concat(volatilityValues);
}
exports.rogersSatchellVolatility = rogersSatchellVolatility;
/**
* Yang-Zhang Volatility
*
* Yang-Zhang volatility is the most efficient volatility estimator that uses
* OHLC data and doesn't require the assumption of zero drift.
*
* @param open - Array of opening prices
* @param high - Array of high prices
* @param low - Array of low prices
* @param close - Array of closing prices
* @param period - Number of periods for calculation (default: 20)
* @param annualized - Whether to annualize the volatility (default: true)
* @returns Array of Yang-Zhang volatility values
*/
function yangZhangVolatility(open, high, low, close, period = 20, annualized = true) {
if (open.length !== high.length || open.length !== low.length || open.length !== close.length) {
throw new Error('Open, high, low, and close arrays must have the same length');
}
if (open.length < period)
return [];
const volatilityValues = [];
for (let i = period - 1; i < open.length; i++) {
// Calculate overnight returns
const overnightReturns = [];
for (let j = i - period + 1; j <= i; j++) {
overnightReturns.push(Math.log(open[j] / close[j - 1]));
}
// Calculate close-to-close returns
const closeToCloseReturns = [];
for (let j = i - period + 1; j <= i; j++) {
closeToCloseReturns.push(Math.log(close[j] / close[j - 1]));
}
// Calculate Garman-Klass volatility
let gkSum = 0;
for (let j = i - period + 1; j <= i; j++) {
const term1 = Math.pow(Math.log(high[j] / close[j]), 2) / 2;
const term2 = Math.pow(Math.log(low[j] / close[j]), 2) / 2;
const term3 = Math.pow(Math.log(close[j] / open[j]), 2);
gkSum += Math.max(0, term1 - term2 - term3);
}
const gkVol = gkSum / period;
// Calculate overnight volatility
const overnightMean = overnightReturns.reduce((sum, val) => sum + val, 0) / period;
const overnightVar = overnightReturns.reduce((sum, val) => sum + Math.pow(val - overnightMean, 2), 0) / (period - 1);
// Calculate close-to-close volatility
const closeToCloseMean = closeToCloseReturns.reduce((sum, val) => sum + val, 0) / period;
const closeToCloseVar = closeToCloseReturns.reduce((sum, val) => sum + Math.pow(val - closeToCloseMean, 2), 0) / (period - 1);
// Yang-Zhang volatility
let volatility = Math.sqrt(overnightVar + gkVol + closeToCloseVar);
if (annualized) {
volatility *= Math.sqrt(252);
}
volatilityValues.push(volatility);
}
// Pad the beginning with NaN to match input length
const padLength = open.length - volatilityValues.length;
return new Array(padLength).fill(NaN).concat(volatilityValues);
}
exports.yangZhangVolatility = yangZhangVolatility;
/**
* Volatility Ratio
*
* Calculates the ratio of current volatility to historical volatility.
* Values above 1 indicate higher than normal volatility.
*
* @param prices - Array of price data
* @param shortPeriod - Short period for current volatility (default: 10)
* @param longPeriod - Long period for historical volatility (default: 20)
* @returns Array of volatility ratio values
*/
function volatilityRatio(prices, shortPeriod = 10, longPeriod = 20) {
if (shortPeriod >= longPeriod) {
throw new Error('Short period must be less than long period');
}
const shortVol = historicalVolatility(prices, shortPeriod);
const longVol = historicalVolatility(prices, longPeriod);
const ratioValues = [];
const minLength = Math.min(shortVol.length, longVol.length);
for (let i = 0; i < minLength; i++) {
const ratio = longVol[i] !== 0 ? shortVol[i] / longVol[i] : 0;
ratioValues.push(ratio);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - ratioValues.length;
return new Array(padLength).fill(NaN).concat(ratioValues);
}
exports.volatilityRatio = volatilityRatio;
/**
* Collection of volatility-based technical indicators
*/
exports.VolatilityIndicators = {
keltnerChannels,
standardDeviation,
variance,
averageDeviation,
historicalVolatility,
parkinsonVolatility,
garmanKlassVolatility,
rogersSatchellVolatility,
yangZhangVolatility,
volatilityRatio
};
//# sourceMappingURL=volatility.js.map