UNPKG

meridianalgo-js

Version:

Advanced algorithmic trading library for Node.js & TypeScript with 100+ technical indicators, pattern recognition, and risk management tools.

803 lines 34.2 kB
"use strict"; /** * @fileoverview Technical Indicators Module for MeridianAlgo * @description Comprehensive collection of technical analysis indicators for quantitative finance * @author MeridianAlgo * @version 1.0.0 * @license MIT */ var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.Indicators = exports.IndicatorError = void 0; /** * Custom error class for indicator-related errors * @class IndicatorError * @extends Error * @description Thrown when indicator calculations encounter invalid inputs or conditions * @example * ```typescript * try { * Indicators.sma([], 5); * } catch (error) { * if (error instanceof IndicatorError) { * console.log('Indicator error:', error.message); * } * } * ``` */ class IndicatorError extends Error { constructor(message) { super(message); this.name = 'IndicatorError'; Object.setPrototypeOf(this, IndicatorError.prototype); } } exports.IndicatorError = IndicatorError; /** * Collection of technical analysis indicators with robust error handling and multiple calculation methods. * * This class provides a comprehensive suite of technical analysis indicators commonly used in quantitative finance. * All methods include input validation, error handling, and return consistent data structures. * * @class Indicators * @description Main class containing all technical analysis indicators * @example * ```typescript * import { Indicators } from 'meridianalgo-js'; * * const prices = [100, 102, 101, 103, 105, 104, 106, 108, 107, 109]; * * // Calculate Simple Moving Average * const sma = Indicators.sma(prices, 5); * console.log('SMA:', sma); * * // Calculate RSI * const rsi = Indicators.rsi(prices, 14); * console.log('RSI:', rsi); * * // Calculate MACD * const macd = Indicators.macd(prices, 12, 26, 9); * console.log('MACD:', macd); * ``` */ class Indicators { /** * Validates input parameters for indicator functions * @private */ static _validateInput(data, period, minPeriod = 1, requireMultiplePoints = true) { if (!Array.isArray(data)) { throw new IndicatorError('Input data must be an array of numbers'); } if (data.length === 0) { throw new IndicatorError('Input data array cannot be empty'); } if (data.some(isNaN)) { throw new IndicatorError('Input data contains invalid (NaN) values'); } if (typeof period !== 'number' || isNaN(period) || period < minPeriod || !Number.isInteger(period)) { throw new IndicatorError(`Period must be an integer >= ${minPeriod}`); } if (requireMultiplePoints && data.length < period) { throw new IndicatorError(`Insufficient data points. Need at least ${period} data points, got ${data.length}`); } } /** * Calculate Simple Moving Average (SMA) * * The Simple Moving Average is the average of a security's price over a given time period. * It's one of the most widely used technical indicators and helps smooth out price data * to identify trends and potential reversal points. * * @param data - Array of price data (typically closing prices) * @param period - Number of periods to use for calculation (must be positive integer) * @returns Array of SMA values with same length as input data (padded with NaN for insufficient data) * * @throws {IndicatorError} When data is invalid or period is invalid * * @example * ```typescript * const prices = [100, 102, 101, 103, 105, 104, 106, 108, 107, 109]; * const sma5 = Indicators.sma(prices, 5); * console.log('5-period SMA:', sma5); * // Output: [NaN, NaN, NaN, NaN, 102.2, 103, 103.8, 104.6, 105.4, 106.2] * ``` * * @see {@link https://www.investopedia.com/terms/s/sma.asp} Simple Moving Average definition */ static sma(data, period) { try { this._validateInput(data, period, 1, false); // Don't require multiple points if (data.length < period) { return []; } const result = []; for (let i = period - 1; i < data.length; i++) { let sum = 0; for (let j = i - period + 1; j <= i; j++) { sum += data[j]; } result.push(sum / period); } return result; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in SMA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Exponential Moving Average (EMA) * @param data - Array of price data * @param period - Number of periods to use for calculation * @returns Array of EMA values */ static ema(data, period) { try { this._validateInput(data, period, 1); if (data.length === 0) return []; const k = 2 / (period + 1); const ema = [data[0]]; // First value is the same as the first data point for (let i = 1; i < data.length; i++) { ema.push(data[i] * k + ema[i - 1] * (1 - k)); } return ema; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in EMA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Weighted Moving Average (WMA) * @param data - Array of price data * @param period - Number of periods to use for calculation * @returns Array of WMA values */ static wma(data, period) { try { this._validateInput(data, period); if (data.length < period) return []; const result = []; const weight = period * (period + 1) / 2; // Sum of weights for (let i = period - 1; i < data.length; i++) { let sum = 0; for (let j = 0; j < period; j++) { sum += data[i - period + 1 + j] * (j + 1); } result.push(sum / weight); } return result; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in WMA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Double Exponential Moving Average (DEMA) * @param data - Array of price data * @param period - Number of periods to use for calculation * @returns Array of DEMA values */ static dema(data, period) { try { this._validateInput(data, period); const ema1 = this.ema(data, period); const ema2 = this.ema(ema1, period); return ema1.map((val, i) => 2 * val - ema2[i]); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in DEMA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Triple Exponential Moving Average (TEMA) * @param data - Array of price data * @param period - Number of periods to use for calculation * @returns Array of TEMA values */ static tema(data, period) { try { this._validateInput(data, period); const ema1 = this.ema(data, period); const ema2 = this.ema(ema1, period); const ema3 = this.ema(ema2, period); return ema1.map((val, i) => 3 * val - 3 * ema2[i] + ema3[i]); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in TEMA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Kaufman's Adaptive Moving Average (KAMA) * @param data - Array of price data * @param period - Number of periods to use for calculation * @param fast - Fast EMA period (default: 2) * @param slow - Slow EMA period (default: 30) * @returns Array of KAMA values */ static kama(data, period, fast = 2, slow = 30) { try { this._validateInput(data, period); if (fast >= slow) { throw new IndicatorError('Fast period must be less than slow period'); } if (data.length < period) return []; const kama = [data[0]]; // Initialize with first price // Calculate efficiency ratio (ER) const change = Math.abs(data[data.length - 1] - data[0]); const volatility = data.slice(1).reduce((sum, val, i) => sum + Math.abs(val - data[i]), 0); const er = volatility !== 0 ? change / volatility : 0; // Calculate smoothing constant (SC) const fastSC = 2 / (fast + 1); const slowSC = 2 / (slow + 1); const sc = Math.pow(er * (fastSC - slowSC) + slowSC, 2); // Calculate KAMA for (let i = 1; i < data.length; i++) { kama.push(kama[i - 1] + sc * (data[i] - kama[i - 1])); } return kama; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in KAMA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate T3 Moving Average * @param data - Array of price data * @param period - Number of periods to use for calculation * @param volumeFactor - Volume factor (default: 0.7) * @returns Array of T3 values */ static t3(data, period, volumeFactor = 0.7) { try { this._validateInput(data, period); if (volumeFactor <= 0 || volumeFactor >= 1) { throw new IndicatorError('Volume factor must be between 0 and 1'); } const ema1 = this.ema(data, period); const ema2 = this.ema(ema1, period); const ema3 = this.ema(ema2, period); const ema4 = this.ema(ema3, period); const ema5 = this.ema(ema4, period); const ema6 = this.ema(ema5, period); const c1 = -Math.pow(volumeFactor, 3); const c2 = 3 * Math.pow(volumeFactor, 2) + 3 * Math.pow(volumeFactor, 3); const c3 = -6 * Math.pow(volumeFactor, 2) - 3 * volumeFactor - 3 * Math.pow(volumeFactor, 3); const c4 = 1 + 3 * volumeFactor + Math.pow(volumeFactor, 3) + 3 * Math.pow(volumeFactor, 2); const t3 = []; for (let i = 0; i < data.length; i++) { if (i < 6 * (period - 1)) { t3.push(NaN); } else { t3.push(c1 * ema6[i] + c2 * ema5[i] + c3 * ema4[i] + c4 * ema3[i]); } } return t3; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in T3 calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Generic moving average function that supports multiple types * @param type - Type of moving average ('sma', 'ema', 'wma', 'dema', 'tema', 'kama', 't3') * @param data - Array of price data * @param period - Number of periods to use for calculation * @param args - Additional arguments specific to the moving average type * @returns Array of moving average values */ static movingAverage(type, data, period, ...args) { switch (type.toLowerCase()) { case 'sma': return this.sma(data, period); case 'ema': return this.ema(data, period); case 'wma': return this.wma(data, period); case 'dema': return this.dema(data, period); case 'tema': return this.tema(data, period); case 'kama': return this.kama(data, period, ...args); case 't3': return this.t3(data, period, ...args); default: throw new IndicatorError(`Unsupported moving average type: ${type}`); } } /** * Calculate Relative Strength Index (RSI) * @param data - Array of price data * @param period - Number of periods to use for calculation (default: 14) * @param maType - Type of moving average to use ('sma', 'ema', etc.) * @returns Array of RSI values */ static rsi(data, period = 14, maType = 'ema') { try { this._validateInput(data, period, 2); if (data.length < period + 1) return []; const deltas = []; for (let i = 1; i < data.length; i++) { deltas.push(data[i] - data[i - 1]); } const gains = deltas.map(d => Math.max(0, d)); const losses = deltas.map(d => Math.abs(Math.min(0, d))); // Use the specified moving average type for smoothing const avgGain = this.movingAverage(maType, gains, period); const avgLoss = this.movingAverage(maType, losses, period); const rsi = []; for (let i = 0; i < avgGain.length; i++) { const rs = avgLoss[i] === 0 ? Infinity : avgGain[i] / avgLoss[i]; rsi.push(100 - (100 / (1 + rs))); } // Add leading NaN values to match input length return new Array(data.length - rsi.length).fill(NaN).concat(rsi); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in RSI calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate MACD (Moving Average Convergence Divergence) * @param data - Array of price data * @param fast - Fast EMA period (default: 12) * @param slow - Slow EMA period (default: 26) * @param signal - Signal line period (default: 9) * @param maType - Type of moving average to use ('ema' or 'sma') * @returns Object containing MACD line, signal line, and histogram */ static macd(data, fast = 12, slow = 26, signal = 9, maType = 'ema') { try { this._validateInput(data, Math.max(fast, slow, signal)); if (fast >= slow) { throw new IndicatorError('Fast period must be less than slow period'); } // Calculate MACD line (fast MA - slow MA) const fastMA = this.movingAverage(maType, data, fast); const slowMA = this.movingAverage(maType, data, slow); // Align the arrays and calculate MACD const offset = Math.abs(fastMA.length - slowMA.length); const macd = []; for (let i = 0; i < Math.min(fastMA.length, slowMA.length); i++) { macd.push(fastMA[i + (fastMA.length > slowMA.length ? offset : 0)] - slowMA[i + (slowMA.length > fastMA.length ? offset : 0)]); } // Calculate signal line (MA of MACD) const signalLine = this.movingAverage(maType, macd, signal); // Calculate histogram (MACD - Signal) const histogram = []; const signalOffset = macd.length - signalLine.length; for (let i = 0; i < signalLine.length; i++) { histogram.push(macd[i + signalOffset] - signalLine[i]); } // Pad the beginning with NaN to match input length const padLength = data.length - macd.length; const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr); return { macd: padArray(macd), signal: padArray(signalLine), histogram: padArray(histogram) }; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in MACD calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Bollinger Bands * @param data - Array of price data * @param period - Number of periods to use for calculation (default: 20) * @param stdDev - Number of standard deviations for the bands (default: 2) * @param maType - Type of moving average to use for the middle band * @returns Object containing upper, middle, and lower band values */ static bollingerBands(data, period = 20, stdDev = 2, maType = 'sma') { try { this._validateInput(data, period); if (data.length < period) { const empty = []; return { upper: empty, middle: empty, lower: empty }; } const middle = this.movingAverage(maType, data, period); const upper = []; const lower = []; for (let i = period - 1; i < data.length; i++) { const slice = data.slice(i - period + 1, i + 1); const mean = middle[i - period + 1]; const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period; const std = Math.sqrt(variance); upper.push(mean + stdDev * std); lower.push(mean - stdDev * std); } // Pad the beginning with NaN to match input length const padLength = data.length - upper.length; const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr); return { upper: padArray(upper), middle: padArray(middle.slice(period - 1)), lower: padArray(lower) }; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in Bollinger Bands calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Stochastic Oscillator * @param high - Array of high prices * @param low - Array of low prices * @param close - Array of closing prices * @param kPeriod - %K period (default: 14) * @param dPeriod - %D period (default: 3) * @param smooth - Smoothing period for %K (default: 1) * @returns Object containing %K and %D values */ static stochastic(high, low, close, kPeriod = 14, dPeriod = 3, smooth = 1) { try { if (high.length !== low.length || high.length !== close.length) { throw new IndicatorError('High, low, and close arrays must have the same length'); } this._validateInput(high, kPeriod); this._validateInput(high, dPeriod); if (high.length < kPeriod) { return { k: [], d: [] }; } // Calculate %K const k = []; for (let i = kPeriod - 1; i < close.length; i++) { const highSlice = high.slice(i - kPeriod + 1, i + 1); const lowSlice = low.slice(i - kPeriod + 1, i + 1); const highestHigh = Math.max(...highSlice); const lowestLow = Math.min(...lowSlice); const range = highestHigh - lowestLow; const currentClose = close[i]; const stochK = range !== 0 ? 100 * ((currentClose - lowestLow) / range) : 0; k.push(Math.min(100, Math.max(0, stochK))); // Clamp between 0 and 100 } // Smooth %K if needed let smoothedK = k; if (smooth > 1) { smoothedK = this.sma(k, smooth); } // Calculate %D (signal line) const d = this.sma(smoothedK, dPeriod); // Pad the beginning with NaN to match input length const padLength = close.length - smoothedK.length; const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr); return { k: padArray(smoothedK), d: padArray(d) }; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in Stochastic Oscillator calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Average True Range (ATR) * @param high - Array of high prices * @param low - Array of low prices * @param close - Array of closing prices * @param period - Number of periods to use for calculation (default: 14) * @param maType - Type of moving average to use (default: 'sma') * @returns Array of ATR values */ static atr(high, low, close, period = 14, maType = 'sma') { try { if (high.length !== low.length || high.length !== close.length) { throw new IndicatorError('High, low, and close arrays must have the same length'); } this._validateInput(high, period); if (high.length < 2) return []; // Calculate True Range (TR) const tr = [high[0] - low[0]]; // First TR is just high - low for (let i = 1; i < high.length; i++) { const tr1 = high[i] - low[i]; const tr2 = Math.abs(high[i] - close[i - 1]); const tr3 = Math.abs(low[i] - close[i - 1]); tr.push(Math.max(tr1, tr2, tr3)); } // Calculate ATR using the specified moving average return this.movingAverage(maType, tr, period); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in ATR calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Volume Moving Average * @param volume - Array of volume values * @param period - Number of periods to use for calculation (default: 20) * @param maType - Type of moving average to use (default: 'sma') * @returns Array of volume moving average values */ static volumeMA(volume, period = 20, maType = 'sma') { try { this._validateInput(volume, period); return this.movingAverage(maType, volume, period); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in Volume MA calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate On-Balance Volume (OBV) * @param close - Array of closing prices * @param volume - Array of volume values * @returns Array of OBV values */ static obv(close, volume) { try { if (close.length !== volume.length) { throw new IndicatorError('Close and volume arrays must have the same length'); } if (close.length === 0) return []; const obv = [volume[0]]; // Start with the first volume for (let i = 1; i < close.length; i++) { if (close[i] > close[i - 1]) { // If price went up, add volume obv.push(obv[i - 1] + volume[i]); } else if (close[i] < close[i - 1]) { // If price went down, subtract volume obv.push(obv[i - 1] - volume[i]); } else { // If price didn't change, keep the same OBV obv.push(obv[i - 1]); } } return obv; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in OBV calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Donchian Channels (Price Channels) * @param high - Array of high prices * @param low - Array of low prices * @param period - Number of periods to use for calculation (default: 20) * @returns Object containing upper, middle, and lower channel values */ static donchianChannels(high, low, period = 20) { try { if (high.length !== low.length) { throw new IndicatorError('High and low arrays must have the same length'); } this._validateInput(high, period); if (high.length < period) { const empty = []; return { upper: empty, middle: empty, lower: empty }; } const upper = []; const lower = []; for (let i = period - 1; i < high.length; i++) { const highSlice = high.slice(i - period + 1, i + 1); const lowSlice = low.slice(i - period + 1, i + 1); upper.push(Math.max(...highSlice)); lower.push(Math.min(...lowSlice)); } const middle = upper.map((u, i) => (u + lower[i]) / 2); // Pad the beginning with NaN to match input length const padLength = high.length - upper.length; const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr); return { upper: padArray(upper), middle: padArray(middle), lower: padArray(lower) }; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in Donchian Channels calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Williams %R * @param high - Array of high prices * @param low - Array of low prices * @param close - Array of closing prices * @param period - Number of periods to use for calculation (default: 14) * @returns Array of Williams %R values */ static williamsR(high, low, close, period = 14) { try { if (high.length !== low.length || high.length !== close.length) { throw new IndicatorError('High, low, and close arrays must have the same length'); } this._validateInput(high, period); if (high.length < period) return []; const result = []; for (let i = period - 1; i < close.length; i++) { const highSlice = high.slice(i - period + 1, i + 1); const lowSlice = low.slice(i - period + 1, i + 1); const highestHigh = Math.max(...highSlice); const lowestLow = Math.min(...lowSlice); const range = highestHigh - lowestLow; if (range === 0) { result.push(0); // Avoid division by zero } else { result.push((-100 * (highestHigh - close[i])) / range); } } // Pad the beginning with NaN to match input length const padLength = close.length - result.length; return new Array(padLength).fill(NaN).concat(result); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in Williams %R calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Commodity Channel Index (CCI) * @param high - Array of high prices * @param low - Array of low prices * @param close - Array of closing prices * @param period - Number of periods to use for calculation (default: 20) * @returns Array of CCI values */ static cci(high, low, close, period = 20) { try { if (high.length !== low.length || high.length !== close.length) { throw new IndicatorError('High, low, and close arrays must have the same length'); } this._validateInput(high, period); if (high.length < period) return []; // Calculate Typical Price const typicalPrice = []; for (let i = 0; i < high.length; i++) { typicalPrice.push((high[i] + low[i] + close[i]) / 3); } // Calculate SMA of Typical Price const smaTp = this.sma(typicalPrice, period); // Calculate Mean Deviation const meanDeviation = []; for (let i = period - 1; i < typicalPrice.length; i++) { const slice = typicalPrice.slice(i - period + 1, i + 1); const mean = smaTp[i - period + 1]; const sumDeviation = slice.reduce((sum, val) => sum + Math.abs(val - mean), 0); meanDeviation.push(sumDeviation / period); } // Calculate CCI const cci = []; for (let i = 0; i < meanDeviation.length; i++) { const tpIndex = i + period - 1; const deviation = meanDeviation[i]; if (deviation === 0) { cci.push(0); // Avoid division by zero } else { cci.push((typicalPrice[tpIndex] - smaTp[i]) / (0.015 * deviation)); } } // Pad the beginning with NaN to match input length const padLength = high.length - cci.length; return new Array(padLength).fill(NaN).concat(cci); } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in CCI calculation: ${error instanceof Error ? error.message : String(error)}`); } } /** * Calculate Average Directional Index (ADX) * @param high - Array of high prices * @param low - Array of low prices * @param close - Array of closing prices * @param period - Number of periods to use for calculation (default: 14) * @returns Object containing ADX, +DI, and -DI values */ static adx(high, low, close, period = 14) { try { if (high.length !== low.length || high.length !== close.length) { throw new IndicatorError('High, low, and close arrays must have the same length'); } this._validateInput(high, period); if (high.length < period * 2) { const empty = []; return { adx: empty, plusDI: empty, minusDI: empty }; } // Calculate +DM, -DM, and True Range const plusDM = [0]; const minusDM = [0]; const tr = [high[0] - low[0]]; // First TR is just high - low for (let i = 1; i < high.length; i++) { // Calculate directional movements const upMove = high[i] - high[i - 1]; const downMove = low[i - 1] - low[i]; // +DM is the up move if it's greater than the down move and positive plusDM.push(upMove > downMove && upMove > 0 ? upMove : 0); // -DM is the down move if it's greater than the up move and positive minusDM.push(downMove > upMove && downMove > 0 ? downMove : 0); // True Range is the greatest of: // 1. Current High - Current Low // 2. Current High - Previous Close (absolute value) // 3. Current Low - Previous Close (absolute value) const tr1 = high[i] - low[i]; const tr2 = Math.abs(high[i] - close[i - 1]); const tr3 = Math.abs(low[i] - close[i - 1]); tr.push(Math.max(tr1, tr2, tr3)); } // Smooth the DMs and TR const smoothTR = this.ema(tr, period); const smoothPlusDM = this.ema(plusDM, period); const smoothMinusDM = this.ema(minusDM, period); // Calculate +DI and -DI const plusDI = []; const minusDI = []; for (let i = 0; i < smoothTR.length; i++) { plusDI.push(smoothTR[i] !== 0 ? (100 * smoothPlusDM[i]) / smoothTR[i] : 0); minusDI.push(smoothTR[i] !== 0 ? (100 * smoothMinusDM[i]) / smoothTR[i] : 0); } // Calculate DX and ADX const dx = []; for (let i = 0; i < plusDI.length; i++) { const sumDI = plusDI[i] + minusDI[i]; const diffDI = Math.abs(plusDI[i] - minusDI[i]); dx.push(sumDI !== 0 ? (100 * diffDI) / sumDI : 0); } const adx = this.ema(dx, period); // Pad the beginning with NaN to match input length const padLength = high.length - adx.length; const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr); return { adx: padArray(adx), plusDI: padArray(plusDI.slice(0, adx.length)), minusDI: padArray(minusDI.slice(0, adx.length)) }; } catch (error) { if (error instanceof IndicatorError) throw error; throw new IndicatorError(`Error in ADX calculation: ${error instanceof Error ? error.message : String(error)}`); } } } exports.Indicators = Indicators; _a = Indicators; // Alias for backward compatibility Indicators.priceChannels = _a.donchianChannels; //# sourceMappingURL=indicators.js.map