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

fix(AC, MACD, ADX, WSMA): Fix replacing values #734

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"*.{js,jsx,ts,tsx}": ["yarn fix:code"]
"*.{js,jsx,ts,tsx}": ["npm run fix:code"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"release:minor": "generate-changelog -m -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version minor",
"release:patch": "generate-changelog -p -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version patch",
"start:benchmark": "tsc --noEmit && node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only ./src/start/startBenchmark.ts",
"test": "npm run test:dev -- --coverage",
"test": "npm run test:types && npm run test:dev -- --coverage",
"test:dev": "vitest run --passWithNoTests",
"test:types": "npm run lint:types"
},
Expand Down
24 changes: 14 additions & 10 deletions src/ABANDS/AccelerationBands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,26 @@ describe('AccelerationBands', () => {
describe('update', () => {
it("doesn't crash when supplying zeroes", () => {
const accBands = new AccelerationBands(20, 2);
return accBands.update({
close: 0,
high: 0,
low: 0,
});
return accBands.updates([
{
close: 0,
high: 0,
low: 0,
},
]);
});
});
});

describe('FaserAccelerationBands', () => {
it("doesn't crash when supplying zeroes", () => {
const accBands = new FasterAccelerationBands(20, 2);
return accBands.update({
close: 0,
high: 0,
low: 0,
});
return accBands.updates([
{
close: 0,
high: 0,
low: 0,
},
]);
});
});
19 changes: 14 additions & 5 deletions src/ABANDS/AccelerationBands.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {Big} from '../index.js';
import {Big, TechnicalIndicator} from '../index.js';
import {FasterSMA, SMA} from '../SMA/SMA.js';
import {NotEnoughDataError} from '../error/index.js';
import type {BandsResult, FasterBandsResult} from '../util/BandsResult.js';
import type {Indicator} from '../Indicator.js';
import type {FasterMovingAverageTypes, MovingAverageTypes} from '../MA/MovingAverageTypes.js';
import type {FasterMovingAverage, MovingAverage} from '../MA/MovingAverage.js';
import type {HighLowClose, HighLowCloseNumber} from '../util/index.js';

export class AccelerationBands implements Indicator<BandsResult, HighLowClose> {
export class AccelerationBands extends TechnicalIndicator<BandsResult, HighLowClose> {
private readonly lowerBand: MovingAverage;
private readonly middleBand: MovingAverage;
private readonly upperBand: MovingAverage;
Expand Down Expand Up @@ -37,16 +36,21 @@ export class AccelerationBands implements Indicator<BandsResult, HighLowClose> {
public readonly width: number,
SmoothingIndicator: MovingAverageTypes = SMA
) {
super();
this.lowerBand = new SmoothingIndicator(interval);
this.middleBand = new SmoothingIndicator(interval);
this.upperBand = new SmoothingIndicator(interval);
}

updates(prices: HighLowClose[]) {
prices.forEach(price => this.update(price));
}

get isStable(): boolean {
return this.middleBand.isStable;
}

update({high, low, close}: HighLowClose): void {
update({high, low, close}: HighLowClose) {
const highPlusLow = new Big(high).plus(low);
const coefficient = highPlusLow.eq(0) ? new Big(0) : new Big(high).minus(low).div(highPlusLow).mul(this.width);

Expand All @@ -71,7 +75,7 @@ export class AccelerationBands implements Indicator<BandsResult, HighLowClose> {
}
}

export class FasterAccelerationBands implements Indicator<FasterBandsResult, HighLowCloseNumber> {
export class FasterAccelerationBands extends TechnicalIndicator<FasterBandsResult, HighLowCloseNumber> {
private readonly lowerBand: FasterMovingAverage;
private readonly middleBand: FasterMovingAverage;
private readonly upperBand: FasterMovingAverage;
Expand All @@ -81,11 +85,16 @@ export class FasterAccelerationBands implements Indicator<FasterBandsResult, Hig
public readonly width: number,
SmoothingIndicator: FasterMovingAverageTypes = FasterSMA
) {
super();
this.lowerBand = new SmoothingIndicator(interval);
this.middleBand = new SmoothingIndicator(interval);
this.upperBand = new SmoothingIndicator(interval);
}

updates(prices: HighLowCloseNumber[]) {
prices.forEach(price => this.update(price));
}

update({high, low, close}: HighLowCloseNumber): void {
const highPlusLow = high + low;
const coefficient = highPlusLow === 0 ? 0 : ((high - low) / highPlusLow) * this.width;
Expand Down
529 changes: 278 additions & 251 deletions src/AC/AC.test.ts

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions src/AC/AC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ export class AC extends BigIndicatorSeries<HighLow> {
}

override update(input: HighLow, replace: boolean = false): void | Big {
const ao = this.ao.update(input);
const ao = this.ao.update(input, replace);
if (ao) {
this.signal.update(ao);
this.signal.update(ao, replace);
if (this.signal.isStable) {
const result = this.setResult(ao.sub(this.signal.getResult()), replace);
this.momentum.update(result);
this.momentum.update(result, replace);
return this.result;
}
}
Expand All @@ -63,12 +63,12 @@ export class FasterAC extends NumberIndicatorSeries<HighLowNumber> {
}

override update(input: HighLowNumber, replace: boolean = false): void | number {
const ao = this.ao.update(input);
const ao = this.ao.update(input, replace);
if (ao) {
this.signal.update(ao);
this.signal.update(ao, replace);
if (this.signal.isStable) {
const result = this.setResult(ao - this.signal.getResult(), replace);
this.momentum.update(result);
this.momentum.update(result, replace);
return this.result;
}
}
Expand Down
40 changes: 20 additions & 20 deletions src/ADX/ADX.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import {ADX, FasterADX} from './ADX.js';

describe('ADX', () => {
// Test data verified with:
// https://tulipindicators.org/adx
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},
];

describe('getResult', () => {
it('calculates the Average Directional Index (ADX)', () => {
// Test data verified with:
// https://tulipindicators.org/adx
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 = [41.38, 44.29, 49.42, 54.92, 59.99, 65.29, 67.36];

const adx = new ADX(5);
Expand Down
9 changes: 5 additions & 4 deletions src/ADX/ADX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ export class ADX extends BigIndicatorSeries<HighLowClose> {
return this.dx.pdi;
}

update(candle: HighLowClose, replace: boolean = false): Big | void {
// TODO: Implement "replace" parameter
update(candle: HighLowClose): Big | void {
const result = this.dx.update(candle);
if (result) {
this.adx.update(result);
}
if (this.adx.isStable) {
return this.setResult(this.adx.getResult(), replace);
return this.setResult(this.adx.getResult(), false);
}
}
}
Expand All @@ -82,13 +83,13 @@ export class FasterADX extends NumberIndicatorSeries<HighLowCloseNumber> {
return this.dx.pdi;
}

update(candle: HighLowCloseNumber, replace: boolean = false): void | number {
update(candle: HighLowCloseNumber): void | number {
const result = this.dx.update(candle);
if (result !== undefined) {
this.adx.update(result);
}
if (this.adx.isStable) {
return this.setResult(this.adx.getResult(), replace);
return this.setResult(this.adx.getResult(), false);
}
}
}
7 changes: 2 additions & 5 deletions src/BBANDS/BollingerBands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ describe('BollingerBands', () => {
describe('prices', () => {
it('does not cache more prices than necessary to fill the interval', () => {
const bb = new BollingerBands(3);
bb.update(1);
bb.update(2);
bb.updates([1, 2]);
expect(bb.prices.length).toBe(2);
bb.update(3);
expect(bb.prices.length).toBe(3);
Expand Down Expand Up @@ -150,9 +149,7 @@ describe('FasterBollingerBands', () => {
81.59, 81.06, 82.87, 83.0, 83.61, 83.15, 82.84, 83.99, 84.55, 84.36, 85.53, 86.54, 86.89, 87.77, 87.29,
];
const fasterBB = new FasterBollingerBands(5, 2);
for (const price of prices) {
fasterBB.update(price);
}
fasterBB.updates(prices);
expect(fasterBB.isStable).toBe(true);
const actual = fasterBB.getResult();
expect(actual.lower.toFixed(2)).toBe('85.29');
Expand Down
25 changes: 19 additions & 6 deletions src/BBANDS/BollingerBands.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Big, type BigSource} from '../index.js';
import {Big, TechnicalIndicator, type BigSource} from '../index.js';
import {SMA} from '../SMA/SMA.js';
import {NotEnoughDataError} from '../error/index.js';
import type {BandsResult, FasterBandsResult} from '../util/BandsResult.js';
import type {Indicator} from '../Indicator.js';
import {getFasterAverage, getFasterStandardDeviation, getStandardDeviation} from '../util/index.js';

/**
Expand All @@ -20,7 +19,7 @@ import {getFasterAverage, getFasterStandardDeviation, getStandardDeviation} from
*
* @see https://www.investopedia.com/terms/b/bollingerbands.asp
*/
export class BollingerBands implements Indicator<BandsResult> {
export class BollingerBands extends TechnicalIndicator<BandsResult, BigSource> {
public readonly prices: Big[] = [];
private result: BandsResult | undefined;

Expand All @@ -32,12 +31,19 @@ export class BollingerBands implements Indicator<BandsResult> {
constructor(
public readonly interval: number,
public readonly deviationMultiplier: number = 2
) {}
) {
super();
}

get isStable(): boolean {
return this.result !== undefined;
}

updates(prices: BigSource[]) {
prices.forEach(price => this.update(price));
return this.result;
}

update(price: BigSource): void | BandsResult {
this.prices.push(new Big(price));

Expand All @@ -64,14 +70,21 @@ export class BollingerBands implements Indicator<BandsResult> {
}
}

export class FasterBollingerBands implements Indicator<FasterBandsResult> {
export class FasterBollingerBands extends TechnicalIndicator<FasterBandsResult, BigSource> {
public readonly prices: number[] = [];
private result: FasterBandsResult | undefined;

constructor(
public readonly interval: number,
public readonly deviationMultiplier: number = 2
) {}
) {
super();
}

updates(prices: number[]) {
prices.forEach(price => this.update(price));
return this.result;
}

update(price: number): void | FasterBandsResult {
this.prices.push(price);
Expand Down
27 changes: 25 additions & 2 deletions src/CCI/CCI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,33 @@ describe('CCI', () => {
];
const expectations = ['166.67', '82.02', '95.50', '130.91', '99.16', '116.34', '71.93'];

describe('replace', () => {
it('guarantees that a replacement is done correctly', () => {
const interval = 5;
const cci = new CCI(interval);
const cciWithReplace = new CCI(interval);

const correct = {close: 300, high: 300, low: 300};
const wrong = {close: 1_000, high: 1_000, low: 1_000};

cci.updates(candles);
cci.update(correct);

cciWithReplace.updates(candles);
cciWithReplace.update(wrong);

const expected = cci.getResult();
const actual = cciWithReplace.getResult();

expect(actual.toFixed()).toBe(expected.toFixed());
});
});

describe('getResult', () => {
it('calculates the Commodity Channel Index (CCI)', () => {
const cci = new CCI(5);
const fasterCCI = new FasterCCI(5);
const interval = 5;
const cci = new CCI(interval);
const fasterCCI = new FasterCCI(interval);
for (const candle of candles) {
cci.update(candle);
fasterCCI.update(candle);
Expand Down
Loading
Loading