diff --git a/src/SMA/SMA.test.ts b/src/SMA/SMA.test.ts index a154162e1..e5260d980 100644 --- a/src/SMA/SMA.test.ts +++ b/src/SMA/SMA.test.ts @@ -1,5 +1,5 @@ import Big from 'big.js'; -import {NotEnoughDataError, SMA} from '..'; +import {NotEnoughDataError, SMA, FasterSMA} from '..'; import prices from '../test/fixtures/prices.json'; import testData from '../test/fixtures/SMA/LTC-USDT-1m.json'; @@ -126,3 +126,49 @@ describe('SMA', () => { }); }); }); + +describe('FasterSMA', () => { + describe('getResult', () => { + it('calculates the moving average based on the last 5 prices', () => { + // Test data taken from: + // https://github.com/TulipCharts/tulipindicators/blob/v0.8.0/tests/untest.txt#L359-L361 + 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, + ]; + const expectations = [ + '82.426', + '82.738', + '83.094', + '83.318', + '83.628', + '83.778', + '84.254', + '84.994', + '85.574', + '86.218', + '86.804', + ]; + const sma = new FasterSMA(5); + for (const price of prices) { + const actual = sma.update(price); + if (actual) { + const expected = expectations.shift()!; + expect(actual.toFixed(3)).toBe(expected); + } + } + expect(sma.isStable()).toBeTrue(); + expect(sma.getResult()).toBe(86.804); + }); + + it('throws an error when there is not enough input data', () => { + const sma = new FasterSMA(5); + + try { + sma.getResult(); + fail('Expected error'); + } catch (error) { + expect(error).toBeInstanceOf(NotEnoughDataError); + } + }); + }); +}); diff --git a/src/SMA/SMA.ts b/src/SMA/SMA.ts index e7d00f01a..836ae94b3 100644 --- a/src/SMA/SMA.ts +++ b/src/SMA/SMA.ts @@ -1,5 +1,6 @@ import Big, {BigSource} from 'big.js'; import {MovingAverage} from '../MA/MovingAverage'; +import {NotEnoughDataError} from '../error'; /** * Simple Moving Average (SMA) @@ -30,3 +31,34 @@ export class SMA extends MovingAverage { return sum.div(prices.length); } } + +export class FasterSMA { + protected result?: number; + public readonly prices: number[] = []; + + constructor(public readonly interval: number) {} + + isStable(): boolean { + return this.prices.length === this.interval; + } + + getResult(): number { + if (!this.result) { + throw new NotEnoughDataError(); + } + return this.result; + } + + update(price: number): number | void { + this.prices.push(price); + + if (this.prices.length > this.interval) { + this.prices.shift(); + } + + if (this.prices.length === this.interval) { + const sum = this.prices.reduce((a, b) => a + b, 0); + return (this.result = sum / this.prices.length); + } + } +} diff --git a/src/index.ts b/src/index.ts index b40b35e0b..57505883e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,6 @@ export * from './MACD/MACD'; export * from './MOM/MOM'; export * from './ROC/ROC'; export * from './RSI/RSI'; -export * from './SMA/FasterSMA'; export * from './SMA/SMA'; export * from './SMMA/SMMA'; export * from './STOCH/StochasticOscillator'; diff --git a/src/start/startBenchmark.ts b/src/start/startBenchmark.ts index 75f202556..a4ac85d96 100644 --- a/src/start/startBenchmark.ts +++ b/src/start/startBenchmark.ts @@ -3,7 +3,7 @@ import {BollingerBands} from '../BBANDS/BollingerBands'; import {SMA} from '../SMA/SMA'; import {FasterSMA} from '../SMA/FasterSMA'; import candles from '../test/fixtures/candles/100-candles.json'; -import {fasterGetAverage, getAverage, getFasterStandardDeviation, getStandardDeviation} from '../util'; +import {getFasterAverage, getAverage, getFasterStandardDeviation, getStandardDeviation} from '../util'; const interval = 20; const prices = candles.map(candle => parseInt(candle.close, 10)); @@ -32,7 +32,7 @@ new Benchmark.Suite('Technical Indicators') return getAverage(prices); }) .add('fasterGetAverage', () => { - return fasterGetAverage(prices); + return getFasterAverage(prices); }) .add('getStandardDeviation', () => { return getStandardDeviation(prices); diff --git a/src/util/getAverage.test.ts b/src/util/getAverage.test.ts index fb629b4dd..68942f4f3 100644 --- a/src/util/getAverage.test.ts +++ b/src/util/getAverage.test.ts @@ -1,4 +1,4 @@ -import {fasterGetAverage, getAverage} from './getAverage'; +import {getFasterAverage, getAverage} from './getAverage'; describe('getAverage', () => { it('does not fail when entering an empty array', () => { @@ -8,17 +8,15 @@ describe('getAverage', () => { it('returns the average of all given prices', () => { const prices = [20, 30, 40]; - const average = getAverage(prices); expect(average.valueOf()).toBe('30'); }); }); -describe('fasterGetAverage', () => { +describe('getFasterAverage', () => { it('only works with the primitive data type number', () => { const prices = [20, 30, 40]; - - const average = fasterGetAverage(prices); + const average = getFasterAverage(prices); expect(average).toBe(30); }); }); diff --git a/src/util/getAverage.ts b/src/util/getAverage.ts index 094c511ac..6aed7d8c6 100644 --- a/src/util/getAverage.ts +++ b/src/util/getAverage.ts @@ -12,6 +12,6 @@ export function getAverage(values: BigSource[]): Big { return sum.div(values.length); } -export function fasterGetAverage(values: number[]): number { +export function getFasterAverage(values: number[]): number { return values.reduce((sum: number, x: number) => sum + x, 0) / values.length; } diff --git a/src/util/getStandardDeviation.ts b/src/util/getStandardDeviation.ts index 8ebae2325..027c21488 100644 --- a/src/util/getStandardDeviation.ts +++ b/src/util/getStandardDeviation.ts @@ -1,4 +1,4 @@ -import {fasterGetAverage, getAverage} from './getAverage'; +import {getFasterAverage, getAverage} from './getAverage'; import Big, {BigSource} from 'big.js'; /** @@ -15,8 +15,8 @@ export function getStandardDeviation(values: BigSource[], average?: BigSource): } export function getFasterStandardDeviation(values: number[], average?: number): number { - const middle = average || fasterGetAverage(values); + const middle = average || getFasterAverage(values); const squaredDifferences = values.map(value => value - middle).map(value => value * value); - const averageDifference = fasterGetAverage(squaredDifferences); + const averageDifference = getFasterAverage(squaredDifferences); return Math.sqrt(averageDifference); }