Skip to content

Commit

Permalink
feat: Add Moving Average Convergence Divergence (MACD) indicator (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
bennycode committed May 31, 2020
1 parent b2eca1e commit 38a645d
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Provide a TypeScript implementation for common technical indicators with arbitra
1. Double Exponential Moving Average (DEMA)
1. Double Moving Average (DMA)
1. Exponential Moving Average (EMA)
1. Moving Average Convergence Divergence (MACD)
1. Rate-of-Change (ROC)
1. Relative Strength Index (RSI)
1. Simple Moving Average (SMA)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"ema",
"exponential-moving-average",
"indicator",
"macd",
"moving-average",
"roc",
"rsi",
Expand Down
4 changes: 3 additions & 1 deletion src/DMA/DMA.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Big, {BigSource} from 'big.js';
import {SMA} from '..';

export type DMAResult = {long: Big; short: Big};

export class DMA {
public readonly long: SMA;
public readonly short: SMA;
Expand All @@ -17,7 +19,7 @@ export class DMA {
this.long.update(price);
}

getResult(): {long: Big; short: Big} {
getResult(): DMAResult {
return {
long: this.long.getResult(),
short: this.short.getResult(),
Expand Down
66 changes: 66 additions & 0 deletions src/MACD/MACD.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {MACD} from './MACD';
import Big from 'big.js';

import prices from '../test/fixtures/prices.json';
import results from '../test/fixtures/MACD/results.json';

describe('MACD', () => {
describe('getResult', () => {
it('calculates MACD diff, signal & result with 12/26/9', () => {
const indicator = new MACD({longInterval: 26, shortInterval: 12, signalInterval: 9, useDEMA: false});
prices.forEach((price, index) => {
indicator.update(new Big(price));

const {macd, signal, diff} = indicator.getResult();

const resMACD = new Big(results.macd[index]);
const resSignal = new Big(results.signal[index]);
const resDiff = new Big(results.diff[index]);

expect(macd.toPrecision(12)).toEqual(resMACD.toPrecision(12));
expect(signal.toPrecision(12)).toEqual(resSignal.toPrecision(12));
expect(diff.toPrecision(12)).toEqual(resDiff.toPrecision(12));
});
});

it('throws an error when there is not enough input data', () => {
const indicator = new MACD({longInterval: 26, shortInterval: 12, signalInterval: 9, useDEMA: true});
expect(() => indicator.getResult()).toThrowError();
});
});

describe('isStable', () => {
it('knows when it can return reliable data', () => {
const longInterval = 18;
const indicator = new MACD({longInterval, shortInterval: 9, signalInterval: 9, useDEMA: false});

const mockedPrices = [
new Big('0.00019040'),
new Big('0.00019071'),
new Big('0.00019198'),
new Big('0.00019220'),
new Big('0.00019214'),
new Big('0.00019205'),
new Big('0.00019214'),
new Big('0.00019222'),
new Big('0.00019144'),
new Big('0.00019128'),
new Big('0.00019159'),
new Big('0.00019143'),
new Big('0.00019199'),
new Big('0.00019214'),
new Big('0.00019119'),
new Big('0.00019202'),
new Big('0.00019220'),
new Big('0.00019207'),
];

expect(mockedPrices.length).toBe(longInterval);
expect(indicator.isStable()).toBe(false);

mockedPrices.forEach(price => indicator.update(price));

expect(indicator.isStable()).toBe(true);
});
});
});
80 changes: 80 additions & 0 deletions src/MACD/MACD.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {EMA} from '../EMA/EMA';
import Big, {BigSource} from 'big.js';
import {DEMA} from '..';

export type MACDConfig = {
longInterval: number;
shortInterval: number;
signalInterval: number;
useDEMA: boolean;
};

export type MACDResult = {
diff: Big;
macd: Big;
signal: Big;
};

export class MACD {
private readonly long: EMA | DEMA;
private readonly short: EMA | DEMA;
private readonly signal: EMA | DEMA;

private readonly config: any;
private age: number = 0;

private result: MACDResult | undefined;

constructor(config: MACDConfig) {
this.config = config;

this.long = config.useDEMA ? new DEMA(config.longInterval) : new EMA(config.longInterval);
this.short = config.useDEMA ? new DEMA(config.shortInterval) : new EMA(config.shortInterval);
this.signal = config.useDEMA ? new DEMA(config.signalInterval) : new EMA(config.signalInterval);
}

isStable(): boolean {
return this.age >= this.config.longInterval;
}

update(_price: BigSource): void {
const price = new Big(_price);

this.short.update(price);
this.long.update(price);

const shortEMA = this.short.getResult();
const longEMA = this.long.getResult();

/**
* Standard MACD is the short (usually 12 periods) EMA less the long (usually 26 periods) EMA. Closing prices are
* used to form the moving averages.
*/
const diff = shortEMA.sub(longEMA);

/**
* A short (usually 9 periods) EMA of MACD is plotted along side to act as a signal line to identify turns in the
* indicator.
*/
this.signal.update(diff);

/**
* The MACD-Histogram represents the difference between MACD and its 9-day EMA, the signal line.
*/
this.result = {
diff: diff,
macd: diff.sub(this.signal.getResult()),
signal: this.signal.getResult(),
};

this.age++;
}

getResult(): MACDResult {
if (!this.result) {
throw Error('Not enough input data');
}

return this.result;
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './DEMA/DEMA';
export * from './DMA/DMA';
export * from './EMA/EMA';
export * from './MACD/MACD';
export * from './ROC/ROC';
export * from './RSI/RSI';
export * from './SMA/SMA';
Expand Down
125 changes: 125 additions & 0 deletions src/test/fixtures/MACD/results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{
"macd": [
0,
-3.6376068376068362,
-2.4639072734799217,
-5.031340295620034,
-5.521802745494824,
-6.076152724792792,
-3.053848272963074,
0.33568310636747434,
2.9473056543174314,
-1.7093100949105917,
0.8774837070937913,
2.1231125756183227,
2.585623647541957,
-1.5571775822256484,
1.0154188470421683,
1.8525149219271597,
-0.42461879714695083,
0.3167899361939739,
-0.7270321370494885,
-3.3439676735158663,
-4.504658691176687,
-4.107132512783021,
-1.6595236920538383,
-1.7529125979171347,
0.7180893397975616,
1.8572538942113148,
-0.07210193668202258,
3.4517711329096086,
5.350885446717413,
6.247517316069724,
7.3480176240854345,
1.5457357005888681,
2.1198449676882127,
-2.0235768893083987,
-2.839360636962912,
-4.3281862834160645,
0.2946340051329588,
1.6766181261237905,
-1.2825765539962966
],
"signal": [
-0.00000000000000162,
-0.9094017094017091,
-1.5253785277716898,
-2.7832136016766986,
-4.163664288050406,
-5.6827024692486034,
-6.446164537489373,
-6.362243760897504,
-5.625417347318147,
-6.052744871045796,
-5.833373944272349,
-5.30259580036777,
-4.65618988848228,
-5.045484284038693,
-4.791629572278152,
-4.328500841796362,
-4.4346555410831,
-4.355458057034607,
-4.537216091296979,
-5.373208009675945,
-6.499372682470117,
-7.526155810665873,
-7.941036733679333,
-8.379264883158616,
-8.199742548209226,
-7.735429074656398,
-7.753454558826904,
-6.890511775599502,
-5.55279041392015,
-3.990911084902719,
-2.153906678881361,
-1.767472753734144,
-1.2375115118120907,
-1.7434057341391904,
-2.4532458933799184,
-3.535292464233935,
-3.4616339629506956,
-3.0424794314197485,
-3.363123569918823
],
"diff": [
-0.00000000000000162,
-4.547008547008545,
-3.9892858012516115,
-7.814553897296733,
-9.68546703354523,
-11.758855194041395,
-9.500012810452446,
-6.02656065453003,
-2.678111693000716,
-7.762054965956388,
-4.955890237178558,
-3.179483224749447,
-2.070566240940323,
-6.6026618662643415,
-3.776210725235984,
-2.4759859198692027,
-4.859274338230051,
-4.038668120840633,
-5.264248228346467,
-8.717175683191812,
-11.004031373646804,
-11.633288323448895,
-9.600560425733171,
-10.13217748107575,
-7.4816532084116645,
-5.8781751804450835,
-7.825556495508927,
-3.4387406426898934,
-0.20190496720273643,
2.256606231167005,
5.1941109452040735,
-0.2217370531452758,
0.8823334558761218,
-3.766982623447589,
-5.29260653034283,
-7.863478747649999,
-3.166999957817737,
-1.365861305295958,
-4.64570012391512
]
}

0 comments on commit 38a645d

Please sign in to comment.