From 32b65b7b59d986a02d87fe4cfff2c584ac663a2b Mon Sep 17 00:00:00 2001 From: Pratik Galoria Date: Mon, 22 Jul 2019 18:10:46 +0530 Subject: [PATCH] core(feat): Add SMA indicator --- README.md | 58 ++++++++++++++++++++++- package.json | 2 +- src/indicators.js | 30 ++++++++++++ src/utils/indicator.js | 22 +++++++++ src/utils/symbols.js | 12 +++++ tests/indicators.test.js | 100 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 src/indicators.js create mode 100644 src/utils/indicator.js create mode 100644 src/utils/symbols.js create mode 100644 tests/indicators.test.js diff --git a/README.md b/README.md index fae6388..5fbd193 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,61 @@ # showr-core ![Travis (.org)](https://img.shields.io/travis/pratikgaloria/showr-core.svg) -[![npm (scoped)](https://img.shields.io/npm/v/@showr/core.svg)](https://www.npmjs.com/package/@showr/core) [![Coverage Status](https://coveralls.io/repos/github/pratikgaloria/showr-core/badge.svg?branch=master)](https://coveralls.io/github/pratikgaloria/showr-core?branch=master) +[![npm (scoped)](https://img.shields.io/npm/v/@showr/core.svg)](https://www.npmjs.com/package/@showr/core) +![GitHub issues](https://img.shields.io/github/issues/pratikgaloria/showr-core.svg) +![npm bundle size](https://img.shields.io/bundlephobia/min/@showr/core.svg) + +Showr: Open source JavaScript library to generate technical indicators over stock market data sets. + +## Usage + +### Indicators + +To generate a value over any point, call `value` function of any indicator. This function accepts below parameters: + +1. `point`: OHLC data point for which you need to generate an Indicator +2. `dataset`: Historical dataset consisting OHLC data points. +3. `options`: Other options for an indicator. For example, range, period etc. + +To set custom options, use `set` function of any indicator. + +Example: + +#### Importing indicator + +```JavaScript +import { SMA } from '@showr/core/indicators'; +``` + +#### Using indicator + +```JavaScript +// Historical dataset +const dataset = [{ + timestamp: '07/20/2019T09:30:00Z', + open: 142, + high: 145, + low: 135, + close: 140, +}, { + timestamp: '07/21/2019T09:30:00Z', + open: 140, + high: 150, + low: 130, + close: 145, +}]; + +// Latest point +const point = { + timestamp: '07/22/2019T09:30:00Z', + open: 145, + high: 160, + low: 140, + close: 150, +}; + +// To calculate a simple moving average for a given Point over the last 3 periods. +console.log(SMA.set({ period: 3}).value(point, dataset)); // 145 -Core library for showr +``` diff --git a/package.json b/package.json index 1c7c3ca..8f3a56d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@showr/core", - "version": "1.0.0", + "version": "1.1.0", "description": "Core library for Showr", "main": "build/index.js", "scripts": { diff --git a/src/indicators.js b/src/indicators.js new file mode 100644 index 0000000..79efa73 --- /dev/null +++ b/src/indicators.js @@ -0,0 +1,30 @@ +import Indicator from './utils/indicator'; +import { symbols } from './utils/symbols'; + +export const SMA = new Indicator('SMA', { period: 5, attribute: symbols.CLOSE }, function execute( + point, + dataset, +) { + const { period, attribute } = this.options; + + if (dataset.length < period - 1) { + return point[attribute]; + } + + if (dataset.length === period - 1) { + const total = dataset.reduce((acc, data) => acc + data[attribute], 0); + return (point[attribute] + total) / period; + } + + const reverseDataset = dataset.slice(0).reverse(); + let total = point[attribute]; + for (let i = 0; i < period - 1; i += 1) { + total += reverseDataset[i][attribute]; + } + + return total / period; +}); + +export default { + SMA, +}; diff --git a/src/utils/indicator.js b/src/utils/indicator.js new file mode 100644 index 0000000..f1c10bd --- /dev/null +++ b/src/utils/indicator.js @@ -0,0 +1,22 @@ +class Indicator { + constructor(name, options, execute) { + this.name = name; + this.options = options; + this.execute = execute; + } + + get value() { + return this.execute; + } + + set(options) { + this.options = { + ...this.options, + ...options, + }; + + return this; + } +} + +export default Indicator; diff --git a/src/utils/symbols.js b/src/utils/symbols.js new file mode 100644 index 0000000..33a927a --- /dev/null +++ b/src/utils/symbols.js @@ -0,0 +1,12 @@ +export const symbols = { + TIMESTAMP: 'timestamp', + OPEN: 'open', + HIGH: 'high', + LOW: 'low', + CLOSE: 'close', + VOLUME: 'volume', +}; + +export default { + symbols, +}; diff --git a/tests/indicators.test.js b/tests/indicators.test.js new file mode 100644 index 0000000..968ac24 --- /dev/null +++ b/tests/indicators.test.js @@ -0,0 +1,100 @@ +import { SMA } from '../src/indicators'; +import { symbols } from '../src/utils/symbols'; + +describe('Indicators', () => { + describe('SMA', () => { + const dataset5 = [ + { + [symbols.CLOSE]: 10, + }, + { + [symbols.CLOSE]: 12, + }, + { + [symbols.CLOSE]: 14, + }, + { + [symbols.CLOSE]: 16, + }, + { + [symbols.CLOSE]: 18, + }, + ]; + + it('Should return a valid value for a given point.', () => { + const point = { [symbols.CLOSE]: 20 }; + const expectedValue = 16; + + expect(SMA.value(point, dataset5)).toBe(expectedValue); + }); + + it('Should return the same value as attribute if dataset has fewer points than range.', () => { + const dataset = [ + { + [symbols.CLOSE]: 10, + }, + { + [symbols.CLOSE]: 12, + }, + { + [symbols.CLOSE]: 14, + }, + ]; + const point = { [symbols.CLOSE]: 20 }; + const expectedValue = 20; + + expect(SMA.value(point, dataset)).toBe(expectedValue); + }); + + it('Should return a valid value if dataset points are as equal as range.', () => { + const dataset = [ + { + [symbols.CLOSE]: 10, + }, + { + [symbols.CLOSE]: 12, + }, + { + [symbols.CLOSE]: 14, + }, + { + [symbols.CLOSE]: 16, + }, + ]; + const point = { [symbols.CLOSE]: 20 }; + const expectedValue = 14.4; + + expect(SMA.value(point, dataset)).toBe(expectedValue); + }); + + it('Should return a valid value for a given period.', () => { + const point = { [symbols.CLOSE]: 20 }; + const expectedValue = 18; + + expect(SMA.set({ period: 3 }).value(point, dataset5)).toBe(expectedValue); + }); + + it('Should return a valid value for any given attribute', () => { + const dataset = [ + { + [symbols.HIGH]: 10, + }, + { + [symbols.HIGH]: 12, + }, + ]; + const point = { [symbols.HIGH]: 14 }; + const expectedValue = 12; + + expect(SMA.set({ period: 3, attribute: symbols.HIGH }).value(point, dataset)).toBe( + expectedValue, + ); + }); + + it('Should return NaN if attribute is not present', () => { + const point = { [symbols.HIGH]: 20 }; + + expect(SMA.value(point, dataset5)).toBeNaN(); + }); + }); +});