From 5923be1078fb2c4964785d36fdf1d26096c19ac6 Mon Sep 17 00:00:00 2001 From: Benny Neugebauer Date: Sun, 31 Jan 2021 18:08:39 +0100 Subject: [PATCH] fix: Align MACD results with Tulip Indicators (#171) --- README.md | 13 ++- src/MACD/MACD.test.ts | 67 ++++++++++----- src/MACD/MACD.ts | 18 ++-- src/test/fixtures/MACD/results.json | 125 ---------------------------- 4 files changed, 67 insertions(+), 156 deletions(-) delete mode 100644 src/test/fixtures/MACD/results.json diff --git a/README.md b/README.md index 38c0d77cb..9c8ecb759 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Trading Signals -![Language Details](https://img.shields.io/github/languages/top/bennyn/trading-signals) ![Code Coverage](https://img.shields.io/codecov/c/github/bennyn/trading-signals/main) ![License](https://img.shields.io/npm/l/trading-signals.svg) ![Package Version](https://img.shields.io/npm/v/trading-signals.svg) ![Dependency Updates](https://img.shields.io/david/bennyn/trading-signals.svg) +![Language Details](https://img.shields.io/github/languages/top/bennycode/trading-signals) ![Code Coverage](https://img.shields.io/codecov/c/github/bennycode/trading-signals/main) ![License](https://img.shields.io/npm/l/trading-signals.svg) ![Package Version](https://img.shields.io/npm/v/trading-signals.svg) ![Dependency Updates](https://img.shields.io/david/bennycode/trading-signals.svg) Technical indicators and overlays to run technical analysis with JavaScript / TypeScript. @@ -52,9 +52,14 @@ console.log(sma.getResult().valueOf()); // "20" console.log(sma.getResult().toFixed(2)); // "20.00" ``` +## Alternatives + +- [Tulip Indicators [ANSI C]](https://github.com/TulipCharts/tulipindicators) +- [Pandas TA [Python]](https://github.com/twopirllc/pandas-ta) + ## Maintainers -[![Benny Neugebauer on Stack Exchange][stack_exchange_bennyn_badge]][stack_exchange_bennyn_url] +[![Benny Neugebauer on Stack Exchange][stack_exchange_bennycode_badge]][stack_exchange_bennycode_url] ## Contributing @@ -76,5 +81,5 @@ If you like this project, you might also like these related projects: - [**binance-api-node**](https://github.com/Ashlar/binance-api-node), Heavily tested and Promise-based Binance API. [1]: http://mikemcl.github.io/big.js/ -[stack_exchange_bennyn_badge]: https://stackexchange.com/users/flair/203782.png?theme=default -[stack_exchange_bennyn_url]: https://stackexchange.com/users/203782/benny-neugebauer?tab=accounts +[stack_exchange_bennycode_badge]: https://stackexchange.com/users/flair/203782.png?theme=default +[stack_exchange_bennycode_url]: https://stackexchange.com/users/203782/benny-neugebauer?tab=accounts diff --git a/src/MACD/MACD.test.ts b/src/MACD/MACD.test.ts index 82b951fbd..24dce891c 100644 --- a/src/MACD/MACD.test.ts +++ b/src/MACD/MACD.test.ts @@ -1,31 +1,60 @@ import {MACD} from './MACD'; import Big from 'big.js'; - -import prices from '../test/fixtures/prices.json'; -import results from '../test/fixtures/MACD/results.json'; -import {NotEnoughDataError} from '../error'; +import {DEMA, EMA, NotEnoughDataError} from '..'; describe('MACD', () => { describe('getResult', () => { - it('calculates MACD diff, signal & result with 12/26/9', () => { - const indicator = new MACD({longInterval: 26, shortInterval: 12, signalInterval: 9, useDEMA: false}); - prices.forEach((price, index) => { - indicator.update(new Big(price)); - - const {macd, signal, diff} = indicator.getResult(); + it('is compatible with values from "Tulip Indicators (TI)"', () => { + /** @see https://tulipindicators.org/macd */ + const inputs = [ + 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 expectedMacds = [ + undefined, + undefined, + undefined, + undefined, + '0.62', + '0.35', + '0.11', + '0.42', + '0.58', + '0.42', + '0.68', + '0.93', + '0.89', + '0.98', + '0.62', + ]; + const indicator = new MACD({longInterval: 5, shortInterval: 2, signalInterval: 9, indicator: EMA}); - const resMACD = new Big(results.macd[index]); - const resSignal = new Big(results.signal[index]); - const resDiff = new Big(results.diff[index]); + for (const [index, input] of Object.entries(inputs)) { + indicator.update(input); - expect(macd.toPrecision(12)).toEqual(resMACD.toPrecision(12)); - expect(signal.toPrecision(12)).toEqual(resSignal.toPrecision(12)); - expect(diff.toPrecision(12)).toEqual(resDiff.toPrecision(12)); - }); + const expectedMacd = expectedMacds[parseInt(index, 10)]; + if (expectedMacd !== undefined) { + const {macd} = indicator.getResult(); + expect(macd.toFixed(2)).toBe(expectedMacd); + } + } }); it('throws an error when there is not enough input data', () => { - const macd = new MACD({longInterval: 26, shortInterval: 12, signalInterval: 9, useDEMA: true}); + const macd = new MACD({longInterval: 26, shortInterval: 12, signalInterval: 9, indicator: DEMA}); try { macd.getResult(); @@ -39,7 +68,7 @@ describe('MACD', () => { describe('isStable', () => { it('knows when it can return reliable data', () => { const longInterval = 18; - const macd = new MACD({longInterval, shortInterval: 9, signalInterval: 9, useDEMA: false}); + const macd = new MACD({longInterval, shortInterval: 9, signalInterval: 9, indicator: EMA}); const mockedPrices = [ new Big('0.00019040'), diff --git a/src/MACD/MACD.ts b/src/MACD/MACD.ts index 8d28fe037..8f6f60790 100644 --- a/src/MACD/MACD.ts +++ b/src/MACD/MACD.ts @@ -3,14 +3,14 @@ import Big, {BigSource} from 'big.js'; import {DEMA, NotEnoughDataError} from '..'; export type MACDConfig = { + indicator: typeof EMA | typeof DEMA; longInterval: number; shortInterval: number; signalInterval: number; - useDEMA: boolean; }; export type MACDResult = { - diff: Big; + histogram: Big; macd: Big; signal: Big; }; @@ -24,9 +24,9 @@ export class MACD { private result: MACDResult | undefined; constructor(private readonly config: MACDConfig) { - this.long = config.useDEMA ? new DEMA(config.longInterval) : new EMA(config.longInterval); - this.short = config.useDEMA ? new DEMA(config.shortInterval) : new EMA(config.shortInterval); - this.signal = config.useDEMA ? new DEMA(config.signalInterval) : new EMA(config.signalInterval); + this.long = new config.indicator(config.longInterval); + this.short = new config.indicator(config.shortInterval); + this.signal = new config.indicator(config.signalInterval); } get isStable(): boolean { @@ -54,13 +54,15 @@ export class MACD { */ this.signal.update(diff); + const signal = this.signal.getResult(); + /** * The MACD-Histogram represents the difference between MACD and its 9-day EMA, the signal line. */ this.result = { - diff: diff, - macd: diff.sub(this.signal.getResult()), - signal: this.signal.getResult(), + histogram: diff.sub(signal), + macd: diff, + signal, }; this.age++; diff --git a/src/test/fixtures/MACD/results.json b/src/test/fixtures/MACD/results.json deleted file mode 100644 index 935ba1976..000000000 --- a/src/test/fixtures/MACD/results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "macd": [ - 0, - -3.6376068376068362, - -2.4639072734799217, - -5.031340295620034, - -5.521802745494824, - -6.076152724792792, - -3.053848272963074, - 0.33568310636747434, - 2.9473056543174314, - -1.7093100949105917, - 0.8774837070937913, - 2.1231125756183227, - 2.585623647541957, - -1.5571775822256484, - 1.0154188470421683, - 1.8525149219271597, - -0.42461879714695083, - 0.3167899361939739, - -0.7270321370494885, - -3.3439676735158663, - -4.504658691176687, - -4.107132512783021, - -1.6595236920538383, - -1.7529125979171347, - 0.7180893397975616, - 1.8572538942113148, - -0.07210193668202258, - 3.4517711329096086, - 5.350885446717413, - 6.247517316069724, - 7.3480176240854345, - 1.5457357005888681, - 2.1198449676882127, - -2.0235768893083987, - -2.839360636962912, - -4.3281862834160645, - 0.2946340051329588, - 1.6766181261237905, - -1.2825765539962966 - ], - "signal": [ - -0.00000000000000162, - -0.9094017094017091, - -1.5253785277716898, - -2.7832136016766986, - -4.163664288050406, - -5.6827024692486034, - -6.446164537489373, - -6.362243760897504, - -5.625417347318147, - -6.052744871045796, - -5.833373944272349, - -5.30259580036777, - -4.65618988848228, - -5.045484284038693, - -4.791629572278152, - -4.328500841796362, - -4.4346555410831, - -4.355458057034607, - -4.537216091296979, - -5.373208009675945, - -6.499372682470117, - -7.526155810665873, - -7.941036733679333, - -8.379264883158616, - -8.199742548209226, - -7.735429074656398, - -7.753454558826904, - -6.890511775599502, - -5.55279041392015, - -3.990911084902719, - -2.153906678881361, - -1.767472753734144, - -1.2375115118120907, - -1.7434057341391904, - -2.4532458933799184, - -3.535292464233935, - -3.4616339629506956, - -3.0424794314197485, - -3.363123569918823 - ], - "diff": [ - -0.00000000000000162, - -4.547008547008545, - -3.9892858012516115, - -7.814553897296733, - -9.68546703354523, - -11.758855194041395, - -9.500012810452446, - -6.02656065453003, - -2.678111693000716, - -7.762054965956388, - -4.955890237178558, - -3.179483224749447, - -2.070566240940323, - -6.6026618662643415, - -3.776210725235984, - -2.4759859198692027, - -4.859274338230051, - -4.038668120840633, - -5.264248228346467, - -8.717175683191812, - -11.004031373646804, - -11.633288323448895, - -9.600560425733171, - -10.13217748107575, - -7.4816532084116645, - -5.8781751804450835, - -7.825556495508927, - -3.4387406426898934, - -0.20190496720273643, - 2.256606231167005, - 5.1941109452040735, - -0.2217370531452758, - 0.8823334558761218, - -3.766982623447589, - -5.29260653034283, - -7.863478747649999, - -3.166999957817737, - -1.365861305295958, - -4.64570012391512 - ] -}