UNPKG

trading-signals

Version:

Technical indicators to run technical analysis with JavaScript / TypeScript.

1,617 lines (1,579 loc) 60.2 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AC: () => AC, ADX: () => ADX, AO: () => AO, ATR: () => ATR, AccelerationBands: () => AccelerationBands, BaseIndicatorSeries: () => BaseIndicatorSeries, Big: () => import_big26.default, BigIndicatorSeries: () => BigIndicatorSeries, BollingerBands: () => BollingerBands, BollingerBandsWidth: () => BollingerBandsWidth, CCI: () => CCI, CG: () => CG, DEMA: () => DEMA, DMA: () => DMA, DX: () => DX, EMA: () => EMA, FasterAC: () => FasterAC, FasterADX: () => FasterADX, FasterAO: () => FasterAO, FasterATR: () => FasterATR, FasterAccelerationBands: () => FasterAccelerationBands, FasterBollingerBands: () => FasterBollingerBands, FasterBollingerBandsWidth: () => FasterBollingerBandsWidth, FasterCCI: () => FasterCCI, FasterCG: () => FasterCG, FasterDEMA: () => FasterDEMA, FasterDMA: () => FasterDMA, FasterDX: () => FasterDX, FasterEMA: () => FasterEMA, FasterLinearRegression: () => FasterLinearRegression, FasterMACD: () => FasterMACD, FasterMAD: () => FasterMAD, FasterMOM: () => FasterMOM, FasterMovingAverage: () => FasterMovingAverage, FasterOBV: () => FasterOBV, FasterPeriod: () => FasterPeriod, FasterRMA: () => FasterRMA, FasterROC: () => FasterROC, FasterRSI: () => FasterRSI, FasterSMA: () => FasterSMA, FasterStochasticOscillator: () => FasterStochasticOscillator, FasterStochasticRSI: () => FasterStochasticRSI, FasterTR: () => FasterTR, FasterWMA: () => FasterWMA, FasterWSMA: () => FasterWSMA, LinearRegression: () => LinearRegression, MACD: () => MACD, MAD: () => MAD, MOM: () => MOM, MovingAverage: () => MovingAverage, NotEnoughDataError: () => NotEnoughDataError, NumberIndicatorSeries: () => NumberIndicatorSeries, OBV: () => OBV, Period: () => Period, RMA: () => RMA, ROC: () => ROC, RSI: () => RSI, SMA: () => SMA, StochasticOscillator: () => StochasticOscillator, StochasticRSI: () => StochasticRSI, TR: () => TR, TechnicalIndicator: () => TechnicalIndicator, WMA: () => WMA, WSMA: () => WSMA, getAverage: () => getAverage, getFasterAverage: () => getFasterAverage, getFasterStandardDeviation: () => getFasterStandardDeviation, getMaximum: () => getMaximum, getMinimum: () => getMinimum, getStandardDeviation: () => getStandardDeviation, getStreaks: () => getStreaks, pushUpdate: () => pushUpdate }); module.exports = __toCommonJS(index_exports); var import_big26 = __toESM(require("big.js"), 1); // src/error/NotEnoughDataError.ts var NotEnoughDataError = class extends Error { constructor(message = "Not enough data") { super(message); Object.setPrototypeOf(this, new.target.prototype); this.name = "NotEnoughDataError"; } }; // src/Indicator.ts var TechnicalIndicator = class { result; getResult() { try { return this.getResultOrThrow(); } catch { return null; } } getResultOrThrow() { if (this.result === void 0) { throw new NotEnoughDataError(); } return this.result; } get isStable() { return this.result !== void 0; } add(input) { return this.update(input, false); } replace(input) { return this.update(input, true); } updates(inputs, replace = false) { return inputs.map((input) => this.update(input, replace)); } }; var BaseIndicatorSeries = class extends TechnicalIndicator { previousHighest; highest; previousLowest; lowest; previousResult; }; var BigIndicatorSeries = class extends BaseIndicatorSeries { setResult(value, replace) { if (replace) { this.highest = this.previousHighest; this.lowest = this.previousLowest; this.result = this.previousResult; } if (this.highest === void 0) { this.highest = value; } else if (value.gt(this.highest)) { this.previousHighest = this.highest; this.highest = value; } else { this.previousHighest = this.highest; } if (this.lowest === void 0) { this.lowest = value; } else if (value.lt(this.lowest)) { this.previousLowest = this.lowest; this.lowest = value; } else { this.previousLowest = this.lowest; } this.previousResult = this.result; return this.result = value; } }; var NumberIndicatorSeries = class extends BaseIndicatorSeries { setResult(value, replace) { if (replace) { this.highest = this.previousHighest; this.lowest = this.previousLowest; this.result = this.previousResult; } if (this.highest === void 0) { this.highest = value; } else if (value > this.highest) { this.previousHighest = this.highest; this.highest = value; } else { this.previousHighest = this.highest; } if (this.lowest === void 0) { this.lowest = value; } else if (value < this.lowest) { this.previousLowest = this.lowest; this.lowest = value; } else { this.previousLowest = this.lowest; } this.previousResult = this.result; return this.result = value; } }; // src/MA/MovingAverage.ts var MovingAverage = class extends BigIndicatorSeries { constructor(interval) { super(); this.interval = interval; } }; var FasterMovingAverage = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; } }; // src/ABANDS/AccelerationBands.ts var import_big2 = __toESM(require("big.js"), 1); // src/util/getAverage.ts var import_big = __toESM(require("big.js"), 1); function getAverage(values) { const sum = values.reduce((prev, current) => { return prev.add(current); }, new import_big.default(0)); return sum.div(values.length || 1); } function getFasterAverage(values) { return values.length ? values.reduce((sum, x) => sum + x, 0) / values.length : 0; } // src/util/pushUpdate.ts function pushUpdate(array, replace, item, maxLength) { if (array.length > 0 && replace === true) { array[array.length - 1] = item; } else { array.push(item); } if (array.length > maxLength) { return array.shift(); } return null; } // src/SMA/SMA.ts var SMA = class extends MovingAverage { prices = []; update(price, replace) { pushUpdate(this.prices, replace, price, this.interval); if (this.prices.length === this.interval) { return this.setResult(getAverage(this.prices), replace); } return null; } }; var FasterSMA = class extends FasterMovingAverage { prices = []; update(price, replace) { pushUpdate(this.prices, replace, price, this.interval); if (this.prices.length === this.interval) { return this.setResult(getFasterAverage(this.prices), replace); } return null; } }; // src/ABANDS/AccelerationBands.ts var AccelerationBands = class extends TechnicalIndicator { /** * Acceleration Bands (ABANDS) * Type: Volatility * * Acceleration bands created by Price Headley are set as an envelope around a moving average. The upper and lower * bands are of equal distance from the middle band. * * Two consecutive closes outside Acceleration Bands suggest an entry point in the direction of the breakout (either * bullish or bearish). A long position is usually kept till the first close back inside the bands. * * @param interval The interval that is being used for the three moving averages which create lower, middle and upper * bands * @param width A coefficient specifying the distance between the middle band and upper/lower bands * @param SmoothingIndicator Which moving average (SMA, EMA, ...) to use * * @see https://www.tradingtechnologies.com/xtrader-help/x-study/technical-indicator-definitions/acceleration-bands-abands/ * @see https://www.motivewave.com/studies/acceleration_bands.htm * @see https://github.com/QuantConnect/Lean/blob/master/Indicators/AccelerationBands.cs * @see https://github.com/twopirllc/pandas-ta/blob/master/pandas_ta/volatility/accbands.py */ constructor(interval, width, SmoothingIndicator = SMA) { super(); this.interval = interval; this.width = width; this.lowerBand = new SmoothingIndicator(interval); this.middleBand = new SmoothingIndicator(interval); this.upperBand = new SmoothingIndicator(interval); } lowerBand; middleBand; upperBand; get isStable() { return this.middleBand.isStable; } update({ high, low, close }, replace) { const highPlusLow = new import_big2.default(high).plus(low); const coefficient = highPlusLow.eq(0) ? new import_big2.default(0) : new import_big2.default(high).minus(low).div(highPlusLow).mul(this.width); this.lowerBand.update(new import_big2.default(low).mul(new import_big2.default(1).minus(coefficient)), replace); this.middleBand.update(close, replace); this.upperBand.update(new import_big2.default(high).mul(new import_big2.default(1).plus(coefficient)), replace); if (this.isStable) { return this.result = { lower: this.lowerBand.getResultOrThrow(), middle: this.middleBand.getResultOrThrow(), upper: this.upperBand.getResultOrThrow() }; } return null; } }; var FasterAccelerationBands = class extends TechnicalIndicator { constructor(interval, width, SmoothingIndicator = FasterSMA) { super(); this.interval = interval; this.width = width; this.lowerBand = new SmoothingIndicator(interval); this.middleBand = new SmoothingIndicator(interval); this.upperBand = new SmoothingIndicator(interval); } lowerBand; middleBand; upperBand; update({ high, low, close }, replace) { const highPlusLow = high + low; const coefficient = highPlusLow === 0 ? 0 : (high - low) / highPlusLow * this.width; this.lowerBand.update(low * (1 - coefficient), replace); this.middleBand.update(close, replace); this.upperBand.update(high * (1 + coefficient), replace); if (this.isStable) { return this.result = { lower: this.lowerBand.getResultOrThrow(), middle: this.middleBand.getResultOrThrow(), upper: this.upperBand.getResultOrThrow() }; } return null; } get isStable() { return this.middleBand.isStable; } }; // src/AO/AO.ts var import_big3 = __toESM(require("big.js"), 1); var AO = class extends BigIndicatorSeries { constructor(shortInterval, longInterval, SmoothingIndicator = SMA) { super(); this.shortInterval = shortInterval; this.longInterval = longInterval; this.short = new SmoothingIndicator(shortInterval); this.long = new SmoothingIndicator(longInterval); } long; short; update({ low, high }, replace) { const candleSum = new import_big3.default(low).add(high); const medianPrice = candleSum.div(2); this.short.update(medianPrice, replace); this.long.update(medianPrice, replace); if (this.long.isStable) { return this.setResult(this.short.getResultOrThrow().sub(this.long.getResultOrThrow()), replace); } return null; } }; var FasterAO = class extends NumberIndicatorSeries { constructor(shortInterval, longInterval, SmoothingIndicator = FasterSMA) { super(); this.shortInterval = shortInterval; this.longInterval = longInterval; this.short = new SmoothingIndicator(shortInterval); this.long = new SmoothingIndicator(longInterval); } long; short; update({ low, high }, replace) { const medianPrice = (low + high) / 2; this.short.update(medianPrice, replace); this.long.update(medianPrice, replace); if (this.long.isStable) { return this.setResult(this.short.getResultOrThrow() - this.long.getResultOrThrow(), replace); } return null; } }; // src/MOM/MOM.ts var import_big4 = __toESM(require("big.js"), 1); var MOM = class extends BigIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.historyLength = interval + 1; this.history = []; } history; historyLength; update(value, replace) { pushUpdate(this.history, replace, value, this.historyLength); if (this.history.length === this.historyLength) { return this.setResult(new import_big4.default(value).minus(this.history[0]), replace); } return null; } }; var FasterMOM = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.historyLength = interval + 1; this.history = []; } history; historyLength; update(value, replace) { pushUpdate(this.history, replace, value, this.historyLength); if (this.history.length === this.historyLength) { return this.setResult(value - this.history[0], replace); } return null; } }; // src/AC/AC.ts var AC = class extends BigIndicatorSeries { constructor(shortAO, longAO, signalInterval) { super(); this.shortAO = shortAO; this.longAO = longAO; this.signalInterval = signalInterval; this.ao = new AO(shortAO, longAO); this.momentum = new MOM(1); this.signal = new SMA(signalInterval); } ao; momentum; signal; update(input, replace) { const ao = this.ao.update(input, replace); if (ao) { this.signal.update(ao, replace); if (this.signal.isStable) { const result = this.setResult(ao.sub(this.signal.getResultOrThrow()), replace); this.momentum.update(result, replace); return result; } } return null; } }; var FasterAC = class extends NumberIndicatorSeries { constructor(shortAO, longAO, signalInterval) { super(); this.shortAO = shortAO; this.longAO = longAO; this.signalInterval = signalInterval; this.ao = new FasterAO(shortAO, longAO); this.momentum = new FasterMOM(1); this.signal = new FasterSMA(signalInterval); } ao; momentum; signal; update(input, replace) { const ao = this.ao.update(input, replace); if (ao) { this.signal.update(ao, replace); if (this.signal.isStable) { const result = this.setResult(ao - this.signal.getResultOrThrow(), replace); this.momentum.update(result, replace); return result; } } return null; } }; // src/WSMA/WSMA.ts var import_big5 = __toESM(require("big.js"), 1); var WSMA = class extends MovingAverage { constructor(interval) { super(interval); this.interval = interval; this.indicator = new SMA(interval); this.smoothingFactor = new import_big5.default(1).div(this.interval); } indicator; smoothingFactor; update(price, replace) { const sma = this.indicator.update(price, replace); if (replace && this.previousResult) { const smoothed = new import_big5.default(price).minus(this.previousResult).mul(this.smoothingFactor); return this.setResult(smoothed.plus(this.previousResult), replace); } else if (!replace && this.result !== void 0) { const smoothed = new import_big5.default(price).minus(this.result).mul(this.smoothingFactor); return this.setResult(smoothed.plus(this.result), replace); } else if (this.result === void 0 && sma !== null) { return this.setResult(sma, replace); } return null; } }; var FasterWSMA = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.indicator = new FasterSMA(interval); this.smoothingFactor = 1 / this.interval; } indicator; smoothingFactor; update(price, replace) { const sma = this.indicator.update(price, replace); if (replace && this.previousResult !== void 0) { const smoothed = (price - this.previousResult) * this.smoothingFactor; return this.setResult(smoothed + this.previousResult, replace); } else if (!replace && this.result !== void 0) { const smoothed = (price - this.result) * this.smoothingFactor; return this.setResult(smoothed + this.result, replace); } else if (this.result === void 0 && sma !== null) { return this.setResult(sma, replace); } return null; } }; // src/TR/TR.ts var import_big7 = __toESM(require("big.js"), 1); // src/util/getMaximum.ts var import_big6 = __toESM(require("big.js"), 1); function getMaximum(values) { let max = new import_big6.default(Number.MIN_SAFE_INTEGER); for (const value of values) { if (max.lt(value)) { max = new import_big6.default(value); } } return max; } // src/TR/TR.ts var TR = class extends BigIndicatorSeries { previousCandle; secondLastCandle; update(candle, replace) { const high = new import_big7.default(candle.high); const highLow = high.minus(candle.low); if (this.previousCandle && replace) { this.previousCandle = this.secondLastCandle; } if (this.previousCandle) { const highClose = high.minus(this.previousCandle.close).abs(); const lowClose = new import_big7.default(candle.low).minus(this.previousCandle.close).abs(); this.secondLastCandle = this.previousCandle; this.previousCandle = candle; return this.setResult(getMaximum([highLow, highClose, lowClose]), replace); } this.secondLastCandle = this.previousCandle; this.previousCandle = candle; return this.setResult(highLow, replace); } }; var FasterTR = class extends NumberIndicatorSeries { previousCandle; twoPreviousCandle; update(candle, replace) { const { high, low } = candle; const highLow = high - low; if (this.previousCandle && replace) { this.previousCandle = this.twoPreviousCandle; } if (this.previousCandle) { const highClose = Math.abs(high - this.previousCandle.close); const lowClose = Math.abs(low - this.previousCandle.close); this.twoPreviousCandle = this.previousCandle; this.previousCandle = candle; return this.setResult(Math.max(highLow, highClose, lowClose), replace); } this.twoPreviousCandle = this.previousCandle; this.previousCandle = candle; return this.setResult(highLow, replace); } }; // src/ATR/ATR.ts var ATR = class extends BigIndicatorSeries { constructor(interval, SmoothingIndicator = WSMA) { super(); this.interval = interval; this.tr = new TR(); this.smoothing = new SmoothingIndicator(interval); } tr; smoothing; update(candle, replace) { const trueRange = this.tr.update(candle, replace); this.smoothing.update(trueRange, replace); if (this.smoothing.isStable) { return this.setResult(this.smoothing.getResultOrThrow(), replace); } return null; } }; var FasterATR = class extends NumberIndicatorSeries { constructor(interval, SmoothingIndicator = FasterWSMA) { super(); this.interval = interval; this.tr = new FasterTR(); this.smoothing = new SmoothingIndicator(interval); } tr; smoothing; update(candle, replace) { const trueRange = this.tr.update(candle, replace); this.smoothing.update(trueRange, replace); if (this.smoothing.isStable) { return this.setResult(this.smoothing.getResultOrThrow(), replace); } return null; } }; // src/DX/DX.ts var import_big8 = __toESM(require("big.js"), 1); var DX = class extends BigIndicatorSeries { constructor(interval, SmoothingIndicator = WSMA) { super(); this.interval = interval; this.atr = new ATR(this.interval, SmoothingIndicator); this.movesDown = new SmoothingIndicator(this.interval); this.movesUp = new SmoothingIndicator(this.interval); } movesUp; movesDown; previousCandle; secondLastCandle; atr; /** Negative (Minus) Directional Indicator (-DI) */ mdi; /** Positive (Plus) Directional Indicator (+DI) */ pdi; updateState(candle, pdm, mdm, replace) { this.atr.update(candle, replace); this.movesDown.update(mdm, replace); this.movesUp.update(pdm, replace); if (this.previousCandle) { this.secondLastCandle = this.previousCandle; } this.previousCandle = candle; } update(candle, replace) { if (!this.previousCandle) { this.updateState(candle, 0, 0, replace); return null; } if (this.secondLastCandle && replace) { this.previousCandle = this.secondLastCandle; } const currentHigh = new import_big8.default(candle.high); const previousHigh = new import_big8.default(this.previousCandle.high); const currentLow = new import_big8.default(candle.low); const previousLow = new import_big8.default(this.previousCandle.low); const higherHigh = currentHigh.minus(previousHigh); const lowerLow = previousLow.minus(currentLow); const noHigherHighs = higherHigh.lt(0); const lowsRiseFaster = higherHigh.lt(lowerLow); const pdm = noHigherHighs || lowsRiseFaster ? new import_big8.default(0) : higherHigh; const noLowerLows = lowerLow.lt(0); const highsRiseFaster = lowerLow.lt(higherHigh); const mdm = noLowerLows || highsRiseFaster ? new import_big8.default(0) : lowerLow; this.updateState(candle, pdm, mdm, replace); if (this.movesUp.isStable) { this.pdi = this.movesUp.getResultOrThrow().div(this.atr.getResultOrThrow()); this.mdi = this.movesDown.getResultOrThrow().div(this.atr.getResultOrThrow()); const dmDiff = this.pdi.minus(this.mdi).abs(); const dmSum = this.pdi.plus(this.mdi); if (dmSum.eq(0)) { return this.setResult(new import_big8.default(0), replace); } return this.setResult(dmDiff.div(dmSum).mul(100), replace); } return null; } }; var FasterDX = class extends NumberIndicatorSeries { constructor(interval, SmoothingIndicator = FasterWSMA) { super(); this.interval = interval; this.atr = new FasterATR(this.interval, SmoothingIndicator); this.movesDown = new SmoothingIndicator(this.interval); this.movesUp = new SmoothingIndicator(this.interval); } movesUp; movesDown; previousCandle; secondLastCandle; atr; mdi; pdi; updateState(candle, pdm, mdm, replace) { this.atr.update(candle, replace); this.movesUp.update(pdm, replace); this.movesDown.update(mdm, replace); if (this.previousCandle) { this.secondLastCandle = this.previousCandle; } this.previousCandle = candle; } update(candle, replace) { if (!this.previousCandle) { this.updateState(candle, 0, 0, replace); return null; } if (this.secondLastCandle && replace) { this.previousCandle = this.secondLastCandle; } const currentHigh = candle.high; const previousHigh = this.previousCandle.high; const currentLow = candle.low; const previousLow = this.previousCandle.low; const higherHigh = currentHigh - previousHigh; const lowerLow = previousLow - currentLow; const noHigherHighs = higherHigh < 0; const lowsRiseFaster = higherHigh < lowerLow; const pdm = noHigherHighs || lowsRiseFaster ? 0 : higherHigh; const noLowerLows = lowerLow < 0; const highsRiseFaster = lowerLow < higherHigh; const mdm = noLowerLows || highsRiseFaster ? 0 : lowerLow; this.updateState(candle, pdm, mdm, replace); if (this.movesUp.isStable) { this.pdi = this.movesUp.getResultOrThrow() / this.atr.getResultOrThrow(); this.mdi = this.movesDown.getResultOrThrow() / this.atr.getResultOrThrow(); const dmDiff = Math.abs(this.pdi - this.mdi); const dmSum = this.pdi + this.mdi; if (dmSum === 0) { return this.setResult(0, replace); } return this.setResult(dmDiff / dmSum * 100, replace); } return null; } }; // src/ADX/ADX.ts var ADX = class extends BigIndicatorSeries { constructor(interval, SmoothingIndicator = WSMA) { super(); this.interval = interval; this.smoothed = new SmoothingIndicator(this.interval); this.dx = new DX(this.interval, SmoothingIndicator); } dx; smoothed; get mdi() { return this.dx.mdi; } get pdi() { return this.dx.pdi; } update(candle, replace) { const result = this.dx.update(candle, replace); if (result !== null) { this.smoothed.update(result, replace); } if (this.smoothed.isStable) { return this.setResult(this.smoothed.getResultOrThrow(), replace); } return null; } }; var FasterADX = class extends NumberIndicatorSeries { constructor(interval, SmoothingIndicator = FasterWSMA) { super(); this.interval = interval; this.smoothed = new SmoothingIndicator(this.interval); this.dx = new FasterDX(interval, SmoothingIndicator); } dx; smoothed; get mdi() { return this.dx.mdi; } get pdi() { return this.dx.pdi; } update(candle, replace) { const result = this.dx.update(candle, replace); if (result !== null) { this.smoothed.update(result, replace); } if (this.smoothed.isStable) { return this.setResult(this.smoothed.getResultOrThrow(), replace); } return null; } }; // src/BBANDS/BollingerBands.ts var import_big12 = __toESM(require("big.js"), 1); // src/util/getMinimum.ts var import_big9 = __toESM(require("big.js"), 1); function getMinimum(values) { let min = new import_big9.default(Number.MAX_SAFE_INTEGER); for (const value of values) { if (min.gt(value)) { min = new import_big9.default(value); } } return min; } // src/util/getStandardDeviation.ts var import_big10 = __toESM(require("big.js"), 1); function getStandardDeviation(values, average) { const middle = average || getAverage(values); const squaredDifferences = values.map((value) => new import_big10.default(value).sub(middle).pow(2)); return getAverage(squaredDifferences).sqrt(); } function getFasterStandardDeviation(values, average) { const middle = average || getFasterAverage(values); const squaredDifferences = values.map((value) => value - middle).map((value) => value * value); const averageDifference = getFasterAverage(squaredDifferences); return Math.sqrt(averageDifference); } // src/util/getStreaks.ts function getStreaks(prices, keepSide) { const streaks = []; let currentStreak = 0; function saveStreak(i) { const endPrice = prices[i - 1]; const startPrice = prices[i - currentStreak - 1]; const percentage = (endPrice - startPrice) / startPrice * 100; streaks.push({ length: currentStreak, percentage }); } for (let i = 1; i < prices.length; i++) { const isUpward = keepSide === "up" && prices[i] > prices[i - 1]; const isDownward = keepSide === "down" && prices[i] < prices[i - 1]; if (isUpward || isDownward) { currentStreak++; } else { if (currentStreak > 0) { saveStreak(i); } currentStreak = 0; } } if (currentStreak > 0) { saveStreak(prices.length); } return streaks; } // src/util/Period.ts var import_big11 = __toESM(require("big.js"), 1); var Period = class extends TechnicalIndicator { constructor(interval) { super(); this.interval = interval; this.values = []; } values; /** Highest return value during the current period. */ _highest; /** Lowest return value during the current period. */ _lowest; get highest() { return this._highest; } get lowest() { return this._lowest; } update(value, replace) { pushUpdate(this.values, replace, new import_big11.default(value), this.interval); if (this.values.length === this.interval) { this._lowest = getMinimum(this.values); this._highest = getMaximum(this.values); return this.result = { highest: this._highest, lowest: this._lowest }; } return null; } }; var FasterPeriod = class extends TechnicalIndicator { constructor(interval) { super(); this.interval = interval; this.values = []; } values; /** Highest return value during the current period. */ _highest; /** Lowest return value during the current period. */ _lowest; get highest() { return this._highest; } get lowest() { return this._lowest; } update(value, replace) { pushUpdate(this.values, replace, value, this.interval); if (this.values.length === this.interval) { this._lowest = Math.min(...this.values); this._highest = Math.max(...this.values); return this.result = { highest: this._highest, lowest: this._lowest }; } return null; } }; // src/BBANDS/BollingerBands.ts var BollingerBands = class extends TechnicalIndicator { /** * @param interval - The time period to be used in calculating the Middle Band * @param deviationMultiplier - The number of standard deviations away from the Middle Band that the Upper and Lower * Bands should be */ constructor(interval, deviationMultiplier = 2) { super(); this.interval = interval; this.deviationMultiplier = deviationMultiplier; } prices = []; update(price, replace) { const dropOut = pushUpdate(this.prices, replace, new import_big12.default(price), this.interval); if (dropOut) { const middle = getAverage(this.prices); const standardDeviation = getStandardDeviation(this.prices, middle); return this.result = { lower: middle.sub(standardDeviation.times(this.deviationMultiplier)), middle, upper: middle.add(standardDeviation.times(this.deviationMultiplier)) }; } return null; } }; var FasterBollingerBands = class extends TechnicalIndicator { constructor(interval, deviationMultiplier = 2) { super(); this.interval = interval; this.deviationMultiplier = deviationMultiplier; } prices = []; update(price, replace) { const dropOut = pushUpdate(this.prices, replace, price, this.interval); if (dropOut) { const middle = getFasterAverage(this.prices); const standardDeviation = getFasterStandardDeviation(this.prices, middle); return this.result = { lower: middle - standardDeviation * this.deviationMultiplier, middle, upper: middle + standardDeviation * this.deviationMultiplier }; } return null; } }; // src/BBW/BollingerBandsWidth.ts var BollingerBandsWidth = class extends BigIndicatorSeries { constructor(bollingerBands) { super(); this.bollingerBands = bollingerBands; } update(price, replace) { const result = this.bollingerBands.update(price, replace); if (result) { return this.setResult(result.upper.minus(result.lower).div(result.middle), replace); } return null; } }; var FasterBollingerBandsWidth = class extends NumberIndicatorSeries { constructor(bollingerBands) { super(); this.bollingerBands = bollingerBands; } update(price, replace) { const result = this.bollingerBands.update(price, replace); if (result) { return this.setResult((result.upper - result.lower) / result.middle, replace); } return null; } }; // src/CCI/CCI.ts var import_big14 = __toESM(require("big.js"), 1); // src/MAD/MAD.ts var import_big13 = __toESM(require("big.js"), 1); var MAD = class _MAD extends BigIndicatorSeries { constructor(interval) { super(); this.interval = interval; } prices = []; update(price, replace) { pushUpdate(this.prices, replace, price, this.interval); if (this.prices.length === this.interval) { return this.setResult(_MAD.getResultFromBatch(this.prices), replace); } return null; } static getResultFromBatch(prices, average) { const mean = average || getAverage(prices); let sum = new import_big13.default(0); for (let i = 0; i < prices.length; i++) { const deviation = new import_big13.default(prices[i]).minus(mean).abs(); sum = sum.plus(deviation); } return sum.div(prices.length || 1); } }; var FasterMAD = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; } prices = []; update(price, replace) { pushUpdate(this.prices, replace, price, this.interval); if (this.prices.length === this.interval) { const mean = getFasterAverage(this.prices); let sum = 0; for (let i = 0; i < this.interval; i++) { const deviation = Math.abs(this.prices[i] - mean); sum += deviation; } return this.setResult(sum / this.interval, replace); } return null; } static getResultFromBatch(prices, average) { const mean = average || getFasterAverage(prices); let sum = 0; for (let i = 0; i < prices.length; i++) { const deviation = Math.abs(prices[i] - mean); sum += deviation; } return sum / prices.length; } }; // src/CCI/CCI.ts var CCI = class extends BigIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.sma = new SMA(this.interval); this.typicalPrices = []; } sma; typicalPrices; update(candle, replace) { const typicalPrice = this.cacheTypicalPrice(candle, replace); this.sma.update(typicalPrice, replace); if (this.sma.isStable) { const mean = this.sma.getResultOrThrow(); const meanDeviation = MAD.getResultFromBatch(this.typicalPrices, mean); const numerator = typicalPrice.minus(mean); const denominator = new import_big14.default(0.015).mul(meanDeviation); const result = numerator.div(denominator); return this.setResult(result, replace); } return null; } cacheTypicalPrice({ high, low, close }, replace) { const typicalPrice = new import_big14.default(high).plus(low).plus(close).div(3); pushUpdate(this.typicalPrices, replace, typicalPrice, this.interval); return typicalPrice; } }; var FasterCCI = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.sma = new FasterSMA(this.interval); this.typicalPrices = []; } sma; typicalPrices; update(candle, replace) { const typicalPrice = this.cacheTypicalPrice(candle, replace); this.sma.update(typicalPrice, replace); if (this.sma.isStable) { const mean = this.sma.getResultOrThrow(); const meanDeviation = FasterMAD.getResultFromBatch(this.typicalPrices, mean); const numerator = typicalPrice - mean; const denominator = 0.015 * meanDeviation; return this.setResult(numerator / denominator, replace); } return null; } cacheTypicalPrice({ high, low, close }, replace) { const typicalPrice = (high + low + close) / 3; pushUpdate(this.typicalPrices, replace, typicalPrice, this.interval); return typicalPrice; } }; // src/CG/CG.ts var import_big15 = __toESM(require("big.js"), 1); var CG = class extends BigIndicatorSeries { constructor(interval, signalInterval) { super(); this.interval = interval; this.signalInterval = signalInterval; this.signal = new SMA(signalInterval); } signal; prices = []; get isStable() { return this.signal.isStable; } update(price, replace) { pushUpdate(this.prices, replace, new import_big15.default(price), this.interval); let nominator = new import_big15.default(0); let denominator = new import_big15.default(0); for (let i = 0; i < this.prices.length; ++i) { const price2 = this.prices[i]; nominator = nominator.plus(price2.mul(i + 1)); denominator = denominator.plus(price2); } const cg = denominator.gt(0) ? nominator.div(denominator) : new import_big15.default(0); this.signal.update(cg, replace); if (this.signal.isStable) { return this.setResult(cg, replace); } return null; } }; var FasterCG = class extends NumberIndicatorSeries { constructor(interval, signalInterval) { super(); this.interval = interval; this.signalInterval = signalInterval; this.signal = new FasterSMA(signalInterval); } signal; prices = []; get isStable() { return this.signal.isStable; } update(price, replace) { pushUpdate(this.prices, replace, price, this.interval); let nominator = 0; let denominator = 0; for (let i = 0; i < this.prices.length; ++i) { const price2 = this.prices[i]; nominator = nominator + price2 * (i + 1); denominator = denominator + price2; } const cg = denominator > 0 ? nominator / denominator : 0; this.signal.update(cg, replace); if (this.signal.isStable) { return this.setResult(cg, replace); } return null; } }; // src/EMA/EMA.ts var import_big16 = __toESM(require("big.js"), 1); var EMA = class extends MovingAverage { constructor(interval) { super(interval); this.interval = interval; this.weightFactor = 2 / (this.interval + 1); } pricesCounter = 0; weightFactor; update(_price, replace) { if (!replace) { this.pricesCounter++; } else if (replace && this.pricesCounter === 0) { this.pricesCounter++; } const price = new import_big16.default(_price); if (replace && this.previousResult) { return this.setResult( price.times(this.weightFactor).add(this.previousResult.times(1 - this.weightFactor)), replace ); } return this.setResult( price.times(this.weightFactor).add((this.result ?? price).times(1 - this.weightFactor)), replace ); } getResultOrThrow() { if (this.pricesCounter < this.interval) { throw new NotEnoughDataError(); } return this.result; } get isStable() { try { this.getResultOrThrow(); return true; } catch { return false; } } }; var FasterEMA = class extends FasterMovingAverage { constructor(interval) { super(interval); this.interval = interval; this.weightFactor = 2 / (this.interval + 1); } pricesCounter = 0; weightFactor; update(price, replace) { if (!replace) { this.pricesCounter++; } else if (replace && this.pricesCounter === 0) { this.pricesCounter++; } if (replace && this.previousResult !== void 0) { return this.setResult(price * this.weightFactor + this.previousResult * (1 - this.weightFactor), replace); } return this.setResult( price * this.weightFactor + (this.result !== void 0 ? this.result : price) * (1 - this.weightFactor), replace ); } getResultOrThrow() { if (this.pricesCounter < this.interval) { throw new NotEnoughDataError(); } return this.result; } get isStable() { try { this.getResultOrThrow(); return true; } catch { return false; } } }; // src/DEMA/DEMA.ts var DEMA = class extends BigIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.inner = new EMA(interval); this.outer = new EMA(interval); } inner; outer; update(price, replace) { const innerResult = this.inner.update(price, replace); const outerResult = this.outer.update(innerResult, replace); return this.setResult(innerResult.times(2).sub(outerResult), replace); } get isStable() { return this.outer.isStable; } }; var FasterDEMA = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; this.inner = new FasterEMA(interval); this.outer = new FasterEMA(interval); } inner; outer; update(price, replace) { const innerResult = this.inner.update(price, replace); const outerResult = this.outer.update(innerResult, replace); return this.setResult(innerResult * 2 - outerResult, replace); } get isStable() { return this.outer.isStable; } }; // src/DMA/DMA.ts var DMA = class extends TechnicalIndicator { short; long; constructor(short, long, Indicator = SMA) { super(); this.short = new Indicator(short); this.long = new Indicator(long); } get isStable() { return this.long.isStable; } update(price, replace) { this.short.update(price, replace); this.long.update(price, replace); if (this.isStable) { return this.result = { long: this.long.getResultOrThrow(), short: this.short.getResultOrThrow() }; } return null; } }; var FasterDMA = class extends TechnicalIndicator { short; long; constructor(short, long, SmoothingIndicator = FasterSMA) { super(); this.short = new SmoothingIndicator(short); this.long = new SmoothingIndicator(long); } get isStable() { return this.long.isStable; } update(price, replace) { this.short.update(price, replace); this.long.update(price, replace); if (this.isStable) { return this.result = { long: this.long.getResultOrThrow(), short: this.short.getResultOrThrow() }; } return null; } }; // src/MACD/MACD.ts var import_big17 = __toESM(require("big.js"), 1); var MACD = class extends TechnicalIndicator { prices = []; long; short; signal; constructor(config) { super(); this.long = new config.indicator(config.longInterval); this.short = new config.indicator(config.shortInterval); this.signal = new config.indicator(config.signalInterval); } update(_price, replace) { const price = new import_big17.default(_price); pushUpdate(this.prices, replace, price, this.long.interval); const short = this.short.update(price, replace); const long = this.long.update(price, replace); if (this.prices.length === this.long.interval) { const macd = short.sub(long); const signal = this.signal.update(macd, replace); return this.result = { histogram: macd.sub(signal), macd, signal }; } return null; } }; var FasterMACD = class extends TechnicalIndicator { constructor(short, long, signal) { super(); this.short = short; this.long = long; this.signal = signal; } prices = []; update(price, replace) { pushUpdate(this.prices, replace, price, this.long.interval); const short = this.short.update(price, replace); const long = this.long.update(price, replace); if (this.prices.length === this.long.interval) { const macd = short - long; const signal = this.signal.update(macd, replace); return this.result = { histogram: macd - signal, macd, signal }; } return null; } }; // src/OBV/OBV.ts var import_big18 = __toESM(require("big.js"), 1); var OBV = class extends BigIndicatorSeries { candles = []; update(candle, replace) { pushUpdate(this.candles, replace, candle, 2); if (this.candles.length === 1) { return null; } const prevCandle = this.candles[this.candles.length - 2]; const prevPrice = prevCandle.close; const prevResult = this.result ?? new import_big18.default(0); const currentPrice = new import_big18.default(candle.close); const nextResult = currentPrice.gt(prevPrice) ? candle.volume : currentPrice.lt(prevPrice) ? -candle.volume : 0; return this.setResult(prevResult.add(nextResult), replace); } }; var FasterOBV = class extends NumberIndicatorSeries { candles = []; update(candle, replace) { pushUpdate(this.candles, replace, candle, 2); if (this.candles.length === 1) { return null; } const prevCandle = this.candles[this.candles.length - 2]; const prevPrice = prevCandle.close; const prevResult = this.result ?? 0; const currentPrice = candle.close; const nextResult = currentPrice > prevPrice ? candle.volume : currentPrice < prevPrice ? -candle.volume : 0; return this.setResult(prevResult + nextResult, false); } }; // src/RMA/RMA.ts var import_big19 = __toESM(require("big.js"), 1); var RMA = class extends MovingAverage { constructor(interval) { super(interval); this.interval = interval; this.weightFactor = 1 / this.interval; } pricesCounter = 0; weightFactor; update(_price, replace) { if (!replace) { this.pricesCounter++; } else if (replace && this.pricesCounter === 0) { this.pricesCounter++; } const price = new import_big19.default(_price); if (replace && this.previousResult) { return this.setResult( price.times(this.weightFactor).add(this.previousResult.times(1 - this.weightFactor)), replace ); } return this.setResult( price.times(this.weightFactor).add((this.result ?? price).times(1 - this.weightFactor)), replace ); } getResultOrThrow() { if (this.pricesCounter < this.interval) { throw new NotEnoughDataError(); } return this.result; } get isStable() { try { this.getResultOrThrow(); return true; } catch { return false; } } }; var FasterRMA = class extends FasterMovingAverage { constructor(interval) { super(interval); this.interval = interval; this.weightFactor = 1 / this.interval; } pricesCounter = 0; weightFactor; update(price, replace) { if (!replace) { this.pricesCounter++; } else if (replace && this.pricesCounter === 0) { this.pricesCounter++; } if (replace && this.previousResult !== void 0) { return this.setResult(price * this.weightFactor + this.previousResult * (1 - this.weightFactor), replace); } return this.setResult( price * this.weightFactor + (this.result !== void 0 ? this.result : price) * (1 - this.weightFactor), replace ); } getResultOrThrow() { if (this.pricesCounter < this.interval) { throw new NotEnoughDataError(); } return this.result; } get isStable() { try { this.getResultOrThrow(); return true; } catch { return false; } } }; // src/ROC/ROC.ts var import_big20 = __toESM(require("big.js"), 1); var ROC = class extends BigIndicatorSeries { constructor(interval) { super(); this.interval = interval; } prices = []; update(price, replace) { const comparePrice = pushUpdate(this.prices, replace, price, this.interval); if (comparePrice) { return this.setResult(new import_big20.default(price).sub(comparePrice).div(comparePrice), replace); } return null; } }; var FasterROC = class extends NumberIndicatorSeries { constructor(interval) { super(); this.interval = interval; } prices = []; update(price, replace) { const comparePrice = pushUpdate(this.prices, replace, price, this.interval); if (comparePrice) { return this.setResult((price - comparePrice) / comparePrice, replace); } return null; } }; // src/RSI/RSI.ts var import_big21 = __toESM(require("big.js"), 1); var RSI = class extends BigIndicatorSeries { constructor(interval, SmoothingIndicator = WSMA) { super(); this.interval = interval; this.avgGain = new SmoothingIndicator(this.interval); this.avgLoss = new SmoothingIndicator(this.interval); } previousPrices = []; avgGain; avgLoss; maxValue = new import_big21.default(100); update(price, replace) { pushUpdate(this.previousPrices, replace, price, this.interval); if (this.previousPrices.length < 2) { return null; } const currentPrice = new import_big21.default(price); const previousPrice = new import_big21.default(this.previousPrices[this.previousPrices.length - 2]); if (currentPrice.gt(previousPrice)) { this.avgLoss.update(new import_big21.default(0), replace); this.avgGain.update(currentPrice.sub(previousPrice), replace); } else { this.avgLoss.update(previousPrice.sub(currentPrice), replace); this.avgGain.update(new import_big21.default(0), replace); } if (this.avgGain.isStable) { const avgLoss = this.avgLoss.getResultOrThrow(); if (avgLoss.eq(0)) { return this.setResult(new import_big21.default(100), replace); } const relativeStrength = this.avgGain.getResultOrThrow().div(avgLoss); return this.setResult(this.maxValue.minus(this.maxValue.div(relativeStrength.add(1))), replace); } return null; } }; var FasterRSI = class extends NumberIndicatorSeries { constructor(interval, SmoothingIndicator = FasterWSMA) { super(); this.interval = interval; this.avgGain = new SmoothingIndicator(this.interval); this.avgLoss = new SmoothingIndicator(this.interval); } previousPrices = []; avgGain; avgLoss; maxValue = 100; update(price, replace) { pushUpdate(this.previousPrices, replace, price, this.interval); if (this.previousPrices.length < 2) { return null; } const currentPrice = price; const previousPrice = this.previousPrices[this.previousPrices.length - 2]; if (currentPrice > previousPrice) { this.avgLoss.update(0, replace); this.avgGain.update(price - previousPrice, replace); } else { this.avgLoss.update(previousPrice - currentPrice, replace); this.avgGain.update(0, replace); } if (this.avgGain.isStable) { const avgLoss = this.avgLoss.getResultOrThrow