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

refactor(SMMA,WSMA): Replaced SMMA with WSMA #362

Merged
merged 3 commits into from
Nov 14, 2021
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ All indicators can be updated over time by streaming data (prices or candles) to
1. Rate-of-Change (ROC)
1. Relative Strength Index (RSI)
1. Simple Moving Average (SMA)
1. Smoothed Moving Average (SMMA)
1. Stochastic Oscillator (STOCH)
1. True Range (TR)
1. Wilder's Smoothed Moving Average (WSMA)
Expand Down
38 changes: 4 additions & 34 deletions src/ADX/ADX.test.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,16 @@
import {ADX} from './ADX';
import {EMA, NotEnoughDataError} from '..';
import {NotEnoughDataError} from '..';

describe('ADX', () => {
describe('getResult', () => {
it('supports different smoothing indicators', () => {
// Test data verified with:
// https://github.com/TulipCharts/tulipindicators/commit/41e59fb33cef5bc97b03d2751dab2b006525c23f
const highs = [
148.115, 148.226, 148.027, 149.603, 149.739, 150.782, 151.247, 151.084, 151.855, 151.874, 150.977, 151.693,
151.877, 151.65, 151.316, 150.525, 150.072, 150.656, 150.669, 149.428, 150.144, 151.386, 151.188, 150.325,
149.625, 149.237, 147.656, 147.188, 147.792,
];
const lows = [
147.734, 147.786, 147.787, 148.026, 149.368, 149.957, 150.467, 150.407, 150.967, 151.003, 150.69, 150.373,
151.312, 151.028, 150.525, 149.725, 149.562, 149.885, 149.122, 149.193, 149.497, 149.887, 149.945, 149.493,
148.715, 148.321, 146.874, 146.813, 147.335,
];
const closes = [
147.846, 148.027, 148.026, 149.603, 149.682, 150.782, 150.469, 151.084, 151.855, 151.003, 150.69, 151.693,
151.312, 151.028, 150.525, 149.725, 150.072, 150.656, 149.122, 149.326, 150.144, 151.386, 150.232, 149.625,
148.888, 148.321, 146.874, 147.166, 147.792,
];
const adx = new ADX(14, EMA);
for (let i = 0; i < Object.keys(highs).length; i++) {
adx.update({
close: closes[i],
high: highs[i],
low: lows[i],
});
}
expect(adx.isStable).toBeTrue();
expect(adx.getResult().adx.toFixed(2)).toBe('20.28');
});

it('throws an error when there is not enough input data', () => {
const adx = new ADX(14);

try {
adx.getResult();
fail('Expected error');
} catch (error) {
expect(adx.isStable).toBeFalse();
expect(error).toBeInstanceOf(NotEnoughDataError);
}
});
Expand Down Expand Up @@ -79,11 +50,10 @@ describe('ADX', () => {

/**
* Expectation from:
* https://github.com/anandanand84/technicalindicators/blob/v3.1.0/test/directionalmovement/ADX.js#L128-L130
* https://github.com/anandanand84/technicalindicators/blob/v3.1.0/test/directionalmovement/ADX.js#L128
*/
expect(adx.isStable).toBeTrue();
expect(adx.getResult().adx.toFixed(3)).toBe('17.288');
expect(adx.getResult().mdi.toFixed(3)).toBe('24.460');
expect(adx.getResult().pdi.toFixed(3)).toBe('27.420');
});
});
});
10 changes: 7 additions & 3 deletions src/ADX/ADX.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {Big} from 'big.js';
import {NotEnoughDataError} from '../error';
import {ATR, HighLowClose, MovingAverageTypeContext, SMMA} from '..';
import {Indicator} from '../Indicator';
import {getAverage} from '../util/getAverage';
import {MovingAverage} from '../MA/MovingAverage';
import {ATR} from '../ATR/ATR';
import {HighLowClose} from '../util/HighLowClose';
import {MovingAverageTypeContext} from '../MA/MovingAverageTypeContext';
import {WSMA} from '../WSMA/WSMA';

export type ADXResult = {
adx: Big;
Expand All @@ -26,7 +29,8 @@ export type ADXResult = {
* A strong trend is indicated by readings above 50. ADX values of 75-100 signal an extremely strong trend.
*
* 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.
* If ADX decreases, it means that volatility is decreasing, and the current trend is slowing down and may even
* reverse.
*
* @see https://www.investopedia.com/terms/a/adx.asp
* @see https://learn.tradimo.com/technical-analysis-how-to-work-with-indicators/adx-determing-the-strength-of-price-movement
Expand All @@ -42,7 +46,7 @@ export class ADX implements Indicator<ADXResult> {
private pdi: Big = new Big(0);
private mdi: Big = new Big(0);

constructor(public interval: number, SmoothingIndicator: MovingAverageTypeContext = SMMA) {
constructor(public interval: number, SmoothingIndicator: MovingAverageTypeContext = WSMA) {
this.atr = new ATR(interval, SmoothingIndicator);
this.smoothedPDM = new SmoothingIndicator(interval);
this.smoothedMDM = new SmoothingIndicator(interval);
Expand Down
45 changes: 30 additions & 15 deletions src/ATR/ATR.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
import {Big} from 'big.js';
import {ATR} from './ATR';
import {NotEnoughDataError} from '..';

import data from '../test/fixtures/ATR/data.json';

const candles = data.candles;
const atr14results = data.interval_14;

describe('ATR', () => {
const candles = [
{close: 81.59, high: 82.15, low: 81.29},
{close: 81.06, high: 81.89, low: 80.64},
{close: 82.87, high: 83.03, low: 81.31},
{close: 83.0, high: 83.3, low: 82.65},
{close: 83.61, high: 83.85, low: 83.07},
{close: 83.15, high: 83.9, low: 83.11},
{close: 82.84, high: 83.33, low: 82.49},
{close: 83.99, high: 84.3, low: 82.3},
{close: 84.55, high: 84.84, low: 84.15},
{close: 84.36, high: 85.0, low: 84.11},
{close: 85.53, high: 85.9, low: 84.03},
{close: 86.54, high: 86.58, low: 85.39},
{close: 86.89, high: 86.98, low: 85.76},
{close: 87.77, high: 88.0, low: 87.17},
{close: 87.29, high: 87.87, low: 87.01},
];
const expectations = ['1.12', '1.05', '1.01', '1.21', '1.14', '1.09', '1.24', '1.23', '1.23', '1.21', '1.14'];

describe('getResult', () => {
it('calculates the ATR with interval 14', () => {
const atr = new ATR(14);

candles.forEach((candle, index) => {
const atr = new ATR(5);
for (const candle of candles) {
atr.update(candle);

if (atr.isStable) {
const result = new Big(Number(atr14results[index]));
expect(atr.getResult().toFixed(4)).toEqual(result.toFixed(4));
const expected = expectations.shift();
expect(atr.getResult().toFixed(2)).toBe(expected!);
}
});
}
expect(atr.isStable).toBeTrue();

expect(atr.getResult().toFixed(2)).toBe('1.14');

expect(atr.lowest!.toFixed(2)).toBe('1.01');

expect(atr.lowest!.toFixed(2)).toBe('0.55');
expect(atr.highest!.toFixed(2)).toBe('1.37');
expect(atr.highest!.toFixed(2)).toBe('1.24');
});

it('throws an error when there is not enough input data', () => {
Expand Down
44 changes: 6 additions & 38 deletions src/ATR/ATR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import Big from 'big.js';
import {BigIndicatorSeries} from '../Indicator';
import {MovingAverage} from '../MA/MovingAverage';
import {MovingAverageTypeContext} from '../MA/MovingAverageTypeContext';
import {SMMA} from '../SMMA/SMMA';
import {TR} from '../TR/TR';
import {HighLowClose} from '../util';
import {WSMA} from '../WSMA/WSMA';

/**
* Average True Range (ATR)
Expand All @@ -16,53 +17,20 @@ import {HighLowClose} from '../util';
* @see https://www.investopedia.com/terms/a/atr.asp
*/
export class ATR extends BigIndicatorSeries {
private readonly candles: HighLowClose[] = [];
private readonly tr: TR;
private readonly smoothing: MovingAverage;
private prevCandle: HighLowClose | undefined;

constructor(public readonly interval: number, SmoothingIndicator: MovingAverageTypeContext = SMMA) {
constructor(public readonly interval: number, SmoothingIndicator: MovingAverageTypeContext = WSMA) {
super();
this.tr = new TR();
this.smoothing = new SmoothingIndicator(interval);
}

override get isStable(): boolean {
return this.candles.length > this.interval;
}

override update(candle: HighLowClose): Big | void {
this.candles.push(candle);

if (!this.prevCandle) {
this.prevCandle = candle;
return;
}

/**
* The interval is used as a lookback period,
* therefore one extra candle is kept
*/
if (this.candles.length > this.interval + 1) {
this.candles.shift();
}

const trueRange = this.trueRange(this.prevCandle, candle);

const trueRange = this.tr.update(candle);
this.smoothing.update(trueRange);

this.prevCandle = candle;
if (this.smoothing.isStable) {
return this.setResult(this.smoothing.getResult());
}
}

private trueRange(prevCandle: HighLowClose, currentCandle: HighLowClose): Big {
const prevClose = new Big(prevCandle.close);
const low = new Big(currentCandle.low);
const high = new Big(currentCandle.high);
return this.getMaximum([high.sub(low), high.sub(prevClose).abs(), low.sub(prevClose).abs()]);
}

private getMaximum(values: Big[]): Big {
return values.reduce((max: Big, current: Big) => (current.gt(max) ? current : max), values[0]);
}
}
3 changes: 1 addition & 2 deletions src/MA/MovingAverageTypeContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {EMA} from '../EMA/EMA';
import {WSMA} from '../WSMA/WSMA';
import {SMA} from '../SMA/SMA';
import {SMMA} from '../SMMA/SMMA';

export type MovingAverageTypeContext = typeof EMA | typeof WSMA | typeof SMA | typeof SMMA;
export type MovingAverageTypeContext = typeof EMA | typeof WSMA | typeof SMA;
6 changes: 4 additions & 2 deletions src/RSI/RSI.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {Big} from 'big.js';
import {RSI} from './RSI';
import {NotEnoughDataError, SMMA} from '..';

import dataFile from '../test/fixtures/RSI/500-candles-WRX-BTC-1h.json';
import prices from '../test/fixtures/prices.json';
import results from '../test/fixtures/RSI/results.json';

import {NotEnoughDataError} from '../error';
import {WSMA} from '../WSMA/WSMA';

const rsi2results = results.interval_2;
const rsi12results = results.interval_12;
const rsi26results = results.interval_26;
Expand Down Expand Up @@ -79,7 +81,7 @@ describe('RSI', () => {
/** @see https://github.com/bennycode/trading-signals/issues/64 */
it(`is compatible with results calculated by 'tulind'`, () => {
const interval = 14;
const rsi = new RSI(interval, SMMA);
const rsi = new RSI(interval, WSMA);
const ohlc = Object.values(dataFile);
const closes = ohlc.map(candle => candle[4]);
const results: string[] = [];
Expand Down
5 changes: 3 additions & 2 deletions src/RSI/RSI.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Big, {BigSource} from 'big.js';
import {MovingAverageTypeContext, SMMA} from '..';
import {MovingAverage} from '../MA/MovingAverage';
import {BigIndicatorSeries} from '../Indicator';
import {MovingAverageTypeContext} from '../MA/MovingAverageTypeContext';
import {WSMA} from '../WSMA/WSMA';

/**
* Relative Strength Index (RSI)
Expand All @@ -24,7 +25,7 @@ export class RSI extends BigIndicatorSeries {
private readonly avgGain: MovingAverage;
private readonly avgLoss: MovingAverage;

constructor(public readonly interval: number, Indicator: MovingAverageTypeContext = SMMA) {
constructor(public readonly interval: number, Indicator: MovingAverageTypeContext = WSMA) {
super();
this.avgGain = new Indicator(this.interval);
this.avgLoss = new Indicator(this.interval);
Expand Down
69 changes: 0 additions & 69 deletions src/SMMA/SMMA.test.ts

This file was deleted.

Loading