UNPKG

fast-technical-indicators

Version:

High-performance technical indicators with zero dependencies - compatible with technicalindicators package

170 lines (169 loc) 7.08 kB
// Helper function for Wilder Smoothing (RMA - TradingView style) function wilderSmoothing(values, period) { const result = []; for (let i = 0; i < values.length; i++) { if (i < period) { // For the first 'period' values, use simple average const sum = values.slice(0, i + 1).reduce((a, b) => a + b, 0); result.push(sum / (i + 1)); } else { // Wilder smoothing: (previous * (period - 1) + current) / period const prev = result[i - 1]; const smoothed = (prev * (period - 1) + values[i]) / period; result.push(smoothed); } } return result; } export function dmi(input) { const { period = 14, adxSmoothing = 14, high, low, close } = input; if (high.length !== low.length || low.length !== close.length) { return []; } const result = []; // Calculate +DM, -DM, and TR arrays const plusDM = []; const minusDM = []; const trueRanges = []; // Start from index 1, as we need previous values for calculations for (let i = 1; i < close.length; i++) { const upMove = high[i] - high[i - 1]; const downMove = low[i - 1] - low[i]; // +DM calculation const plusDMValue = (upMove > downMove && upMove > 0) ? upMove : 0; plusDM.push(plusDMValue); // -DM calculation const minusDMValue = (downMove > upMove && downMove > 0) ? downMove : 0; minusDM.push(minusDMValue); // True Range calculation const highLow = high[i] - low[i]; const highPrevClose = Math.abs(high[i] - close[i - 1]); const lowPrevClose = Math.abs(low[i] - close[i - 1]); trueRanges.push(Math.max(highLow, highPrevClose, lowPrevClose)); } // Apply Wilder smoothing to +DM, -DM, and TR const smoothedPlusDM = wilderSmoothing(plusDM, period); const smoothedMinusDM = wilderSmoothing(minusDM, period); const smoothedTR = wilderSmoothing(trueRanges, period); // Calculate DX values const dxValues = []; for (let i = 0; i < smoothedPlusDM.length; i++) { const plusDI = smoothedTR[i] !== 0 ? (smoothedPlusDM[i] * 100) / smoothedTR[i] : 0; const minusDI = smoothedTR[i] !== 0 ? (smoothedMinusDM[i] * 100) / smoothedTR[i] : 0; const diDiff = Math.abs(plusDI - minusDI); const diSum = plusDI + minusDI; const dx = diSum !== 0 ? (diDiff / diSum) * 100 : 0; dxValues.push(dx); } // Apply Wilder smoothing to DX for ADX const smoothedDX = wilderSmoothing(dxValues, adxSmoothing); // Build final results for (let i = 0; i < smoothedDX.length; i++) { const plusDI = smoothedTR[i] !== 0 ? (smoothedPlusDM[i] * 100) / smoothedTR[i] : 0; const minusDI = smoothedTR[i] !== 0 ? (smoothedMinusDM[i] * 100) / smoothedTR[i] : 0; result.push({ pdi: plusDI, mdi: minusDI, adx: smoothedDX[i] }); } return result; } export class DMI { constructor(input) { this.highValues = []; this.lowValues = []; this.closeValues = []; // For streaming Wilder smoothing state this.plusDMValues = []; this.minusDMValues = []; this.trValues = []; this.dxValues = []; this.results = []; this.period = input.period || 14; this.adxSmoothing = input.adxSmoothing || 14; } nextValue(high, low, close) { this.highValues.push(high); this.lowValues.push(low); this.closeValues.push(close); // Need at least 2 values to calculate DM and TR if (this.highValues.length < 2) { return undefined; } // Calculate +DM, -DM, and TR for the current period const prevHigh = this.highValues[this.highValues.length - 2]; const prevLow = this.lowValues[this.lowValues.length - 2]; const prevClose = this.closeValues[this.closeValues.length - 2]; const upMove = high - prevHigh; const downMove = prevLow - low; const plusDMValue = (upMove > downMove && upMove > 0) ? upMove : 0; const minusDMValue = (downMove > upMove && downMove > 0) ? downMove : 0; const highLow = high - low; const highPrevClose = Math.abs(high - prevClose); const lowPrevClose = Math.abs(low - prevClose); const trValue = Math.max(highLow, highPrevClose, lowPrevClose); // Store raw values this.plusDMValues.push(plusDMValue); this.minusDMValues.push(minusDMValue); this.trValues.push(trValue); // Apply Wilder smoothing to +DM, -DM, and TR if (this.plusDMValues.length < this.period) { return undefined; } else if (this.plusDMValues.length === this.period) { // First calculation - use all values const sum1 = this.plusDMValues.reduce((a, b) => a + b, 0); const sum2 = this.minusDMValues.reduce((a, b) => a + b, 0); const sum3 = this.trValues.reduce((a, b) => a + b, 0); this.smoothedPlusDM = sum1 / this.period; this.smoothedMinusDM = sum2 / this.period; this.smoothedTR = sum3 / this.period; } else { // Subsequent calculations - Wilder smoothing this.smoothedPlusDM = (this.smoothedPlusDM * (this.period - 1) + plusDMValue) / this.period; this.smoothedMinusDM = (this.smoothedMinusDM * (this.period - 1) + minusDMValue) / this.period; this.smoothedTR = (this.smoothedTR * (this.period - 1) + trValue) / this.period; } // Calculate +DI and -DI const plusDI = this.smoothedTR !== 0 ? (this.smoothedPlusDM * 100) / this.smoothedTR : 0; const minusDI = this.smoothedTR !== 0 ? (this.smoothedMinusDM * 100) / this.smoothedTR : 0; // Calculate DX const diDiff = Math.abs(plusDI - minusDI); const diSum = plusDI + minusDI; const dx = diSum !== 0 ? (diDiff / diSum) * 100 : 0; this.dxValues.push(dx); // Apply Wilder smoothing to DX for ADX if (this.dxValues.length < this.adxSmoothing) { const result = { pdi: plusDI, mdi: minusDI, adx: 0 // ADX not ready yet }; this.results.push(result); return result; } else if (this.dxValues.length === this.adxSmoothing) { // First ADX calculation const dxSum = this.dxValues.reduce((a, b) => a + b, 0); this.smoothedDX = dxSum / this.adxSmoothing; } else { // Subsequent ADX calculations - Wilder smoothing this.smoothedDX = (this.smoothedDX * (this.adxSmoothing - 1) + dx) / this.adxSmoothing; } const result = { pdi: plusDI, mdi: minusDI, adx: this.smoothedDX || 0 }; this.results.push(result); return result; } getResult() { return this.results; } } DMI.calculate = dmi;