Skip to content

Commit

Permalink
feat(ADX,DX): Add faster implementation (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
bennycode authored Dec 6, 2021
1 parent c821c64 commit 575c4d4
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 83 deletions.
20 changes: 18 additions & 2 deletions src/ADX/ADX.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ADX} from './ADX';
import {ADX, FasterADX} from './ADX';

describe('ADX', () => {
describe('getResult', () => {
Expand Down Expand Up @@ -26,23 +26,39 @@ describe('ADX', () => {
const expectations = [41.38, 44.29, 49.42, 54.92, 59.99, 65.29, 67.36];

const adx = new ADX(5);
const fasterADX = new FasterADX(5);

for (const candle of candles) {
adx.update(candle);
if (adx.isStable) {
fasterADX.update(candle);
if (adx.isStable && fasterADX.isStable) {
const expected = expectations.shift();
expect(adx.getResult().toFixed(2)).toBe(`${expected}`);
expect(fasterADX.getResult().toFixed(2)).toBe(`${expected}`);
}
}

expect(adx.isStable).toBeTrue();
expect(fasterADX.isStable).toBeTrue();

expect(adx.getResult().toFixed(2)).toBe('67.36');
expect(fasterADX.getResult().toFixed(2)).toBe('67.36');

expect(adx.lowest!.toFixed(2)).toBe('41.38');
expect(fasterADX.lowest!.toFixed(2)).toBe('41.38');

expect(adx.highest!.toFixed(2)).toBe('67.36');
expect(fasterADX.highest!.toFixed(2)).toBe('67.36');

// Verify uptrend detection (+DI > -DI):
expect(adx.pdi!.gt(adx.mdi!)).toBeTrue();
expect(fasterADX.pdi > fasterADX.mdi).toBeTrue();

expect(adx.pdi!.toFixed(2)).toBe('0.42');
expect(fasterADX.pdi!.toFixed(2)).toBe('0.42');

expect(adx.mdi!.toFixed(2)).toBe('0.06');
expect(fasterADX.mdi!.toFixed(2)).toBe('0.06');
});
});
});
43 changes: 36 additions & 7 deletions src/ADX/ADX.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {Big} from 'big.js';
import {BigIndicatorSeries} from '../Indicator';
import {MovingAverage} from '../MA/MovingAverage';
import {HighLowClose} from '../util/HighLowClose';
import {MovingAverageTypes} from '../MA/MovingAverageTypes';
import {WSMA} from '../WSMA/WSMA';
import {DX} from '../DX/DX';
import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator';
import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage';
import {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose';
import {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes';
import {FasterWSMA, WSMA} from '../WSMA/WSMA';
import {DX, FasterDX} from '../DX/DX';

/**
* Average Directional Index (ADX)
Expand Down Expand Up @@ -35,8 +35,8 @@ export class ADX extends BigIndicatorSeries<HighLowClose> {

constructor(public readonly interval: number, SmoothingIndicator: MovingAverageTypes = WSMA) {
super();
this.dx = new DX(interval, SmoothingIndicator);
this.adx = new SmoothingIndicator(this.interval);
this.dx = new DX(interval, SmoothingIndicator);
}

get mdi(): Big | void {
Expand All @@ -57,3 +57,32 @@ export class ADX extends BigIndicatorSeries<HighLowClose> {
}
}
}

export class FasterADX extends NumberIndicatorSeries<HighLowCloseNumber> {
private readonly dx: FasterDX;
private readonly adx: FasterMovingAverage;

constructor(public readonly interval: number, SmoothingIndicator: FasterMovingAverageTypes = FasterWSMA) {
super();
this.adx = new SmoothingIndicator(this.interval);
this.dx = new FasterDX(interval, SmoothingIndicator);
}

get mdi(): number | void {
return this.dx.mdi;
}

get pdi(): number | void {
return this.dx.pdi;
}

update(candle: HighLowCloseNumber): void | number {
const result = this.dx.update(candle);
if (result) {
this.adx.update(result);
}
if (this.adx.isStable) {
return this.setResult(this.adx.getResult());
}
}
}
4 changes: 2 additions & 2 deletions src/BBANDS/BollingerBands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class BollingerBands implements Indicator<BandsResult> {
}

getResult(): BandsResult {
if (!this.result) {
if (this.result === undefined) {
throw new NotEnoughDataError();
}

Expand Down Expand Up @@ -84,7 +84,7 @@ export class FasterBollingerBands implements Indicator<FasterBandsResult> {
}

getResult(): FasterBandsResult {
if (!this.result) {
if (this.result === undefined) {
throw new NotEnoughDataError();
}

Expand Down
23 changes: 19 additions & 4 deletions src/DX/DX.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DX} from './DX';
import {DX, FasterDX} from './DX';

describe('DX', () => {
describe('getResult', () => {
Expand Down Expand Up @@ -38,19 +38,29 @@ describe('DX', () => {
];

const dx = new DX(5);
const fasterDX = new FasterDX(5);

for (const candle of candles) {
dx.update(candle);
if (dx.isStable) {
const expected = expectations.shift();
expect(dx.getResult().toFixed(2)).toBe(expected!);
fasterDX.update(candle);
if (dx.isStable && fasterDX.isStable) {
const expected = expectations.shift()!;
expect(dx.getResult().toFixed(2)).toBe(expected);
expect(fasterDX.getResult().toFixed(2)).toBe(expected);
}
}

expect(dx.isStable).toBeTrue();
expect(fasterDX.isStable).toBeTrue();

expect(dx.getResult().toFixed(2)).toBe('75.61');
expect(fasterDX.getResult().toFixed(2)).toBe('75.61');

expect(dx.lowest!.toFixed(2)).toBe('11.09');
expect(fasterDX.lowest!.toFixed(2)).toBe('11.09');

expect(dx.highest!.toFixed(2)).toBe('86.51');
expect(fasterDX.highest!.toFixed(2)).toBe('86.51');
});

it('returns zero when there is no trend', () => {
Expand All @@ -63,13 +73,18 @@ describe('DX', () => {
];

const dx = new DX(5);
const fasterDX = new FasterDX(5);

for (const candle of candles) {
dx.update(candle);
fasterDX.update(candle);
}

expect(dx.isStable).toBeTrue();
expect(fasterDX.isStable).toBeTrue();

expect(dx.getResult().valueOf()).toBe('0');
expect(fasterDX.getResult().valueOf()).toBe(0);
});
});
});
100 changes: 83 additions & 17 deletions src/DX/DX.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {BigIndicatorSeries} from '../Indicator';
import {HighLowClose} from '../util';
import Big from 'big.js';
import {MovingAverage} from '../MA/MovingAverage';
import {MovingAverageTypes} from '../MA/MovingAverageTypes';
import {WSMA} from '../WSMA/WSMA';
import {ATR} from '../ATR/ATR';
import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator';
import {HighLowClose, HighLowCloseNumber} from '../util';
import Big, {BigSource} from 'big.js';
import {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage';
import {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes';
import {FasterWSMA, WSMA} from '../WSMA/WSMA';
import {ATR, FasterATR} from '../ATR/ATR';

/**
* Directional Movement Index (DMI / DX)
Expand All @@ -31,17 +31,21 @@ export class DX extends BigIndicatorSeries<HighLowClose> {

constructor(public readonly interval: number, SmoothingIndicator: MovingAverageTypes = WSMA) {
super();
this.movesUp = new SmoothingIndicator(this.interval);
this.movesDown = new SmoothingIndicator(this.interval);
this.atr = new ATR(this.interval, SmoothingIndicator);
this.movesDown = new SmoothingIndicator(this.interval);
this.movesUp = new SmoothingIndicator(this.interval);
}

private updateState(candle: HighLowClose, pdm: BigSource = 0, mdm: BigSource = 0): void {
this.atr.update(candle);
this.movesDown.update(mdm);
this.movesUp.update(pdm);
this.previousCandle = candle;
}

update(candle: HighLowClose): Big | void {
if (!this.previousCandle) {
this.atr.update(candle);
this.movesUp.update(new Big(0));
this.movesDown.update(new Big(0));
this.previousCandle = candle;
this.updateState(candle);
return;
}

Expand All @@ -66,10 +70,7 @@ export class DX extends BigIndicatorSeries<HighLowClose> {
// Minus Directional Movement (-DM)
const mdm = noLowerLows || highsRiseFaster ? new Big(0) : lowerLow;

this.movesUp.update(pdm);
this.movesDown.update(mdm);
this.atr.update(candle);
this.previousCandle = candle;
this.updateState(candle, pdm, mdm);

if (this.movesUp.isStable) {
this.pdi = this.movesUp.getResult().div(this.atr.getResult());
Expand All @@ -87,3 +88,68 @@ export class DX extends BigIndicatorSeries<HighLowClose> {
}
}
}

export class FasterDX extends NumberIndicatorSeries<HighLowCloseNumber> {
private readonly movesUp: FasterMovingAverage;
private readonly movesDown: FasterMovingAverage;
private previousCandle?: HighLowCloseNumber;
private readonly atr: FasterATR;
public mdi?: number;
public pdi?: number;

constructor(public readonly interval: number, SmoothingIndicator: FasterMovingAverageTypes = FasterWSMA) {
super();
this.atr = new FasterATR(this.interval, SmoothingIndicator);
this.movesDown = new SmoothingIndicator(this.interval);
this.movesUp = new SmoothingIndicator(this.interval);
}

private updateState(candle: HighLowCloseNumber, pdm: number = 0, mdm: number = 0): void {
this.atr.update(candle);
this.movesUp.update(pdm);
this.movesDown.update(mdm);
this.previousCandle = candle;
}

update(candle: HighLowCloseNumber): number | void {
if (!this.previousCandle) {
this.updateState(candle);
return;
}

const currentHigh = candle.high;
const previousHigh = this.previousCandle.high;

const currentLow = candle.low;
const previousLow = this.previousCandle.low;

const higherHigh = currentHigh - previousHigh;
const lowerLow = previousLow - currentLow;

const noHigherHighs = higherHigh < 0;
const lowsRiseFaster = higherHigh < lowerLow;

const pdm = noHigherHighs || lowsRiseFaster ? 0 : higherHigh;

const noLowerLows = lowerLow < 0;
const highsRiseFaster = lowerLow < higherHigh;

const mdm = noLowerLows || highsRiseFaster ? 0 : lowerLow;

this.updateState(candle, pdm, mdm);

if (this.movesUp.isStable) {
this.pdi = this.movesUp.getResult() / this.atr.getResult();
this.mdi = this.movesDown.getResult() / this.atr.getResult();

const dmDiff = Math.abs(this.pdi - this.mdi);
const dmSum = this.pdi + this.mdi;

if (dmSum === 0) {
return this.setResult(0);
}

return this.setResult((dmDiff / dmSum) * 100);
}
}
}
4 changes: 2 additions & 2 deletions src/EMA/EMA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class EMA extends MovingAverage {
const price = new Big(_price);

// If it's the first update there is no previous result and a default has to be set.
if (!this.result) {
if (this.result === undefined) {
this.result = price;
}

Expand Down Expand Up @@ -62,7 +62,7 @@ export class FasterEMA extends FasterMovingAverage {
this.pricesCounter++;

// If it's the first update there is no previous result and a default has to be set.
if (!this.result) {
if (this.result === undefined) {
this.result = price;
}

Expand Down
10 changes: 5 additions & 5 deletions src/Indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ export abstract class BigIndicatorSeries<Input = BigSource> implements Indicator
}

getResult(): Big {
if (!this.result) {
if (this.result === undefined) {
throw new NotEnoughDataError();
}

return this.result;
}

protected setResult(value: Big): Big {
if (!this.highest || value.gt(this.highest)) {
if (this.highest === undefined || value.gt(this.highest)) {
this.highest = value;
}

if (!this.lowest || value.lt(this.lowest)) {
if (this.lowest === undefined || value.lt(this.lowest)) {
this.lowest = value;
}

Expand Down Expand Up @@ -71,11 +71,11 @@ export abstract class NumberIndicatorSeries<Input = number> implements Indicator
}

protected setResult(value: number): number {
if (!this.highest || value > this.highest) {
if (this.highest === undefined || value > this.highest) {
this.highest = value;
}

if (!this.lowest || value < this.lowest) {
if (this.lowest === undefined || value < this.lowest) {
this.lowest = value;
}

Expand Down
2 changes: 1 addition & 1 deletion src/MA/MovingAverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class MyAverage extends FasterMovingAverage {
total = 0;

update(price: number): number | void {
if (!this.result) {
if (this.result === undefined) {
this.result = 0;
}
this.iterations += 1;
Expand Down
Loading

0 comments on commit 575c4d4

Please sign in to comment.