diff --git a/src/AC/AC.test.ts b/src/AC/AC.test.ts index 75ffa5d22..87e2d7317 100644 --- a/src/AC/AC.test.ts +++ b/src/AC/AC.test.ts @@ -259,6 +259,8 @@ describe('AC', () => { expect(ac.isStable).toBeTrue(); expect(ac.getResult().toFixed(2)).toBe('-21.97'); expect(ac.momentum.getResult().toFixed(2)).toBe('-9.22'); + expect(ac.lowest!.toFixed(2)).toBe('-21.97'); + expect(ac.highest!.toFixed(2)).toBe('11.65'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/AC/AC.ts b/src/AC/AC.ts index 4d1860303..80a7afb55 100644 --- a/src/AC/AC.ts +++ b/src/AC/AC.ts @@ -12,14 +12,13 @@ import {MOM} from '../MOM/MOM'; * down before buy orders come in. The Accelerator Oscillator also looks at whether there is an acceleration in the * change of momentum. */ -export class AC implements SimpleIndicator { +export class AC extends SimpleIndicator { public readonly ao: AO; public readonly momentum: MOM; public readonly signal: SMA; - private result: Big | undefined; - constructor(public readonly shortAO: number, public readonly longAO: number, public readonly signalInterval: number) { + super(); this.ao = new AO(shortAO, longAO); this.signal = new SMA(signalInterval); this.momentum = new MOM(1); @@ -42,8 +41,8 @@ export class AC implements SimpleIndicator { if (ao) { this.signal.update(ao); if (this.signal.isStable) { - this.result = ao.sub(this.signal.getResult()); - this.momentum.update(this.result); + const result = this.setResult(ao.sub(this.signal.getResult())); + this.momentum.update(result); return this.result; } } diff --git a/src/AO/AO.test.ts b/src/AO/AO.test.ts index c23c5245d..ccf1e6c55 100644 --- a/src/AO/AO.test.ts +++ b/src/AO/AO.test.ts @@ -31,6 +31,7 @@ describe('AO', () => { 5.3673, 4.5294, 4.764, 4.1044, 1.6913, -1.3769, -4.2062, -7.7196, -10.6241, -11.4972, -9.6358, -7.9344, ]; const ao = new AO(5, 34); + for (let i = 0; i < lows.length; i++) { const newResult = ao.update(lows[i], highs[i]); if (ao.isStable) { @@ -40,6 +41,9 @@ describe('AO', () => { expect(parseFloat(actual)).toBe(expected!); } } + + expect(ao.lowest!.toFixed(2)).toBe('-11.50'); + expect(ao.highest!.toFixed(2)).toBe('33.35'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/AO/AO.ts b/src/AO/AO.ts index ba4329f5f..472212f41 100644 --- a/src/AO/AO.ts +++ b/src/AO/AO.ts @@ -7,13 +7,12 @@ import {NotEnoughDataError} from '../error'; * The Awesome Oscillator (AO) is an indicator used to measure market momentum. * It has been developed by the technical analyst and charting enthusiast Bill Williams. */ -export class AO implements SimpleIndicator { +export class AO extends SimpleIndicator { public readonly long: SMA; public readonly short: SMA; - private result: Big | undefined; - constructor(public readonly shortInterval: number, public readonly longInterval: number) { + super(); this.short = new SMA(shortInterval); this.long = new SMA(longInterval); } @@ -38,8 +37,8 @@ export class AO implements SimpleIndicator { this.long.update(medianPrice); if (this.short.isStable && this.long.isStable) { - this.result = this.short.getResult().sub(this.long.getResult()); - return this.result; + const result = this.setResult(this.short.getResult().sub(this.long.getResult())); + return result; } } } diff --git a/src/ATR/ATR.test.ts b/src/ATR/ATR.test.ts index 394a1ac58..a783f77a0 100644 --- a/src/ATR/ATR.test.ts +++ b/src/ATR/ATR.test.ts @@ -20,6 +20,9 @@ describe('ATR', () => { expect(atr.getResult().toFixed(4)).toEqual(res.toFixed(4)); } }); + + expect(atr.lowest!.toFixed(2)).toBe('0.00'); + expect(atr.highest!.toFixed(2)).toBe('1.37'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/ATR/ATR.ts b/src/ATR/ATR.ts index 55083b339..e37c5b822 100644 --- a/src/ATR/ATR.ts +++ b/src/ATR/ATR.ts @@ -11,14 +11,13 @@ export type ATRCandle = {close: BigSource; high: BigSource; low: BigSource}; * traders prepared to continue to bid up or sell down a stock through the course of the day. Decreasing range suggests * waning interest. */ -export class ATR implements SimpleIndicator { +export class ATR extends SimpleIndicator { private readonly smma: SMMA; private readonly candles: ATRCandle[] = []; - - private result: Big | undefined; private prevCandle: ATRCandle | undefined; constructor(public readonly interval: number) { + super(); this.smma = new SMMA(interval); } @@ -46,7 +45,7 @@ export class ATR implements SimpleIndicator { this.smma.update(trueRange); - this.result = this.smma.getResult(); + this.setResult(this.smma.getResult()); this.prevCandle = candle; } diff --git a/src/CG/CG.test.ts b/src/CG/CG.test.ts index b12438c51..33d59f4f2 100644 --- a/src/CG/CG.test.ts +++ b/src/CG/CG.test.ts @@ -14,6 +14,8 @@ describe('CG', () => { expect(cg.isStable).toBeFalse(); cg.update(60); expect(cg.isStable).toBeTrue(); + expect(cg.lowest!.toFixed(2)).toBe('1.00'); + expect(cg.highest!.toFixed(2)).toBe('3.67'); }); }); diff --git a/src/CG/CG.ts b/src/CG/CG.ts index de7774f35..31b0af6eb 100644 --- a/src/CG/CG.ts +++ b/src/CG/CG.ts @@ -13,17 +13,17 @@ import {NotEnoughDataError} from '../error'; * profitable trading * @see http://www.mesasoftware.com/papers/TheCGOscillator.pdf */ -export class CG implements SimpleIndicator { +export class CG extends SimpleIndicator { public signal: SMA; private readonly prices: Big[] = []; - private result: Big | undefined; get isStable(): boolean { return this.prices.length >= this.interval && this.signal.isStable; } constructor(public readonly interval: number, public readonly signalInterval: number) { + super(); this.signal = new SMA(signalInterval); } @@ -47,7 +47,7 @@ export class CG implements SimpleIndicator { this.signal.update(cg); - this.result = cg; + this.setResult(cg); } getResult(): Big { diff --git a/src/DEMA/DEMA.test.ts b/src/DEMA/DEMA.test.ts index 56b59069b..5043484a4 100644 --- a/src/DEMA/DEMA.test.ts +++ b/src/DEMA/DEMA.test.ts @@ -17,6 +17,9 @@ describe('DEMA', () => { const result = new Big(dema10results[index]); expect(dema.getResult().toPrecision(12)).toEqual(result.toPrecision(12)); }); + + expect(dema.lowest!.toFixed(2)).toBe('24.89'); + expect(dema.highest!.toFixed(2)).toBe('83.22'); }); it('throws an error when there is not enough input data', () => { @@ -38,6 +41,8 @@ describe('DEMA', () => { dema.update(1); dema.update(2); expect(dema.isStable).toBeTrue(); + expect(dema.lowest!.toFixed(2)).toBe('1.00'); + expect(dema.highest!.toFixed(2)).toBe('1.89'); }); }); }); diff --git a/src/DEMA/DEMA.ts b/src/DEMA/DEMA.ts index a3ced8dda..8f3df6795 100644 --- a/src/DEMA/DEMA.ts +++ b/src/DEMA/DEMA.ts @@ -2,13 +2,12 @@ import Big, {BigSource} from 'big.js'; import {EMA, NotEnoughDataError} from '..'; import {SimpleIndicator} from '../Indicator'; -export class DEMA implements SimpleIndicator { - private result: Big | undefined; - +export class DEMA extends SimpleIndicator { private readonly inner: EMA; private readonly outer: EMA; constructor(public readonly interval: number) { + super(); this.inner = new EMA(interval); this.outer = new EMA(interval); } @@ -16,8 +15,7 @@ export class DEMA implements SimpleIndicator { update(price: BigSource): void { this.inner.update(price); this.outer.update(this.inner.getResult()); - - this.result = this.inner.getResult().times(2).sub(this.outer.getResult()); + this.setResult(this.inner.getResult().times(2).sub(this.outer.getResult())); } get isStable(): boolean { diff --git a/src/EMA/EMA.test.ts b/src/EMA/EMA.test.ts index cedb1a740..23a21842c 100644 --- a/src/EMA/EMA.test.ts +++ b/src/EMA/EMA.test.ts @@ -10,34 +10,46 @@ const ema26results = results.weight_26; describe('EMA', () => { describe('getResult', () => { - it('correctly calculates EMAs with weight 10', () => { + it('calculates EMAs with weight 10', () => { const ema = new EMA(10); + prices.forEach((price, index) => { ema.update(new Big(price)); const actual = ema.getResult(); const expected = new Big(ema10results[index]); expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12)); }); + + expect(ema.lowest!.toFixed(2)).toBe('38.20'); + expect(ema.highest!.toFixed(2)).toBe('81.00'); }); - it('correctly calculates EMAs with weight 12', () => { + it('calculates EMAs with weight 12', () => { const ema = new EMA(12); + prices.forEach((price, index) => { ema.update(new Big(price)); const actual = ema.getResult(); const expected = new Big(ema12results[index]); expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12)); }); + + expect(ema.lowest!.toFixed(2)).toBe('40.43'); + expect(ema.highest!.toFixed(2)).toBe('81.00'); }); - it('correctly calculates EMAs with weight 26', () => { + it('calculates EMAs with weight 26', () => { const ema = new EMA(26); + prices.forEach((price, index) => { ema.update(new Big(price)); const actual = ema.getResult(); const expected = new Big(ema26results[index]); expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12)); }); + + expect(ema.lowest!.toFixed(2)).toBe('48.29'); + expect(ema.highest!.toFixed(2)).toBe('81.00'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/EMA/EMA.ts b/src/EMA/EMA.ts index 7f08a0c79..619ac8ecb 100644 --- a/src/EMA/EMA.ts +++ b/src/EMA/EMA.ts @@ -12,6 +12,6 @@ export class EMA extends MovingAverage { const weightFactor = 2 / (this.interval + 1); - this.result = price.times(weightFactor).add(this.result.times(1 - weightFactor)); + this.setResult(price.times(weightFactor).add(this.result.times(1 - weightFactor))); } } diff --git a/src/Indicator.ts b/src/Indicator.ts index ca19b104a..1a893780d 100644 --- a/src/Indicator.ts +++ b/src/Indicator.ts @@ -8,4 +8,28 @@ export interface Indicator { update(...args: any): void; } -export interface SimpleIndicator extends Indicator {} +export abstract class SimpleIndicator implements Indicator { + highest?: Big; + lowest?: Big; + protected result?: Big; + + abstract isStable: boolean; + + abstract getResult(): Big; + + protected setResult(value: Big): Big { + this.result = value; + + if (!this.highest || value.gt(this.highest)) { + this.highest = value; + } + + if (!this.lowest || value.lt(this.lowest)) { + this.lowest = value; + } + + return this.result; + } + + abstract update(...args: any): void; +} diff --git a/src/MA/MovingAverage.ts b/src/MA/MovingAverage.ts index 5186c3c3c..728381475 100644 --- a/src/MA/MovingAverage.ts +++ b/src/MA/MovingAverage.ts @@ -2,10 +2,10 @@ import Big, {BigSource} from 'big.js'; import {NotEnoughDataError} from '../error'; import {SimpleIndicator} from '../Indicator'; -export abstract class MovingAverage implements SimpleIndicator { - protected result: Big | undefined; - - constructor(public readonly interval: number) {} +export abstract class MovingAverage extends SimpleIndicator { + constructor(public readonly interval: number) { + super(); + } get isStable(): boolean { return !!this.result; diff --git a/src/MOM/MOM.test.ts b/src/MOM/MOM.test.ts index a32edfb2e..9820a7da7 100644 --- a/src/MOM/MOM.test.ts +++ b/src/MOM/MOM.test.ts @@ -10,6 +10,7 @@ describe('MOM', () => { ]; const outputs = [1.56, 1.78, 1.12, 1.55, 0.75, 2.38, 3.7, 2.9, 3.22, 2.93]; const momentum = new MOM(5); + for (const input of inputs) { momentum.update(input); if (momentum.isStable) { @@ -18,6 +19,9 @@ describe('MOM', () => { expect(parseFloat(actual)).toBe(expected!); } } + + expect(momentum.lowest!.toFixed(2)).toBe('0.75'); + expect(momentum.highest!.toFixed(2)).toBe('3.70'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/MOM/MOM.ts b/src/MOM/MOM.ts index 2b06bbf31..40d34379e 100644 --- a/src/MOM/MOM.ts +++ b/src/MOM/MOM.ts @@ -6,12 +6,12 @@ import {NotEnoughDataError} from '../error'; /** * The Momentum indicator returns the change between the current price and the price n times ago. */ -export class MOM implements SimpleIndicator { +export class MOM extends SimpleIndicator { private readonly history: BigSource[]; private readonly historyLength: number; - private result: Big | undefined; constructor(public readonly length: number) { + super(); this.historyLength = length + 1; this.history = getFixedArray(this.historyLength); } @@ -19,7 +19,7 @@ export class MOM implements SimpleIndicator { update(value: BigSource): void { this.history.push(value); if (this.history.length === this.historyLength) { - this.result = new Big(value).minus(this.history[0]); + this.setResult(new Big(value).minus(this.history[0])); } } diff --git a/src/ROC/ROC.test.ts b/src/ROC/ROC.test.ts index e70535e22..a44e66668 100644 --- a/src/ROC/ROC.test.ts +++ b/src/ROC/ROC.test.ts @@ -43,8 +43,9 @@ const results = [ describe('ROC', () => { describe('getResult', () => { - it('should correctly calculate ROC with interval 5', () => { + it('calculates ROC with interval 5', () => { const roc = new ROC(5); + prices.forEach((price, index) => { roc.update(new BigNumber(price)); @@ -55,6 +56,9 @@ describe('ROC', () => { const expected = new BigNumber(Number(results[index])); expect(roc.getResult().toFixed(2)).toEqual(expected.toFixed(2)); }); + + expect(roc.lowest!.toFixed(2)).toBe('0.01'); + expect(roc.highest!.toFixed(2)).toBe('0.04'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/ROC/ROC.ts b/src/ROC/ROC.ts index 16688a983..0b8661478 100644 --- a/src/ROC/ROC.ts +++ b/src/ROC/ROC.ts @@ -2,11 +2,11 @@ import Big, {BigSource} from 'big.js'; import {NotEnoughDataError} from '../error'; import {SimpleIndicator} from '../Indicator'; -export class ROC implements SimpleIndicator { +export class ROC extends SimpleIndicator { private readonly priceHistory: Big[] = []; - private result: Big | undefined; constructor(public readonly interval: number) { + super(); this.interval = interval; } @@ -32,7 +32,7 @@ export class ROC implements SimpleIndicator { const comparePrice = this.priceHistory.shift() as Big; // (Close - Close periods ago) / (Close periods ago) - this.result = price.sub(comparePrice).div(comparePrice); + this.setResult(price.sub(comparePrice).div(comparePrice)); } getResult(): Big { diff --git a/src/RSI/RSI.test.ts b/src/RSI/RSI.test.ts index a287abe65..0e3e0f598 100644 --- a/src/RSI/RSI.test.ts +++ b/src/RSI/RSI.test.ts @@ -15,7 +15,7 @@ describe('RSI', () => { it('throws an error when there is not enough input data', () => { const rsi = new RSI(2); rsi.update(0); - + expect(rsi.isStable).toBeFalse(); try { rsi.getResult(); fail('Expected error'); @@ -26,6 +26,7 @@ describe('RSI', () => { it('calculates RSI with interval 2', () => { const rsi = new RSI(2); + prices.forEach((price, index) => { rsi.update(price); if (rsi.isStable) { @@ -33,10 +34,14 @@ describe('RSI', () => { expect(rsi.getResult().toPrecision(12)).toEqual(expected.toPrecision(12)); } }); + + expect(rsi.lowest!.toFixed(2)).toBe('0.00'); + expect(rsi.highest!.toFixed(2)).toBe('89.13'); }); it('calculates RSI with interval 12', () => { const rsi = new RSI(12); + prices.forEach((price, index) => { rsi.update(price); if (rsi.isStable) { @@ -44,10 +49,14 @@ describe('RSI', () => { expect(rsi.getResult().toPrecision(12)).toEqual(expected.toPrecision(12)); } }); + + expect(rsi.lowest!.toFixed(2)).toBe('0.00'); + expect(rsi.highest!.toFixed(2)).toBe('55.65'); }); it('calculates RSI with interval 26', () => { const rsi = new RSI(26); + prices.forEach((price, index) => { rsi.update(price); if (rsi.isStable) { @@ -55,6 +64,9 @@ describe('RSI', () => { expect(rsi.getResult().toPrecision(12)).toEqual(expected.toPrecision(12)); } }); + + expect(rsi.lowest!.toFixed(2)).toBe('0.00'); + expect(rsi.highest!.toFixed(2)).toBe('51.14'); }); it('prevents division by zero errors when the average gain and average loss equal 0', () => { @@ -71,12 +83,14 @@ describe('RSI', () => { const ohlc = Object.values(dataFile); const closes = ohlc.map(candle => candle[4]); const results: string[] = []; + for (const close of closes) { rsi.update(close); if (rsi.isStable) { results.push(rsi.getResult().valueOf()); } } + expect(closes.length).toBe(500); expect(results.length).toBe(closes.length - interval); expect(results[0].startsWith('78.997289972899')).toBeTrue(); diff --git a/src/RSI/RSI.ts b/src/RSI/RSI.ts index a504e2acf..8564f06ab 100644 --- a/src/RSI/RSI.ts +++ b/src/RSI/RSI.ts @@ -3,17 +3,15 @@ import {EMA, NotEnoughDataError, SMMA} from '..'; import {MovingAverage} from '../MA/MovingAverage'; import {SimpleIndicator} from '../Indicator'; -export class RSI implements SimpleIndicator { +export class RSI extends SimpleIndicator { private readonly prices: Big[] = []; - private result: Big; - private readonly avgGain: MovingAverage; private readonly avgLoss: MovingAverage; constructor(public readonly interval: number, Indicator: typeof EMA | typeof SMMA = SMMA) { + super(); this.avgGain = new Indicator(this.interval); this.avgLoss = new Indicator(this.interval); - this.result = new Big(0); } update(price: BigSource): void { @@ -38,7 +36,7 @@ export class RSI implements SimpleIndicator { // as long as there are not enough values as the required interval, the result should always be 0 if (this.prices.length <= this.interval) { - this.result = new Big(0); + this.setResult(new Big(0)); return; } @@ -47,7 +45,7 @@ export class RSI implements SimpleIndicator { : this.avgGain.getResult().div(this.avgLoss.getResult()); const max = new Big(100); - this.result = max.minus(max.div(relativeStrength.add(1))); + this.setResult(max.minus(max.div(relativeStrength.add(1)))); this.prices.shift(); } @@ -56,10 +54,13 @@ export class RSI implements SimpleIndicator { if (!this.isStable) { throw new NotEnoughDataError(); } - return this.result; + return this.result!; } get isStable(): boolean { - return this.result.gt(0); + if (this.result) { + return this.result.gt(0); + } + return false; } } diff --git a/src/SMA/SMA.test.ts b/src/SMA/SMA.test.ts index 743c2b5f4..8d017c155 100644 --- a/src/SMA/SMA.test.ts +++ b/src/SMA/SMA.test.ts @@ -21,26 +21,28 @@ describe('SMA', () => { sma.update('10'); sma.update(new Big(30)); expect(sma.getResult().valueOf()).toBe('20'); + expect(sma.lowest!.toFixed(2)).toBe('20.00'); + expect(sma.highest!.toFixed(2)).toBe('30.00'); }); }); describe('getResult', () => { it('matches the moving average logic from Binance exchange', () => { - const shortMovingAverageLength = 20; - const twentyCandles = testData.slice(Math.max(testData.length - shortMovingAverageLength, 1)); - const shortMovingAverage = new SMA(shortMovingAverageLength); + const shortInterval = 20; + const twentyCandles = testData.slice(Math.max(testData.length - shortInterval, 1)); + const short = new SMA(shortInterval); twentyCandles.forEach(candle => { - shortMovingAverage.update(new Big(candle.close)); + short.update(new Big(candle.close)); }); - expect(shortMovingAverage.getResult()!.toFixed(8)).toBe('85.78650000'); + expect(short.getResult()!.toFixed(8)).toBe('85.78650000'); - const longMovingAverageLength = 40; - const fourtyCandles = testData.slice(Math.max(testData.length - longMovingAverageLength, 1)); - const longMovingAverage = new SMA(longMovingAverageLength); + const longInterval = 40; + const fourtyCandles = testData.slice(Math.max(testData.length - longInterval, 1)); + const long = new SMA(longInterval); fourtyCandles.forEach(candle => { - longMovingAverage.update(new Big(candle.close)); + long.update(new Big(candle.close)); }); - expect(longMovingAverage.getResult()!.toFixed(8)).toBe('85.95125000'); + expect(long.getResult()!.toFixed(8)).toBe('85.95125000'); }); it('calculates SMAs with Binance candles', () => { @@ -69,6 +71,8 @@ describe('SMA', () => { const actual = sma.getResult(); const expected = new Big(SMA10results[interval - 1]); expect(actual.toPrecision(12)).toEqual(expected.toPrecision(12)); + expect(sma.lowest!.toFixed(2)).toBe('52.50'); + expect(sma.highest!.toFixed(2)).toBe('52.50'); }); it('calculates SMAs with window length 12', () => { @@ -78,6 +82,8 @@ describe('SMA', () => { const actual = sma.getResult(); const expected = new Big(SMA12results[interval - 1]); expect(actual.toPrecision(12)).toEqual(expected.toPrecision(12)); + expect(sma.lowest!.toFixed(2)).toBe('57.58'); + expect(sma.highest!.toFixed(2)).toBe('57.58'); }); it('calculates SMAs with window length 26', () => { @@ -87,6 +93,8 @@ describe('SMA', () => { const actual = sma.getResult(); const expected = new Big(SMA26results[interval - 1]); expect(actual.toPrecision(12)).toEqual(expected.toPrecision(12)); + expect(sma.lowest!.toFixed(2)).toBe('51.15'); + expect(sma.highest!.toFixed(2)).toBe('51.15'); }); it('throws an error when there is not enough input data', () => { diff --git a/src/SMA/SMA.ts b/src/SMA/SMA.ts index 9bd610842..f912e4023 100644 --- a/src/SMA/SMA.ts +++ b/src/SMA/SMA.ts @@ -13,7 +13,7 @@ export class SMA extends MovingAverage { if (this.prices.length === this.interval) { const sum = this.prices.reduce((a, b) => a.plus(b), new Big('0')); - this.result = sum.div(this.prices.length); + this.setResult(sum.div(this.prices.length)); } } } diff --git a/src/SMMA/SMMA.test.ts b/src/SMMA/SMMA.test.ts index 9b117ebe2..5b2b6420c 100644 --- a/src/SMMA/SMMA.test.ts +++ b/src/SMMA/SMMA.test.ts @@ -10,31 +10,43 @@ const smma26results = results.interval_26; describe('SMMA', () => { describe('getResult', () => { - it('correctly calculates SMMAs with interval 2', () => { + it('calculates SMMAs with interval 2', () => { const smma = new SMMA(2); + prices.forEach((price, index) => { smma.update(new BigNumber(price)); const expected = new BigNumber(smma2results[index]); expect(smma.getResult().toPrecision(12)).toEqual(expected.toPrecision(12)); }); + + expect(smma.lowest!.toFixed(2)).toBe('18.65'); + expect(smma.highest!.toFixed(2)).toBe('89.71'); }); - it('correctly calculates SMMAs with interval 12', () => { + it('calculates SMMAs with interval 12', () => { const smma = new SMMA(12); + prices.forEach((price, index) => { smma.update(new BigNumber(price)); const expected = new BigNumber(smma12results[index]); expect(smma.getResult().toPrecision(12)).toEqual(expected.toPrecision(12)); }); + + expect(smma.lowest!.toFixed(2)).toBe('45.80'); + expect(smma.highest!.toFixed(2)).toBe('59.12'); }); - it('correctly calculates SMMAs with interval 26', () => { + it('calculates SMMAs with interval 26', () => { const smma = new SMMA(26); + prices.forEach((price, index) => { smma.update(new BigNumber(price)); const expected = new BigNumber(smma26results[index]); expect(smma.getResult().toPrecision(12)).toEqual(expected.toPrecision(12)); }); + + expect(smma.lowest!.toFixed(2)).toBe('49.71'); + expect(smma.highest!.toFixed(2)).toBe('55.76'); }); }); }); diff --git a/src/SMMA/SMMA.ts b/src/SMMA/SMMA.ts index 3a6c80657..3907c54f4 100644 --- a/src/SMMA/SMMA.ts +++ b/src/SMMA/SMMA.ts @@ -3,15 +3,12 @@ import {SMA} from '../'; import {MovingAverage} from '../MA/MovingAverage'; class SMMA extends MovingAverage { - protected result: Big; - private readonly prices: Big[] = []; private readonly sma: SMA; constructor(public readonly interval: number) { super(interval); this.sma = new SMA(interval); - this.result = new Big(0); } update(price: BigSource): void { @@ -21,17 +18,19 @@ class SMMA extends MovingAverage { this.sma.update(price); } else if (this.prices.length === this.interval) { this.sma.update(price); - this.result = this.sma.getResult(); + this.setResult(this.sma.getResult()); } else { - this.result = this.result - .times(this.interval - 1) - .add(price) - .div(this.interval); + this.setResult( + this.getResult() + .times(this.interval - 1) + .add(price) + .div(this.interval) + ); } } getResult(): Big { - return this.result; + return this.result || new Big(0); } }