Skip to content

Commit

Permalink
feat(AC,AO,ATR,CG,DEMA,EMA,MOM,ROC,RSI,SMA,SMMA): Save highest & lowe…
Browse files Browse the repository at this point in the history
…st values for all simple indicators (#295)
  • Loading branch information
bennycode authored Aug 15, 2021
1 parent 9ca50e4 commit 7c6433b
Show file tree
Hide file tree
Showing 24 changed files with 159 additions and 70 deletions.
2 changes: 2 additions & 0 deletions src/AC/AC.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ describe('AC', () => {
expect(ac.isStable).toBeTrue();
expect(ac.getResult().toFixed(2)).toBe('-21.97');
expect(ac.momentum.getResult().toFixed(2)).toBe('-9.22');
expect(ac.lowest!.toFixed(2)).toBe('-21.97');
expect(ac.highest!.toFixed(2)).toBe('11.65');
});

it('throws an error when there is not enough input data', () => {
Expand Down
9 changes: 4 additions & 5 deletions src/AC/AC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import {MOM} from '../MOM/MOM';
* down before buy orders come in. The Accelerator Oscillator also looks at whether there is an acceleration in the
* change of momentum.
*/
export class AC implements SimpleIndicator {
export class AC extends SimpleIndicator {
public readonly ao: AO;
public readonly momentum: MOM;
public readonly signal: SMA;

private result: Big | undefined;

constructor(public readonly shortAO: number, public readonly longAO: number, public readonly signalInterval: number) {
super();
this.ao = new AO(shortAO, longAO);
this.signal = new SMA(signalInterval);
this.momentum = new MOM(1);
Expand All @@ -42,8 +41,8 @@ export class AC implements SimpleIndicator {
if (ao) {
this.signal.update(ao);
if (this.signal.isStable) {
this.result = ao.sub(this.signal.getResult());
this.momentum.update(this.result);
const result = this.setResult(ao.sub(this.signal.getResult()));
this.momentum.update(result);
return this.result;
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/AO/AO.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('AO', () => {
5.3673, 4.5294, 4.764, 4.1044, 1.6913, -1.3769, -4.2062, -7.7196, -10.6241, -11.4972, -9.6358, -7.9344,
];
const ao = new AO(5, 34);

for (let i = 0; i < lows.length; i++) {
const newResult = ao.update(lows[i], highs[i]);
if (ao.isStable) {
Expand All @@ -40,6 +41,9 @@ describe('AO', () => {
expect(parseFloat(actual)).toBe(expected!);
}
}

expect(ao.lowest!.toFixed(2)).toBe('-11.50');
expect(ao.highest!.toFixed(2)).toBe('33.35');
});

it('throws an error when there is not enough input data', () => {
Expand Down
9 changes: 4 additions & 5 deletions src/AO/AO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import {NotEnoughDataError} from '../error';
* The Awesome Oscillator (AO) is an indicator used to measure market momentum.
* It has been developed by the technical analyst and charting enthusiast Bill Williams.
*/
export class AO implements SimpleIndicator {
export class AO extends SimpleIndicator {
public readonly long: SMA;
public readonly short: SMA;

private result: Big | undefined;

constructor(public readonly shortInterval: number, public readonly longInterval: number) {
super();
this.short = new SMA(shortInterval);
this.long = new SMA(longInterval);
}
Expand All @@ -38,8 +37,8 @@ export class AO implements SimpleIndicator {
this.long.update(medianPrice);

if (this.short.isStable && this.long.isStable) {
this.result = this.short.getResult().sub(this.long.getResult());
return this.result;
const result = this.setResult(this.short.getResult().sub(this.long.getResult()));
return result;
}
}
}
3 changes: 3 additions & 0 deletions src/ATR/ATR.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ describe('ATR', () => {
expect(atr.getResult().toFixed(4)).toEqual(res.toFixed(4));
}
});

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

it('throws an error when there is not enough input data', () => {
Expand Down
7 changes: 3 additions & 4 deletions src/ATR/ATR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ export type ATRCandle = {close: BigSource; high: BigSource; low: BigSource};
* traders prepared to continue to bid up or sell down a stock through the course of the day. Decreasing range suggests
* waning interest.
*/
export class ATR implements SimpleIndicator {
export class ATR extends SimpleIndicator {
private readonly smma: SMMA;
private readonly candles: ATRCandle[] = [];

private result: Big | undefined;
private prevCandle: ATRCandle | undefined;

constructor(public readonly interval: number) {
super();
this.smma = new SMMA(interval);
}

Expand Down Expand Up @@ -46,7 +45,7 @@ export class ATR implements SimpleIndicator {

this.smma.update(trueRange);

this.result = this.smma.getResult();
this.setResult(this.smma.getResult());
this.prevCandle = candle;
}

Expand Down
2 changes: 2 additions & 0 deletions src/CG/CG.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ describe('CG', () => {
expect(cg.isStable).toBeFalse();
cg.update(60);
expect(cg.isStable).toBeTrue();
expect(cg.lowest!.toFixed(2)).toBe('1.00');
expect(cg.highest!.toFixed(2)).toBe('3.67');
});
});

Expand Down
6 changes: 3 additions & 3 deletions src/CG/CG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import {NotEnoughDataError} from '../error';
* profitable trading
* @see http://www.mesasoftware.com/papers/TheCGOscillator.pdf
*/
export class CG implements SimpleIndicator {
export class CG extends SimpleIndicator {
public signal: SMA;

private readonly prices: Big[] = [];
private result: Big | undefined;

get isStable(): boolean {
return this.prices.length >= this.interval && this.signal.isStable;
}

constructor(public readonly interval: number, public readonly signalInterval: number) {
super();
this.signal = new SMA(signalInterval);
}

Expand All @@ -47,7 +47,7 @@ export class CG implements SimpleIndicator {

this.signal.update(cg);

this.result = cg;
this.setResult(cg);
}

getResult(): Big {
Expand Down
5 changes: 5 additions & 0 deletions src/DEMA/DEMA.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ describe('DEMA', () => {
const result = new Big(dema10results[index]);
expect(dema.getResult().toPrecision(12)).toEqual(result.toPrecision(12));
});

expect(dema.lowest!.toFixed(2)).toBe('24.89');
expect(dema.highest!.toFixed(2)).toBe('83.22');
});

it('throws an error when there is not enough input data', () => {
Expand All @@ -38,6 +41,8 @@ describe('DEMA', () => {
dema.update(1);
dema.update(2);
expect(dema.isStable).toBeTrue();
expect(dema.lowest!.toFixed(2)).toBe('1.00');
expect(dema.highest!.toFixed(2)).toBe('1.89');
});
});
});
8 changes: 3 additions & 5 deletions src/DEMA/DEMA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ import Big, {BigSource} from 'big.js';
import {EMA, NotEnoughDataError} from '..';
import {SimpleIndicator} from '../Indicator';

export class DEMA implements SimpleIndicator {
private result: Big | undefined;

export class DEMA extends SimpleIndicator {
private readonly inner: EMA;
private readonly outer: EMA;

constructor(public readonly interval: number) {
super();
this.inner = new EMA(interval);
this.outer = new EMA(interval);
}

update(price: BigSource): void {
this.inner.update(price);
this.outer.update(this.inner.getResult());

this.result = this.inner.getResult().times(2).sub(this.outer.getResult());
this.setResult(this.inner.getResult().times(2).sub(this.outer.getResult()));
}

get isStable(): boolean {
Expand Down
18 changes: 15 additions & 3 deletions src/EMA/EMA.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,46 @@ const ema26results = results.weight_26;

describe('EMA', () => {
describe('getResult', () => {
it('correctly calculates EMAs with weight 10', () => {
it('calculates EMAs with weight 10', () => {
const ema = new EMA(10);

prices.forEach((price, index) => {
ema.update(new Big(price));
const actual = ema.getResult();
const expected = new Big(ema10results[index]);
expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12));
});

expect(ema.lowest!.toFixed(2)).toBe('38.20');
expect(ema.highest!.toFixed(2)).toBe('81.00');
});

it('correctly calculates EMAs with weight 12', () => {
it('calculates EMAs with weight 12', () => {
const ema = new EMA(12);

prices.forEach((price, index) => {
ema.update(new Big(price));
const actual = ema.getResult();
const expected = new Big(ema12results[index]);
expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12));
});

expect(ema.lowest!.toFixed(2)).toBe('40.43');
expect(ema.highest!.toFixed(2)).toBe('81.00');
});

it('correctly calculates EMAs with weight 26', () => {
it('calculates EMAs with weight 26', () => {
const ema = new EMA(26);

prices.forEach((price, index) => {
ema.update(new Big(price));
const actual = ema.getResult();
const expected = new Big(ema26results[index]);
expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12));
});

expect(ema.lowest!.toFixed(2)).toBe('48.29');
expect(ema.highest!.toFixed(2)).toBe('81.00');
});

it('throws an error when there is not enough input data', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/EMA/EMA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ export class EMA extends MovingAverage {

const weightFactor = 2 / (this.interval + 1);

this.result = price.times(weightFactor).add(this.result.times(1 - weightFactor));
this.setResult(price.times(weightFactor).add(this.result.times(1 - weightFactor)));
}
}
26 changes: 25 additions & 1 deletion src/Indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,28 @@ export interface Indicator<T> {
update(...args: any): void;
}

export interface SimpleIndicator extends Indicator<Big> {}
export abstract class SimpleIndicator implements Indicator<Big> {
highest?: Big;
lowest?: Big;
protected result?: Big;

abstract isStable: boolean;

abstract getResult(): Big;

protected setResult(value: Big): Big {
this.result = value;

if (!this.highest || value.gt(this.highest)) {
this.highest = value;
}

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

return this.result;
}

abstract update(...args: any): void;
}
8 changes: 4 additions & 4 deletions src/MA/MovingAverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import Big, {BigSource} from 'big.js';
import {NotEnoughDataError} from '../error';
import {SimpleIndicator} from '../Indicator';

export abstract class MovingAverage implements SimpleIndicator {
protected result: Big | undefined;

constructor(public readonly interval: number) {}
export abstract class MovingAverage extends SimpleIndicator {
constructor(public readonly interval: number) {
super();
}

get isStable(): boolean {
return !!this.result;
Expand Down
4 changes: 4 additions & 0 deletions src/MOM/MOM.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('MOM', () => {
];
const outputs = [1.56, 1.78, 1.12, 1.55, 0.75, 2.38, 3.7, 2.9, 3.22, 2.93];
const momentum = new MOM(5);

for (const input of inputs) {
momentum.update(input);
if (momentum.isStable) {
Expand All @@ -18,6 +19,9 @@ describe('MOM', () => {
expect(parseFloat(actual)).toBe(expected!);
}
}

expect(momentum.lowest!.toFixed(2)).toBe('0.75');
expect(momentum.highest!.toFixed(2)).toBe('3.70');
});

it('throws an error when there is not enough input data', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/MOM/MOM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import {NotEnoughDataError} from '../error';
/**
* The Momentum indicator returns the change between the current price and the price n times ago.
*/
export class MOM implements SimpleIndicator {
export class MOM extends SimpleIndicator {
private readonly history: BigSource[];
private readonly historyLength: number;
private result: Big | undefined;

constructor(public readonly length: number) {
super();
this.historyLength = length + 1;
this.history = getFixedArray<BigSource>(this.historyLength);
}

update(value: BigSource): void {
this.history.push(value);
if (this.history.length === this.historyLength) {
this.result = new Big(value).minus(this.history[0]);
this.setResult(new Big(value).minus(this.history[0]));
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/ROC/ROC.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ const results = [

describe('ROC', () => {
describe('getResult', () => {
it('should correctly calculate ROC with interval 5', () => {
it('calculates ROC with interval 5', () => {
const roc = new ROC(5);

prices.forEach((price, index) => {
roc.update(new BigNumber(price));

Expand All @@ -55,6 +56,9 @@ describe('ROC', () => {
const expected = new BigNumber(Number(results[index]));
expect(roc.getResult().toFixed(2)).toEqual(expected.toFixed(2));
});

expect(roc.lowest!.toFixed(2)).toBe('0.01');
expect(roc.highest!.toFixed(2)).toBe('0.04');
});

it('throws an error when there is not enough input data', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/ROC/ROC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import Big, {BigSource} from 'big.js';
import {NotEnoughDataError} from '../error';
import {SimpleIndicator} from '../Indicator';

export class ROC implements SimpleIndicator {
export class ROC extends SimpleIndicator {
private readonly priceHistory: Big[] = [];
private result: Big | undefined;

constructor(public readonly interval: number) {
super();
this.interval = interval;
}

Expand All @@ -32,7 +32,7 @@ export class ROC implements SimpleIndicator {
const comparePrice = this.priceHistory.shift() as Big;

// (Close - Close <interval> periods ago) / (Close <interval> periods ago)
this.result = price.sub(comparePrice).div(comparePrice);
this.setResult(price.sub(comparePrice).div(comparePrice));
}

getResult(): Big {
Expand Down
Loading

0 comments on commit 7c6433b

Please sign in to comment.