Skip to content

Commit

Permalink
Fix ichimoku indicators calculation (#452) (#455)
Browse files Browse the repository at this point in the history
Fix #452

- [x] Fix indicators calculation
- [x] Reduce duplication
- [x] Update documentation
  • Loading branch information
Thomas-Heniart authored Apr 18, 2024
1 parent 30b8e35 commit 63307fc
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 109 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
coverage/
dist/
.idea/
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"bollinger-bands",
"finance",
"financial-instruments",
"ichimoku",
"indicators",
"macd",
"quant",
Expand Down
35 changes: 35 additions & 0 deletions src/helper/numArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,41 @@ export function shiftRightBy(n: number, values: number[]): number[] {
return shiftRightAndFillBy(n, 0, values);
}

/**
* Shift values left by given amount and fill with value.
* @param n shift amount.
* @param fill fill value.
* @param values values array.
* @returns shifted and filled values.
*/
export function shiftLeftAndFillBy(
n: number,
fill: number,
values: number[]
): number[] {
const result = new Array<number>(values.length);

for (let i = 0; i < result.length; i++) {
if (i < n) {
result[i] = values[i+n];
} else {
result[i] = fill;
}
}

return result;
}

/**
* Shifts values left by given amount.
* @param n shift amount.
* @param values values array.
* @return shifted values.
*/
export function shiftLeftBy(n: number, values: number[]): number[] {
return shiftLeftAndFillBy(n, 0, values);
}

/**
* Change between the current value and the value n before.
* @param n shift amount.
Expand Down
8 changes: 4 additions & 4 deletions src/indicator/momentum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ The [ichimokuCloud](./ichimokuCloud.ts), also known as Ichimoku Kinko Hyo, calcu
```
Tenkan-sen (Conversion Line) = (9-Period High + 9-Period Low) / 2
Kijun-sen (Base Line) = (26-Period High + 26-Period Low) / 2
Senkou Span A (Leading Span A) = (Conversion Line + Base Line) / 2
Senkou Span B (Leading Span B) = (52-Period High + 52-Period Low) / 2
Chikou Span (Lagging Span) = Closing plotted 26 days in the past.
Senkou Span A (Leading Span A) = (Conversion Line + Base Line) / 2 projected 26 periods in the future
Senkou Span B (Leading Span B) = (52-Period High + 52-Period Low) / 2 projected 26 periods in the future
Chikou Span (Lagging Span) = Closing plotted 26 periods in the past.
```

```TypeScript
import { ichimokuCloud } from 'indicatorts';

const defaultConfig = { short: 9, medium: 26, long: 52, close: 26 };
const { conversion, base, leadingSpanA, leadingSpanB, leadingSpan } = ichimokuCloud(highs, lows, closings, defaultConfig);
const { tenkan, kijub, ssa, ssb, leadingSpan } = ichimokuCloud(highs, lows, closings, defaultConfig);
```

## Percentage Price Oscillator (PPO)
Expand Down
105 changes: 64 additions & 41 deletions src/indicator/momentum/ichimokuCloud.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,70 @@
// Copyright (c) 2022 Onur Cinar. All Rights Reserved.
// https://github.com/cinar/indicatorts

import { deepStrictEqual } from 'assert';
import { roundDigitsAll } from '../../helper/numArray';
import { ichimokuCloud } from './ichimokuCloud';
import {deepStrictEqual} from 'assert';
import {roundDigitsAll} from '../../helper/numArray';
import {ichimokuCloud} from './ichimokuCloud';

describe('Ichimoku Cloud', () => {
const highs = [10, 11, 12, 13, 14, 15, 16, 17];
const lows = [1, 2, 3, 4, 5, 6, 7, 8];
const closings = [5, 6, 7, 8, 9, 10, 11, 12];

it('should be able to compute with a config', () => {
const conversion = [5.5, 6, 7, 8, 9, 10, 11, 12];
const base = [5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9];
const leadingSpanA = [5.5, 6, 6.75, 7.5, 8.25, 9, 9.75, 10.5];
const leadingSpanB = [5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9];
const laggingSpan = [0, 0, 0, 0, 0, 0, 0, 0];

const actual = ichimokuCloud(highs, lows, closings, {
short: 2,
medium: 24,
long: 48,
close: 28,
});
deepStrictEqual(roundDigitsAll(2, actual.conversion), conversion);
deepStrictEqual(roundDigitsAll(2, actual.base), base);
deepStrictEqual(roundDigitsAll(2, actual.leadingSpanA), leadingSpanA);
deepStrictEqual(roundDigitsAll(2, actual.leadingSpanB), leadingSpanB);
deepStrictEqual(roundDigitsAll(2, actual.laggingSpan), laggingSpan);
});

it('should be able to compute without a config', () => {
const conversion = [5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9];
const base = [5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9];
const leadingSpanA = [5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9];
const leadingSpanB = [5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9];
const laggingSpan = [0, 0, 0, 0, 0, 0, 0, 0];

const actual = ichimokuCloud(highs, lows, closings);
deepStrictEqual(roundDigitsAll(2, actual.conversion), conversion);
deepStrictEqual(roundDigitsAll(2, actual.base), base);
deepStrictEqual(roundDigitsAll(2, actual.leadingSpanA), leadingSpanA);
deepStrictEqual(roundDigitsAll(2, actual.leadingSpanB), leadingSpanB);
deepStrictEqual(roundDigitsAll(2, actual.laggingSpan), laggingSpan);
});
it('calculates Tenkan-sen as the middle point between high and low over the configured short period', () => {
const highs = [2, 4]
const lows = [1, 3]
const closings = [1.5, 3.5]

{
const {tenkan} = ichimokuCloud(highs, lows, closings, {short: 1})
deepStrictEqual(roundDigitsAll(2, tenkan), [(2 + 1) / 2, (4 + 3) / 2]);
}
{
const {tenkan} = ichimokuCloud(highs, lows, closings, {short: 2})
deepStrictEqual(roundDigitsAll(2, tenkan), [0, (4 + 1) / 2]);
}
})

it('calculates Kijun-sen as the middle point between high and low over the configured medium period', () => {
const highs = [2, 4]
const lows = [1, 3]
const closings = [1.5, 3.5]

{
const {kijun} = ichimokuCloud(highs, lows, closings, {medium: 1})
deepStrictEqual(roundDigitsAll(2, kijun), [(2 + 1) / 2, (4 + 3) / 2]);
}
{
const {kijun} = ichimokuCloud(highs, lows, closings, {medium: 2})
deepStrictEqual(roundDigitsAll(2, kijun), [0, (4 + 1) / 2]);
}
})

it('calculates SSA (Senkou-Span A) as the average between Tenkan-Sen and Kijun-Sen projected in the future by the medium period', () => {
const highs = [2, 4]
const lows = [1, 3]
const closings = [1.5, 3.5]

const {ssa} = ichimokuCloud(highs, lows, closings, {short: 1, medium: 2})

const tenkan = (4 + 3) / 2
const kijun = (4 + 1) / 2
deepStrictEqual(roundDigitsAll(2, ssa), [0, 0, 0, (tenkan + kijun) / 2]);
})

it('calculates SSB (Senkou-Span B) as the middle point between high and low over the configured long period projected in the future by the medium period', () => {
const highs = [2, 4, 8, 10]
const lows = [1, 3, 6, 3]
const closings = [1.5, 3.5, 7.5, 3.5]

const {ssb} = ichimokuCloud(highs, lows, closings, {medium: 2, long: 3})

deepStrictEqual(roundDigitsAll(2, ssb), [0, 0, 0, 0, (8 + 1) / 2, (10 + 3) / 2]);
})

it('laggingSpan (Chikou-Span) is closings projected in the past by the close periods', () => {
const highs = [2, 4, 8, 10]
const lows = [1, 3, 6, 3]
const closings = [1.5, 3.5, 7.5, 3.5]

const {laggingSpan} = ichimokuCloud(highs, lows, closings, {close: 2})

deepStrictEqual(roundDigitsAll(2, laggingSpan), [7.5, 3.5, 0, 0]);
})
});
Loading

0 comments on commit 63307fc

Please sign in to comment.