From 1064ebb80d6eb87cd6c9c343efdee90c6af0ef4d Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Mon, 27 Dec 2021 20:59:07 +0100 Subject: [PATCH] feat(ROC): Add faster implementation (#385) --- README.md | 2 +- src/MOM/MOM.ts | 3 +- src/ROC/ROC.test.ts | 63 +++++++++++-------------------------- src/ROC/ROC.ts | 42 ++++++++++++++++--------- src/start/startBenchmark.ts | 28 +++++++++++++++++ 5 files changed, 76 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index c7e0a6a6e..29735b942 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ All indicators can be updated over time by streaming data (prices or candles) to 1. Dual Moving Average (DMA) 1. Exponential Moving Average (EMA) 1. Mean Absolute Deviation (MAD) -1. Momentum (MOM) +1. Momentum (MOM / MTM) 1. Moving Average Convergence Divergence (MACD) 1. Rate-of-Change (ROC) 1. Relative Strength Index (RSI) diff --git a/src/MOM/MOM.ts b/src/MOM/MOM.ts index 3b9c6981f..3cde4d0b8 100644 --- a/src/MOM/MOM.ts +++ b/src/MOM/MOM.ts @@ -3,11 +3,12 @@ import Big, {BigSource} from 'big.js'; import {getFixedArray} from '../util/getFixedArray'; /** - * Momentum Indicator (MOM) + * Momentum Indicator (MOM / MTM) * Type: Momentum * * The Momentum indicator returns the change between the current price and the price n times ago. * + * @see https://en.wikipedia.org/wiki/Momentum_(technical_analysis) * @see https://www.warriortrading.com/momentum-indicator/ */ export class MOM extends BigIndicatorSeries { diff --git a/src/ROC/ROC.test.ts b/src/ROC/ROC.test.ts index fe9e80679..5b2dd043b 100644 --- a/src/ROC/ROC.test.ts +++ b/src/ROC/ROC.test.ts @@ -1,66 +1,39 @@ import {Big} from 'big.js'; -import {ROC} from './ROC'; +import {FasterROC, ROC} from './ROC'; import {NotEnoughDataError} from '../error'; describe('ROC', () => { describe('getResult', () => { it('identifies an up-trending asset by a positive ROC', () => { + // Test data verified with: + // https://tulipindicators.org/roc const prices = [ 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, ]; - /************************************************* - Result calculation: - Close | Calculation | Result - ------------------------------------------------ - 81.59 | ----------------------- | Unstable (null) - 81.06 | ----------------------- | Unstable (null) - 82.87 | ----------------------- | Unstable (null) - 83.00 | ----------------------- | Unstable (null) - 83.61 | ----------------------- | Unstable (null) - 83.15 | (83.15 - 81.59) / 81.59 | 0.01911999019 - 82.84 | (82.84 - 81.06) / 81.06 | 0.02195904268 - 83.99 | (83.99 - 82.87) / 82.87 | 0.0135151442 - 84.55 | (84.55 - 83.00) / 83.00 | 0.01867469879 - 84.36 | (84.36 - 83.61) / 83.61 | 0.00897021887 - 85.53 | (85.53 - 83.15) / 83.15 | 0.02862297053 - 86.54 | (86.54 - 82.84) / 82.84 | 0.04466441332 - 86.89 | (86.89 - 83.99) / 83.99 | 0.03452791999 - 87.77 | (87.77 - 84.55) / 84.55 | 0.03808397397 - 87.29 | (87.29 - 84.36) / 84.36 | 0.03473210052 - *************************************************/ - const results = [ - null, - null, - null, - null, - null, - 0.01911999019, - 0.02195904268, - 0.0135151442, - 0.01867469879, - 0.00897021887, - 0.02862297053, - 0.04466441332, - 0.03452791999, - 0.03808397397, - 0.03473210052, + + const expectations = [ + 0.01911999019, 0.02195904268, 0.0135151442, 0.01867469879, 0.00897021887, 0.02862297053, 0.04466441332, + 0.03452791999, 0.03808397397, 0.03473210052, ]; const roc = new ROC(5); + const fasterROC = new FasterROC(5); - prices.forEach((price, index) => { - roc.update(new Big(price)); + for (const price of prices) { + roc.update(price); + fasterROC.update(price); - if (!roc.isStable) { - return; + if (roc.isStable) { + const expected = expectations.shift()!; + expect(roc.getResult().toFixed(2)).toEqual(expected.toFixed(2)); } - - const expected = new Big(Number(results[index])); - expect(roc.getResult().toFixed(2)).toEqual(expected.toFixed(2)); - }); + } expect(roc.lowest!.toFixed(2)).toBe('0.01'); + expect(fasterROC.lowest!.toFixed(2)).toBe('0.01'); + expect(roc.highest!.toFixed(2)).toBe('0.04'); + expect(fasterROC.highest!.toFixed(2)).toBe('0.04'); }); it('identifies a down-trending asset by a negative ROC', () => { diff --git a/src/ROC/ROC.ts b/src/ROC/ROC.ts index b36131f7f..3cf5865f8 100644 --- a/src/ROC/ROC.ts +++ b/src/ROC/ROC.ts @@ -1,5 +1,5 @@ import Big, {BigSource} from 'big.js'; -import {BigIndicatorSeries} from '../Indicator'; +import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator'; /** * Rate Of Change Indicator (ROC) @@ -11,29 +11,41 @@ import {BigIndicatorSeries} from '../Indicator'; * @see https://www.investopedia.com/terms/r/rateofchange.asp */ export class ROC extends BigIndicatorSeries { - private readonly priceHistory: BigSource[] = []; + public readonly prices: Big[] = []; constructor(public readonly interval: number) { super(); - this.interval = interval; } override update(price: BigSource): Big | void { - this.priceHistory.push(price); - if (this.priceHistory.length <= this.interval) { - /** - * The priceHistory needs to have N prices in it before a result can be calculated with the following value. For - * an interval of 5, the first result can be given on the 6th value. - */ - return; - } + this.prices.push(new Big(price)); /** - * Take the price this.interval periods ago. + * The priceHistory needs to have N prices in it before a result can be calculated with the following value. For + * an interval of 5, the first result can be given on the 6th value. */ - const comparePrice = this.priceHistory.shift() as Big; + if (this.prices.length > this.interval) { + const comparePrice = this.prices.shift()!; - // (Close - Close periods ago) / (Close periods ago) - return this.setResult(new Big(price).sub(comparePrice).div(comparePrice)); + return this.setResult(new Big(price).sub(comparePrice).div(comparePrice)); + } + } +} + +export class FasterROC extends NumberIndicatorSeries { + public readonly prices: number[] = []; + + constructor(public readonly interval: number) { + super(); + } + + override update(price: number): void | number { + this.prices.push(price); + + if (this.prices.length > this.interval) { + const comparePrice = this.prices.shift()!; + + return this.setResult((price - comparePrice) / comparePrice); + } } } diff --git a/src/start/startBenchmark.ts b/src/start/startBenchmark.ts index 648d599f0..cf13a239d 100644 --- a/src/start/startBenchmark.ts +++ b/src/start/startBenchmark.ts @@ -10,6 +10,7 @@ import { BollingerBandsWidth, CCI, CG, + DEMA, DMA, DX, EMA, @@ -22,6 +23,7 @@ import { FasterBollingerBandsWidth, FasterCCI, FasterCG, + FasterDEMA, FasterDMA, FasterDX, FasterEMA, @@ -29,6 +31,7 @@ import { FasterMAD, FasterMOM, FasterPeriod, + FasterROC, FasterRSI, FasterSMA, FasterStochasticRSI, @@ -43,6 +46,7 @@ import { MAD, MOM, Period, + ROC, RSI, SMA, StochasticRSI, @@ -169,6 +173,18 @@ new Benchmark.Suite('Technical Indicators') fasterCG.update(price); } }) + .add('DEMA', () => { + const dema = new DEMA(interval); + for (const price of prices) { + dema.update(price); + } + }) + .add('FasterDEMA', () => { + const fasterDEMA = new FasterDEMA(interval); + for (const price of prices) { + fasterDEMA.update(price); + } + }) .add('DMA', () => { const dma = new DMA(3, 6); for (const price of prices) { @@ -258,6 +274,18 @@ new Benchmark.Suite('Technical Indicators') fasterPeriod.update(price); } }) + .add('ROC', () => { + const roc = new ROC(interval); + for (const price of prices) { + roc.update(price); + } + }) + .add('FasterROC', () => { + const fasterROC = new FasterROC(interval); + for (const price of prices) { + fasterROC.update(price); + } + }) .add('RSI', () => { const rsi = new RSI(interval); for (const price of prices) {