UNPKG

trading-signals

Version:

Technical indicators to run technical analysis with JavaScript / TypeScript.

1,675 lines (1,635 loc) 54.8 kB
// src/index.ts import { default as default2 } from "big.js"; // 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 import Big2 from "big.js"; // src/util/getAverage.ts import Big from "big.js"; function getAverage(values) { const sum = values.reduce((prev, current) => { return prev.add(current); }, new Big(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 Big2(high).plus(low); const coefficient = highPlusLow.eq(0) ? new Big2(0) : new Big2(high).minus(low).div(highPlusLow).mul(this.width); this.lowerBand.update(new Big2(low).mul(new Big2(1).minus(coefficient)), replace); this.middleBand.update(close, replace); this.upperBand.update(new Big2(high).mul(new Big2(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 import Big3 from "big.js"; 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 Big3(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 import Big4 from "big.js"; 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 Big4(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 import Big5 from "big.js"; var WSMA = class extends MovingAverage { constructor(interval) { super(interval); this.interval = interval; this.indicator = new SMA(interval); this.smoothingFactor = new Big5(1).div(this.interval); } indicator; smoothingFactor; update(price, replace) { const sma = this.indicator.update(price, replace); if (replace && this.previousResult) { const smoothed = new Big5(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 Big5(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 import Big7 from "big.js"; // src/util/getMaximum.ts import Big6 from "big.js"; function getMaximum(values) { let max = new Big6(Number.MIN_SAFE_INTEGER); for (const value of values) { if (max.lt(value)) { max = new Big6(value); } } return max; } // src/TR/TR.ts var TR = class extends BigIndicatorSeries { previousCandle; secondLastCandle; update(candle, replace) { const high = new Big7(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 Big7(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 import Big8 from "big.js"; 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 Big8(candle.high); const previousHigh = new Big8(this.previousCandle.high); const currentLow = new Big8(candle.low); const previousLow = new Big8(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 Big8(0) : higherHigh; const noLowerLows = lowerLow.lt(0); const highsRiseFaster = lowerLow.lt(higherHigh); const mdm = noLowerLows || highsRiseFaster ? new Big8(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 Big8(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 import Big12 from "big.js"; // src/util/getMinimum.ts import Big9 from "big.js"; function getMinimum(values) { let min = new Big9(Number.MAX_SAFE_INTEGER); for (const value of values) { if (min.gt(value)) { min = new Big9(value); } } return min; } // src/util/getStandardDeviation.ts import Big10 from "big.js"; function getStandardDeviation(values, average) { const middle = average || getAverage(values); const squaredDifferences = values.map((value) => new Big10(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 import Big11 from "big.js"; 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 Big11(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 Big12(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 import Big14 from "big.js"; // src/MAD/MAD.ts import Big13 from "big.js"; 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 Big13(0); for (let i = 0; i < prices.length; i++) { const deviation = new Big13(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 Big14(0.015).mul(meanDeviation); const result = numerator.div(denominator); return this.setResult(result, replace); } return null; } cacheTypicalPrice({ high, low, close }, replace) { const typicalPrice = new Big14(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 import Big15 from "big.js"; 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 Big15(price), this.interval); let nominator = new Big15(0); let denominator = new Big15(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 Big15(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 import Big16 from "big.js"; 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 Big16(_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 import Big17 from "big.js"; 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 Big17(_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 import Big18 from "big.js"; 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 Big18(0); const currentPrice = new Big18(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 import Big19 from "big.js"; 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 Big19(_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 import Big20 from "big.js"; 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 Big20(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 import Big21 from "big.js"; 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 Big21(100); update(price, replace) { pushUpdate(this.previousPrices, replace, price, this.interval); if (this.previousPrices.length < 2) { return null; } const currentPrice = new Big21(price); const previousPrice = new Big21(this.previousPrices[this.previousPrices.length - 2]); if (currentPrice.gt(previousPrice)) { this.avgLoss.update(new Big21(0), replace); this.avgGain.update(currentPrice.sub(previousPrice), replace); } else { this.avgLoss.update(previousPrice.sub(currentPrice), replace); this.avgGain.update(new Big21(0), replace); } if (this.avgGain.isStable) { const avgLoss = this.avgLoss.getResultOrThrow(); if (avgLoss.eq(0)) { return this.setResult(new Big21(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(); if (avgLoss === 0) { return this.setResult(100, replace); } const relativeStrength = this.avgGain.getResultOrThrow() / avgLoss; return this.setResult(this.maxValue - this.maxValue / (relativeStrength + 1), replace); } return null; } }; // src/STOCH/StochasticOscillator.ts import Big22 from "big.js"; var StochasticOscillator = class extends TechnicalIndicator { /** * Constructs a Stochastic Oscillator. * * @param n The %k period * @param m The %k slowing period * @param p The %d period */ constructor(n, m, p) { super(); this.n = n; this.m = m; this.p = p; this.periodM = new SMA(m); this.periodP = new SMA(p); } periodM; periodP; candles = []; update(candle, replace) { pushUpdate(this.candles, replace, candle, this.n); if (this.candles.length === this.n) { const highest = getMaximum(this.candles.map((candle2) => candle2.high)); const lowest = getMinimum(this.candles.map((candle2) => candle2.low)); const divisor = new Big22(highest).minus(lowest); let fastK = new Big22(100).mul(new Big22(candle.close).minus(lowest)); fastK = fastK.div(divisor.eq(0) ? 1 : divisor); const stochK = this.periodM.update(fastK, replace); const stochD = stochK && this.periodP.update(stochK, replace); if (stochK && stochD) { return this.result = { stochD, stochK }; } } return null; } }; var FasterStochasticOscillator = class extends TechnicalIndicator { /** * @param n The %k period * @param m The %k slowing period * @param p The %d period */ constructor(n, m, p) { super(); this.n = n; this.m = m; this.p = p; this.periodM = new FasterSMA(m); this.periodP = new FasterSMA(p); } candles = []; periodM; periodP; update(candle, replace) { pushUpdate(this.candles, replace, candle, this.n); if (this.candles.length === this.n) { const highest = Math.max(...this.candles.map((candle2) => candle2.high)); const lowest = Math.min(...this.candles.map((candle2) => candle2.low)); const divisor = highest - lowest; let fastK = (candle.close - lowest) * 100; fastK = fastK / (divisor === 0 ? 1 : divisor); const stochK = this.periodM.update(fastK, replace); const stochD = stochK && this.periodP.update(stochK, replace); if (stochK !== null && stochD !== null) { return this.result = { stochD, stochK }; } } return null; } }; // src/STOCH/StochasticRSI.ts import Big23 from "big.js"; var StochasticRSI = class extends BigIndicatorSeries { constructor(interval, SmoothingIndicator = WSMA) { super(); this.interval = interval; this.period = new Period(interval); this.rsi = new RSI(interval, SmoothingIndicator); } period; rsi; update(price, replace) { const rsiResult = this.rsi.update(price, replace); if (rsiResult) { const periodResult = this.period.update(rsiResult, replace); if (periodResult) { const min = periodResult.lowest; const max = periodResult.highest; const denominator = max.minus(min); if (denominator.eq(0)) { return this.setResult(new Big23(100), false); } const numerator = rsiResult.minus(min); return this.setResult(numerator.div(denominator), false); } } return null; } }; var FasterStochasticRSI = class extends NumberIndicatorSeries { constructor(interval, SmoothingIndicator = FasterWSMA) { super(); this.interval = interval; this.period = new FasterPeriod(interval); this.rsi = new FasterRSI(interval, SmoothingIndicator); } period; rsi; update(price, replace) { const rsiResult = this.rsi.update(price, replace); if (rsiResult) { const periodResult = this.period.update(rsiResult, replace); if (periodResult) { const min = periodResult.lowest; const max = periodResult.highest; const denominator = max - min; if (denominator === 0) { return this.setResult(100, replace); } const numerator = rsiResult - min; return this.setResult(numerator / denominator, replace); } } return null; } }; // src/LINREG/LinearRegression.ts import Big24 from "big.js"; var LinearRegression = class extends TechnicalIndicator { prices = []; period; constructor(config) { super(); this.period = config.period; } calculateRegression(prices) { const n = new Big24(prices.length); const xValues = Array.from({ length: prices.length }, (_, i) => new Big24(i)); const yValues = prices.map((p) => new Big24(p)); const sumX = xValues.reduce((sum, x) => sum.add(x), new Big24(0)); const sumY = yValues.reduce((sum, y) => sum.add(y), new Big24(0)); const sumXY = xValues.reduce((sum, x, i) => sum.add(x.mul(yValues[i])), new Big24(0)); const sumXX = xValues.reduce((sum, x) => sum.add(x.mul(x)), new Big24(