diff --git a/README.md b/README.md index 60303d0fd..438bc6143 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ The "trading-signals" library provides a TypeScript implementation for common te 1. Stochastic Oscillator (STOCH) 1. Wilder's Smoothed Moving Average (WSMA) +Utility Methods: + +1. Average +1. Standard Deviation +1. Rolling Standard Deviation + ## Usage ```typescript diff --git a/src/BBANDS/BollingerBands.ts b/src/BBANDS/BollingerBands.ts index 4f5856622..958a92dae 100644 --- a/src/BBANDS/BollingerBands.ts +++ b/src/BBANDS/BollingerBands.ts @@ -3,7 +3,7 @@ import {SMA} from '../SMA/SMA'; import {NotEnoughDataError} from '../error'; import {BandsResult} from '../util/BandsResult'; import {Indicator} from '../Indicator'; -import {getAverage} from '../util/getAverage'; +import {getStandardDeviation} from '../util'; /** * Bollinger Bands (BBANDS) @@ -42,8 +42,7 @@ export class BollingerBands implements Indicator { } const middle = SMA.getResultFromBatch(this.prices); - const squareDiffs = this.prices.map((price: Big) => price.sub(middle).pow(2)); - const standardDeviation = getAverage(squareDiffs).sqrt(); + const standardDeviation = getStandardDeviation(this.prices, middle); this.result = { lower: middle.sub(standardDeviation.times(this.deviationMultiplier)), diff --git a/src/start/startBenchmark.ts b/src/start/startBenchmark.ts index fa5251124..75f202556 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} from '../util'; +import {fasterGetAverage, getAverage, getFasterStandardDeviation, getStandardDeviation} from '../util'; const interval = 20; const prices = candles.map(candle => parseInt(candle.close, 10)); @@ -34,6 +34,12 @@ new Benchmark.Suite('Technical Indicators') .add('fasterGetAverage', () => { return fasterGetAverage(prices); }) + .add('getStandardDeviation', () => { + return getStandardDeviation(prices); + }) + .add('getFasterStandardDeviation', () => { + return getFasterStandardDeviation(prices); + }) .on('cycle', (event: Event) => { console.info(String(event.target)); }) diff --git a/src/util/getStandardDeviation.test.ts b/src/util/getStandardDeviation.test.ts new file mode 100644 index 000000000..b3a75d893 --- /dev/null +++ b/src/util/getStandardDeviation.test.ts @@ -0,0 +1,30 @@ +import {SMA} from '../SMA/SMA'; +import {getFasterStandardDeviation, getStandardDeviation} from './getStandardDeviation'; + +describe('getStandardDeviation', () => { + it('returns the standard deviation', () => { + const prices = [9, 2, 5, 4, 12, 7, 8, 11, 9, 3, 7, 4, 12, 5, 4, 10, 9, 6, 9, 4]; + const std = getStandardDeviation(prices); + expect(std.toFixed(2)).toBe('2.98'); + }); + + it('can be used to calculate a "Window Rolling Standard Deviation / Standard Deviation Over Period"', () => { + // Test data taken from: + // https://github.com/TulipCharts/tulipindicators/blob/v0.8.0/tests/untest.txt#L367-L369 + const prices = [81.59, 81.06, 82.87, 83.0, 83.61]; + const average = SMA.getResultFromBatch(prices); + const stdDev = getStandardDeviation(prices, average); + expect(stdDev.toFixed(2)).toBe('0.95'); + }); +}); + +describe('getFasterStandardDeviation', () => { + it('only works with the primitive data type number', () => { + const prices = [9, 2, 5, 4, 12, 7, 8, 11, 9, 3, 7, 4, 12, 5, 4, 10, 9, 6, 9, 4]; + const std = getFasterStandardDeviation(prices); + expect(std.toFixed(2)).toBe('2.98'); + const fivePrices = [81.59, 81.06, 82.87, 83.0, 83.61]; + const stdDev = getStandardDeviation(fivePrices, SMA.getResultFromBatch(fivePrices)); + expect(stdDev.toFixed(2)).toBe('0.95'); + }); +}); diff --git a/src/util/getStandardDeviation.ts b/src/util/getStandardDeviation.ts new file mode 100644 index 000000000..8ebae2325 --- /dev/null +++ b/src/util/getStandardDeviation.ts @@ -0,0 +1,22 @@ +import {fasterGetAverage, getAverage} from './getAverage'; +import Big, {BigSource} from 'big.js'; + +/** + * Standard deviation calculates how prices for a collection of prices are spread out from the average price of these + * prices. + * + * @see https://www.mathsisfun.com/data/standard-deviation-formulas.html + * @see https://www.youtube.com/watch?v=9-8E8L_77-8 + */ +export function getStandardDeviation(values: BigSource[], average?: BigSource): Big { + const middle = average || getAverage(values); + const squaredDifferences = values.map((value: BigSource) => new Big(value).sub(middle).pow(2)); + return getAverage(squaredDifferences).sqrt(); +} + +export function getFasterStandardDeviation(values: number[], average?: number): number { + const middle = average || fasterGetAverage(values); + const squaredDifferences = values.map(value => value - middle).map(value => value * value); + const averageDifference = fasterGetAverage(squaredDifferences); + return Math.sqrt(averageDifference); +} diff --git a/src/util/index.ts b/src/util/index.ts index 7670153d4..2346b3113 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -3,4 +3,5 @@ export * from './getAverage'; export * from './getFixedArray'; export * from './getMaximum'; export * from './getMinimum'; +export * from './getStandardDeviation'; export * from './HighLowClose';