Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(ADX): Add test for "isStable" #744

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/ADX/ADX.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {ADX, FasterADX} from './ADX.js';
describe('ADX', () => {
// Test data verified with:
// https://tulipindicators.org/adx
// @see https://github.com/TulipCharts/tulipindicators/blob/v0.9.1/tests/untest.txt#L36-L37
const candles = [
{close: 81.59, high: 82.15, low: 81.29},
{close: 81.06, high: 81.89, low: 80.64},
Expand All @@ -21,6 +22,9 @@ describe('ADX', () => {
{close: 87.29, high: 87.87, low: 87.01},
];

// @see https://github.com/TulipCharts/tulipindicators/blob/v0.9.1/tests/untest.txt#L38
const expectations = [41.38, 44.29, 49.42, 54.92, 59.99, 65.29, 67.36];

describe('replace', () => {
it('replaces the most recently added value', () => {
const interval = 5;
Expand All @@ -46,10 +50,9 @@ describe('ADX', () => {

describe('getResult', () => {
it('calculates the Average Directional Index (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);
const interval = 5;
const adx = new ADX(interval);
const fasterADX = new FasterADX(interval);

for (const candle of candles) {
adx.add(candle);
Expand Down Expand Up @@ -84,4 +87,26 @@ describe('ADX', () => {
expect(fasterADX.mdi?.toFixed(2)).toBe('0.06');
});
});

describe('isStable', () => {
it('requires at least (2x interval - 1) candles to produce a meaningful result (because of +DI and -DI warm-up)', () => {
const interval = 5;
const necessaryCandlesAmount = 2 * interval - 1;
const initialCandles = candles.slice(0, necessaryCandlesAmount - 1);
const adx = new ADX(interval);
const fasterADX = new FasterADX(interval);

// Add necessary candles - 1
adx.updates(initialCandles);
fasterADX.updates(initialCandles);
expect(adx.isStable).toBe(false);
expect(fasterADX.isStable).toBe(false);

// Add one more candle to make it stable
adx.add({close: 10, high: 11, low: 9});
fasterADX.add({close: 10, high: 11, low: 9});
expect(adx.isStable).toBe(true);
expect(fasterADX.isStable).toBe(true);
});
});
});
39 changes: 23 additions & 16 deletions src/ADX/ADX.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type {Big} from 'big.js';
import {DX, FasterDX} from '../DX/DX.js';
import {BigIndicatorSeries, NumberIndicatorSeries} from '../Indicator.js';
import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js';
import type {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose.js';
import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js';
import type {HighLowClose, HighLowCloseNumber} from '../util/HighLowClose.js';
import {FasterWSMA, WSMA} from '../WSMA/WSMA.js';
import {DX, FasterDX} from '../DX/DX.js';

/**
* Average Directional Index (ADX)
Expand All @@ -19,46 +18,54 @@ import {DX, FasterDX} from '../DX/DX.js';
* Generally, ADX readings below 20 indicate trend weakness, and readings above 40 indicate trend strength.
* A strong trend is indicated by readings above 50. ADX values of 75-100 signal an extremely strong trend.
*
* Interpretation:
* If ADX increases, it means that volatility is increasing and indicating the beginning of a new trend.
* If ADX decreases, it means that volatility is decreasing, and the current trend is slowing down and may even
* reverse.
* When +DI is above -DI, then there is more upward pressure than downward pressure in the market.
*
* Note:
* The ADX calculation relies on the DX becoming stable before producing meaningful results.
* For an interval of 5, at least 9 candles are required. The first 5 candles are used to stabilize the DX, which then generates the initial ADX value.
* The subsequent 4 candles produce additional ADX values, allowing it to stabilize with 5 values for an interval of 5.
*
* @see https://www.investopedia.com/terms/a/adx.asp
* @see https://en.wikipedia.org/wiki/Average_directional_movement_index
* @see https://paperswithbacktest.com/wiki/wilders-adx-dmi-indicator-calculation-method
* @see https://www.youtube.com/watch?v=n2J1H3NeF70
* @see https://learn.tradimo.com/technical-analysis-how-to-work-with-indicators/adx-determing-the-strength-of-price-movement
* @see https://medium.com/codex/algorithmic-trading-with-average-directional-index-in-python-2b5a20ecf06a
*/
export class ADX extends BigIndicatorSeries<HighLowClose> {
private readonly dx: DX;
private readonly adx: MovingAverage;
private readonly smoothed: MovingAverage;

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

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

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

update(candle: HighLowClose, replace: boolean) {
const result = this.dx.update(candle, replace);

if (result !== null) {
this.adx.update(result, replace);
this.smoothed.update(result, replace);
}

if (this.adx.isStable) {
return this.setResult(this.adx.getResult(), replace);
if (this.smoothed.isStable) {
return this.setResult(this.smoothed.getResult(), replace);
}

return null;
Expand All @@ -67,14 +74,14 @@ export class ADX extends BigIndicatorSeries<HighLowClose> {

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

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

Expand All @@ -90,11 +97,11 @@ export class FasterADX extends NumberIndicatorSeries<HighLowCloseNumber> {
const result = this.dx.update(candle, replace);

if (result !== null) {
this.adx.update(result, replace);
this.smoothed.update(result, replace);
}

if (this.adx.isStable) {
return this.setResult(this.adx.getResult(), replace);
if (this.smoothed.isStable) {
return this.setResult(this.smoothed.getResult(), replace);
}

return null;
Expand Down
6 changes: 3 additions & 3 deletions src/CCI/CCI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ import Big from 'big.js';
* which makes it an oscillator. Values above +100 imply an overbought condition, while values below −100 imply an
* oversold condition.
*
* Note: Traders often combine CCI with other indicators to confirm trends or signals, as using it alone can lead to false signals.
* It’s particularly useful in volatile markets or when identifying shorter-term trading opportunities.
*
* According to
* [Investopia.com](https://www.investopedia.com/articles/active-trading/031914/how-traders-can-utilize-cci-commodity-channel-index-trade-stock-trends.asp#multiple-timeframe-cci-strategy),
* traders often buy when the CCI dips below -100 and then rallies back above -100 to sell the security when it moves
Expand All @@ -27,6 +24,9 @@ import Big from 'big.js';
* +100 and above: Indicates an overbought condition or the start of a strong uptrend.
* Values near 0 often signal a lack of clear momentum.
*
* Note: Traders often combine CCI with other indicators to confirm trends or signals, as using it alone can lead to false signals.
* It’s particularly useful in volatile markets or when identifying shorter-term trading opportunities.
*
* @see https://en.wikipedia.org/wiki/Commodity_channel_index
*/
export class CCI extends BigIndicatorSeries<HighLowClose> {
Expand Down
4 changes: 2 additions & 2 deletions src/DX/DX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ export class DX extends BigIndicatorSeries<HighLowClose> {
private previousCandle?: HighLowClose;
private secondLastCandle?: HighLowClose;
private readonly atr: ATR;
/** Minus Directional Indicator (-DI) */
/** Negative (Minus) Directional Indicator (-DI) */
public mdi?: Big;
/** Plus Directional Indicator (+DI) */
/** Positive (Plus) Directional Indicator (+DI) */
public pdi?: Big;

constructor(
Expand Down