Skip to content

Commit 0f80739

Browse files
authored
feat(util): Calculate streaks (#725)
1 parent c847d3d commit 0f80739

File tree

6 files changed

+94
-9
lines changed

6 files changed

+94
-9
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ sma.update('10');
9090
sma.replace('40');
9191

9292
// You can add arbitrary-precision decimals:
93-
sma.update(new Big(30));
93+
sma.update(new Big(30.0009));
9494

9595
// You can get the result in various formats:
96-
console.log(sma.getResult().toFixed(2)); // "40.00"
97-
console.log(sma.getResult().toFixed(4)); // "40.0000"
96+
console.log(sma.getResult().toFixed(2)); // "50.00"
97+
console.log(sma.getResult().toFixed(4)); // "50.0003"
9898
```
9999

100100
### When to use `update(...)`?

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"release:major": "generate-changelog -M -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version major",
8787
"release:minor": "generate-changelog -m -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version minor",
8888
"release:patch": "generate-changelog -p -x \"chore,test\" && npm run changelog:commit && npm run docs:release && npm version patch",
89-
"start:benchmark": "ts-node ./src/start/startBenchmark.ts",
89+
"start:benchmark": "tsc --noEmit && node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only ./src/start/startBenchmark.ts",
9090
"test": "npm run test:dev -- --coverage",
9191
"test:dev": "vitest run --passWithNoTests",
9292
"test:types": "npm run lint:types"

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export {default as Big, BigSource} from 'big.js';
1+
export {default as Big, type BigSource} from 'big.js';
22
export * from './ABANDS/AccelerationBands.js';
33
export * from './AC/AC.js';
44
export * from './ADX/ADX.js';

src/util/getStreaks.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {getStreaks} from './getStreaks.js';
2+
3+
describe('getStreaks', () => {
4+
const input = [10, 20, 30, 40, 32, 42, 50, 45, 44, 41, 59, 90, 100];
5+
6+
describe('uptrends', () => {
7+
it('keeps track of upward streak lengths', () => {
8+
const actual = getStreaks(input, 'up');
9+
expect(actual.map(s => s.length)).toStrictEqual([3, 2, 3]);
10+
});
11+
12+
it('keeps track of price increases during an upward streak', () => {
13+
const actual = getStreaks(input, 'up');
14+
expect(actual.map(s => s.percentage)).toStrictEqual([300, 56.25, 143.90243902439025]);
15+
});
16+
});
17+
18+
describe('downtrends', () => {
19+
it('keeps track of downward streak lengths', () => {
20+
const actual = getStreaks(input, 'down');
21+
expect(actual.map(s => s.length)).toStrictEqual([1, 3]);
22+
});
23+
24+
it('keeps track of price decreases during a downward streak', () => {
25+
const actual = getStreaks(input, 'down');
26+
expect(actual.map(s => s.percentage)).toStrictEqual([-20, -18]);
27+
});
28+
});
29+
30+
describe('special cases', () => {
31+
it("doesn't record a streak of 1", () => {
32+
const actual = getStreaks([1], 'up');
33+
expect(actual.map(s => s.length)).toStrictEqual([]);
34+
});
35+
});
36+
});

src/util/getStreaks.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export type Streak = {
2+
/** Length of the streak */
3+
length: number;
4+
/** Price change percentage during the streak */
5+
percentage: number;
6+
};
7+
8+
/**
9+
* Tracks the lengths (streaks) of continuous price movements (up or down).
10+
*
11+
* @param prices A series of prices
12+
* @param keepSide If you want to receive only uptrends or downtrends
13+
* @returns An array of objects representing the filtered streaks
14+
*/
15+
export function getStreaks(prices: number[], keepSide: 'up' | 'down'): Streak[] {
16+
const streaks: Streak[] = [];
17+
let currentStreak = 0;
18+
19+
function saveStreak(i: number) {
20+
const endPrice = prices[i - 1];
21+
const startPrice = prices[i - currentStreak - 1];
22+
const percentage = ((endPrice - startPrice) / startPrice) * 100;
23+
streaks.push({length: currentStreak, percentage});
24+
}
25+
26+
for (let i = 1; i < prices.length; i++) {
27+
const isUpward = keepSide === 'up' && prices[i] > prices[i - 1];
28+
const isDownward = keepSide === 'down' && prices[i] < prices[i - 1];
29+
if (isUpward || isDownward) {
30+
currentStreak++;
31+
} else {
32+
// Save the streak if it ends
33+
if (currentStreak > 0) {
34+
saveStreak(i);
35+
}
36+
// Reset the streak
37+
currentStreak = 0;
38+
}
39+
}
40+
41+
// Append the final streak if it exists
42+
if (currentStreak > 0) {
43+
saveStreak(prices.length);
44+
}
45+
46+
return streaks;
47+
}

vitest.config.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {defineConfig} from 'vitest/config';
33
export default defineConfig({
44
test: {
55
coverage: {
6-
branches: 0,
7-
functions: 0,
86
include: ['**/*.{ts,tsx}', '!**/*.d.ts', '!**/cli.ts', '!**/index.ts', '!**/start*.ts'],
9-
lines: 0,
107
provider: 'v8',
118
reporter: ['html', 'lcov', 'text'],
12-
statements: 0,
9+
thresholds: {
10+
branches: 100,
11+
functions: 100,
12+
lines: 100,
13+
statements: 100,
14+
},
1315
},
1416
environment: 'node',
1517
globals: true,

0 commit comments

Comments
 (0)