From cb61140a32a4b7a25bbe5669e5b7546289937746 Mon Sep 17 00:00:00 2001 From: Roman Mukhin Date: Tue, 31 Mar 2020 13:51:54 +0200 Subject: [PATCH] added momentum indicator --- src/indicator/index.js | 6 ++- src/indicator/momentum.js | 56 +++++++++++++++++++++ test/spec/bundle/_fixtures/data/momentum.js | 26 +++++++++- test/spec/bundle/indicator/momentumSpec.js | 36 +++++++++++++ test/spec/common/techanSpec.js | 8 +++ 5 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 test/spec/bundle/indicator/momentumSpec.js diff --git a/src/indicator/index.js b/src/indicator/index.js index 5057148b..c32b7e7c 100644 --- a/src/indicator/index.js +++ b/src/indicator/index.js @@ -9,7 +9,8 @@ module.exports = function(d3) { atr = require('./atr')(indicatorMixin, accessor.ohlc, sma), circularbuffer = require('../util')().circularbuffer, sroc_init = require('./sroc'), - vwap = require('./vwap')(indicatorMixin, accessor.ohlc); + vwap = require('./vwap')(indicatorMixin, accessor.ohlc), + momentum = require('./momentum')(indicatorMixin, accessor.ohlc); return { atr: atr, @@ -28,7 +29,8 @@ module.exports = function(d3) { williams: require('./williams')(indicatorMixin, accessor.ohlc, ema), adx: require('./adx')(d3.max, indicatorMixin, accessor.ohlc, ema), bollinger: require('./bollinger')(indicatorMixin, accessor.ohlc, sma), - vwap: vwap + vwap: vwap, + momentum: momentum }; }; diff --git a/src/indicator/momentum.js b/src/indicator/momentum.js index e69de29b..279fe1c0 100644 --- a/src/indicator/momentum.js +++ b/src/indicator/momentum.js @@ -0,0 +1,56 @@ +'use strict'; + +module.exports = function(indicatorMixin, accessor_ohlc) { // Injected dependencies + return function() { // Closure function + var p = {}, // Container for private, direct access mixed in variables + pastSamples, + currentIndex; + + function indicator(data) { + indicator.init(); + return data.map(momentum).filter(function(d) { return d.value !== null; }); + } + + function momentum(d, i) { + var currentValue = p.accessor(d), + value = indicator.mom(currentValue); + //console.log({ date: p.accessor.d(d), value: value }, i, currentValue); + return { date: p.accessor.d(d), value: value }; + } + + indicator.mom = function(currentValue) { + var value = null; + var pastValue; + + if (currentIndex+1 <= p.period) { + pastSamples.push(currentValue); + } + else { + var accessIndex = (currentIndex - p.period) % p.period; + pastValue = pastSamples[accessIndex]; + pastSamples[accessIndex] = currentValue; + } + + if (!!pastValue) { + value = currentValue - pastValue; + // round to 2 decimal points: value = Math.round((value + 0.0001) * 100) / 100; + } + + currentIndex++; + return value; + }; + + indicator.init = function() { + pastSamples = []; + currentIndex = 0; + return indicator; + }; + + // Mixin 'superclass' methods and variables + indicatorMixin(indicator, p) + .accessor(accessor_ohlc()) + .period(12); + + return indicator; + }; +}; \ No newline at end of file diff --git a/test/spec/bundle/_fixtures/data/momentum.js b/test/spec/bundle/_fixtures/data/momentum.js index 6d790761..7dafa43b 100644 --- a/test/spec/bundle/_fixtures/data/momentum.js +++ b/test/spec/bundle/_fixtures/data/momentum.js @@ -1 +1,25 @@ -module.exports = [{ date: new Date(2014, 2, 5), momentum: -1.06 }]; \ No newline at end of file +module.exports = { + input: [ + { date: new Date("2020-05-01"), close: 44.34 }, // -5 + { date: new Date("2020-05-02"), close: 44.09 }, // -4 + { date: new Date("2020-05-03"), close: 44.15 }, // -3 + { date: new Date("2020-05-04"), close: 43.61 }, // -2 + { date: new Date("2020-05-05"), close: 44.33 }, // -1 + { date: new Date("2020-05-06"), close: 44.83 }, // 0 + { date: new Date("2020-05-07"), close: 43.59 }, // 1 + { date: new Date("2020-05-08"), close: 43.33 }, // 2 + { date: new Date("2020-05-09"), close: 42.01 }, // 3 + { date: new Date("2020-05-10"), close: 43.21 }, // 4 + { date: new Date("2020-05-11"), close: 43.99 }, // 5 + { date: new Date("2020-05-12"), close: 44.32 }, // 6 + ], + expected: [ + { date: new Date("2020-05-06"), value: 0.49 }, + { date: new Date("2020-05-07"), value: -0.50 }, + { date: new Date("2020-05-08"), value: -0.82}, + { date: new Date("2020-05-09"), value: -1.60}, + { date: new Date("2020-05-10"), value: -1.12}, + { date: new Date("2020-05-11"), value: -0.84}, + { date: new Date("2020-05-12"), value: 0.73}, + ] + }; \ No newline at end of file diff --git a/test/spec/bundle/indicator/momentumSpec.js b/test/spec/bundle/indicator/momentumSpec.js new file mode 100644 index 00000000..f4baa0c1 --- /dev/null +++ b/test/spec/bundle/indicator/momentumSpec.js @@ -0,0 +1,36 @@ +techanModule('indicator/momentum', function(specBuilder) { + 'use strict'; + + var techan = require('../../../../src/techan'), + data = require('./../_fixtures/data/momentum'); + + var actualInit = function() { + return techan.indicator.momentum; + }; + + specBuilder.require(require('../../../../src/indicator/momentum'), function(instanceBuilder) { + instanceBuilder.instance('actual', actualInit, function(scope) { + describe('And momentum is initialised with period(5)', function () { + var momentum; + + beforeEach(function () { + momentum = scope.momentum.period(5); // reduce window to 5 day (against default 12) + }); + + it('Then on default invoke, momentum should calculate correct values', function() { + var mom = momentum(data.input); + expect(mom.length).toEqual(data.expected.length); + + var accessor = techan.accessor.value(); + + mom.forEach(function(d, i) { + // round to 2 decimal digits for test comparation + accessor.v(d, Math.round((accessor.v(d) + 0.0001) * 100) / 100); + //console.log("rawValue/rounded", rawValue, roundedValue); + expect(d).toEqual(data.expected[i]); + }); + }); + }); + }); + }); + }); \ No newline at end of file diff --git a/test/spec/common/techanSpec.js b/test/spec/common/techanSpec.js index fd1862c2..13de55f7 100644 --- a/test/spec/common/techanSpec.js +++ b/test/spec/common/techanSpec.js @@ -138,6 +138,14 @@ function techanSpec(techan) { expect(techan.indicator.wilderma()).toBeDefined(); }); + it('Then techan.indicator.momentum should be defined', function () { + expect(techan.indicator.momentum).toBeDefined(); + }); + + it('Then techan.indicator.momentum can be constructed', function () { + expect(techan.indicator.momentum()).toBeDefined(); + }); + it('Then techan.plot should be defined', function () { expect(techan.plot).toBeDefined(); });