diff --git a/src/ADX/ADX.test.ts b/src/ADX/ADX.test.ts index 3e41b8da3..60cf0970e 100644 --- a/src/ADX/ADX.test.ts +++ b/src/ADX/ADX.test.ts @@ -9,27 +9,69 @@ const adx14results = data.interval_14; describe('ADX', () => { describe('getResult', () => { - it('should correctly calculate the ADX with interval 14', () => { - const adx = new ADX(14); + it('calculates the ADX with interval 14', () => { + const indicator = new ADX(14); candles.forEach((candle, index) => { - adx.update(candle); + indicator.update(candle); - if (adx.isStable) { + if (indicator.isStable) { const res = new BigNumber(adx14results[index] as number); - expect(adx.getResult().toFixed(4)).toEqual(res.toFixed(4)); + expect(indicator.getResult().adx.toFixed(4)).toEqual(res.toFixed(4)); } }); }); it('throws an error when there is not enough input data', () => { - const adx = new ADX(14); + const indicator = new ADX(14); try { - adx.getResult(); + indicator.getResult(); fail('Expected error'); } catch (error) { expect(error).toBeInstanceOf(NotEnoughDataError); } }); + + it('returns the directional indicators (+DI & -DI)', () => { + /** + * Test data from: + * https://github.com/anandanand84/technicalindicators/blob/v3.1.0/test/directionalmovement/ADX.js#L23-L28 + */ + const data = { + close: [ + 29.87, 30.24, 30.1, 28.9, 28.92, 28.48, 28.56, 27.56, 28.47, 28.28, 27.49, 27.23, 26.35, 26.33, 27.03, 26.22, + 26.01, 25.46, 27.03, 27.45, 28.36, 28.43, 27.95, 29.01, 29.38, 29.36, 28.91, 30.61, 30.05, 30.19, 31.12, + 30.54, 29.78, 30.04, 30.49, 31.47, 32.05, 31.97, 31.13, 31.66, 32.64, 32.59, 32.19, 32.1, 32.93, 33.0, 31.94, + ], + high: [ + 30.2, 30.28, 30.45, 29.35, 29.35, 29.29, 28.83, 28.73, 28.67, 28.85, 28.64, 27.68, 27.21, 26.87, 27.41, 26.94, + 26.52, 26.52, 27.09, 27.69, 28.45, 28.53, 28.67, 29.01, 29.87, 29.8, 29.75, 30.65, 30.6, 30.76, 31.17, 30.89, + 30.04, 30.66, 30.6, 31.97, 32.1, 32.03, 31.63, 31.85, 32.71, 32.76, 32.58, 32.13, 33.12, 33.19, 32.52, + ], + low: [ + 29.41, 29.32, 29.96, 28.74, 28.56, 28.41, 28.08, 27.43, 27.66, 27.83, 27.4, 27.09, 26.18, 26.13, 26.63, 26.13, + 25.43, 25.35, 25.88, 26.96, 27.14, 28.01, 27.88, 27.99, 28.76, 29.14, 28.71, 28.93, 30.03, 29.39, 30.14, + 30.43, 29.35, 29.99, 29.52, 30.94, 31.54, 31.36, 30.92, 31.2, 32.13, 32.23, 31.97, 31.56, 32.21, 32.63, 31.76, + ], + }; + + const indicator = new ADX(14); + + for (let i = 0; i < Object.keys(data.low).length; i++) { + indicator.update({ + close: data.close[i], + high: data.high[i], + low: data.low[i], + }); + } + + /** + * Expectation from: + * https://github.com/anandanand84/technicalindicators/blob/v3.1.0/test/directionalmovement/ADX.js#L128-L130 + */ + expect(indicator.getResult().adx.toFixed(3)).toBe('17.288'); + expect(indicator.getResult().mdi.toFixed(3)).toBe('24.460'); + expect(indicator.getResult().pdi.toFixed(3)).toBe('27.420'); + }); }); }); diff --git a/src/ADX/ADX.ts b/src/ADX/ADX.ts index a04ebb56a..b8ce6295f 100644 --- a/src/ADX/ADX.ts +++ b/src/ADX/ADX.ts @@ -1,7 +1,15 @@ import {Big} from 'big.js'; import {NotEnoughDataError} from '../error'; import {ATR, ATRCandle, MathAnalysis, SMMA} from '..'; -import {SimpleIndicator} from '../Indicator'; +import {Indicator} from '../Indicator'; + +export type ADXResult = { + adx: Big; + /** Minus Directional Indicator (-DI) */ + mdi: Big; + /** Plus Directional Indicator (+DI) */ + pdi: Big; +}; /** * Average Directional Index @@ -14,14 +22,16 @@ import {SimpleIndicator} from '../Indicator'; * Generally, ADX readings below 20 indicate trend weakness, and readings above 40 indicate trend strength. * An extremely strong trend is indicated by readings above 50. */ -export class ADX implements SimpleIndicator { +export class ADX implements Indicator { private readonly candles: ATRCandle[] = []; private readonly atr: ATR; private readonly smoothedPDM: SMMA; private readonly smoothedMDM: SMMA; private readonly dxValues: Big[] = []; private prevCandle: ATRCandle | undefined; - private result: Big | undefined; + private adx: Big | undefined; + private pdi: Big = new Big(0); + private mdi: Big = new Big(0); constructor(public interval: number) { this.atr = new ATR(interval); @@ -55,7 +65,7 @@ export class ADX implements SimpleIndicator { this.smoothedMDM.update(mdm); this.smoothedPDM.update(pdm); - // prevCandle isn't needed anymore therefore we can update it for the next iteration. + // Previous candle isn't needed anymore therefore we can update it for the next iteration. this.prevCandle = candle; if (this.candles.length <= this.interval) { @@ -69,7 +79,7 @@ export class ADX implements SimpleIndicator { * * This is the green Plus Directional Indicator line (+DI) when plotting. */ - const pdi = this.smoothedPDM.getResult().div(this.atr.getResult()).times(100); + this.pdi = this.smoothedPDM.getResult().div(this.atr.getResult()).times(100); /** * Divide the smoothed Minus Directional Movement (-DM) @@ -78,7 +88,7 @@ export class ADX implements SimpleIndicator { * * This is the red Minus Directional Indicator line (-DI) when plotting. */ - const mdi = this.smoothedMDM.getResult().div(this.atr.getResult()).times(100); + this.mdi = this.smoothedMDM.getResult().div(this.atr.getResult()).times(100); /** * The Directional Movement Index (DX) equals @@ -87,7 +97,7 @@ export class ADX implements SimpleIndicator { * * Multiply by 100 to move the decimal point two places. */ - const dx = pdi.sub(mdi).abs().div(pdi.add(mdi)).times(100); + const dx = this.pdi.sub(this.mdi).abs().div(this.pdi.add(this.mdi)).times(100); /** * The dx values only really have to be kept for the very first ADX calculation @@ -105,11 +115,11 @@ export class ADX implements SimpleIndicator { return; } - if (!this.result) { + if (!this.adx) { /** * The first ADX value is simply a average of DX. */ - this.result = MathAnalysis.getAverage(this.dxValues); + this.adx = MathAnalysis.getAverage(this.dxValues); return; } @@ -119,17 +129,21 @@ export class ADX implements SimpleIndicator { * adding the most recent DX value, * and dividing this total by . */ - this.result = this.result + this.adx = this.adx .times(this.interval - 1) .add(dx) .div(this.interval); } - getResult(): Big { - if (!this.result) { + getResult(): ADXResult { + if (!this.adx) { throw new NotEnoughDataError(); } - return this.result; + return { + adx: this.adx, + mdi: this.mdi, + pdi: this.pdi, + }; } private directionalMovement(prevCandle: ATRCandle, currentCandle: ATRCandle): {mdm: Big; pdm: Big} { diff --git a/yarn.lock b/yarn.lock index e847fc527..c1d758805 100644 --- a/yarn.lock +++ b/yarn.lock @@ -306,14 +306,6 @@ "@typescript-eslint/typescript-estree" "4.26.0" debug "^4.3.1" -"@typescript-eslint/scope-manager@4.25.0": - version "4.25.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz#9d86a5bcc46ef40acd03d85ad4e908e5aab8d4ca" - integrity sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w== - dependencies: - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/visitor-keys" "4.25.0" - "@typescript-eslint/scope-manager@4.26.0": version "4.26.0" resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.0.tgz#60d1a71df162404e954b9d1c6343ff3bee496194" @@ -322,11 +314,6 @@ "@typescript-eslint/types" "4.26.0" "@typescript-eslint/visitor-keys" "4.26.0" -"@typescript-eslint/types@4.25.0": - version "4.25.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.25.0.tgz#0e444a5c5e3c22d7ffa5e16e0e60510b3de5af87" - integrity sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ== - "@typescript-eslint/types@4.26.0": version "4.26.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.0.tgz#7c6732c0414f0a69595f4f846ebe12616243d546" @@ -345,19 +332,6 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.25.0": - version "4.25.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz#942e4e25888736bff5b360d9b0b61e013d0cfa25" - integrity sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg== - dependencies: - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/visitor-keys" "4.25.0" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" - "@typescript-eslint/typescript-estree@4.26.0": version "4.26.0" resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.0.tgz#aea17a40e62dc31c63d5b1bbe9a75783f2ce7109" @@ -371,14 +345,6 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.25.0": - version "4.25.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz#863e7ed23da4287c5b469b13223255d0fde6aaa7" - integrity sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg== - dependencies: - "@typescript-eslint/types" "4.25.0" - eslint-visitor-keys "^2.0.0" - "@typescript-eslint/visitor-keys@4.26.0": version "4.26.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.0.tgz#26d2583169222815be4dcd1da4fe5459bc3bcc23" @@ -1160,18 +1126,6 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globby@^11.0.1: - version "11.0.2" - resolved "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" - integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - globby@^11.0.3: version "11.0.3" resolved "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" @@ -2019,13 +1973,6 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: dependencies: lru-cache "^6.0.0" -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -2232,13 +2179,6 @@ tsutils@^3.17.1, tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"