Skip to content

Commit

Permalink
feat(ADX): Return directional indicators (+DI & -DI)
Browse files Browse the repository at this point in the history
  • Loading branch information
bennycode committed Jun 11, 2021
1 parent 047a426 commit 458466f
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 80 deletions.
56 changes: 49 additions & 7 deletions src/ADX/ADX.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
40 changes: 27 additions & 13 deletions src/ADX/ADX.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -105,11 +115,11 @@ export class ADX implements SimpleIndicator {
return;
}

if (!this.result) {
if (!this.adx) {
/**
* The first ADX value is simply a <interval> average of DX.
*/
this.result = MathAnalysis.getAverage(this.dxValues);
this.adx = MathAnalysis.getAverage(this.dxValues);
return;
}

Expand All @@ -119,17 +129,21 @@ export class ADX implements SimpleIndicator {
* adding the most recent DX value,
* and dividing this total by <interval>.
*/
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} {
Expand Down
60 changes: 0 additions & 60 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -306,14 +306,6 @@
"@typescript-eslint/typescript-estree" "4.26.0"
debug "^4.3.1"

"@typescript-eslint/[email protected]":
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/[email protected]":
version "4.26.0"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.0.tgz#60d1a71df162404e954b9d1c6343ff3bee496194"
Expand All @@ -322,11 +314,6 @@
"@typescript-eslint/types" "4.26.0"
"@typescript-eslint/visitor-keys" "4.26.0"

"@typescript-eslint/[email protected]":
version "4.25.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.25.0.tgz#0e444a5c5e3c22d7ffa5e16e0e60510b3de5af87"
integrity sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ==

"@typescript-eslint/[email protected]":
version "4.26.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.0.tgz#7c6732c0414f0a69595f4f846ebe12616243d546"
Expand All @@ -345,19 +332,6 @@
semver "^7.3.2"
tsutils "^3.17.1"

"@typescript-eslint/[email protected]":
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/[email protected]":
version "4.26.0"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.0.tgz#aea17a40e62dc31c63d5b1bbe9a75783f2ce7109"
Expand All @@ -371,14 +345,6 @@
semver "^7.3.5"
tsutils "^3.21.0"

"@typescript-eslint/[email protected]":
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/[email protected]":
version "4.26.0"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.0.tgz#26d2583169222815be4dcd1da4fe5459bc3bcc23"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 458466f

Please sign in to comment.