From 4a1e6fc8ed60e91ecce0db5252255173d29168ea Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Fri, 6 Dec 2024 18:24:36 +0100 Subject: [PATCH 01/12] fix(MACD): Fix replacing values --- src/EMA/EMA.test.ts | 17 +++++++++++++++++ src/MACD/MACD.test.ts | 27 +++++++++++++++++++++++++++ src/MACD/MACD.ts | 11 ++++++++++- src/WSMA/WSMA.test.ts | 18 ++++++++++++++++++ src/WSMA/WSMA.ts | 2 +- vitest.config.ts | 1 + 6 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/EMA/EMA.test.ts b/src/EMA/EMA.test.ts index 6e131ba12..6ac3c3d8c 100644 --- a/src/EMA/EMA.test.ts +++ b/src/EMA/EMA.test.ts @@ -22,6 +22,23 @@ describe('EMA', () => { ] as const; describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const interval = 5; + const ema = new EMA(interval); + const emaWithReplace = new EMA(interval); + + ema.updates([prices[0], prices[1], prices[2], prices[3], prices[4]]); + + emaWithReplace.updates([prices[0], prices[1], prices[2], '8239239']); + emaWithReplace.replace(prices[3]); + emaWithReplace.update(prices[4]); + + const actual = emaWithReplace.getResult().toFixed(); + const expected = ema.getResult().toFixed(); + + expect(actual).toBe(expected); + }); + it('replaces recently added values', () => { const interval = 5; const ema = new EMA(interval); diff --git a/src/MACD/MACD.test.ts b/src/MACD/MACD.test.ts index 1abe8f6c5..cba2f6c5f 100644 --- a/src/MACD/MACD.test.ts +++ b/src/MACD/MACD.test.ts @@ -2,6 +2,33 @@ import {FasterMACD, MACD} from './MACD.js'; import {Big, DEMA, EMA, FasterEMA, NotEnoughDataError} from '../index.js'; describe('MACD', () => { + describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const macd = new MACD({ + indicator: EMA, + longInterval: 5, + shortInterval: 2, + signalInterval: 9, + }); + const macdWithReplace = new MACD({ + indicator: EMA, + longInterval: 5, + shortInterval: 2, + signalInterval: 9, + }); + + macd.updates(['81.59', '81.06', '82.87', '83.0', '90', '83.61']); + + macdWithReplace.updates(['81.59', '81.06', '82.87', '83.0', '100']); + macdWithReplace.replace(90); + macdWithReplace.update('83.61'); + + expect(macdWithReplace.getResult().histogram.toFixed()).toBe(macd.getResult().histogram.toFixed()); + expect(macdWithReplace.getResult().macd.toFixed()).toBe(macd.getResult().macd.toFixed()); + expect(macdWithReplace.getResult().signal.toFixed()).toBe(macd.getResult().signal.toFixed()); + }); + }); + describe('update', () => { it('can replace recently added values', () => { const macd = new MACD({ diff --git a/src/MACD/MACD.ts b/src/MACD/MACD.ts index 258dcf9ff..2b2cffb4c 100644 --- a/src/MACD/MACD.ts +++ b/src/MACD/MACD.ts @@ -48,6 +48,15 @@ export class MACD implements Indicator { return this.result !== undefined; } + replace(price: BigSource) { + return this.update(price, true); + } + + updates(prices: BigSource[]) { + prices.forEach(price => this.update(price)); + return this.result; + } + update(_price: BigSource, replace: boolean = false): void | MACDResult { const price = new Big(_price); if (this.prices.length && replace) { @@ -74,7 +83,7 @@ export class MACD implements Indicator { * A short (usually 9 periods) EMA of MACD is plotted along side to act as a signal line to identify turns in the * indicator. It gets updated once the long EMA has enough input data. */ - const signal = this.signal.update(macd); + const signal = this.signal.update(macd, replace); /** * The MACD histogram is calculated as the MACD indicator minus the signal line (usually 9 periods) EMA. diff --git a/src/WSMA/WSMA.test.ts b/src/WSMA/WSMA.test.ts index 8640a355b..4f8982b32 100644 --- a/src/WSMA/WSMA.test.ts +++ b/src/WSMA/WSMA.test.ts @@ -3,6 +3,24 @@ import {NotEnoughDataError} from '../error/index.js'; describe('WSMA', () => { describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const interval = 3; + + const wsma = new WSMA(interval); + const wsmaWithReplace = new WSMA(interval); + + wsma.updates([11, 12, 13, 14, 15]); + + wsmaWithReplace.updates([11, 12, 13, 50]); + wsmaWithReplace.replace(14); + wsmaWithReplace.update(15); + + const actual = wsmaWithReplace.getResult().toFixed(); + const expected = wsma.getResult().toFixed(); + + expect(actual).toBe(expected); + }); + it('replaces recently added values', () => { const interval = 3; diff --git a/src/WSMA/WSMA.ts b/src/WSMA/WSMA.ts index f01e45c4e..9a5d20724 100644 --- a/src/WSMA/WSMA.ts +++ b/src/WSMA/WSMA.ts @@ -28,7 +28,7 @@ export class WSMA extends MovingAverage { this.smoothingFactor = new Big(1).div(this.interval); } - updates(prices: BigSource[]): Big | void { + updates(prices: BigSource[]) { prices.forEach(price => this.update(price)); return this.result; } diff --git a/vitest.config.ts b/vitest.config.ts index e7bac83fb..0b1b860bf 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,7 @@ import {defineConfig} from 'vitest/config'; export default defineConfig({ test: { + bail: 1, coverage: { include: ['**/*.{ts,tsx}', '!**/*.d.ts', '!**/cli.ts', '!**/index.ts', '!**/start*.ts'], provider: 'v8', From 3821f08bc52fa059d92d90607fa84cc855a0dd8c Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Thu, 12 Dec 2024 13:50:56 +0100 Subject: [PATCH 02/12] add updates method --- src/ABANDS/AccelerationBands.ts | 10 +++++++++- src/BBANDS/BollingerBands.ts | 9 +++++++++ src/DMA/DMA.ts | 8 ++++++++ src/Indicator.ts | 13 +++++++++++++ src/MACD/MACD.ts | 5 +++++ src/STOCH/StochasticOscillator.ts | 10 ++++++++++ src/util/Period.ts | 18 +++++++++++++++--- src/util/index.ts | 1 + 8 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/ABANDS/AccelerationBands.ts b/src/ABANDS/AccelerationBands.ts index 21445b65e..3dcbbc518 100644 --- a/src/ABANDS/AccelerationBands.ts +++ b/src/ABANDS/AccelerationBands.ts @@ -42,11 +42,15 @@ export class AccelerationBands implements Indicator { this.upperBand = new SmoothingIndicator(interval); } + updates(prices: HighLowClose[]) { + prices.forEach(price => this.update(price)); + } + get isStable(): boolean { return this.middleBand.isStable; } - update({high, low, close}: HighLowClose): void { + update({high, low, close}: HighLowClose) { const highPlusLow = new Big(high).plus(low); const coefficient = highPlusLow.eq(0) ? new Big(0) : new Big(high).minus(low).div(highPlusLow).mul(this.width); @@ -86,6 +90,10 @@ export class FasterAccelerationBands implements Indicator this.update(price)); + } + update({high, low, close}: HighLowCloseNumber): void { const highPlusLow = high + low; const coefficient = highPlusLow === 0 ? 0 : ((high - low) / highPlusLow) * this.width; diff --git a/src/BBANDS/BollingerBands.ts b/src/BBANDS/BollingerBands.ts index ecc16af34..14877657b 100644 --- a/src/BBANDS/BollingerBands.ts +++ b/src/BBANDS/BollingerBands.ts @@ -38,6 +38,10 @@ export class BollingerBands implements Indicator { return this.result !== undefined; } + updates(prices: BigSource[]) { + prices.forEach(price => this.update(price)); + } + update(price: BigSource): void | BandsResult { this.prices.push(new Big(price)); @@ -73,6 +77,11 @@ export class FasterBollingerBands implements Indicator { public readonly deviationMultiplier: number = 2 ) {} + updates(prices: number[]) { + prices.forEach(price => this.update(price)); + return this.result; + } + update(price: number): void | FasterBandsResult { this.prices.push(price); diff --git a/src/DMA/DMA.ts b/src/DMA/DMA.ts index 29ddae1a6..d6eb023df 100644 --- a/src/DMA/DMA.ts +++ b/src/DMA/DMA.ts @@ -36,6 +36,10 @@ export class DMA implements Indicator { return this.long.isStable; } + updates(prices: BigSource[]) { + prices.forEach(price => this.update(price)); + } + update(price: BigSource, replace: boolean = false): void { this.short.update(price, replace); this.long.update(price, replace); @@ -62,6 +66,10 @@ export class FasterDMA implements Indicator { return this.long.isStable; } + updates(prices: number[]) { + prices.forEach(price => this.update(price)); + } + update(price: number, replace: boolean = false): void { this.short.update(price, replace); this.long.update(price, replace); diff --git a/src/Indicator.ts b/src/Indicator.ts index 759d76fbb..a610778e7 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -7,6 +7,9 @@ export interface Indicator { isStable: boolean; update(input: Input): void | Result; + + // TODO: Implement this function once for all indicators, make sure all indicators return "this.result" here + updates(input: Input[]): void | Result; } /** @@ -74,6 +77,11 @@ export abstract class BigIndicatorSeries implements Indicator return (this.result = value); } + updates(prices: Input[]): void | Big { + prices.forEach(price => this.update(price)); + return this.result; + } + abstract update(input: Input, replace?: boolean): void | Big; replace(input: Input) { @@ -137,6 +145,11 @@ export abstract class NumberIndicatorSeries implements Indicator return (this.result = value); } + updates(prices: Input[]): number | void { + prices.forEach(price => this.update(price)); + return this.result; + } + abstract update(input: Input, replace?: boolean): void | number; replace(input: Input) { diff --git a/src/MACD/MACD.ts b/src/MACD/MACD.ts index 2b2cffb4c..6daca1845 100644 --- a/src/MACD/MACD.ts +++ b/src/MACD/MACD.ts @@ -127,6 +127,11 @@ export class FasterMACD implements Indicator { return this.result !== undefined; } + updates(prices: number[]) { + prices.forEach(price => this.update(price)); + return this.result; + } + update(price: number, replace: boolean = false): void | FasterMACDResult { if (this.prices.length && replace) { this.prices[this.prices.length - 1] = price; diff --git a/src/STOCH/StochasticOscillator.ts b/src/STOCH/StochasticOscillator.ts index 534aa1cda..c39ad0f91 100644 --- a/src/STOCH/StochasticOscillator.ts +++ b/src/STOCH/StochasticOscillator.ts @@ -60,6 +60,11 @@ export class StochasticOscillator implements Indicator this.update(candle)); + return this.result; + } + getResult(): StochasticResult { if (this.result === undefined) { throw new NotEnoughDataError(); @@ -130,6 +135,11 @@ export class FasterStochasticOscillator implements Indicator this.update(candle)); + return this.result; + } + update(candle: HighLowCloseNumber): void | FasterStochasticResult { this.candles.push(candle); diff --git a/src/util/Period.ts b/src/util/Period.ts index 88f88691e..7f159e25d 100644 --- a/src/util/Period.ts +++ b/src/util/Period.ts @@ -1,4 +1,4 @@ -import {Big, type BigSource} from '../index.js'; +import {Big, NotEnoughDataError, type BigSource} from '../index.js'; import type {Indicator} from '../Indicator.js'; import {getFixedArray} from './getFixedArray.js'; import {getMinimum} from './getMinimum.js'; @@ -26,12 +26,20 @@ export class Period implements Indicator { } getResult(): PeriodResult { + if (!this.lowest || !this.highest) { + throw new NotEnoughDataError(); + } + return { - highest: this.highest!, - lowest: this.lowest!, + highest: this.highest, + lowest: this.lowest, }; } + updates(values: BigSource[]) { + values.forEach(value => this.update(value)); + } + update(value: BigSource): PeriodResult | void { this.values.push(new Big(value)); if (this.isStable) { @@ -57,6 +65,10 @@ export class FasterPeriod implements Indicator { this.values = getFixedArray(interval); } + updates(values: number[]) { + values.forEach(value => this.update(value)); + } + getResult(): FasterPeriodResult { return { highest: this.highest!, diff --git a/src/util/index.ts b/src/util/index.ts index 96eacad70..d4a4d09e4 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -4,5 +4,6 @@ export * from './getFixedArray.js'; export * from './getMaximum.js'; export * from './getMinimum.js'; export * from './getStandardDeviation.js'; +export * from './getStreaks.js'; export * from './HighLowClose.js'; export * from './Period.js'; From a6884aad2735863edc554c03d4affec1451d5291 Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Thu, 12 Dec 2024 14:28:20 +0100 Subject: [PATCH 03/12] fix tests --- src/ABANDS/AccelerationBands.test.ts | 24 ++++++++++++--------- src/BBANDS/BollingerBands.test.ts | 7 ++---- src/DMA/DMA.test.ts | 14 ++---------- src/DX/DX.test.ts | 6 ++---- src/EMA/EMA.test.ts | 6 ++++-- src/Indicator.test.ts | 3 +-- src/MACD/MACD.test.ts | 28 ++++++++++++------------ src/STOCH/StochasticOscillator.test.ts | 30 +++++++++++++++----------- src/WSMA/WSMA.test.ts | 6 ++++-- src/util/Period.test.ts | 18 ++++++++++++---- 10 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/ABANDS/AccelerationBands.test.ts b/src/ABANDS/AccelerationBands.test.ts index fbac91572..82dbf4558 100644 --- a/src/ABANDS/AccelerationBands.test.ts +++ b/src/ABANDS/AccelerationBands.test.ts @@ -108,11 +108,13 @@ describe('AccelerationBands', () => { describe('update', () => { it("doesn't crash when supplying zeroes", () => { const accBands = new AccelerationBands(20, 2); - return accBands.update({ - close: 0, - high: 0, - low: 0, - }); + return accBands.updates([ + { + close: 0, + high: 0, + low: 0, + }, + ]); }); }); }); @@ -120,10 +122,12 @@ describe('AccelerationBands', () => { describe('FaserAccelerationBands', () => { it("doesn't crash when supplying zeroes", () => { const accBands = new FasterAccelerationBands(20, 2); - return accBands.update({ - close: 0, - high: 0, - low: 0, - }); + return accBands.updates([ + { + close: 0, + high: 0, + low: 0, + }, + ]); }); }); diff --git a/src/BBANDS/BollingerBands.test.ts b/src/BBANDS/BollingerBands.test.ts index 3136fb14d..c76ff0251 100644 --- a/src/BBANDS/BollingerBands.test.ts +++ b/src/BBANDS/BollingerBands.test.ts @@ -7,8 +7,7 @@ describe('BollingerBands', () => { describe('prices', () => { it('does not cache more prices than necessary to fill the interval', () => { const bb = new BollingerBands(3); - bb.update(1); - bb.update(2); + bb.updates([1, 2]); expect(bb.prices.length).toBe(2); bb.update(3); expect(bb.prices.length).toBe(3); @@ -150,9 +149,7 @@ describe('FasterBollingerBands', () => { 81.59, 81.06, 82.87, 83.0, 83.61, 83.15, 82.84, 83.99, 84.55, 84.36, 85.53, 86.54, 86.89, 87.77, 87.29, ]; const fasterBB = new FasterBollingerBands(5, 2); - for (const price of prices) { - fasterBB.update(price); - } + fasterBB.updates(prices); expect(fasterBB.isStable).toBe(true); const actual = fasterBB.getResult(); expect(actual.lower.toFixed(2)).toBe('85.29'); diff --git a/src/DMA/DMA.test.ts b/src/DMA/DMA.test.ts index 490875312..c1e458f9e 100644 --- a/src/DMA/DMA.test.ts +++ b/src/DMA/DMA.test.ts @@ -7,24 +7,14 @@ describe('DMA', () => { it('can replace recently added values', () => { const dma = new DMA(3, 6, SMA); const fasterDMA = new FasterDMA(3, 6, FasterSMA); - dma.update(41); - dma.update(37); - dma.update(20.9); - dma.update(100); - dma.update(30.71); - dma.update(40); + dma.updates([41, 37, 20.9, 100, 30.71, 40]); dma.update(30, true); expect(dma.isStable).toBe(true); expect(dma.getResult().short.toFixed(8)).toBe('53.57000000'); expect(dma.getResult().long.toFixed(8)).toBe('43.26833333'); - fasterDMA.update(41); - fasterDMA.update(37); - fasterDMA.update(20.9); - fasterDMA.update(100); - fasterDMA.update(30.71); - fasterDMA.update(40); + fasterDMA.updates([41, 37, 20.9, 100, 30.71, 40]); fasterDMA.update(30, true); expect(fasterDMA.isStable).toBe(true); diff --git a/src/DX/DX.test.ts b/src/DX/DX.test.ts index 54628d9e5..9d8b5ba9f 100644 --- a/src/DX/DX.test.ts +++ b/src/DX/DX.test.ts @@ -75,10 +75,8 @@ describe('DX', () => { const dx = new DX(5); const fasterDX = new FasterDX(5); - for (const candle of candles) { - dx.update(candle); - fasterDX.update(candle); - } + dx.updates(candles); + fasterDX.updates(candles); expect(dx.isStable).toBe(true); expect(fasterDX.isStable).toBe(true); diff --git a/src/EMA/EMA.test.ts b/src/EMA/EMA.test.ts index 6ac3c3d8c..20e78f5cf 100644 --- a/src/EMA/EMA.test.ts +++ b/src/EMA/EMA.test.ts @@ -27,9 +27,11 @@ describe('EMA', () => { const ema = new EMA(interval); const emaWithReplace = new EMA(interval); - ema.updates([prices[0], prices[1], prices[2], prices[3], prices[4]]); + const subset = [prices[0], prices[1], prices[2]]; - emaWithReplace.updates([prices[0], prices[1], prices[2], '8239239']); + ema.updates([...subset, prices[3], prices[4]]); + + emaWithReplace.updates([...subset, '8239239']); emaWithReplace.replace(prices[3]); emaWithReplace.update(prices[4]); diff --git a/src/Indicator.test.ts b/src/Indicator.test.ts index e29b7325c..7194ea9e0 100644 --- a/src/Indicator.test.ts +++ b/src/Indicator.test.ts @@ -38,8 +38,7 @@ describe('Indicator', () => { it('returns the cross sum', () => { const itc = new IndicatorTestClass(); - itc.update(20); - itc.update(40); + itc.updates([20, 40]); expect(itc.getResult().toString()).toBe('30'); }); }); diff --git a/src/MACD/MACD.test.ts b/src/MACD/MACD.test.ts index cba2f6c5f..8f52d1f27 100644 --- a/src/MACD/MACD.test.ts +++ b/src/MACD/MACD.test.ts @@ -17,15 +17,19 @@ describe('MACD', () => { signalInterval: 9, }); - macd.updates(['81.59', '81.06', '82.87', '83.0', '90', '83.61']); + const subset = ['10', '20', '80', '81.59', '81.06', '82.87', '83.0']; - macdWithReplace.updates(['81.59', '81.06', '82.87', '83.0', '100']); + macd.updates([...subset, '90', '83.61']); + + macdWithReplace.updates([...subset, '100']); macdWithReplace.replace(90); macdWithReplace.update('83.61'); - expect(macdWithReplace.getResult().histogram.toFixed()).toBe(macd.getResult().histogram.toFixed()); - expect(macdWithReplace.getResult().macd.toFixed()).toBe(macd.getResult().macd.toFixed()); - expect(macdWithReplace.getResult().signal.toFixed()).toBe(macd.getResult().signal.toFixed()); + expect(macdWithReplace.short.getResult().toFixed(), 'short').toBe(macd.short.getResult().toFixed()); + expect(macdWithReplace.long.getResult().toFixed(), 'long').toBe(macd.long.getResult().toFixed()); + expect(macdWithReplace.getResult().histogram.toFixed(), 'histogram').toBe(macd.getResult().histogram.toFixed()); + expect(macdWithReplace.getResult().macd.toFixed(), 'macd').toBe(macd.getResult().macd.toFixed()); + expect(macdWithReplace.getResult().signal.toFixed(), 'signal').toBe(macd.getResult().signal.toFixed()); }); }); @@ -39,14 +43,10 @@ describe('MACD', () => { }); const fasterMACD = new FasterMACD(new FasterEMA(2), new FasterEMA(5), new FasterEMA(9)); - macd.update('81.59'); - fasterMACD.update(81.59); - macd.update('81.06'); - fasterMACD.update(81.06); - macd.update('82.87'); - fasterMACD.update(82.87); - macd.update('83.0'); - fasterMACD.update(83.0); + const subset = [81.59, 81.06, 82.87, 83.0]; + macd.updates(subset); + fasterMACD.updates(subset); + macd.update('90'); // this value gets replaced with the next call fasterMACD.update(90); // this value gets replaced with the next call macd.update('83.61', true); @@ -220,7 +220,7 @@ describe('MACD', () => { expect(mockedPrices.length).toBe(longInterval); expect(macd.isStable).toBe(false); - mockedPrices.forEach(price => macd.update(price)); + macd.updates(mockedPrices); expect(macd.isStable).toBe(true); }); diff --git a/src/STOCH/StochasticOscillator.test.ts b/src/STOCH/StochasticOscillator.test.ts index f0151be03..94cde3a2a 100644 --- a/src/STOCH/StochasticOscillator.test.ts +++ b/src/STOCH/StochasticOscillator.test.ts @@ -84,21 +84,25 @@ describe('StochasticOscillator', () => { it('prevents division by zero errors when highest high and lowest low have the same value', () => { const stoch = new StochasticOscillator(5, 3, 3); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - stoch.update({close: 100, high: 100, low: 100}); - const result = stoch.update({close: 100, high: 100, low: 100})!; - expect(result.stochK.toFixed(2)).toBe('0.00'); - expect(result.stochD.toFixed(2)).toBe('0.00'); + stoch.updates([ + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + ]); + const result = stoch.update({close: 100, high: 100, low: 100}); + expect(result?.stochK.toFixed(2)).toBe('0.00'); + expect(result?.stochD.toFixed(2)).toBe('0.00'); const fasterStoch = new FasterStochasticOscillator(1, 2, 2); - fasterStoch.update({close: 100, high: 100, low: 100}); - fasterStoch.update({close: 100, high: 100, low: 100}); + fasterStoch.updates([ + {close: 100, high: 100, low: 100}, + {close: 100, high: 100, low: 100}, + ]); const {stochK, stochD} = fasterStoch.getResult(); expect(stochK.toFixed(2)).toBe('0.00'); expect(stochD.toFixed(2)).toBe('0.00'); diff --git a/src/WSMA/WSMA.test.ts b/src/WSMA/WSMA.test.ts index 4f8982b32..1cd6fc537 100644 --- a/src/WSMA/WSMA.test.ts +++ b/src/WSMA/WSMA.test.ts @@ -9,9 +9,11 @@ describe('WSMA', () => { const wsma = new WSMA(interval); const wsmaWithReplace = new WSMA(interval); - wsma.updates([11, 12, 13, 14, 15]); + const subset = [11, 12, 13]; - wsmaWithReplace.updates([11, 12, 13, 50]); + wsma.updates([...subset, 14, 15]); + + wsmaWithReplace.updates([...subset, 50]); wsmaWithReplace.replace(14); wsmaWithReplace.update(15); diff --git a/src/util/Period.test.ts b/src/util/Period.test.ts index 9ce3d921d..b2727ff55 100644 --- a/src/util/Period.test.ts +++ b/src/util/Period.test.ts @@ -1,22 +1,32 @@ +import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; import {FasterPeriod, Period} from './Period.js'; describe('Period', () => { describe('getResult', () => { it('returns the highest and lowest value of the current period', () => { + const values = [72, 1337]; const period = new Period(2); - period.update(72); - period.update(1337); + period.updates(values); const {highest, lowest} = period.getResult(); expect(lowest.valueOf()).toBe('72'); expect(highest.valueOf()).toBe('1337'); const fasterPeriod = new FasterPeriod(2); - fasterPeriod.update(72); - fasterPeriod.update(1337); + fasterPeriod.updates(values); const {highest: fastestHighest, lowest: fastestLowest} = fasterPeriod.getResult(); expect(fastestLowest).toBe(72); expect(fastestHighest).toBe(1337); }); + + it('throws an error when there is not enough input data', () => { + const period = new Period(2); + try { + period.getResult(); + throw new Error('Expected error'); + } catch (error) { + expect(error).toBeInstanceOf(NotEnoughDataError); + } + }); }); describe('isStable', () => { From 7d242761eaf920c7aac5b76220df29be3465eeea Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Thu, 12 Dec 2024 15:40:45 +0100 Subject: [PATCH 04/12] fix PR --- src/BBANDS/BollingerBands.ts | 1 + src/Indicator.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BBANDS/BollingerBands.ts b/src/BBANDS/BollingerBands.ts index 14877657b..d8aecd8c6 100644 --- a/src/BBANDS/BollingerBands.ts +++ b/src/BBANDS/BollingerBands.ts @@ -40,6 +40,7 @@ export class BollingerBands implements Indicator { updates(prices: BigSource[]) { prices.forEach(price => this.update(price)); + return this.result; } update(price: BigSource): void | BandsResult { diff --git a/src/Indicator.ts b/src/Indicator.ts index a610778e7..61f1c4507 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -8,7 +8,8 @@ export interface Indicator { update(input: Input): void | Result; - // TODO: Implement this function once for all indicators, make sure all indicators return "this.result" here + // TODO: Implement this function for all indicators, ensuring each returns "this.result" + // TODO: Add support for the "replace" parameter in the future updates(input: Input[]): void | Result; } From 071e0c06030cbeaf86f21750e9e9b49f697b99c0 Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Thu, 12 Dec 2024 16:28:02 +0100 Subject: [PATCH 05/12] fix AC --- src/AC/AC.test.ts | 525 ++++++++++++++++++++++++---------------------- src/AC/AC.ts | 12 +- 2 files changed, 282 insertions(+), 255 deletions(-) diff --git a/src/AC/AC.test.ts b/src/AC/AC.test.ts index 5e5b96a0a..3ddd153cc 100644 --- a/src/AC/AC.test.ts +++ b/src/AC/AC.test.ts @@ -3,260 +3,287 @@ import {NotEnoughDataError} from '../error/index.js'; import type {HighLowNumber} from '../util/index.js'; describe('AC', () => { + // Test data verified with: + // https://github.com/jesse-ai/jesse/blob/8e502d070c24bed29db80e1d0938781d8cdb1046/tests/data/test_candles_indicators.py#L4351 + const candles = [ + [1563408000000, 210.8, 225.73, 229.65, 205.71, 609081.49094], + [1563494400000, 225.75, 220.73, 226.23, 212.52, 371622.21865], + [1563580800000, 220.84, 228.2, 235.09, 219.78, 325393.97225], + [1563667200000, 228.25, 225.38, 229.66, 216.99, 270046.1519], + [1563753600000, 225.49, 217.51, 228.34, 212.25, 271310.40446], + [1563840000000, 217.59, 212.48, 219.55, 208.36, 317876.48242], + [1563926400000, 212.55, 216.31, 218.28, 202.0, 331162.6484], + [1564012800000, 216.31, 219.14, 225.12, 215.23, 280370.29627], + [1564099200000, 219.14, 218.81, 220.0, 212.71, 197781.98653], + [1564185600000, 218.81, 207.3, 223.3, 203.0, 301209.41113], + [1564272000000, 207.3, 211.62, 213.52, 198.24, 218801.16693], + [1564358400000, 211.58, 210.89, 215.83, 206.59, 226941.28], + [1564444800000, 210.84, 209.58, 214.36, 204.4, 222683.79393], + [1564531200000, 209.57, 218.42, 218.79, 209.2, 207213.55658], + [1564617600000, 218.42, 216.84, 219.39, 210.54, 186806.18844], + [1564704000000, 216.8, 217.61, 222.18, 214.31, 206867.03039], + [1564790400000, 217.69, 222.14, 224.51, 216.62, 181591.95296], + [1564876800000, 222.14, 221.79, 223.34, 216.9, 135622.0258], + [1564963200000, 221.79, 233.54, 236.25, 221.79, 307956.27211], + [1565049600000, 233.53, 226.28, 239.15, 223.03, 341279.08159], + [1565136000000, 226.31, 226.1, 231.25, 220.95, 279104.7037], + [1565222400000, 226.11, 221.39, 228.5, 215.51, 236886.35423], + [1565308800000, 221.38, 210.53, 221.79, 207.3, 232062.12757], + [1565395200000, 210.52, 206.48, 215.0, 202.6, 252614.02389], + [1565481600000, 206.48, 216.42, 216.94, 206.14, 188474.09048], + [1565568000000, 216.41, 211.41, 216.81, 209.75, 122760.94619], + [1565654400000, 211.58, 209.3, 214.3, 204.0, 166922.48201], + [1565740800000, 209.31, 187.1, 209.9, 183.49, 325228.98931], + [1565827200000, 187.08, 188.03, 189.95, 181.23, 237953.09426], + [1565913600000, 187.98, 184.88, 188.39, 178.04, 282177.01584], + [1566000000000, 184.83, 185.59, 187.0, 181.83, 138799.61508], + [1566086400000, 185.67, 194.33, 197.91, 183.35, 175363.5062], + [1566172800000, 194.32, 202.28, 203.59, 192.7, 239541.5978], + [1566259200000, 202.24, 196.6, 202.75, 194.45, 189297.75494], + [1566345600000, 196.55, 187.45, 197.2, 179.53, 284973.64194], + [1566432000000, 187.45, 190.35, 195.14, 182.8, 245575.98772], + [1566518400000, 190.36, 194.02, 196.19, 188.16, 192548.51552], + [1566604800000, 194.02, 190.6, 194.09, 185.63, 167806.34294], + [1566691200000, 190.6, 186.54, 192.4, 182.8, 169862.91522], + [1566777600000, 186.54, 188.67, 193.7, 186.0, 254397.79472], + [1566864000000, 188.61, 187.24, 189.49, 184.75, 157898.563], + [1566950400000, 187.3, 173.03, 188.25, 166.48, 334480.61761], + [1567036800000, 173.03, 169.01, 173.5, 163.61, 295241.216], + [1567123200000, 169.03, 168.5, 170.77, 165.55, 238616.68868], + [1567209600000, 168.48, 171.57, 174.98, 165.63, 194999.19583], + [1567296000000, 171.52, 170.74, 173.42, 167.61, 191140.52368], + [1567382400000, 170.73, 178.05, 181.0, 170.02, 294627.31247], + [1567468800000, 178.0, 178.75, 183.0, 174.09, 327857.85447], + [1567555200000, 178.79, 174.72, 180.14, 173.0, 286226.25171], + [1567641600000, 174.7, 173.75, 176.19, 168.1, 232753.83596], + [1567728000000, 173.74, 169.08, 177.87, 165.0, 315822.37984], + [1567814400000, 169.11, 177.62, 180.8, 168.3, 253831.23169], + [1567900800000, 177.58, 181.19, 184.18, 176.13, 290083.47501], + [1567987200000, 181.18, 180.54, 185.38, 176.01, 273729.94868], + [1568073600000, 180.52, 179.81, 184.36, 177.0, 238387.50999], + [1568160000000, 179.87, 178.28, 182.8, 173.0, 278555.46708], + [1568246400000, 178.3, 180.72, 182.38, 176.62, 203543.13663], + [1568332800000, 180.71, 180.95, 181.38, 177.54, 264422.54059], + [1568419200000, 180.96, 188.13, 188.79, 179.75, 279371.83423], + [1568505600000, 188.14, 189.03, 190.45, 185.76, 288928.60827], + [1568592000000, 189.05, 197.22, 199.44, 188.3, 551006.81686], + [1568678400000, 197.23, 207.84, 215.13, 195.74, 715863.2262], + [1568764800000, 207.85, 210.21, 217.27, 207.66, 539028.51013], + [1568851200000, 210.27, 220.24, 223.94, 202.3, 844358.82155], + [1568937600000, 220.26, 218.03, 221.54, 212.05, 437804.12669], + [1569024000000, 218.01, 215.05, 221.5, 213.2, 417891.5242], + [1569110400000, 215.04, 211.2, 215.61, 206.1, 445388.94787], + [1569196800000, 211.2, 201.29, 211.68, 198.65, 392437.07084], + [1569283200000, 201.25, 165.81, 202.98, 150.03, 1478218.82714], + [1569369600000, 165.72, 169.96, 174.85, 161.88, 879001.46213], + [1569456000000, 169.96, 165.92, 171.01, 152.11, 779942.17148], + [1569542400000, 165.92, 173.79, 176.72, 161.03, 634932.96707], + [1569628800000, 173.83, 173.49, 175.49, 168.0, 521775.46593], + [1569715200000, 173.5, 169.24, 174.5, 164.12, 410855.12176], + [1569801600000, 169.26, 180.85, 181.24, 165.01, 580295.3997], + [1569888000000, 180.89, 175.66, 185.53, 173.19, 609819.60828], + [1569974400000, 175.65, 180.24, 181.29, 173.65, 348268.1162], + [1570060800000, 180.24, 174.69, 180.72, 169.55, 354756.78478], + [1570147200000, 174.71, 175.55, 178.98, 170.74, 333897.63876], + [1570233600000, 175.55, 176.25, 176.71, 172.02, 278488.61771], + [1570320000000, 176.23, 170.1, 177.04, 167.68, 314932.39629], + [1570406400000, 170.08, 179.85, 182.32, 168.68, 496523.48038], + [1570492800000, 179.88, 180.6, 184.87, 177.0, 400832.37828], + [1570579200000, 180.61, 192.62, 195.53, 178.96, 562506.82189], + [1570665600000, 192.61, 191.14, 194.2, 186.88, 436588.58452], + [1570752000000, 191.18, 180.72, 196.65, 179.41, 621693.63125], + [1570838400000, 180.65, 179.68, 184.64, 177.59, 290415.22038], + [1570924800000, 179.65, 180.99, 184.95, 178.52, 247589.23231], + [1571011200000, 180.98, 186.72, 187.54, 180.43, 279732.84612], + [1571097600000, 186.7, 180.49, 188.37, 175.96, 405466.38109], + [1571184000000, 180.52, 174.47, 181.44, 171.81, 347764.93459], + [1571270400000, 174.52, 177.16, 178.96, 172.61, 298795.8198], + [1571356800000, 177.17, 172.74, 177.44, 168.66, 319602.48508], + [1571443200000, 172.78, 171.79, 174.98, 169.44, 296918.73026], + [1571529600000, 171.84, 175.22, 176.88, 169.21, 299141.07152], + [1571616000000, 175.18, 173.98, 177.9, 171.59, 270608.51385], + [1571702400000, 174.0, 171.2, 175.04, 170.3, 255429.41624], + [1571788800000, 171.19, 162.35, 171.49, 153.45, 746955.09806], + [1571875200000, 162.35, 160.38, 163.72, 158.72, 387310.83766], + [1571961600000, 160.39, 181.5, 187.78, 160.25, 904832.86059], + [1572048000000, 181.53, 179.49, 197.74, 173.8, 1211737.43684], + [1572134400000, 179.42, 183.75, 188.7, 176.22, 724423.40525], + [1572220800000, 183.84, 181.72, 189.48, 180.35, 582179.44545], + [1572307200000, 181.67, 190.46, 192.74, 181.26, 529964.5054], + [1572393600000, 190.45, 183.13, 191.71, 179.28, 537770.43056], + [1572480000000, 183.14, 182.18, 185.27, 177.66, 410969.86104], + [1572566400000, 182.19, 182.85, 184.5, 177.02, 331519.76963], + [1572652800000, 182.86, 182.91, 186.0, 181.53, 179864.39739], + [1572739200000, 182.9, 181.54, 184.7, 178.95, 232621.52147], + [1572825600000, 181.53, 185.71, 188.64, 180.36, 321175.29134], + [1572912000000, 185.71, 188.68, 192.51, 182.22, 389668.6472], + [1572998400000, 188.65, 191.16, 195.09, 187.72, 343219.9224], + [1573084800000, 191.16, 186.68, 192.27, 184.59, 309882.08206], + [1573171200000, 186.67, 183.74, 188.26, 181.41, 365029.75027], + [1573257600000, 183.71, 184.89, 185.79, 182.63, 192073.38044], + [1573344000000, 184.86, 188.96, 191.58, 183.3, 274940.53448], + [1573430400000, 188.96, 184.98, 190.19, 184.06, 255579.93429], + [1573516800000, 184.98, 187.09, 187.65, 182.41, 256782.63119], + [1573603200000, 187.09, 188.11, 189.66, 185.3, 197273.84001], + [1573689600000, 188.07, 184.92, 188.72, 183.34, 245505.29971], + [1573776000000, 184.93, 180.0, 186.7, 177.67, 407466.78964], + [1573862400000, 179.99, 182.37, 183.46, 179.3, 172801.52576], + [1573948800000, 182.37, 183.82, 186.09, 180.0, 198892.4372], + [1574035200000, 183.82, 178.2, 184.06, 175.01, 293551.23632], + [1574121600000, 178.2, 175.94, 178.52, 172.65, 275886.6411], + [1574208000000, 175.93, 174.72, 177.41, 173.5, 216315.93309], + [1574294400000, 174.75, 161.01, 175.85, 157.26, 473895.92992], + [1574380800000, 161.02, 149.56, 162.79, 138.0, 977977.23794], + [1574467200000, 149.55, 151.84, 154.33, 146.11, 369721.0996], + [1574553600000, 151.84, 139.96, 152.86, 138.62, 352319.21558], + [1574640000000, 139.99, 145.69, 151.5, 131.45, 749675.41303], + [1574726400000, 145.81, 147.47, 149.97, 143.37, 354023.04298], + [1574812800000, 147.47, 152.62, 155.54, 140.84, 564796.4284], + [1574899200000, 152.61, 150.72, 154.63, 149.09, 317714.56326], + [1574985600000, 150.69, 154.57, 157.6, 150.23, 328712.25558], + [1575072000000, 154.54, 151.37, 155.25, 149.7, 226863.41299], + [1575158400000, 151.43, 150.73, 152.49, 145.79, 344178.14088], + [1575244800000, 150.72, 148.65, 151.42, 146.71, 233839.0973], + [1575331200000, 148.66, 147.17, 149.93, 145.77, 196329.22503], + [1575417600000, 147.19, 145.38, 151.98, 143.15, 434430.62379], + [1575504000000, 145.45, 148.1, 149.02, 143.64, 299073.53972], + [1575590400000, 148.11, 148.45, 149.77, 145.74, 220674.68581], + [1575676800000, 148.46, 147.14, 149.49, 146.85, 140471.68588], + [1575763200000, 147.16, 150.44, 151.62, 146.11, 205301.6026], + [1575849600000, 150.44, 147.38, 151.19, 146.56, 243775.99249], + [1575936000000, 147.4, 145.56, 148.57, 143.81, 203215.84937], + [1576022400000, 145.53, 143.39, 146.34, 142.12, 157843.10484], + [1576108800000, 143.41, 144.87, 145.85, 139.24, 261615.30437], + [1576195200000, 144.87, 144.8, 146.0, 142.8, 160695.18556], + [1576281600000, 144.8, 141.79, 145.07, 141.18, 126232.59201], + [1576368000000, 141.79, 142.46, 144.12, 139.92, 151189.65877], + [1576454400000, 142.46, 132.73, 142.72, 127.93, 471018.85942], + [1576540800000, 132.72, 121.88, 132.98, 119.11, 563257.36001], + [1576627200000, 121.88, 132.78, 134.87, 116.26, 884960.91334], + [1576713600000, 132.8, 128.1, 134.0, 125.69, 420674.8172], + [1576800000000, 128.1, 128.19, 129.39, 125.84, 213897.4673], + [1576886400000, 128.19, 126.99, 128.4, 126.5, 135196.11641], + [1576972800000, 127.0, 132.09, 133.07, 126.82, 253140.72413], + [1577059200000, 132.12, 127.8, 135.1, 126.0, 421600.75655], + [1577145600000, 127.8, 127.75, 129.69, 126.61, 200637.10098], + [1577232000000, 127.7, 125.09, 127.84, 123.07, 225004.4909], + [1577318400000, 125.09, 125.58, 132.26, 124.32, 274986.52097], + [1577404800000, 125.58, 126.29, 127.1, 121.91, 240012.37451], + [1577491200000, 126.28, 128.11, 129.68, 125.84, 196893.52277], + [1577577600000, 128.11, 134.36, 138.07, 127.52, 316347.26666], + [1577664000000, 134.36, 131.59, 136.24, 130.3, 320347.21956], + [1577750400000, 131.61, 129.16, 133.68, 128.17, 264933.98418], + [1577836800000, 129.16, 130.77, 133.05, 128.68, 144770.52197], + [1577923200000, 130.72, 127.19, 130.78, 126.38, 213757.05806], + [1578009600000, 127.19, 134.35, 135.14, 125.88, 413055.18895], + [1578096000000, 134.37, 134.2, 135.85, 132.5, 184276.17102], + [1578182400000, 134.2, 135.37, 138.19, 134.19, 254120.45343], + [1578268800000, 135.37, 144.15, 144.41, 134.86, 408020.27375], + [1578355200000, 144.14, 142.8, 145.31, 138.76, 447762.17281], + [1578441600000, 142.8, 140.72, 147.77, 137.03, 570465.57764], + [1578528000000, 140.76, 137.74, 141.5, 135.3, 366076.06569], + [1578614400000, 137.73, 144.84, 145.17, 135.32, 409403.59507], + [1578700800000, 144.83, 142.38, 148.05, 142.09, 368350.57939], + [1578787200000, 142.4, 146.54, 146.6, 141.76, 229541.86137], + [1578873600000, 146.56, 143.58, 147.0, 142.27, 207996.61865], + [1578960000000, 143.58, 165.64, 171.7, 143.51, 1108476.31186], + [1579046400000, 165.6, 166.4, 171.98, 159.2, 721687.80381], + [1579132800000, 166.4, 164.21, 167.4, 157.8, 456170.86719], + [1579219200000, 164.24, 169.85, 174.81, 162.14, 767180.67853], + [1579305600000, 169.92, 174.14, 179.5, 164.92, 688783.17982], + [1579392000000, 174.1, 166.79, 178.05, 161.66, 624681.28604], + [1579478400000, 166.79, 166.87, 169.33, 161.24, 358092.8841], + [1579564800000, 166.86, 169.49, 170.32, 164.8, 308007.6353], + [1579651200000, 169.48, 168.07, 171.47, 166.03, 272240.90286], + [1579737600000, 168.07, 162.81, 168.2, 159.21, 373414.34985], + [1579824000000, 162.85, 162.54, 164.45, 155.55, 430013.19902], + [1579910400000, 162.51, 160.35, 162.79, 157.61, 219921.65197], + [1579996800000, 160.36, 167.86, 168.08, 159.41, 251582.55758], + [1580083200000, 167.91, 170.08, 172.56, 165.22, 365894.81917], + [1580169600000, 170.04, 175.64, 176.2, 170.03, 473433.89609], + [1580256000000, 175.58, 173.72, 178.45, 173.33, 317382.90161], + [1580342400000, 173.68, 184.69, 187.0, 170.93, 477721.6609], + [1580428800000, 184.71, 179.99, 185.82, 175.22, 385596.53365], + [1580515200000, 179.94, 183.6, 184.28, 179.23, 259370.12902], + [1580601600000, 183.63, 188.44, 193.43, 179.1, 552621.13619], + [1580688000000, 188.48, 189.69, 195.19, 186.62, 417175.95781], + [1580774400000, 189.74, 188.91, 191.6, 184.69, 366389.69686], + [1580860800000, 188.91, 203.78, 207.61, 188.19, 550942.11417], + [1580947200000, 203.78, 213.19, 216.33, 201.02, 608240.2233], + [1581033600000, 213.16, 223.33, 225.0, 213.14, 629361.15466], + [1581120000000, 223.36, 223.05, 227.75, 213.22, 548551.87465], + [1581206400000, 223.01, 228.49, 230.65, 222.86, 350336.24399], + [1581292800000, 228.53, 222.89, 229.4, 216.37, 510415.49949], + [1581379200000, 222.89, 236.69, 239.15, 218.17, 595576.90276], + [1581465600000, 236.69, 265.74, 275.34, 236.69, 1038073.74782], + [1581552000000, 265.74, 268.32, 277.69, 256.08, 1089679.1537], + [1581638400000, 268.34, 285.15, 287.15, 260.28, 734944.32266], + [1581724800000, 285.11, 264.88, 288.41, 261.86, 860813.14274], + [1581811200000, 264.91, 258.85, 274.0, 237.41, 1110118.46395], + [1581897600000, 258.89, 267.85, 268.77, 242.0, 1110371.39094], + [1581984000000, 267.9, 282.61, 285.88, 258.0, 1115523.43992], + [1582070400000, 282.64, 258.45, 285.0, 251.56, 705973.72988], + [1582156800000, 258.44, 256.96, 264.33, 245.34, 972969.71691], + [1582243200000, 256.97, 265.27, 268.24, 253.61, 525827.8734], + [1582329600000, 265.32, 261.57, 266.81, 256.0, 313062.52133], + [1582416000000, 261.55, 274.48, 275.68, 261.02, 444740.82883], + [1582502400000, 274.5, 265.52, 277.2, 257.09, 696591.72983], + [1582588800000, 265.47, 246.67, 266.22, 244.44, 774791.01027], + [1582675200000, 246.67, 223.93, 250.32, 215.66, 1395879.41507], + [1582761600000, 223.98, 227.79, 238.3, 210.0, 1273793.11658], + [1582848000000, 227.73, 226.76, 234.67, 214.01, 1054994.92397], + [1582934400000, 226.76, 217.21, 233.0, 217.0, 546866.6851], + [1583020800000, 217.29, 217.81, 227.89, 212.36, 715016.01941], + [1583107200000, 217.81, 231.97, 234.4, 216.07, 810051.4833], + [1583193600000, 232.1, 223.91, 232.46, 219.57, 741498.54825], + [1583280000000, 223.84, 224.26, 228.85, 220.23, 443780.33772], + [1583366400000, 224.26, 228.38, 234.09, 224.23, 601479.87587], + [1583452800000, 228.38, 244.88, 245.16, 227.33, 628147.3257], + [1583539200000, 244.93, 237.23, 251.93, 236.0, 633748.89662], + [1583625600000, 237.23, 199.43, 237.23, 195.5, 1278973.53741], + [1583712000000, 199.43, 202.81, 208.62, 190.0, 1661331.83553], + [1583798400000, 202.79, 200.7, 206.2, 195.54, 1020260.107], + [1583884800000, 200.74, 194.61, 203.18, 181.73, 1079824.90167], + [1583971200000, 194.61, 107.82, 195.55, 101.2, 3814533.14046], + ]; + + const mappedCandles = candles.map(c => ({ + high: c[3], + low: c[4], + })); + + describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const ac = new AC(5, 34, 5); + const acWithReplace = new AC(5, 34, 5); + ac.updates(mappedCandles); + acWithReplace.updates(mappedCandles); + + ac.update({ + high: 200, + low: 100, + }); + acWithReplace.update({ + high: 5_000, + low: 3_000, + }); + acWithReplace.replace({ + high: 200, + low: 100, + }); + + expect(acWithReplace.getResult().toFixed()).toBe(ac.getResult().toFixed()); + }); + }); + describe('getResult', () => { it('works with a signal line of SMA(5)', () => { const ac = new AC(5, 34, 5); const fasterAC = new FasterAC(5, 34, 5); - // Test data verified with: - // https://github.com/jesse-ai/jesse/blob/8e502d070c24bed29db80e1d0938781d8cdb1046/tests/data/test_candles_indicators.py#L4351 - const candles = [ - [1563408000000, 210.8, 225.73, 229.65, 205.71, 609081.49094], - [1563494400000, 225.75, 220.73, 226.23, 212.52, 371622.21865], - [1563580800000, 220.84, 228.2, 235.09, 219.78, 325393.97225], - [1563667200000, 228.25, 225.38, 229.66, 216.99, 270046.1519], - [1563753600000, 225.49, 217.51, 228.34, 212.25, 271310.40446], - [1563840000000, 217.59, 212.48, 219.55, 208.36, 317876.48242], - [1563926400000, 212.55, 216.31, 218.28, 202.0, 331162.6484], - [1564012800000, 216.31, 219.14, 225.12, 215.23, 280370.29627], - [1564099200000, 219.14, 218.81, 220.0, 212.71, 197781.98653], - [1564185600000, 218.81, 207.3, 223.3, 203.0, 301209.41113], - [1564272000000, 207.3, 211.62, 213.52, 198.24, 218801.16693], - [1564358400000, 211.58, 210.89, 215.83, 206.59, 226941.28], - [1564444800000, 210.84, 209.58, 214.36, 204.4, 222683.79393], - [1564531200000, 209.57, 218.42, 218.79, 209.2, 207213.55658], - [1564617600000, 218.42, 216.84, 219.39, 210.54, 186806.18844], - [1564704000000, 216.8, 217.61, 222.18, 214.31, 206867.03039], - [1564790400000, 217.69, 222.14, 224.51, 216.62, 181591.95296], - [1564876800000, 222.14, 221.79, 223.34, 216.9, 135622.0258], - [1564963200000, 221.79, 233.54, 236.25, 221.79, 307956.27211], - [1565049600000, 233.53, 226.28, 239.15, 223.03, 341279.08159], - [1565136000000, 226.31, 226.1, 231.25, 220.95, 279104.7037], - [1565222400000, 226.11, 221.39, 228.5, 215.51, 236886.35423], - [1565308800000, 221.38, 210.53, 221.79, 207.3, 232062.12757], - [1565395200000, 210.52, 206.48, 215.0, 202.6, 252614.02389], - [1565481600000, 206.48, 216.42, 216.94, 206.14, 188474.09048], - [1565568000000, 216.41, 211.41, 216.81, 209.75, 122760.94619], - [1565654400000, 211.58, 209.3, 214.3, 204.0, 166922.48201], - [1565740800000, 209.31, 187.1, 209.9, 183.49, 325228.98931], - [1565827200000, 187.08, 188.03, 189.95, 181.23, 237953.09426], - [1565913600000, 187.98, 184.88, 188.39, 178.04, 282177.01584], - [1566000000000, 184.83, 185.59, 187.0, 181.83, 138799.61508], - [1566086400000, 185.67, 194.33, 197.91, 183.35, 175363.5062], - [1566172800000, 194.32, 202.28, 203.59, 192.7, 239541.5978], - [1566259200000, 202.24, 196.6, 202.75, 194.45, 189297.75494], - [1566345600000, 196.55, 187.45, 197.2, 179.53, 284973.64194], - [1566432000000, 187.45, 190.35, 195.14, 182.8, 245575.98772], - [1566518400000, 190.36, 194.02, 196.19, 188.16, 192548.51552], - [1566604800000, 194.02, 190.6, 194.09, 185.63, 167806.34294], - [1566691200000, 190.6, 186.54, 192.4, 182.8, 169862.91522], - [1566777600000, 186.54, 188.67, 193.7, 186.0, 254397.79472], - [1566864000000, 188.61, 187.24, 189.49, 184.75, 157898.563], - [1566950400000, 187.3, 173.03, 188.25, 166.48, 334480.61761], - [1567036800000, 173.03, 169.01, 173.5, 163.61, 295241.216], - [1567123200000, 169.03, 168.5, 170.77, 165.55, 238616.68868], - [1567209600000, 168.48, 171.57, 174.98, 165.63, 194999.19583], - [1567296000000, 171.52, 170.74, 173.42, 167.61, 191140.52368], - [1567382400000, 170.73, 178.05, 181.0, 170.02, 294627.31247], - [1567468800000, 178.0, 178.75, 183.0, 174.09, 327857.85447], - [1567555200000, 178.79, 174.72, 180.14, 173.0, 286226.25171], - [1567641600000, 174.7, 173.75, 176.19, 168.1, 232753.83596], - [1567728000000, 173.74, 169.08, 177.87, 165.0, 315822.37984], - [1567814400000, 169.11, 177.62, 180.8, 168.3, 253831.23169], - [1567900800000, 177.58, 181.19, 184.18, 176.13, 290083.47501], - [1567987200000, 181.18, 180.54, 185.38, 176.01, 273729.94868], - [1568073600000, 180.52, 179.81, 184.36, 177.0, 238387.50999], - [1568160000000, 179.87, 178.28, 182.8, 173.0, 278555.46708], - [1568246400000, 178.3, 180.72, 182.38, 176.62, 203543.13663], - [1568332800000, 180.71, 180.95, 181.38, 177.54, 264422.54059], - [1568419200000, 180.96, 188.13, 188.79, 179.75, 279371.83423], - [1568505600000, 188.14, 189.03, 190.45, 185.76, 288928.60827], - [1568592000000, 189.05, 197.22, 199.44, 188.3, 551006.81686], - [1568678400000, 197.23, 207.84, 215.13, 195.74, 715863.2262], - [1568764800000, 207.85, 210.21, 217.27, 207.66, 539028.51013], - [1568851200000, 210.27, 220.24, 223.94, 202.3, 844358.82155], - [1568937600000, 220.26, 218.03, 221.54, 212.05, 437804.12669], - [1569024000000, 218.01, 215.05, 221.5, 213.2, 417891.5242], - [1569110400000, 215.04, 211.2, 215.61, 206.1, 445388.94787], - [1569196800000, 211.2, 201.29, 211.68, 198.65, 392437.07084], - [1569283200000, 201.25, 165.81, 202.98, 150.03, 1478218.82714], - [1569369600000, 165.72, 169.96, 174.85, 161.88, 879001.46213], - [1569456000000, 169.96, 165.92, 171.01, 152.11, 779942.17148], - [1569542400000, 165.92, 173.79, 176.72, 161.03, 634932.96707], - [1569628800000, 173.83, 173.49, 175.49, 168.0, 521775.46593], - [1569715200000, 173.5, 169.24, 174.5, 164.12, 410855.12176], - [1569801600000, 169.26, 180.85, 181.24, 165.01, 580295.3997], - [1569888000000, 180.89, 175.66, 185.53, 173.19, 609819.60828], - [1569974400000, 175.65, 180.24, 181.29, 173.65, 348268.1162], - [1570060800000, 180.24, 174.69, 180.72, 169.55, 354756.78478], - [1570147200000, 174.71, 175.55, 178.98, 170.74, 333897.63876], - [1570233600000, 175.55, 176.25, 176.71, 172.02, 278488.61771], - [1570320000000, 176.23, 170.1, 177.04, 167.68, 314932.39629], - [1570406400000, 170.08, 179.85, 182.32, 168.68, 496523.48038], - [1570492800000, 179.88, 180.6, 184.87, 177.0, 400832.37828], - [1570579200000, 180.61, 192.62, 195.53, 178.96, 562506.82189], - [1570665600000, 192.61, 191.14, 194.2, 186.88, 436588.58452], - [1570752000000, 191.18, 180.72, 196.65, 179.41, 621693.63125], - [1570838400000, 180.65, 179.68, 184.64, 177.59, 290415.22038], - [1570924800000, 179.65, 180.99, 184.95, 178.52, 247589.23231], - [1571011200000, 180.98, 186.72, 187.54, 180.43, 279732.84612], - [1571097600000, 186.7, 180.49, 188.37, 175.96, 405466.38109], - [1571184000000, 180.52, 174.47, 181.44, 171.81, 347764.93459], - [1571270400000, 174.52, 177.16, 178.96, 172.61, 298795.8198], - [1571356800000, 177.17, 172.74, 177.44, 168.66, 319602.48508], - [1571443200000, 172.78, 171.79, 174.98, 169.44, 296918.73026], - [1571529600000, 171.84, 175.22, 176.88, 169.21, 299141.07152], - [1571616000000, 175.18, 173.98, 177.9, 171.59, 270608.51385], - [1571702400000, 174.0, 171.2, 175.04, 170.3, 255429.41624], - [1571788800000, 171.19, 162.35, 171.49, 153.45, 746955.09806], - [1571875200000, 162.35, 160.38, 163.72, 158.72, 387310.83766], - [1571961600000, 160.39, 181.5, 187.78, 160.25, 904832.86059], - [1572048000000, 181.53, 179.49, 197.74, 173.8, 1211737.43684], - [1572134400000, 179.42, 183.75, 188.7, 176.22, 724423.40525], - [1572220800000, 183.84, 181.72, 189.48, 180.35, 582179.44545], - [1572307200000, 181.67, 190.46, 192.74, 181.26, 529964.5054], - [1572393600000, 190.45, 183.13, 191.71, 179.28, 537770.43056], - [1572480000000, 183.14, 182.18, 185.27, 177.66, 410969.86104], - [1572566400000, 182.19, 182.85, 184.5, 177.02, 331519.76963], - [1572652800000, 182.86, 182.91, 186.0, 181.53, 179864.39739], - [1572739200000, 182.9, 181.54, 184.7, 178.95, 232621.52147], - [1572825600000, 181.53, 185.71, 188.64, 180.36, 321175.29134], - [1572912000000, 185.71, 188.68, 192.51, 182.22, 389668.6472], - [1572998400000, 188.65, 191.16, 195.09, 187.72, 343219.9224], - [1573084800000, 191.16, 186.68, 192.27, 184.59, 309882.08206], - [1573171200000, 186.67, 183.74, 188.26, 181.41, 365029.75027], - [1573257600000, 183.71, 184.89, 185.79, 182.63, 192073.38044], - [1573344000000, 184.86, 188.96, 191.58, 183.3, 274940.53448], - [1573430400000, 188.96, 184.98, 190.19, 184.06, 255579.93429], - [1573516800000, 184.98, 187.09, 187.65, 182.41, 256782.63119], - [1573603200000, 187.09, 188.11, 189.66, 185.3, 197273.84001], - [1573689600000, 188.07, 184.92, 188.72, 183.34, 245505.29971], - [1573776000000, 184.93, 180.0, 186.7, 177.67, 407466.78964], - [1573862400000, 179.99, 182.37, 183.46, 179.3, 172801.52576], - [1573948800000, 182.37, 183.82, 186.09, 180.0, 198892.4372], - [1574035200000, 183.82, 178.2, 184.06, 175.01, 293551.23632], - [1574121600000, 178.2, 175.94, 178.52, 172.65, 275886.6411], - [1574208000000, 175.93, 174.72, 177.41, 173.5, 216315.93309], - [1574294400000, 174.75, 161.01, 175.85, 157.26, 473895.92992], - [1574380800000, 161.02, 149.56, 162.79, 138.0, 977977.23794], - [1574467200000, 149.55, 151.84, 154.33, 146.11, 369721.0996], - [1574553600000, 151.84, 139.96, 152.86, 138.62, 352319.21558], - [1574640000000, 139.99, 145.69, 151.5, 131.45, 749675.41303], - [1574726400000, 145.81, 147.47, 149.97, 143.37, 354023.04298], - [1574812800000, 147.47, 152.62, 155.54, 140.84, 564796.4284], - [1574899200000, 152.61, 150.72, 154.63, 149.09, 317714.56326], - [1574985600000, 150.69, 154.57, 157.6, 150.23, 328712.25558], - [1575072000000, 154.54, 151.37, 155.25, 149.7, 226863.41299], - [1575158400000, 151.43, 150.73, 152.49, 145.79, 344178.14088], - [1575244800000, 150.72, 148.65, 151.42, 146.71, 233839.0973], - [1575331200000, 148.66, 147.17, 149.93, 145.77, 196329.22503], - [1575417600000, 147.19, 145.38, 151.98, 143.15, 434430.62379], - [1575504000000, 145.45, 148.1, 149.02, 143.64, 299073.53972], - [1575590400000, 148.11, 148.45, 149.77, 145.74, 220674.68581], - [1575676800000, 148.46, 147.14, 149.49, 146.85, 140471.68588], - [1575763200000, 147.16, 150.44, 151.62, 146.11, 205301.6026], - [1575849600000, 150.44, 147.38, 151.19, 146.56, 243775.99249], - [1575936000000, 147.4, 145.56, 148.57, 143.81, 203215.84937], - [1576022400000, 145.53, 143.39, 146.34, 142.12, 157843.10484], - [1576108800000, 143.41, 144.87, 145.85, 139.24, 261615.30437], - [1576195200000, 144.87, 144.8, 146.0, 142.8, 160695.18556], - [1576281600000, 144.8, 141.79, 145.07, 141.18, 126232.59201], - [1576368000000, 141.79, 142.46, 144.12, 139.92, 151189.65877], - [1576454400000, 142.46, 132.73, 142.72, 127.93, 471018.85942], - [1576540800000, 132.72, 121.88, 132.98, 119.11, 563257.36001], - [1576627200000, 121.88, 132.78, 134.87, 116.26, 884960.91334], - [1576713600000, 132.8, 128.1, 134.0, 125.69, 420674.8172], - [1576800000000, 128.1, 128.19, 129.39, 125.84, 213897.4673], - [1576886400000, 128.19, 126.99, 128.4, 126.5, 135196.11641], - [1576972800000, 127.0, 132.09, 133.07, 126.82, 253140.72413], - [1577059200000, 132.12, 127.8, 135.1, 126.0, 421600.75655], - [1577145600000, 127.8, 127.75, 129.69, 126.61, 200637.10098], - [1577232000000, 127.7, 125.09, 127.84, 123.07, 225004.4909], - [1577318400000, 125.09, 125.58, 132.26, 124.32, 274986.52097], - [1577404800000, 125.58, 126.29, 127.1, 121.91, 240012.37451], - [1577491200000, 126.28, 128.11, 129.68, 125.84, 196893.52277], - [1577577600000, 128.11, 134.36, 138.07, 127.52, 316347.26666], - [1577664000000, 134.36, 131.59, 136.24, 130.3, 320347.21956], - [1577750400000, 131.61, 129.16, 133.68, 128.17, 264933.98418], - [1577836800000, 129.16, 130.77, 133.05, 128.68, 144770.52197], - [1577923200000, 130.72, 127.19, 130.78, 126.38, 213757.05806], - [1578009600000, 127.19, 134.35, 135.14, 125.88, 413055.18895], - [1578096000000, 134.37, 134.2, 135.85, 132.5, 184276.17102], - [1578182400000, 134.2, 135.37, 138.19, 134.19, 254120.45343], - [1578268800000, 135.37, 144.15, 144.41, 134.86, 408020.27375], - [1578355200000, 144.14, 142.8, 145.31, 138.76, 447762.17281], - [1578441600000, 142.8, 140.72, 147.77, 137.03, 570465.57764], - [1578528000000, 140.76, 137.74, 141.5, 135.3, 366076.06569], - [1578614400000, 137.73, 144.84, 145.17, 135.32, 409403.59507], - [1578700800000, 144.83, 142.38, 148.05, 142.09, 368350.57939], - [1578787200000, 142.4, 146.54, 146.6, 141.76, 229541.86137], - [1578873600000, 146.56, 143.58, 147.0, 142.27, 207996.61865], - [1578960000000, 143.58, 165.64, 171.7, 143.51, 1108476.31186], - [1579046400000, 165.6, 166.4, 171.98, 159.2, 721687.80381], - [1579132800000, 166.4, 164.21, 167.4, 157.8, 456170.86719], - [1579219200000, 164.24, 169.85, 174.81, 162.14, 767180.67853], - [1579305600000, 169.92, 174.14, 179.5, 164.92, 688783.17982], - [1579392000000, 174.1, 166.79, 178.05, 161.66, 624681.28604], - [1579478400000, 166.79, 166.87, 169.33, 161.24, 358092.8841], - [1579564800000, 166.86, 169.49, 170.32, 164.8, 308007.6353], - [1579651200000, 169.48, 168.07, 171.47, 166.03, 272240.90286], - [1579737600000, 168.07, 162.81, 168.2, 159.21, 373414.34985], - [1579824000000, 162.85, 162.54, 164.45, 155.55, 430013.19902], - [1579910400000, 162.51, 160.35, 162.79, 157.61, 219921.65197], - [1579996800000, 160.36, 167.86, 168.08, 159.41, 251582.55758], - [1580083200000, 167.91, 170.08, 172.56, 165.22, 365894.81917], - [1580169600000, 170.04, 175.64, 176.2, 170.03, 473433.89609], - [1580256000000, 175.58, 173.72, 178.45, 173.33, 317382.90161], - [1580342400000, 173.68, 184.69, 187.0, 170.93, 477721.6609], - [1580428800000, 184.71, 179.99, 185.82, 175.22, 385596.53365], - [1580515200000, 179.94, 183.6, 184.28, 179.23, 259370.12902], - [1580601600000, 183.63, 188.44, 193.43, 179.1, 552621.13619], - [1580688000000, 188.48, 189.69, 195.19, 186.62, 417175.95781], - [1580774400000, 189.74, 188.91, 191.6, 184.69, 366389.69686], - [1580860800000, 188.91, 203.78, 207.61, 188.19, 550942.11417], - [1580947200000, 203.78, 213.19, 216.33, 201.02, 608240.2233], - [1581033600000, 213.16, 223.33, 225.0, 213.14, 629361.15466], - [1581120000000, 223.36, 223.05, 227.75, 213.22, 548551.87465], - [1581206400000, 223.01, 228.49, 230.65, 222.86, 350336.24399], - [1581292800000, 228.53, 222.89, 229.4, 216.37, 510415.49949], - [1581379200000, 222.89, 236.69, 239.15, 218.17, 595576.90276], - [1581465600000, 236.69, 265.74, 275.34, 236.69, 1038073.74782], - [1581552000000, 265.74, 268.32, 277.69, 256.08, 1089679.1537], - [1581638400000, 268.34, 285.15, 287.15, 260.28, 734944.32266], - [1581724800000, 285.11, 264.88, 288.41, 261.86, 860813.14274], - [1581811200000, 264.91, 258.85, 274.0, 237.41, 1110118.46395], - [1581897600000, 258.89, 267.85, 268.77, 242.0, 1110371.39094], - [1581984000000, 267.9, 282.61, 285.88, 258.0, 1115523.43992], - [1582070400000, 282.64, 258.45, 285.0, 251.56, 705973.72988], - [1582156800000, 258.44, 256.96, 264.33, 245.34, 972969.71691], - [1582243200000, 256.97, 265.27, 268.24, 253.61, 525827.8734], - [1582329600000, 265.32, 261.57, 266.81, 256.0, 313062.52133], - [1582416000000, 261.55, 274.48, 275.68, 261.02, 444740.82883], - [1582502400000, 274.5, 265.52, 277.2, 257.09, 696591.72983], - [1582588800000, 265.47, 246.67, 266.22, 244.44, 774791.01027], - [1582675200000, 246.67, 223.93, 250.32, 215.66, 1395879.41507], - [1582761600000, 223.98, 227.79, 238.3, 210.0, 1273793.11658], - [1582848000000, 227.73, 226.76, 234.67, 214.01, 1054994.92397], - [1582934400000, 226.76, 217.21, 233.0, 217.0, 546866.6851], - [1583020800000, 217.29, 217.81, 227.89, 212.36, 715016.01941], - [1583107200000, 217.81, 231.97, 234.4, 216.07, 810051.4833], - [1583193600000, 232.1, 223.91, 232.46, 219.57, 741498.54825], - [1583280000000, 223.84, 224.26, 228.85, 220.23, 443780.33772], - [1583366400000, 224.26, 228.38, 234.09, 224.23, 601479.87587], - [1583452800000, 228.38, 244.88, 245.16, 227.33, 628147.3257], - [1583539200000, 244.93, 237.23, 251.93, 236.0, 633748.89662], - [1583625600000, 237.23, 199.43, 237.23, 195.5, 1278973.53741], - [1583712000000, 199.43, 202.81, 208.62, 190.0, 1661331.83553], - [1583798400000, 202.79, 200.7, 206.2, 195.54, 1020260.107], - [1583884800000, 200.74, 194.61, 203.18, 181.73, 1079824.90167], - [1583971200000, 194.61, 107.82, 195.55, 101.2, 3814533.14046], - ]; - - for (const candle of candles) { - const [, , , high, low] = candle; - const input: HighLowNumber = {high, low}; - ac.update(input); - fasterAC.update(input); + for (const candle of mappedCandles) { + ac.update(candle); + fasterAC.update(candle); } // Result verified with: diff --git a/src/AC/AC.ts b/src/AC/AC.ts index 64f78b8e6..451436b1e 100644 --- a/src/AC/AC.ts +++ b/src/AC/AC.ts @@ -34,12 +34,12 @@ export class AC extends BigIndicatorSeries { } override update(input: HighLow, replace: boolean = false): void | Big { - const ao = this.ao.update(input); + const ao = this.ao.update(input, replace); if (ao) { - this.signal.update(ao); + this.signal.update(ao, replace); if (this.signal.isStable) { const result = this.setResult(ao.sub(this.signal.getResult()), replace); - this.momentum.update(result); + this.momentum.update(result, replace); return this.result; } } @@ -63,12 +63,12 @@ export class FasterAC extends NumberIndicatorSeries { } override update(input: HighLowNumber, replace: boolean = false): void | number { - const ao = this.ao.update(input); + const ao = this.ao.update(input, replace); if (ao) { - this.signal.update(ao); + this.signal.update(ao, replace); if (this.signal.isStable) { const result = this.setResult(ao - this.signal.getResult(), replace); - this.momentum.update(result); + this.momentum.update(result, replace); return this.result; } } From b8aec8ee9e34927850220dbfd3cc8e982278ccde Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Thu, 12 Dec 2024 16:36:47 +0100 Subject: [PATCH 06/12] remove replace from ADX --- src/AC/AC.test.ts | 20 ++++++++++---------- src/ADX/ADX.test.ts | 40 ++++++++++++++++++++-------------------- src/ADX/ADX.ts | 8 ++++---- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/AC/AC.test.ts b/src/AC/AC.test.ts index 3ddd153cc..bfcb09a2b 100644 --- a/src/AC/AC.test.ts +++ b/src/AC/AC.test.ts @@ -1,6 +1,5 @@ -import {AC, FasterAC} from './AC.js'; import {NotEnoughDataError} from '../error/index.js'; -import type {HighLowNumber} from '../util/index.js'; +import {AC, FasterAC} from './AC.js'; describe('AC', () => { // Test data verified with: @@ -256,21 +255,22 @@ describe('AC', () => { it('guarantees that a replacement is done correctly', () => { const ac = new AC(5, 34, 5); const acWithReplace = new AC(5, 34, 5); + ac.updates(mappedCandles); acWithReplace.updates(mappedCandles); - ac.update({ + const correct = { high: 200, low: 100, - }); - acWithReplace.update({ + }; + const wrong = { high: 5_000, low: 3_000, - }); - acWithReplace.replace({ - high: 200, - low: 100, - }); + }; + + ac.update(correct); + acWithReplace.update(wrong); + acWithReplace.replace(correct); expect(acWithReplace.getResult().toFixed()).toBe(ac.getResult().toFixed()); }); diff --git a/src/ADX/ADX.test.ts b/src/ADX/ADX.test.ts index 8f7f30b0e..1b8c79452 100644 --- a/src/ADX/ADX.test.ts +++ b/src/ADX/ADX.test.ts @@ -1,28 +1,28 @@ import {ADX, FasterADX} from './ADX.js'; describe('ADX', () => { + // Test data verified with: + // https://tulipindicators.org/adx + const candles = [ + {close: 81.59, high: 82.15, low: 81.29}, + {close: 81.06, high: 81.89, low: 80.64}, + {close: 82.87, high: 83.03, low: 81.31}, + {close: 83.0, high: 83.3, low: 82.65}, + {close: 83.61, high: 83.85, low: 83.07}, + {close: 83.15, high: 83.9, low: 83.11}, + {close: 82.84, high: 83.33, low: 82.49}, + {close: 83.99, high: 84.3, low: 82.3}, + {close: 84.55, high: 84.84, low: 84.15}, + {close: 84.36, high: 85.0, low: 84.11}, + {close: 85.53, high: 85.9, low: 84.03}, + {close: 86.54, high: 86.58, low: 85.39}, + {close: 86.89, high: 86.98, low: 85.76}, + {close: 87.77, high: 88.0, low: 87.17}, + {close: 87.29, high: 87.87, low: 87.01}, + ]; + describe('getResult', () => { it('calculates the Average Directional Index (ADX)', () => { - // Test data verified with: - // https://tulipindicators.org/adx - const candles = [ - {close: 81.59, high: 82.15, low: 81.29}, - {close: 81.06, high: 81.89, low: 80.64}, - {close: 82.87, high: 83.03, low: 81.31}, - {close: 83.0, high: 83.3, low: 82.65}, - {close: 83.61, high: 83.85, low: 83.07}, - {close: 83.15, high: 83.9, low: 83.11}, - {close: 82.84, high: 83.33, low: 82.49}, - {close: 83.99, high: 84.3, low: 82.3}, - {close: 84.55, high: 84.84, low: 84.15}, - {close: 84.36, high: 85.0, low: 84.11}, - {close: 85.53, high: 85.9, low: 84.03}, - {close: 86.54, high: 86.58, low: 85.39}, - {close: 86.89, high: 86.98, low: 85.76}, - {close: 87.77, high: 88.0, low: 87.17}, - {close: 87.29, high: 87.87, low: 87.01}, - ]; - const expectations = [41.38, 44.29, 49.42, 54.92, 59.99, 65.29, 67.36]; const adx = new ADX(5); diff --git a/src/ADX/ADX.ts b/src/ADX/ADX.ts index e8a024f51..da6456523 100644 --- a/src/ADX/ADX.ts +++ b/src/ADX/ADX.ts @@ -50,13 +50,13 @@ export class ADX extends BigIndicatorSeries { return this.dx.pdi; } - update(candle: HighLowClose, replace: boolean = false): Big | void { + update(candle: HighLowClose): Big | void { const result = this.dx.update(candle); if (result) { this.adx.update(result); } if (this.adx.isStable) { - return this.setResult(this.adx.getResult(), replace); + return this.setResult(this.adx.getResult(), false); } } } @@ -82,13 +82,13 @@ export class FasterADX extends NumberIndicatorSeries { return this.dx.pdi; } - update(candle: HighLowCloseNumber, replace: boolean = false): void | number { + update(candle: HighLowCloseNumber): void | number { const result = this.dx.update(candle); if (result !== undefined) { this.adx.update(result); } if (this.adx.isStable) { - return this.setResult(this.adx.getResult(), replace); + return this.setResult(this.adx.getResult(), false); } } } From 702090e0788f9759847c4ea177686e916c969cc7 Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Fri, 13 Dec 2024 09:04:25 +0100 Subject: [PATCH 07/12] prepare tests for StochasticRSI --- src/ADX/ADX.ts | 1 + src/CCI/CCI.ts | 1 + src/DX/DX.ts | 1 + src/Indicator.ts | 1 + src/STOCH/StochasticRSI.test.ts | 34 +++++++++++++++++++++------------ src/STOCH/StochasticRSI.ts | 7 ++++--- src/WSMA/WSMA.ts | 2 +- src/util/Period.ts | 2 ++ 8 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/ADX/ADX.ts b/src/ADX/ADX.ts index da6456523..47df502f8 100644 --- a/src/ADX/ADX.ts +++ b/src/ADX/ADX.ts @@ -50,6 +50,7 @@ export class ADX extends BigIndicatorSeries { return this.dx.pdi; } + // TODO: Implement "replace" parameter update(candle: HighLowClose): Big | void { const result = this.dx.update(candle); if (result) { diff --git a/src/CCI/CCI.ts b/src/CCI/CCI.ts index c11216595..febfb3661 100644 --- a/src/CCI/CCI.ts +++ b/src/CCI/CCI.ts @@ -30,6 +30,7 @@ export class CCI extends BigIndicatorSeries { this.sma = new SMA(this.interval); } + // TODO: Implement "replace" parameter update(candle: HighLowClose): void | Big { const typicalPrice = this.cacheTypicalPrice(candle); this.sma.update(typicalPrice); diff --git a/src/DX/DX.ts b/src/DX/DX.ts index 8e79471e2..43db02a6c 100644 --- a/src/DX/DX.ts +++ b/src/DX/DX.ts @@ -46,6 +46,7 @@ export class DX extends BigIndicatorSeries { this.previousCandle = candle; } + // TODO: Implement "replace" parameter update(candle: HighLowClose): Big | void { if (!this.previousCandle) { this.updateState(candle); diff --git a/src/Indicator.ts b/src/Indicator.ts index 61f1c4507..0438feca8 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -6,6 +6,7 @@ export interface Indicator { isStable: boolean; + // TODO: Add "replace" parameter update(input: Input): void | Result; // TODO: Implement this function for all indicators, ensuring each returns "this.result" diff --git a/src/STOCH/StochasticRSI.test.ts b/src/STOCH/StochasticRSI.test.ts index 2703274d6..22eaec25a 100644 --- a/src/STOCH/StochasticRSI.test.ts +++ b/src/STOCH/StochasticRSI.test.ts @@ -1,6 +1,20 @@ import {FasterStochasticRSI, StochasticRSI} from './StochasticRSI.js'; describe('StochasticRSI', () => { + describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const interval = 2; + const stochRSI = new StochasticRSI(interval); + const stochRSIWithReplace = new StochasticRSI(interval); + + stochRSI.updates([2, 2, 2, 2]); + stochRSIWithReplace.updates([2, 2, 2, 1]); + stochRSIWithReplace.replace(2); + + expect(stochRSI.getResult().valueOf()).toBe(stochRSIWithReplace.getResult().valueOf()); + }); + }); + describe('getResult', () => { it('calculates the Stochastic RSI', () => { // Test data verified with: @@ -9,8 +23,9 @@ describe('StochasticRSI', () => { 81.59, 81.06, 82.87, 83.0, 83.61, 83.15, 82.84, 83.99, 84.55, 84.36, 85.53, 86.54, 86.89, 87.77, 87.29, ]; const expectations = ['0.658', '1.000', '1.000', '1.000', '1.000', '0.000']; - const stochRSI = new StochasticRSI(5); - const fasterStochRSI = new FasterStochasticRSI(5); + const interval = 5; + const stochRSI = new StochasticRSI(interval); + const fasterStochRSI = new FasterStochasticRSI(interval); for (const price of prices) { const result = stochRSI.update(price); const fasterResult = fasterStochRSI.update(price); @@ -34,18 +49,13 @@ describe('StochasticRSI', () => { }); it('catches division by zero errors', () => { - const stochRSI = new StochasticRSI(2); - stochRSI.update(2); - stochRSI.update(2); - stochRSI.update(2); - stochRSI.update(2); + const interval = 2; + const stochRSI = new StochasticRSI(interval); + stochRSI.updates([2, 2, 2, 2]); expect(stochRSI.getResult().valueOf()).toBe('100'); - const fasterStochRSI = new FasterStochasticRSI(2); - fasterStochRSI.update(2); - fasterStochRSI.update(2); - fasterStochRSI.update(2); - fasterStochRSI.update(2); + const fasterStochRSI = new FasterStochasticRSI(interval); + fasterStochRSI.updates([2, 2, 2, 2]); expect(fasterStochRSI.getResult().valueOf()).toBe(100); }); }); diff --git a/src/STOCH/StochasticRSI.ts b/src/STOCH/StochasticRSI.ts index c357018a7..385beefbb 100644 --- a/src/STOCH/StochasticRSI.ts +++ b/src/STOCH/StochasticRSI.ts @@ -33,7 +33,8 @@ export class StochasticRSI extends BigIndicatorSeries { this.rsi = new RSI(interval, SmoothingIndicator); } - override update(price: BigSource, replace: boolean = false): void | Big { + // TODO: Implement "replace" + override update(price: BigSource): void | Big { const rsiResult = this.rsi.update(price); if (rsiResult) { const periodResult = this.period.update(rsiResult); @@ -43,10 +44,10 @@ export class StochasticRSI extends BigIndicatorSeries { const denominator = max.minus(min); // Prevent division by zero: https://github.com/bennycode/trading-signals/issues/378 if (denominator.eq(0)) { - return this.setResult(new Big(100), replace); + return this.setResult(new Big(100), false); } const numerator = rsiResult.minus(min); - return this.setResult(numerator.div(denominator), replace); + return this.setResult(numerator.div(denominator), false); } } } diff --git a/src/WSMA/WSMA.ts b/src/WSMA/WSMA.ts index 9a5d20724..542244085 100644 --- a/src/WSMA/WSMA.ts +++ b/src/WSMA/WSMA.ts @@ -63,7 +63,7 @@ export class FasterWSMA extends NumberIndicatorSeries { } update(price: number, replace: boolean = false): number | void { - const sma = this.indicator.update(price); + const sma = this.indicator.update(price, replace); if (replace && this.previousResult !== undefined) { const smoothed = (price - this.previousResult) * this.smoothingFactor; return this.setResult(smoothed + this.previousResult, replace); diff --git a/src/util/Period.ts b/src/util/Period.ts index 7f159e25d..8411788bc 100644 --- a/src/util/Period.ts +++ b/src/util/Period.ts @@ -40,6 +40,8 @@ export class Period implements Indicator { values.forEach(value => this.update(value)); } + // TODO: Implement "replace" + // Info: This may not work with "getFixedArray" as it shifts values out of our control update(value: BigSource): PeriodResult | void { this.values.push(new Big(value)); if (this.isStable) { From 22e1eb2c661d125b1cc76f730fe644268f30ff5f Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Sun, 15 Dec 2024 04:47:35 -0800 Subject: [PATCH 08/12] add replacements --- .lintstagedrc.json | 2 +- src/CCI/CCI.test.ts | 27 ++++++++++- src/CCI/CCI.ts | 23 +++++----- src/CG/CG.ts | 14 ++---- src/Indicator.ts | 13 +++--- src/MACD/MACD.ts | 18 +++----- src/MAD/MAD.ts | 14 ++---- src/MOM/MOM.ts | 16 ++----- src/RSI/RSI.ts | 18 ++------ src/SMA/SMA.ts | 16 ++----- src/STOCH/StochasticRSI.ts | 11 ++--- src/TR/TR.ts | 4 +- src/WMA/WMA.ts | 14 ++---- src/util/Period.test.ts | 30 +++++++++--- src/util/Period.ts | 71 ++++++++++++++++++++--------- src/util/getLastFromForEach.test.ts | 9 ++++ src/util/getLastFromForEach.ts | 12 +++++ src/util/index.ts | 2 + src/util/pushUpdate.ts | 7 +++ 19 files changed, 182 insertions(+), 139 deletions(-) create mode 100644 src/util/getLastFromForEach.test.ts create mode 100644 src/util/getLastFromForEach.ts create mode 100644 src/util/pushUpdate.ts diff --git a/.lintstagedrc.json b/.lintstagedrc.json index f21a4dd69..ffa891040 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,3 +1,3 @@ { - "*.{js,jsx,ts,tsx}": ["yarn fix:code"] + "*.{js,jsx,ts,tsx}": ["npm run fix:code"] } diff --git a/src/CCI/CCI.test.ts b/src/CCI/CCI.test.ts index 9fd6aedcb..c180baaa8 100644 --- a/src/CCI/CCI.test.ts +++ b/src/CCI/CCI.test.ts @@ -19,10 +19,33 @@ describe('CCI', () => { ]; const expectations = ['166.67', '82.02', '95.50', '130.91', '99.16', '116.34', '71.93']; + describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const interval = 5; + const cci = new CCI(interval); + const cciWithReplace = new CCI(interval); + + const correct = {close: 300, high: 300, low: 300}; + const wrong = {close: 1_000, high: 1_000, low: 1_000}; + + cci.updates(candles); + cci.update(correct); + + cciWithReplace.updates(candles); + cciWithReplace.update(wrong); + + const expected = cci.getResult(); + const actual = cciWithReplace.getResult(); + + expect(actual.toFixed()).toBe(expected.toFixed()); + }); + }); + describe('getResult', () => { it('calculates the Commodity Channel Index (CCI)', () => { - const cci = new CCI(5); - const fasterCCI = new FasterCCI(5); + const interval = 5; + const cci = new CCI(interval); + const fasterCCI = new FasterCCI(interval); for (const candle of candles) { cci.update(candle); fasterCCI.update(candle); diff --git a/src/CCI/CCI.ts b/src/CCI/CCI.ts index febfb3661..a14ff7e58 100644 --- a/src/CCI/CCI.ts +++ b/src/CCI/CCI.ts @@ -1,5 +1,5 @@ import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {Big, type BigSource} from '../index.js'; +import {Big, pushUpdate, type BigSource} from '../index.js'; import type {HighLowClose, HighLowCloseNumber} from '../util/index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; import {FasterMAD, MAD} from '../MAD/MAD.js'; @@ -30,10 +30,9 @@ export class CCI extends BigIndicatorSeries { this.sma = new SMA(this.interval); } - // TODO: Implement "replace" parameter - update(candle: HighLowClose): void | Big { - const typicalPrice = this.cacheTypicalPrice(candle); - this.sma.update(typicalPrice); + update(candle: HighLowClose, replace: boolean = false): void | Big { + const typicalPrice = this.cacheTypicalPrice(candle, replace); + this.sma.update(typicalPrice, replace); if (this.sma.isStable) { const mean = this.sma.getResult(); const meanDeviation = MAD.getResultFromBatch(this.typicalPrices, mean); @@ -43,9 +42,9 @@ export class CCI extends BigIndicatorSeries { } } - private cacheTypicalPrice({high, low, close}: HighLowClose): Big { + private cacheTypicalPrice({high, low, close}: HighLowClose, replace: boolean): Big { const typicalPrice = new Big(high).plus(low).plus(close).div(3); - this.typicalPrices.push(typicalPrice); + pushUpdate(this.typicalPrices, replace, typicalPrice); if (this.typicalPrices.length > this.interval) { this.typicalPrices.shift(); } @@ -63,9 +62,9 @@ export class FasterCCI extends NumberIndicatorSeries { this.sma = new FasterSMA(this.interval); } - override update(candle: HighLowCloseNumber): void | number { - const typicalPrice = this.cacheTypicalPrice(candle); - this.sma.update(typicalPrice); + override update(candle: HighLowCloseNumber, replace: boolean = false): void | number { + const typicalPrice = this.cacheTypicalPrice(candle, replace); + this.sma.update(typicalPrice, replace); if (this.sma.isStable) { const mean = this.sma.getResult(); const meanDeviation = FasterMAD.getResultFromBatch(this.typicalPrices, mean); @@ -75,9 +74,9 @@ export class FasterCCI extends NumberIndicatorSeries { } } - private cacheTypicalPrice({high, low, close}: HighLowCloseNumber): number { + private cacheTypicalPrice({high, low, close}: HighLowCloseNumber, replace: boolean): number { const typicalPrice = (high + low + close) / 3; - this.typicalPrices.push(typicalPrice); + pushUpdate(this.typicalPrices, replace, typicalPrice); if (this.typicalPrices.length > this.interval) { this.typicalPrices.shift(); } diff --git a/src/CG/CG.ts b/src/CG/CG.ts index e2d9e9915..441fd2f92 100644 --- a/src/CG/CG.ts +++ b/src/CG/CG.ts @@ -1,5 +1,5 @@ import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {Big, type BigSource} from '../index.js'; +import {Big, pushUpdate, type BigSource} from '../index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; /** @@ -33,11 +33,7 @@ export class CG extends BigIndicatorSeries { } override update(price: BigSource, replace: boolean = false): void | Big { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = new Big(price); - } else { - this.prices.push(new Big(price)); - } + pushUpdate(this.prices, replace, new Big(price)); if (this.prices.length > this.interval) { this.prices.shift(); @@ -80,11 +76,7 @@ export class FasterCG extends NumberIndicatorSeries { } override update(price: number, replace: boolean = false): void | number { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); diff --git a/src/Indicator.ts b/src/Indicator.ts index 0438feca8..2941150f2 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -6,12 +6,11 @@ export interface Indicator { isStable: boolean; - // TODO: Add "replace" parameter - update(input: Input): void | Result; + replace(input: Input): void | Result; - // TODO: Implement this function for all indicators, ensuring each returns "this.result" - // TODO: Add support for the "replace" parameter in the future - updates(input: Input[]): void | Result; + update(input: Input, replace: boolean): void | Result; + + updates(input: Input[], replace: boolean): void | Result; } /** @@ -80,11 +79,11 @@ export abstract class BigIndicatorSeries implements Indicator } updates(prices: Input[]): void | Big { - prices.forEach(price => this.update(price)); + prices.forEach(price => this.update(price, false)); return this.result; } - abstract update(input: Input, replace?: boolean): void | Big; + abstract update(input: Input, replace: boolean): void | Big; replace(input: Input) { return this.update(input, true); diff --git a/src/MACD/MACD.ts b/src/MACD/MACD.ts index 6daca1845..238e44209 100644 --- a/src/MACD/MACD.ts +++ b/src/MACD/MACD.ts @@ -1,5 +1,5 @@ import type {EMA, FasterEMA} from '../EMA/EMA.js'; -import {Big, NotEnoughDataError, type BigSource, type DEMA, type FasterDEMA} from '../index.js'; +import {Big, NotEnoughDataError, pushUpdate, type BigSource, type DEMA, type FasterDEMA} from '../index.js'; import type {Indicator} from '../Indicator.js'; export type MACDConfig = { @@ -59,11 +59,7 @@ export class MACD implements Indicator { update(_price: BigSource, replace: boolean = false): void | MACDResult { const price = new Big(_price); - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); const short = this.short.update(price, replace); const long = this.long.update(price, replace); @@ -115,6 +111,10 @@ export class FasterMACD implements Indicator { public readonly signal: FasterEMA | FasterDEMA ) {} + replace(input: number): void | FasterMACDResult { + return this.update(input, true); + } + getResult(): FasterMACDResult { if (this.result === undefined) { throw new NotEnoughDataError(); @@ -133,11 +133,7 @@ export class FasterMACD implements Indicator { } update(price: number, replace: boolean = false): void | FasterMACDResult { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); const short = this.short.update(price, replace); const long = this.long.update(price, replace); diff --git a/src/MAD/MAD.ts b/src/MAD/MAD.ts index 34673c979..6d87e91bf 100644 --- a/src/MAD/MAD.ts +++ b/src/MAD/MAD.ts @@ -1,6 +1,6 @@ import {Big, type BigSource} from '../index.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {getAverage, getFasterAverage} from '../util/index.js'; +import {getAverage, getFasterAverage, pushUpdate} from '../util/index.js'; /** * Mean Absolute Deviation (MAD) @@ -19,11 +19,7 @@ export class MAD extends BigIndicatorSeries { } override update(price: BigSource, replace: boolean = false): void | Big { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); @@ -53,11 +49,7 @@ export class FasterMAD extends NumberIndicatorSeries { } override update(price: number, replace: boolean = false): void | number { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); diff --git a/src/MOM/MOM.ts b/src/MOM/MOM.ts index d79d2ce3a..8129cb574 100644 --- a/src/MOM/MOM.ts +++ b/src/MOM/MOM.ts @@ -1,5 +1,5 @@ import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {Big, type BigSource} from '../index.js'; +import {Big, pushUpdate, type BigSource} from '../index.js'; import {getFixedArray} from '../util/getFixedArray.js'; /** @@ -22,11 +22,8 @@ export class MOM extends BigIndicatorSeries { } override update(value: BigSource, replace: boolean = false): void | Big { - if (this.history.length && replace) { - this.history[this.history.length - 1] = value; - } else { - this.history.push(value); - } + pushUpdate(this.history, replace, value); + if (this.history.length === this.historyLength) { return this.setResult(new Big(value).minus(this.history[0]), replace); } @@ -44,11 +41,8 @@ export class FasterMOM extends NumberIndicatorSeries { } override update(value: number, replace: boolean = false): void | number { - if (this.history.length && replace) { - this.history[this.history.length - 1] = value; - } else { - this.history.push(value); - } + pushUpdate(this.history, replace, value); + if (this.history.length === this.historyLength) { return this.setResult(value - this.history[0], replace); } diff --git a/src/RSI/RSI.ts b/src/RSI/RSI.ts index 1e3de797b..8a71d86cc 100644 --- a/src/RSI/RSI.ts +++ b/src/RSI/RSI.ts @@ -1,4 +1,4 @@ -import {Big, type BigSource} from '../index.js'; +import {Big, pushUpdate, type BigSource} from '../index.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; @@ -37,13 +37,7 @@ export class RSI extends BigIndicatorSeries { } override update(price: BigSource, replace: boolean = false): void | Big { - if (this.previousPrices.length && replace) { - // Replace the last price with the provided price - this.previousPrices[this.previousPrices.length - 1] = price; - } else { - // Add the price to the list of previous prices - this.previousPrices.push(price); - } + pushUpdate(this.previousPrices, replace, price); // Ensure at least 2 prices are available for calculation if (this.previousPrices.length < 2) { @@ -89,13 +83,7 @@ export class FasterRSI extends NumberIndicatorSeries { } override update(price: number, replace: boolean = false): void | number { - if (this.previousPrices.length && replace) { - // Replace the last price with the provided price - this.previousPrices[this.previousPrices.length - 1] = price; - } else { - // Add the price to the list of previous prices - this.previousPrices.push(price); - } + pushUpdate(this.previousPrices, replace, price); // Ensure at least 2 prices are available for calculation if (this.previousPrices.length < 2) { diff --git a/src/SMA/SMA.ts b/src/SMA/SMA.ts index 5c75e033e..84dffb949 100644 --- a/src/SMA/SMA.ts +++ b/src/SMA/SMA.ts @@ -1,4 +1,4 @@ -import {Big, type BigSource} from '../index.js'; +import {Big, pushUpdate, type BigSource} from '../index.js'; import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; /** @@ -14,11 +14,7 @@ export class SMA extends MovingAverage { public readonly prices: BigSource[] = []; override update(price: BigSource, replace: boolean = false): Big | void { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); @@ -38,12 +34,8 @@ export class SMA extends MovingAverage { export class FasterSMA extends FasterMovingAverage { public readonly prices: number[] = []; - update(price: number, replace: boolean = false): void | number { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + override update(price: number, replace: boolean = false): void | number { + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); diff --git a/src/STOCH/StochasticRSI.ts b/src/STOCH/StochasticRSI.ts index 385beefbb..e3da1732a 100644 --- a/src/STOCH/StochasticRSI.ts +++ b/src/STOCH/StochasticRSI.ts @@ -33,11 +33,10 @@ export class StochasticRSI extends BigIndicatorSeries { this.rsi = new RSI(interval, SmoothingIndicator); } - // TODO: Implement "replace" - override update(price: BigSource): void | Big { - const rsiResult = this.rsi.update(price); + override update(price: BigSource, replace: boolean = false): void | Big { + const rsiResult = this.rsi.update(price, replace); if (rsiResult) { - const periodResult = this.period.update(rsiResult); + const periodResult = this.period.update(rsiResult, replace); if (periodResult) { const min = periodResult.lowest; const max = periodResult.highest; @@ -67,9 +66,9 @@ export class FasterStochasticRSI extends NumberIndicatorSeries { } override update(price: number, replace: boolean = false): void | number { - const rsiResult = this.rsi.update(price); + const rsiResult = this.rsi.update(price, replace); if (rsiResult !== undefined) { - const periodResult = this.period.update(rsiResult); + const periodResult = this.period.update(rsiResult, replace); if (periodResult) { const min = periodResult.lowest; const max = periodResult.highest; diff --git a/src/TR/TR.ts b/src/TR/TR.ts index 842c22159..90ac230e2 100644 --- a/src/TR/TR.ts +++ b/src/TR/TR.ts @@ -17,7 +17,7 @@ export class TR extends BigIndicatorSeries { private previousCandle?: HighLowClose; private twoPreviousCandle?: HighLowClose; - update(candle: HighLowClose, replace: boolean = false): Big { + override update(candle: HighLowClose, replace: boolean = false): Big { const high = new Big(candle.high); const highLow = high.minus(candle.low); @@ -43,7 +43,7 @@ export class FasterTR extends NumberIndicatorSeries { private previousCandle?: HighLowCloseNumber; private twoPreviousCandle?: HighLowCloseNumber; - update(candle: HighLowCloseNumber, replace: boolean = false): number { + override update(candle: HighLowCloseNumber, replace: boolean = false): number { const {high, low} = candle; const highLow = high - low; diff --git a/src/WMA/WMA.ts b/src/WMA/WMA.ts index bdcc2624f..ee0c09330 100644 --- a/src/WMA/WMA.ts +++ b/src/WMA/WMA.ts @@ -1,4 +1,4 @@ -import {Big, type BigSource} from '../index.js'; +import {Big, pushUpdate, type BigSource} from '../index.js'; import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; /** @@ -18,11 +18,7 @@ export class WMA extends MovingAverage { } override update(price: BigSource, replace: boolean = false): Big | void { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); @@ -51,11 +47,7 @@ export class FasterWMA extends FasterMovingAverage { } override update(price: number, replace: boolean = false): number | void { - if (this.prices.length && replace) { - this.prices[this.prices.length - 1] = price; - } else { - this.prices.push(price); - } + pushUpdate(this.prices, replace, price); if (this.prices.length > this.interval) { this.prices.shift(); diff --git a/src/util/Period.test.ts b/src/util/Period.test.ts index b2727ff55..c03944e65 100644 --- a/src/util/Period.test.ts +++ b/src/util/Period.test.ts @@ -2,16 +2,33 @@ import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; import {FasterPeriod, Period} from './Period.js'; describe('Period', () => { + describe('replace', () => { + it('guarantees that a replacement is done correctly', () => { + const interval = 5; + const period = new Period(interval); + const periodWithReplace = new Period(interval); + + const subset = [30, 40, 50, 60]; + period.updates([...subset, 70]); + periodWithReplace.updates([...subset, 90]); + periodWithReplace.replace(70); + + expect(periodWithReplace.lowest?.toFixed()).toBe(period.lowest?.toFixed()); + expect(periodWithReplace.highest?.toFixed()).toBe(period.highest?.toFixed()); + }); + }); + describe('getResult', () => { it('returns the highest and lowest value of the current period', () => { const values = [72, 1337]; - const period = new Period(2); + const interval = 2; + const period = new Period(interval); period.updates(values); const {highest, lowest} = period.getResult(); expect(lowest.valueOf()).toBe('72'); expect(highest.valueOf()).toBe('1337'); - const fasterPeriod = new FasterPeriod(2); + const fasterPeriod = new FasterPeriod(interval); fasterPeriod.updates(values); const {highest: fastestHighest, lowest: fastestLowest} = fasterPeriod.getResult(); expect(fastestLowest).toBe(72); @@ -49,19 +66,20 @@ describe('Period', () => { '84.36', '85.53', ]; - const period = new Period(5); - const fasterPeriod = new FasterPeriod(5); + const interval = 5; + const period = new Period(interval); + const fasterPeriod = new FasterPeriod(interval); for (const price of prices) { period.update(price); fasterPeriod.update(price); if (period.isStable) { const expected = lowest.shift(); expect(period.lowest?.toFixed(2)).toBe(expected); - expect(fasterPeriod.lowest?.toFixed(2)).toBe(expected); + expect(fasterPeriod._lowest?.toFixed(2)).toBe(expected); } } expect(period.highest?.toFixed(2)).toBe('87.77'); - expect(fasterPeriod.highest?.toFixed(2)).toBe('87.77'); + expect(fasterPeriod._highest?.toFixed(2)).toBe('87.77'); }); }); }); diff --git a/src/util/Period.ts b/src/util/Period.ts index 8411788bc..70f813771 100644 --- a/src/util/Period.ts +++ b/src/util/Period.ts @@ -1,4 +1,4 @@ -import {Big, NotEnoughDataError, type BigSource} from '../index.js'; +import {Big, NotEnoughDataError, pushUpdate, type BigSource} from '../index.js'; import type {Indicator} from '../Indicator.js'; import {getFixedArray} from './getFixedArray.js'; import {getMinimum} from './getMinimum.js'; @@ -15,38 +15,50 @@ export interface FasterPeriodResult { } export class Period implements Indicator { - public values: Big[]; + private readonly values: Big[]; /** Highest return value during the current period. */ - public highest?: Big; + private _highest?: Big; /** Lowest return value during the current period. */ - public lowest?: Big; + private _lowest?: Big; + + get highest() { + return this._highest; + } + + get lowest() { + return this._lowest; + } constructor(public readonly interval: number) { this.values = getFixedArray(interval); } + replace(input: Big.BigSource) { + return this.update(input, true); + } + getResult(): PeriodResult { - if (!this.lowest || !this.highest) { + if (!this._lowest || !this._highest) { throw new NotEnoughDataError(); } return { - highest: this.highest, - lowest: this.lowest, + highest: this._highest, + lowest: this._lowest, }; } updates(values: BigSource[]) { + // TODO: Use foreach with last values.forEach(value => this.update(value)); } - // TODO: Implement "replace" - // Info: This may not work with "getFixedArray" as it shifts values out of our control - update(value: BigSource): PeriodResult | void { - this.values.push(new Big(value)); + override update(value: BigSource, replace: boolean = false): PeriodResult | void { + pushUpdate(this.values, replace, new Big(value)); + if (this.isStable) { - this.lowest = getMinimum(this.values); - this.highest = getMaximum(this.values); + this._lowest = getMinimum(this.values); + this._highest = getMaximum(this.values); return this.getResult(); } } @@ -59,30 +71,47 @@ export class Period implements Indicator { export class FasterPeriod implements Indicator { public values: number[]; /** Highest return value during the current period. */ - public highest?: number; + private _highest?: number; /** Lowest return value during the current period. */ - public lowest?: number; + private _lowest?: number; + + get highest() { + return this._highest; + } + + get lowest() { + return this._lowest; + } constructor(public readonly interval: number) { this.values = getFixedArray(interval); } + replace(input: number): void | FasterPeriodResult { + return this.update(input, true); + } + updates(values: number[]) { values.forEach(value => this.update(value)); } getResult(): FasterPeriodResult { + if (!this._lowest || !this._highest) { + throw new NotEnoughDataError(); + } + return { - highest: this.highest!, - lowest: this.lowest!, + highest: this._highest, + lowest: this._lowest, }; } - update(value: number): FasterPeriodResult | void { - this.values.push(value); + update(value: number, replace: boolean = false): FasterPeriodResult | void { + pushUpdate(this.values, replace, value); + if (this.isStable) { - this.lowest = Math.min(...this.values); - this.highest = Math.max(...this.values); + this._lowest = Math.min(...this.values); + this._highest = Math.max(...this.values); return this.getResult(); } } diff --git a/src/util/getLastFromForEach.test.ts b/src/util/getLastFromForEach.test.ts new file mode 100644 index 000000000..c207bd094 --- /dev/null +++ b/src/util/getLastFromForEach.test.ts @@ -0,0 +1,9 @@ +import {getLastFromForEach} from './getLastFromForEach.js'; + +describe('getLastFromForEach', () => { + it('returns the last value from an forEach execution', () => { + const array = [1, 2, 3, 4]; + const result = getLastFromForEach(array, value => value * 2); + expect(result).toBe(8); + }); +}); diff --git a/src/util/getLastFromForEach.ts b/src/util/getLastFromForEach.ts new file mode 100644 index 000000000..c0dec18c1 --- /dev/null +++ b/src/util/getLastFromForEach.ts @@ -0,0 +1,12 @@ +export function getLastFromForEach( + array: T[], + callback: (value: T, index: number, array: T[]) => R +): R | undefined { + let lastValue: R | undefined; + + array.forEach((item, index) => { + lastValue = callback(item, index, array); + }); + + return lastValue; +} diff --git a/src/util/index.ts b/src/util/index.ts index d4a4d09e4..003f964ff 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,9 +1,11 @@ export * from './BandsResult.js'; export * from './getAverage.js'; export * from './getFixedArray.js'; +export * from './getLastFromForEach.js'; export * from './getMaximum.js'; export * from './getMinimum.js'; export * from './getStandardDeviation.js'; export * from './getStreaks.js'; export * from './HighLowClose.js'; export * from './Period.js'; +export * from './pushUpdate.js'; diff --git a/src/util/pushUpdate.ts b/src/util/pushUpdate.ts new file mode 100644 index 000000000..5c4e7768c --- /dev/null +++ b/src/util/pushUpdate.ts @@ -0,0 +1,7 @@ +export function pushUpdate(array: T[], replace: boolean, update: T) { + if (array.length && replace) { + array[array.length - 1] = update; + } else { + array.push(update); + } +} From cd3fd5b9ad183457060cac3dcfdf60426cf4a94d Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Sat, 28 Dec 2024 22:26:58 -0800 Subject: [PATCH 09/12] fixing types --- package.json | 2 +- src/util/Period.test.ts | 3 +++ src/util/Period.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 36299a29a..99fa792bc 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "release:minor": "generate-changelog -m -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version minor", "release:patch": "generate-changelog -p -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version patch", "start:benchmark": "tsc --noEmit && node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only ./src/start/startBenchmark.ts", - "test": "npm run test:dev -- --coverage", + "test": "npm run test:types && npm run test:dev -- --coverage", "test:dev": "vitest run --passWithNoTests", "test:types": "npm run lint:types" }, diff --git a/src/util/Period.test.ts b/src/util/Period.test.ts index c03944e65..52b1b0b19 100644 --- a/src/util/Period.test.ts +++ b/src/util/Period.test.ts @@ -28,6 +28,9 @@ describe('Period', () => { expect(lowest.valueOf()).toBe('72'); expect(highest.valueOf()).toBe('1337'); + expect(period.lowest?.valueOf()).toBe('72'); + expect(period.highest?.valueOf()).toBe('1337'); + const fasterPeriod = new FasterPeriod(interval); fasterPeriod.updates(values); const {highest: fastestHighest, lowest: fastestLowest} = fasterPeriod.getResult(); diff --git a/src/util/Period.ts b/src/util/Period.ts index 70f813771..33d6e2146 100644 --- a/src/util/Period.ts +++ b/src/util/Period.ts @@ -53,7 +53,7 @@ export class Period implements Indicator { values.forEach(value => this.update(value)); } - override update(value: BigSource, replace: boolean = false): PeriodResult | void { + update(value: BigSource, replace: boolean = false): PeriodResult | void { pushUpdate(this.values, replace, new Big(value)); if (this.isStable) { From 0862e774cc8ad8ea6e7966b17d3da8f12c6b3911 Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Sun, 29 Dec 2024 09:48:02 -0800 Subject: [PATCH 10/12] extend technical indicator --- src/DMA/DMA.ts | 4 +++- src/Indicator.ts | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/DMA/DMA.ts b/src/DMA/DMA.ts index d6eb023df..1f69656f9 100644 --- a/src/DMA/DMA.ts +++ b/src/DMA/DMA.ts @@ -1,4 +1,5 @@ import type {Big, BigSource} from '../index.js'; +import {TechnicalIndicator} from '../index.js'; import type {Indicator} from '../Indicator.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; @@ -23,11 +24,12 @@ export interface FasterDMAResult { * * @see https://faculty.fuqua.duke.edu/~charvey/Teaching/BA453_2002/CCAM/CCAM.htm#_Toc2634228 */ -export class DMA implements Indicator { +export class DMA extends TechnicalIndicator { public readonly short: MovingAverage; public readonly long: MovingAverage; constructor(short: number, long: number, Indicator: MovingAverageTypes = SMA) { + super(); this.short = new Indicator(short); this.long = new Indicator(long); } diff --git a/src/Indicator.ts b/src/Indicator.ts index 2941150f2..43a35393c 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -1,5 +1,5 @@ import {NotEnoughDataError} from './error/index.js'; -import type {Big, BigSource} from './index.js'; +import {getLastFromForEach, type Big, type BigSource} from './index.js'; export interface Indicator { getResult(): Result; @@ -13,6 +13,22 @@ export interface Indicator { updates(input: Input[], replace: boolean): void | Result; } +export abstract class TechnicalIndicator implements Indicator { + accessor isStable: boolean = false; + + abstract getResult(): Result; + + replace(input: Input) { + return this.update(input, true); + } + + abstract update(input: Input, replace: boolean): void | Result; + + updates(inputs: Input[], replace: boolean) { + return getLastFromForEach(inputs, input => this.update(input, replace)); + } +} + /** * Tracks results of an indicator over time and memorizes the highest & lowest result. */ From a747ac85064573ad4d66cb7d8bd227212972a5c2 Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Wed, 1 Jan 2025 23:22:22 -0800 Subject: [PATCH 11/12] fixing compile issues --- src/ABANDS/AccelerationBands.ts | 9 +++++---- src/BBANDS/BollingerBands.ts | 15 +++++++++------ src/DMA/DMA.ts | 4 ++-- src/STOCH/StochasticOscillator.ts | 9 +++++---- src/index.ts | 6 +++--- src/util/Period.test.ts | 4 ++-- vitest.config.ts | 3 +++ 7 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/ABANDS/AccelerationBands.ts b/src/ABANDS/AccelerationBands.ts index 3dcbbc518..7f05c2fc0 100644 --- a/src/ABANDS/AccelerationBands.ts +++ b/src/ABANDS/AccelerationBands.ts @@ -1,13 +1,12 @@ -import {Big} from '../index.js'; +import {Big, TechnicalIndicator} from '../index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; import {NotEnoughDataError} from '../error/index.js'; import type {BandsResult, FasterBandsResult} from '../util/BandsResult.js'; -import type {Indicator} from '../Indicator.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import type {HighLowClose, HighLowCloseNumber} from '../util/index.js'; -export class AccelerationBands implements Indicator { +export class AccelerationBands extends TechnicalIndicator { private readonly lowerBand: MovingAverage; private readonly middleBand: MovingAverage; private readonly upperBand: MovingAverage; @@ -37,6 +36,7 @@ export class AccelerationBands implements Indicator { public readonly width: number, SmoothingIndicator: MovingAverageTypes = SMA ) { + super(); this.lowerBand = new SmoothingIndicator(interval); this.middleBand = new SmoothingIndicator(interval); this.upperBand = new SmoothingIndicator(interval); @@ -75,7 +75,7 @@ export class AccelerationBands implements Indicator { } } -export class FasterAccelerationBands implements Indicator { +export class FasterAccelerationBands extends TechnicalIndicator { private readonly lowerBand: FasterMovingAverage; private readonly middleBand: FasterMovingAverage; private readonly upperBand: FasterMovingAverage; @@ -85,6 +85,7 @@ export class FasterAccelerationBands implements Indicator { +export class BollingerBands extends TechnicalIndicator { public readonly prices: Big[] = []; private result: BandsResult | undefined; @@ -32,7 +31,9 @@ export class BollingerBands implements Indicator { constructor( public readonly interval: number, public readonly deviationMultiplier: number = 2 - ) {} + ) { + super(); + } get isStable(): boolean { return this.result !== undefined; @@ -69,14 +70,16 @@ export class BollingerBands implements Indicator { } } -export class FasterBollingerBands implements Indicator { +export class FasterBollingerBands extends TechnicalIndicator { public readonly prices: number[] = []; private result: FasterBandsResult | undefined; constructor( public readonly interval: number, public readonly deviationMultiplier: number = 2 - ) {} + ) { + super(); + } updates(prices: number[]) { prices.forEach(price => this.update(price)); diff --git a/src/DMA/DMA.ts b/src/DMA/DMA.ts index 1f69656f9..827f7764f 100644 --- a/src/DMA/DMA.ts +++ b/src/DMA/DMA.ts @@ -1,6 +1,5 @@ import type {Big, BigSource} from '../index.js'; import {TechnicalIndicator} from '../index.js'; -import type {Indicator} from '../Indicator.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; @@ -55,11 +54,12 @@ export class DMA extends TechnicalIndicator { } } -export class FasterDMA implements Indicator { +export class FasterDMA extends TechnicalIndicator { public readonly short: FasterMovingAverage; public readonly long: FasterMovingAverage; constructor(short: number, long: number, SmoothingIndicator: FasterMovingAverageTypes = FasterSMA) { + super(); this.short = new SmoothingIndicator(short); this.long = new SmoothingIndicator(long); } diff --git a/src/STOCH/StochasticOscillator.ts b/src/STOCH/StochasticOscillator.ts index c39ad0f91..a5ad87a31 100644 --- a/src/STOCH/StochasticOscillator.ts +++ b/src/STOCH/StochasticOscillator.ts @@ -1,5 +1,4 @@ -import type {Indicator} from '../Indicator.js'; -import {Big} from '../index.js'; +import {Big, TechnicalIndicator} from '../index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; import {getMaximum} from '../util/getMaximum.js'; import {getMinimum} from '../util/getMinimum.js'; @@ -37,7 +36,7 @@ export interface FasterStochasticResult { * @see https://en.wikipedia.org/wiki/Stochastic_oscillator * @see https://www.investopedia.com/terms/s/stochasticoscillator.asp */ -export class StochasticOscillator implements Indicator { +export class StochasticOscillator extends TechnicalIndicator { private readonly periodM: SMA; private readonly periodP: SMA; @@ -56,6 +55,7 @@ export class StochasticOscillator implements Indicator { +export class FasterStochasticOscillator extends TechnicalIndicator { public readonly candles: HighLowCloseNumber[] = []; private result: FasterStochasticResult | undefined; private readonly periodM: FasterSMA; @@ -119,6 +119,7 @@ export class FasterStochasticOscillator implements Indicator { if (period.isStable) { const expected = lowest.shift(); expect(period.lowest?.toFixed(2)).toBe(expected); - expect(fasterPeriod._lowest?.toFixed(2)).toBe(expected); + expect(fasterPeriod.lowest?.toFixed(2)).toBe(expected); } } expect(period.highest?.toFixed(2)).toBe('87.77'); - expect(fasterPeriod._highest?.toFixed(2)).toBe('87.77'); + expect(fasterPeriod.highest?.toFixed(2)).toBe('87.77'); }); }); }); diff --git a/vitest.config.ts b/vitest.config.ts index 0b1b860bf..b452d8408 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,9 @@ import {defineConfig} from 'vitest/config'; export default defineConfig({ + esbuild: { + target: 'es2022', + }, test: { bail: 1, coverage: { From f08199d702d19b54682d53976ba79be9e00466ef Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Wed, 8 Jan 2025 15:50:30 +0100 Subject: [PATCH 12/12] add depchecker --- .dependency-cruiser.cjs | 380 +++++++++++++++++++++ package-lock.json | 542 ++++++++++++++++++++++++++++-- package.json | 4 +- src/ABANDS/AccelerationBands.ts | 23 +- src/AC/AC.ts | 2 +- src/ADX/ADX.ts | 4 +- src/AO/AO.ts | 2 +- src/ATR/ATR.ts | 3 +- src/BBANDS/BollingerBands.ts | 26 +- src/BBW/BollingerBandsWidth.ts | 2 +- src/CCI/CCI.ts | 5 +- src/CG/CG.ts | 4 +- src/DEMA/DEMA.ts | 2 +- src/DMA/DMA.ts | 16 +- src/DX/DX.ts | 9 +- src/EMA/EMA.ts | 7 +- src/Indicator.ts | 13 +- src/MA/MovingAverage.ts | 23 -- src/MACD/MACD.ts | 6 +- src/MAD/MAD.ts | 3 +- src/MOM/MOM.ts | 4 +- src/OBV/OBV.ts | 4 +- src/ROC/ROC.ts | 3 +- src/RSI/RSI.ts | 4 +- src/SMA/SMA.ts | 4 +- src/STOCH/StochasticOscillator.ts | 28 +- src/STOCH/StochasticRSI.ts | 3 +- src/TR/TR.ts | 5 +- src/WMA/WMA.ts | 8 +- src/WSMA/WSMA.ts | 12 +- src/util/BandsResult.ts | 2 +- src/util/HighLowClose.ts | 2 +- src/util/Period.ts | 5 +- src/util/getAverage.ts | 3 +- src/util/getMaximum.ts | 3 +- src/util/getMinimum.ts | 3 +- src/util/getStandardDeviation.ts | 3 +- tsconfig.json | 1 + vitest.config.ts | 2 + 39 files changed, 993 insertions(+), 182 deletions(-) create mode 100644 .dependency-cruiser.cjs diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs new file mode 100644 index 000000000..0219b9b96 --- /dev/null +++ b/.dependency-cruiser.cjs @@ -0,0 +1,380 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +module.exports = { + forbidden: [ + { + name: 'no-circular', + severity: 'warn', + comment: + 'This dependency is part of a circular relationship. You might want to revise ' + + 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + from: {}, + to: { + circular: true, + }, + }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + 'add an exception for it in your dependency-cruiser configuration. By default ' + + 'this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration ' + + 'files (.d.ts), tsconfig.json and some of the babel and webpack configs.', + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files + '[.]d[.]ts$', // TypeScript declaration files + '(^|/)tsconfig[.]json$', // TypeScript config + '(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$', // other configs + ], + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['core'], + path: [ + '^v8/tools/codemap$', + '^v8/tools/consarray$', + '^v8/tools/csvparser$', + '^v8/tools/logreader$', + '^v8/tools/profile_view$', + '^v8/tools/profile$', + '^v8/tools/SourceMap$', + '^v8/tools/splaytree$', + '^v8/tools/tickprocessor-driver$', + '^v8/tools/tickprocessor$', + '^node-inspect/lib/_inspect$', + '^node-inspect/lib/internal/inspect_client$', + '^node-inspect/lib/internal/inspect_repl$', + '^async_hooks$', + '^punycode$', + '^domain$', + '^constants$', + '^sys$', + '^_linklist$', + '^_stream_wrap$', + ], + }, + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['deprecated'], + }, + }, + { + name: 'no-non-package-json', + severity: 'error', + comment: + "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + 'available on live with an non-guaranteed version. Fix it by adding the package to the dependencies ' + + 'in your package.json.', + from: {}, + to: { + dependencyTypes: ['npm-no-pkg', 'npm-unknown'], + }, + }, + { + name: 'not-to-unresolvable', + comment: + "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + 'module: add it to your package.json. In all other cases you likely already know what to do.', + severity: 'error', + from: {}, + to: { + couldNotResolve: true, + }, + }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + 'in your package.json i.e. bot as a devDependencies and in dependencies. This will cause ' + + 'maintenance problems later on.', + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ['type-only'], + }, + }, + + /* rules you might want to tweak for your specific situation: */ + + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$', + }, + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(src)', + pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$', + }, + to: { + dependencyTypes: ['npm-dev'], + // type only dependencies are not a problem as they don't end up in the + // production code or are ignored by the runtime. + dependencyTypesNot: ['type-only'], + pathNot: ['node_modules/@types/'], + }, + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + 'This module depends on an npm package that is declared as an optional dependency ' + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + 'dependency-cruiser configuration.', + from: {}, + to: { + dependencyTypes: ['npm-optional'], + }, + }, + { + name: 'peer-deps-used', + comment: + 'This module depends on an npm package that is declared as a peer dependency ' + + 'in your package.json. This makes sense if your package is e.g. a plugin, but in ' + + 'other cases - maybe not so much. If the use of a peer dependency is intentional ' + + 'add an exception to your dependency-cruiser configuration.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['npm-peer'], + }, + }, + ], + options: { + /* Which modules not to follow further when encountered */ + doNotFollow: { + /* path: an array of regular expressions in strings to match against */ + path: ['node_modules'], + }, + + /* Which modules to exclude */ + // exclude : { + // /* path: an array of regular expressions in strings to match against */ + // path: '', + // }, + + /* Which modules to exclusively include (array of regular expressions in strings) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : [''], + + /* List of module systems to cruise. + When left out dependency-cruiser will fall back to the list of _all_ + module systems it knows of. It's the default because it's the safe option + It might come at a performance penalty, though. + moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] + + As in practice only commonjs ('cjs') and ecmascript modules ('es6') + are widely used, you can limit the moduleSystems to those. + */ + + // moduleSystems: ['cjs', 'es6'], + + /* + false: don't look at JSDoc imports (the default) + true: dependency-cruiser will detect dependencies in JSDoc-style + import statements. Implies "parser": "tsc", so the dependency-cruiser + will use the typescript parser for JavaScript files. + + For this to work the typescript compiler will need to be installed in the + same spot as you're running dependency-cruiser from. + */ + // detectJSDocImports: true, + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: `vscode://file/${process.cwd()}/`, + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + tsPreCompilationDeps: true, + + /* list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + tsConfig: { + fileName: 'tsconfig.json', + }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters + to be passed if your webpack config is a function and takes them (see + webpack documentation for details) + */ + // webpackConfig: { + // fileName: 'webpack.config.js', + // env: {}, + // arguments: {} + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation + */ + // babelConfig: { + // fileName: '.babelrc', + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. The values below should be + suitable for most situations + + If you use webpack: you can also set these in webpack.conf.js. The set + there will override the ones specified here. + */ + enhancedResolveOptions: { + /* What to consider as an 'exports' field in package.jsons */ + exportsFields: ['exports'], + /* List of conditions to check for in the exports field. + Only works when the 'exportsFields' array is non-empty. + */ + conditionNames: ['import', 'require', 'node', 'default', 'types'], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment). If that list is larger than you need you can pass + the extensions you actually use (e.g. [".js", ".jsx"]). This can speed + up module resolution, which is the most expensive step. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* What to consider a 'main' field in package.json */ + mainFields: ['module', 'main', 'types', 'typings'], + /* + A list of alias fields in package.jsons + See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and + the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) + documentation + + Defaults to an empty array (= don't use alias fields). + */ + // aliasFields: ["browser"], + }, + + /* + skipAnalysisNotInRules will make dependency-cruiser execute + analysis strictly necessary for checking the rule set only. + + See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#skipanalysisnotinrules + for details + */ + skipAnalysisNotInRules: true, + + reporterOptions: { + dot: { + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but their innards. + */ + collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + dependency-cruiser falls back to a built-in one. + */ + // theme: { + // graph: { + // /* splines: "ortho" gives straight lines, but is slow on big graphs + // splines: "true" gives bezier curves (fast, not as nice as ortho) + // */ + // splines: "true" + // }, + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: + '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph. If you don't specify a + theme for 'archi' dependency-cruiser will use the one specified in the + dot section above and otherwise use the default one. + */ + // theme: { }, + }, + text: { + highlightFocused: true, + }, + }, + }, +}; +// generated: dependency-cruiser@16.9.0 on 2025-01-08T13:17:47.949Z diff --git a/package-lock.json b/package-lock.json index 659a82958..8d52976b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@types/node": "^18.15.11", "@vitest/coverage-v8": "^1.1.1", "benchmark": "^2.1.4", + "dependency-cruiser": "^16.9.0", "eslint": "^8.50.0", "generate-changelog": "^1.8.0", "husky": "^4.3.8", @@ -1583,10 +1584,11 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1603,11 +1605,35 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-jsx-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz", + "integrity": "sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn-loose": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.4.0.tgz", + "integrity": "sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -1967,6 +1993,105 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/dependency-cruiser": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.9.0.tgz", + "integrity": "sha512-Gc/xHNOBq1nk5i7FPCuexCD0m2OXB/WEfiSHfNYQaQaHZiZltnl5Ixp/ZG38Jvi8aEhKBQTHV4Aw6gmR7rWlOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "acorn-jsx-walk": "^2.0.0", + "acorn-loose": "^8.4.0", + "acorn-walk": "^8.3.4", + "ajv": "^8.17.1", + "commander": "^13.0.0", + "enhanced-resolve": "^5.18.0", + "ignore": "^7.0.0", + "interpret": "^3.1.1", + "is-installed-globally": "^1.0.0", + "json5": "^2.2.3", + "memoize": "^10.0.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.2", + "prompts": "^2.4.2", + "rechoir": "^0.8.0", + "safe-regex": "^2.1.1", + "semver": "^7.6.3", + "teamcity-service-messages": "^0.1.14", + "tsconfig-paths-webpack-plugin": "^4.2.0", + "watskeburt": "^4.2.2" + }, + "bin": { + "depcruise": "bin/dependency-cruise.mjs", + "depcruise-baseline": "bin/depcruise-baseline.mjs", + "depcruise-fmt": "bin/depcruise-fmt.mjs", + "depcruise-wrap-stream-in-html": "bin/wrap-stream-in-html.mjs", + "dependency-cruise": "bin/dependency-cruise.mjs", + "dependency-cruiser": "bin/dependency-cruise.mjs" + }, + "engines": { + "node": "^18.17||>=20" + } + }, + "node_modules/dependency-cruiser/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/dependency-cruiser/node_modules/commander": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz", + "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/dependency-cruiser/node_modules/ignore": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.0.tgz", + "integrity": "sha512-lcX8PNQygAa22u/0BysEY8VhaFRzlOkvdlKczDPnJvrkJD1EuqzEky5VYYKM2iySIuaVIDv9N190DfSreSLw2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/dependency-cruiser/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/dependency-cruiser/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2021,6 +2146,20 @@ "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2428,6 +2567,23 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz", + "integrity": "sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -2583,6 +2739,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/generate-changelog": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/generate-changelog/-/generate-changelog-1.8.0.tgz", @@ -2696,6 +2862,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -2731,6 +2913,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2746,6 +2935,19 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2841,12 +3043,48 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2880,6 +3118,36 @@ "node": ">=0.10.0" } }, + "node_modules/is-installed-globally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", + "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally/node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3026,6 +3294,19 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonc-parser": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", @@ -3041,6 +3322,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3381,6 +3672,22 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, + "node_modules/memoize": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz", + "integrity": "sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3445,6 +3752,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -3675,6 +3992,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", @@ -3716,10 +4040,11 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4016,6 +4341,20 @@ "node": ">=6" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4060,6 +4399,39 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -4069,6 +4441,27 @@ "node": ">=0.10.5" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4202,14 +4595,22 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, + "regexp-tree": "~0.1.1" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4235,18 +4636,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4286,6 +4675,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4452,6 +4848,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -4506,6 +4912,19 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/synckit": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", @@ -4522,6 +4941,23 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/teamcity-service-messages": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz", + "integrity": "sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w==", + "dev": true, + "license": "MIT" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4662,6 +5098,37 @@ } } }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -4963,6 +5430,19 @@ } } }, + "node_modules/watskeburt": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-4.2.2.tgz", + "integrity": "sha512-AOCg1UYxWpiHW1tUwqpJau8vzarZYTtzl2uu99UptBmbzx6kOzCGMfRLF6KIRX4PYekmryn89MzxlRNkL66YyA==", + "dev": true, + "license": "MIT", + "bin": { + "watskeburt": "dist/run-cli.js" + }, + "engines": { + "node": "^18||>=20" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5112,12 +5592,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/package.json b/package.json index 99fa792bc..786d75ca4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@types/node": "^18.15.11", "@vitest/coverage-v8": "^1.1.1", "benchmark": "^2.1.4", + "dependency-cruiser": "^16.9.0", "eslint": "^8.50.0", "generate-changelog": "^1.8.0", "husky": "^4.3.8", @@ -87,8 +88,9 @@ "release:minor": "generate-changelog -m -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version minor", "release:patch": "generate-changelog -p -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version patch", "start:benchmark": "tsc --noEmit && node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only ./src/start/startBenchmark.ts", - "test": "npm run test:types && npm run test:dev -- --coverage", + "test": "npm run test:types && npm run test:imports && npm run test:dev -- --coverage", "test:dev": "vitest run --passWithNoTests", + "test:imports": "depcruise src --include-only \"^src\"", "test:types": "npm run lint:types" }, "type": "module", diff --git a/src/ABANDS/AccelerationBands.ts b/src/ABANDS/AccelerationBands.ts index 7f05c2fc0..d75adf3c4 100644 --- a/src/ABANDS/AccelerationBands.ts +++ b/src/ABANDS/AccelerationBands.ts @@ -1,10 +1,11 @@ -import {Big, TechnicalIndicator} from '../index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; -import {NotEnoughDataError} from '../error/index.js'; import type {BandsResult, FasterBandsResult} from '../util/BandsResult.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import type {HighLowClose, HighLowCloseNumber} from '../util/index.js'; +import Big from 'big.js'; +import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; +import {TechnicalIndicator} from '../Indicator.js'; export class AccelerationBands extends TechnicalIndicator { private readonly lowerBand: MovingAverage; @@ -42,11 +43,7 @@ export class AccelerationBands extends TechnicalIndicator this.update(price)); - } - - get isStable(): boolean { + override get isStable(): boolean { return this.middleBand.isStable; } @@ -55,11 +52,11 @@ export class AccelerationBands extends TechnicalIndicator this.update(price)); - } - update({high, low, close}: HighLowCloseNumber): void { const highPlusLow = high + low; const coefficient = highPlusLow === 0 ? 0 : ((high - low) / highPlusLow) * this.width; @@ -104,7 +97,7 @@ export class FasterAccelerationBands extends TechnicalIndicator { update(candle: HighLowClose): Big | void { const result = this.dx.update(candle); if (result) { - this.adx.update(result); + this.adx.update(result, false); } if (this.adx.isStable) { return this.setResult(this.adx.getResult(), false); diff --git a/src/AO/AO.ts b/src/AO/AO.ts index 5d9d44a0e..ffd8629f7 100644 --- a/src/AO/AO.ts +++ b/src/AO/AO.ts @@ -1,9 +1,9 @@ import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {Big} from '../index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; import type {HighLow, HighLowNumber} from '../util/index.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; +import Big from 'big.js'; /** * Awesome Oscillator (AO) diff --git a/src/ATR/ATR.ts b/src/ATR/ATR.ts index 6474a3f3b..a8b5f4d58 100644 --- a/src/ATR/ATR.ts +++ b/src/ATR/ATR.ts @@ -1,9 +1,8 @@ -import type {Big} from '../index.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import {FasterTR, TR} from '../TR/TR.js'; -import type {HighLowClose, HighLowCloseNumber} from '../util/index.js'; +import type {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose.js'; import {FasterWSMA, WSMA} from '../WSMA/WSMA.js'; /** diff --git a/src/BBANDS/BollingerBands.ts b/src/BBANDS/BollingerBands.ts index 4750e368a..7111a7721 100644 --- a/src/BBANDS/BollingerBands.ts +++ b/src/BBANDS/BollingerBands.ts @@ -1,6 +1,8 @@ -import {Big, TechnicalIndicator, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; +import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; +import {TechnicalIndicator} from '../Indicator.js'; import {SMA} from '../SMA/SMA.js'; -import {NotEnoughDataError} from '../error/index.js'; import type {BandsResult, FasterBandsResult} from '../util/BandsResult.js'; import {getFasterAverage, getFasterStandardDeviation, getStandardDeviation} from '../util/index.js'; @@ -21,7 +23,6 @@ import {getFasterAverage, getFasterStandardDeviation, getStandardDeviation} from */ export class BollingerBands extends TechnicalIndicator { public readonly prices: Big[] = []; - private result: BandsResult | undefined; /** * @param interval - The time period to be used in calculating the Middle Band @@ -35,15 +36,6 @@ export class BollingerBands extends TechnicalIndicator { super(); } - get isStable(): boolean { - return this.result !== undefined; - } - - updates(prices: BigSource[]) { - prices.forEach(price => this.update(price)); - return this.result; - } - update(price: BigSource): void | BandsResult { this.prices.push(new Big(price)); @@ -72,7 +64,6 @@ export class BollingerBands extends TechnicalIndicator { export class FasterBollingerBands extends TechnicalIndicator { public readonly prices: number[] = []; - private result: FasterBandsResult | undefined; constructor( public readonly interval: number, @@ -81,11 +72,6 @@ export class FasterBollingerBands extends TechnicalIndicator this.update(price)); - return this.result; - } - update(price: number): void | FasterBandsResult { this.prices.push(price); @@ -110,8 +96,4 @@ export class FasterBollingerBands extends TechnicalIndicator { this.long = new Indicator(long); } - get isStable(): boolean { + override get isStable(): boolean { return this.long.isStable; } - updates(prices: BigSource[]) { - prices.forEach(price => this.update(price)); - } - update(price: BigSource, replace: boolean = false): void { this.short.update(price, replace); this.long.update(price, replace); @@ -64,14 +60,10 @@ export class FasterDMA extends TechnicalIndicator { this.long = new SmoothingIndicator(long); } - get isStable(): boolean { + override get isStable(): boolean { return this.long.isStable; } - updates(prices: number[]) { - prices.forEach(price => this.update(price)); - } - update(price: number, replace: boolean = false): void { this.short.update(price, replace); this.long.update(price, replace); diff --git a/src/DX/DX.ts b/src/DX/DX.ts index 43db02a6c..3931de887 100644 --- a/src/DX/DX.ts +++ b/src/DX/DX.ts @@ -1,10 +1,11 @@ import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import type {HighLowClose, HighLowCloseNumber} from '../util/index.js'; -import {Big, type BigSource} from '../index.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import {FasterWSMA, WSMA} from '../WSMA/WSMA.js'; import {ATR, FasterATR} from '../ATR/ATR.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; +import type {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose.js'; /** * Directional Movement Index (DMI / DX) @@ -41,8 +42,8 @@ export class DX extends BigIndicatorSeries { private updateState(candle: HighLowClose, pdm: BigSource = 0, mdm: BigSource = 0): void { this.atr.update(candle); - this.movesDown.update(mdm); - this.movesUp.update(pdm); + this.movesDown.update(mdm, false); + this.movesUp.update(pdm, false); this.previousCandle = candle; } diff --git a/src/EMA/EMA.ts b/src/EMA/EMA.ts index f0f69497c..2e4cc06ff 100644 --- a/src/EMA/EMA.ts +++ b/src/EMA/EMA.ts @@ -1,6 +1,7 @@ -import {Big, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import {NotEnoughDataError} from '../error/index.js'; +import Big from 'big.js'; /** * Exponential Moving Average (EMA) @@ -15,7 +16,7 @@ export class EMA extends MovingAverage { private pricesCounter = 0; private readonly weightFactor: number; - constructor(public readonly interval: number) { + constructor(public override readonly interval: number) { super(interval); this.weightFactor = 2 / (this.interval + 1); } @@ -62,7 +63,7 @@ export class FasterEMA extends FasterMovingAverage { private pricesCounter = 0; private readonly weightFactor: number; - constructor(public readonly interval: number) { + constructor(public override readonly interval: number) { super(interval); this.weightFactor = 2 / (this.interval + 1); } diff --git a/src/Indicator.ts b/src/Indicator.ts index 43a35393c..8cbe14d1d 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -1,5 +1,6 @@ -import {NotEnoughDataError} from './error/index.js'; -import {getLastFromForEach, type Big, type BigSource} from './index.js'; +import type {BigSource} from 'big.js'; +import {NotEnoughDataError} from './error/NotEnoughDataError.js'; +import {getLastFromForEach} from './util/getLastFromForEach.js'; export interface Indicator { getResult(): Result; @@ -14,17 +15,21 @@ export interface Indicator { } export abstract class TechnicalIndicator implements Indicator { - accessor isStable: boolean = false; + protected result: Result | undefined; abstract getResult(): Result; + get isStable(): boolean { + return this.result !== undefined; + } + replace(input: Input) { return this.update(input, true); } abstract update(input: Input, replace: boolean): void | Result; - updates(inputs: Input[], replace: boolean) { + updates(inputs: Input[], replace: boolean = false) { return getLastFromForEach(inputs, input => this.update(input, replace)); } } diff --git a/src/MA/MovingAverage.ts b/src/MA/MovingAverage.ts index 6d546c3d5..3e07b6f94 100644 --- a/src/MA/MovingAverage.ts +++ b/src/MA/MovingAverage.ts @@ -1,4 +1,3 @@ -import type {Big, BigSource} from '../index.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; /** @@ -13,32 +12,10 @@ export abstract class MovingAverage extends BigIndicatorSeries { constructor(public readonly interval: number) { super(); } - - updates(prices: BigSource[]): Big | void { - prices.forEach(price => this.update(price)); - return this.result; - } - - abstract update(price: BigSource, replace?: boolean): Big | void; - - replace(price: BigSource) { - return this.update(price, true); - } } export abstract class FasterMovingAverage extends NumberIndicatorSeries { constructor(public readonly interval: number) { super(); } - - updates(prices: number[]): number | void { - prices.forEach(price => this.update(price)); - return this.result; - } - - abstract update(price: number, replace?: boolean): number | void; - - replace(price: number) { - return this.update(price, true); - } } diff --git a/src/MACD/MACD.ts b/src/MACD/MACD.ts index 238e44209..1566e7f08 100644 --- a/src/MACD/MACD.ts +++ b/src/MACD/MACD.ts @@ -1,6 +1,10 @@ +import type {BigSource} from 'big.js'; +import Big from 'big.js'; +import type {DEMA, FasterDEMA} from '../DEMA/DEMA.js'; import type {EMA, FasterEMA} from '../EMA/EMA.js'; -import {Big, NotEnoughDataError, pushUpdate, type BigSource, type DEMA, type FasterDEMA} from '../index.js'; +import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; import type {Indicator} from '../Indicator.js'; +import {pushUpdate} from '../util/pushUpdate.js'; export type MACDConfig = { indicator: typeof EMA | typeof DEMA; diff --git a/src/MAD/MAD.ts b/src/MAD/MAD.ts index 6d87e91bf..355a21fa1 100644 --- a/src/MAD/MAD.ts +++ b/src/MAD/MAD.ts @@ -1,4 +1,5 @@ -import {Big, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; import {getAverage, getFasterAverage, pushUpdate} from '../util/index.js'; diff --git a/src/MOM/MOM.ts b/src/MOM/MOM.ts index 8129cb574..72b907ed4 100644 --- a/src/MOM/MOM.ts +++ b/src/MOM/MOM.ts @@ -1,6 +1,8 @@ +import type {BigSource} from 'big.js'; +import Big from 'big.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {Big, pushUpdate, type BigSource} from '../index.js'; import {getFixedArray} from '../util/getFixedArray.js'; +import {pushUpdate} from '../util/pushUpdate.js'; /** * Momentum Indicator (MOM / MTM) diff --git a/src/OBV/OBV.ts b/src/OBV/OBV.ts index aa36114f1..bff208be5 100644 --- a/src/OBV/OBV.ts +++ b/src/OBV/OBV.ts @@ -1,6 +1,6 @@ -import {Big} from '../index.js'; +import Big from 'big.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import type {OpenHighLowCloseVolume, OpenHighLowCloseVolumeNumber} from '../util/index.js'; +import type {OpenHighLowCloseVolume, OpenHighLowCloseVolumeNumber} from '../util/HighLowClose.js'; /** * On-Balance Volume (OBV) diff --git a/src/ROC/ROC.ts b/src/ROC/ROC.ts index 9b9891c54..256d4c180 100644 --- a/src/ROC/ROC.ts +++ b/src/ROC/ROC.ts @@ -1,5 +1,6 @@ -import {Big, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; +import Big from 'big.js'; /** * Rate Of Change Indicator (ROC) diff --git a/src/RSI/RSI.ts b/src/RSI/RSI.ts index 8a71d86cc..c11b1a642 100644 --- a/src/RSI/RSI.ts +++ b/src/RSI/RSI.ts @@ -1,8 +1,10 @@ -import {Big, pushUpdate, type BigSource} from '../index.js'; import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import {FasterWSMA, WSMA} from '../WSMA/WSMA.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; +import {pushUpdate} from '../util/pushUpdate.js'; /** * Relative Strength Index (RSI) diff --git a/src/SMA/SMA.ts b/src/SMA/SMA.ts index 84dffb949..39f48d5d9 100644 --- a/src/SMA/SMA.ts +++ b/src/SMA/SMA.ts @@ -1,5 +1,7 @@ -import {Big, pushUpdate, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; +import {pushUpdate} from '../util/pushUpdate.js'; +import Big from 'big.js'; /** * Simple Moving Average (SMA) diff --git a/src/STOCH/StochasticOscillator.ts b/src/STOCH/StochasticOscillator.ts index a5ad87a31..b3c635619 100644 --- a/src/STOCH/StochasticOscillator.ts +++ b/src/STOCH/StochasticOscillator.ts @@ -1,9 +1,10 @@ -import {Big, TechnicalIndicator} from '../index.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; import {getMaximum} from '../util/getMaximum.js'; import {getMinimum} from '../util/getMinimum.js'; -import {NotEnoughDataError} from '../error/index.js'; -import type {HighLowClose, HighLowCloseNumber} from '../util/index.js'; +import Big from 'big.js'; +import {TechnicalIndicator} from '../Indicator.js'; +import type {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose.js'; +import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; export interface StochasticResult { /** Slow stochastic indicator (%D) */ @@ -39,9 +40,7 @@ export interface FasterStochasticResult { export class StochasticOscillator extends TechnicalIndicator { private readonly periodM: SMA; private readonly periodP: SMA; - private readonly candles: HighLowClose[] = []; - private result?: StochasticResult; /** * Constructs a Stochastic Oscillator. @@ -60,11 +59,6 @@ export class StochasticOscillator extends TechnicalIndicator this.update(candle)); - return this.result; - } - getResult(): StochasticResult { if (this.result === undefined) { throw new NotEnoughDataError(); @@ -97,15 +91,10 @@ export class StochasticOscillator extends TechnicalIndicator { public readonly candles: HighLowCloseNumber[] = []; - private result: FasterStochasticResult | undefined; private readonly periodM: FasterSMA; private readonly periodP: FasterSMA; @@ -132,15 +121,6 @@ export class FasterStochasticOscillator extends TechnicalIndicator this.update(candle)); - return this.result; - } - update(candle: HighLowCloseNumber): void | FasterStochasticResult { this.candles.push(candle); diff --git a/src/STOCH/StochasticRSI.ts b/src/STOCH/StochasticRSI.ts index e3da1732a..b25e6b24c 100644 --- a/src/STOCH/StochasticRSI.ts +++ b/src/STOCH/StochasticRSI.ts @@ -1,9 +1,10 @@ import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; import {FasterRSI, RSI} from '../RSI/RSI.js'; -import {Big, type BigSource} from '../index.js'; import {FasterPeriod, Period} from '../util/Period.js'; import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js'; import {FasterWSMA, WSMA} from '../WSMA/WSMA.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; /** * Stochastic RSI (STOCHRSI) diff --git a/src/TR/TR.ts b/src/TR/TR.ts index 90ac230e2..5947f1b2a 100644 --- a/src/TR/TR.ts +++ b/src/TR/TR.ts @@ -1,6 +1,7 @@ -import {Big} from '../index.js'; +import Big from 'big.js'; import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js'; -import {getMaximum, type HighLowClose, type HighLowCloseNumber} from '../util/index.js'; +import {getMaximum} from '../util/getMaximum.js'; +import type {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose.js'; /** * True Range (TR) diff --git a/src/WMA/WMA.ts b/src/WMA/WMA.ts index ee0c09330..bc8b09b00 100644 --- a/src/WMA/WMA.ts +++ b/src/WMA/WMA.ts @@ -1,5 +1,7 @@ -import {Big, pushUpdate, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; +import {pushUpdate} from '../util/pushUpdate.js'; /** * Weighted Moving Average (WMA) @@ -13,7 +15,7 @@ import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js'; export class WMA extends MovingAverage { public readonly prices: BigSource[] = []; - constructor(public readonly interval: number) { + constructor(public override readonly interval: number) { super(interval); } @@ -42,7 +44,7 @@ export class WMA extends MovingAverage { export class FasterWMA extends FasterMovingAverage { public readonly prices: number[] = []; - constructor(public readonly interval: number) { + constructor(public override readonly interval: number) { super(interval); } diff --git a/src/WSMA/WSMA.ts b/src/WSMA/WSMA.ts index 542244085..e995726d8 100644 --- a/src/WSMA/WSMA.ts +++ b/src/WSMA/WSMA.ts @@ -1,7 +1,8 @@ -import {Big, type BigSource} from '../index.js'; import {MovingAverage} from '../MA/MovingAverage.js'; import {FasterSMA, SMA} from '../SMA/SMA.js'; import {NumberIndicatorSeries} from '../Indicator.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; /** * Wilder's Smoothed Moving Average (WSMA) @@ -22,17 +23,12 @@ export class WSMA extends MovingAverage { private readonly indicator: SMA; private readonly smoothingFactor: Big; - constructor(public readonly interval: number) { + constructor(public override readonly interval: number) { super(interval); this.indicator = new SMA(interval); this.smoothingFactor = new Big(1).div(this.interval); } - updates(prices: BigSource[]) { - prices.forEach(price => this.update(price)); - return this.result; - } - update(price: BigSource, replace: boolean = false): Big | void { const sma = this.indicator.update(price, replace); if (replace && this.previousResult) { @@ -57,7 +53,7 @@ export class FasterWSMA extends NumberIndicatorSeries { this.smoothingFactor = 1 / this.interval; } - updates(prices: number[]): number | void { + override updates(prices: number[]): number | void { prices.forEach(price => this.update(price)); return this.result; } diff --git a/src/util/BandsResult.ts b/src/util/BandsResult.ts index 63cebb8c6..f36328a02 100644 --- a/src/util/BandsResult.ts +++ b/src/util/BandsResult.ts @@ -1,4 +1,4 @@ -import type {Big} from '../index.js'; +import type {Big} from 'big.js'; export interface BandsResult { lower: Big; diff --git a/src/util/HighLowClose.ts b/src/util/HighLowClose.ts index 4e15a6aa3..c4252a447 100644 --- a/src/util/HighLowClose.ts +++ b/src/util/HighLowClose.ts @@ -1,4 +1,4 @@ -import type {BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; export type HighLow = {high: BigSource; low: BigSource}; diff --git a/src/util/Period.ts b/src/util/Period.ts index 33d6e2146..1c296a02e 100644 --- a/src/util/Period.ts +++ b/src/util/Period.ts @@ -1,8 +1,11 @@ -import {Big, NotEnoughDataError, pushUpdate, type BigSource} from '../index.js'; import type {Indicator} from '../Indicator.js'; import {getFixedArray} from './getFixedArray.js'; import {getMinimum} from './getMinimum.js'; import {getMaximum} from './getMaximum.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; +import {NotEnoughDataError} from '../error/NotEnoughDataError.js'; +import {pushUpdate} from './pushUpdate.js'; export interface PeriodResult { highest: Big; diff --git a/src/util/getAverage.ts b/src/util/getAverage.ts index 1f68640bf..ccc8bc40c 100644 --- a/src/util/getAverage.ts +++ b/src/util/getAverage.ts @@ -1,4 +1,5 @@ -import {Big, type BigSource} from '../index.js'; +import type {BigSource} from 'big.js'; +import Big from 'big.js'; /** * Return the mean / average value. diff --git a/src/util/getMaximum.ts b/src/util/getMaximum.ts index d2bbecd25..b252722d6 100644 --- a/src/util/getMaximum.ts +++ b/src/util/getMaximum.ts @@ -1,4 +1,5 @@ -import {Big, type BigSource} from '../index.js'; +import Big from 'big.js'; +import type {BigSource} from 'big.js'; export function getMaximum(values: BigSource[]): Big { let max = new Big(Number.MIN_SAFE_INTEGER); diff --git a/src/util/getMinimum.ts b/src/util/getMinimum.ts index 76be88478..4c96c14e0 100644 --- a/src/util/getMinimum.ts +++ b/src/util/getMinimum.ts @@ -1,4 +1,5 @@ -import {Big, type BigSource} from '../index.js'; +import Big from 'big.js'; +import type {BigSource} from 'big.js'; export function getMinimum(values: BigSource[]): Big { let min = new Big(Number.MAX_SAFE_INTEGER); diff --git a/src/util/getStandardDeviation.ts b/src/util/getStandardDeviation.ts index fb4cfeeed..780148a4f 100644 --- a/src/util/getStandardDeviation.ts +++ b/src/util/getStandardDeviation.ts @@ -1,5 +1,6 @@ +import type {BigSource} from 'big.js'; +import Big from 'big.js'; import {getFasterAverage, getAverage} from './getAverage.js'; -import {Big, type BigSource} from '../index.js'; /** * Standard deviation calculates how prices for a collection of prices are spread out from the average price of these diff --git a/tsconfig.json b/tsconfig.json index ee25594ad..6aefa2dae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "moduleResolution": "nodenext", "newLine": "lf", "noEmitOnError": true, + "noImplicitOverride": true, "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, diff --git a/vitest.config.ts b/vitest.config.ts index b452d8408..bf8f2e052 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,8 @@ import {defineConfig} from 'vitest/config'; export default defineConfig({ esbuild: { + // Allows using the "accessor" keyword in TypeScript: + // https://github.com/vitest-dev/vitest/issues/5976#issuecomment-2190804966 target: 'es2022', }, test: {