ohlcv-indicators 1.9.3 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/moving-averages/bollingerBands.js +34 -22
- package/src/moving-averages/donchianChannel.js +13 -9
- package/src/moving-averages/macd.js +11 -15
- package/src/moving-averages/relativeVolume.js +6 -3
- package/src/moving-averages/sma.js +1 -1
- package/src/oscillators/rsi.js +10 -4
- package/src/oscillators/volumeOscillator.js +8 -4
- package/src/studies/candleStudies.js +46 -61
- package/src/studies/findCrosses.js +95 -117
- package/src/utilities/parsing-utilities.js +88 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
4
4
|
"description": "A JavaScript library featuring optimized trading indicators for OHLCV arrays [{open, high, low, close, value}]. This library is optimized for backtesting and trading bot development.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -6,17 +6,25 @@ export const bollingerBands = (main, index, size, times, bollingerBandsStudies =
|
|
|
6
6
|
const value = main.verticalOhlcv.close[index]
|
|
7
7
|
|
|
8
8
|
if (!main.instances.hasOwnProperty(`bollinger_bands`)) {
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
Object.assign(main.instances, {bollinger_bands: {
|
|
10
11
|
instance: new FasterBollingerBands(size, times),
|
|
11
12
|
heightInstance: bollingerBandsStudies ? new FasterSMA(size) : null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
main.verticalOhlcv
|
|
13
|
+
}})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Object.assign(main.verticalOhlcv, {
|
|
17
|
+
bollinger_bands_upper: new Array(main.len).fill(null),
|
|
18
|
+
bollinger_bands_middle: new Array(main.len).fill(null),
|
|
19
|
+
bollinger_bands_lower: new Array(main.len).fill(null)
|
|
20
|
+
})
|
|
16
21
|
|
|
17
22
|
if (bollingerBandsStudies) {
|
|
18
|
-
|
|
19
|
-
main.verticalOhlcv
|
|
23
|
+
|
|
24
|
+
Object.assign(main.verticalOhlcv, {
|
|
25
|
+
bollinger_bands_range: new Array(main.len).fill(null),
|
|
26
|
+
bollinger_bands_height: new Array(main.len).fill(null),
|
|
27
|
+
})
|
|
20
28
|
}
|
|
21
29
|
}
|
|
22
30
|
|
|
@@ -27,13 +35,15 @@ export const bollingerBands = (main, index, size, times, bollingerBandsStudies =
|
|
|
27
35
|
try {
|
|
28
36
|
result = instance.getResult();
|
|
29
37
|
} catch (err) {
|
|
30
|
-
|
|
38
|
+
//do nothing
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
if(!result) return true
|
|
42
|
+
|
|
33
43
|
const { upper, middle, lower } = result;
|
|
34
|
-
main.verticalOhlcv
|
|
35
|
-
main.verticalOhlcv
|
|
36
|
-
main.verticalOhlcv
|
|
44
|
+
main.verticalOhlcv.bollinger_bands_upper[index] = upper;
|
|
45
|
+
main.verticalOhlcv.bollinger_bands_middle[index] = middle;
|
|
46
|
+
main.verticalOhlcv.bollinger_bands_lower[index] = lower;
|
|
37
47
|
|
|
38
48
|
if (!bollingerBandsStudies) return true;
|
|
39
49
|
|
|
@@ -41,21 +51,23 @@ export const bollingerBands = (main, index, size, times, bollingerBandsStudies =
|
|
|
41
51
|
let height = null;
|
|
42
52
|
let heightMean;
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
height = upper - lower;
|
|
54
|
+
range = (value - lower) / (upper - lower);
|
|
55
|
+
height = upper - lower;
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
heightInstance.update(height);
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
59
|
+
try {
|
|
60
|
+
heightMean = heightInstance.getResult();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
//do nothing
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
main.verticalOhlcv
|
|
58
|
-
main.verticalOhlcv[`bollinger_bands_height`][index] = classifySize(height, heightMean, 1.5);
|
|
65
|
+
main.verticalOhlcv.bollinger_bands_range[index] = range;
|
|
59
66
|
|
|
67
|
+
if(heightMean)
|
|
68
|
+
{
|
|
69
|
+
main.verticalOhlcv.bollinger_bands_height[index] = classifySize(height, heightMean, 1.5);
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
return true;
|
|
61
73
|
};
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
const avg = (h, l) => (h + l) / 2;
|
|
2
2
|
|
|
3
3
|
export const donchianChannels = (main, index, size, offset) => {
|
|
4
|
-
|
|
5
|
-
const highs = verticalOhlcv.high;
|
|
6
|
-
const lows = verticalOhlcv.low;
|
|
4
|
+
|
|
5
|
+
const highs = main.verticalOhlcv.high;
|
|
6
|
+
const lows = main.verticalOhlcv.low;
|
|
7
7
|
|
|
8
8
|
// If we don't have enough data to fill one size ending at (index - offset), return
|
|
9
9
|
if ((index - offset) < (size - 1)) return true;
|
|
10
10
|
|
|
11
11
|
// Ensure that the required arrays exist
|
|
12
12
|
if (!main.instances.hasOwnProperty('donchian_channels')) {
|
|
13
|
+
|
|
13
14
|
main.instances['donchian_channels'] = {};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
Object.assign(main.verticalOhlcv, {
|
|
17
|
+
donchian_channel_upper: new Array(main.len).fill(null),
|
|
18
|
+
donchian_channel_basis: new Array(main.len).fill(null),
|
|
19
|
+
donchian_channel_lower: new Array(main.len).fill(null),
|
|
20
|
+
})
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
|
|
@@ -37,9 +41,9 @@ export const donchianChannels = (main, index, size, offset) => {
|
|
|
37
41
|
const placementIndex = index;
|
|
38
42
|
|
|
39
43
|
if (placementIndex >= 0 && placementIndex < main.len) {
|
|
40
|
-
verticalOhlcv['donchian_channel_upper'][placementIndex] = upper;
|
|
41
|
-
verticalOhlcv['donchian_channel_basis'][placementIndex] = basis;
|
|
42
|
-
verticalOhlcv['donchian_channel_lower'][placementIndex] = lower;
|
|
44
|
+
main.verticalOhlcv['donchian_channel_upper'][placementIndex] = upper;
|
|
45
|
+
main.verticalOhlcv['donchian_channel_basis'][placementIndex] = basis;
|
|
46
|
+
main.verticalOhlcv['donchian_channel_lower'][placementIndex] = lower;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
return true;
|
|
@@ -14,33 +14,29 @@ export const macd = (main, index, fastLine = 12, slowLine = 26, signalLine = 9)
|
|
|
14
14
|
new FasterEMA(signalLine)
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
-
main.verticalOhlcv
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Object.assign(main.verticalOhlcv, {
|
|
18
|
+
macd_diff: new Array(main.len).fill(null),
|
|
19
|
+
macd_dea: new Array(main.len).fill(null),
|
|
20
|
+
macd_histogram: new Array(main.len).fill(null),
|
|
21
|
+
})
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
const macdInstance = main.instances[`macd`];
|
|
23
25
|
macdInstance.update(value);
|
|
24
26
|
|
|
25
27
|
let macdResult;
|
|
28
|
+
|
|
26
29
|
try {
|
|
27
30
|
macdResult = macdInstance.getResult();
|
|
28
31
|
} catch (err) {
|
|
29
|
-
|
|
32
|
+
//do nothing
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
const { macd, signal, histogram } = macdResult;
|
|
33
35
|
|
|
34
|
-
if (
|
|
35
|
-
main.verticalOhlcv['macd_diff'][index] = macd;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (signal !== null) {
|
|
39
|
-
main.verticalOhlcv['macd_dea'][index] = signal;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (histogram !== null) {
|
|
43
|
-
main.verticalOhlcv['macd_histogram'][index] = histogram;
|
|
36
|
+
if (macdResult) {
|
|
37
|
+
main.verticalOhlcv['macd_diff'][index] = macdResult.macd;
|
|
38
|
+
main.verticalOhlcv['macd_dea'][index] = macdResult.signal;
|
|
39
|
+
main.verticalOhlcv['macd_histogram'][index] = macdResult.histogram;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
return true;
|
|
@@ -6,8 +6,11 @@ export const relativeVolume = (main, index, size = 10) => {
|
|
|
6
6
|
|
|
7
7
|
if (!main.instances.hasOwnProperty(`relative_volume_${size}`)) {
|
|
8
8
|
main.instances[`relative_volume_${size}`] = new FasterSMA(size);
|
|
9
|
-
|
|
10
|
-
main.verticalOhlcv
|
|
9
|
+
|
|
10
|
+
Object.assign(main.verticalOhlcv, {
|
|
11
|
+
[`relative_volume_${size}`]: new Array(main.len).fill(null),
|
|
12
|
+
[`relative_volume_sma_${size}`]: new Array(main.len).fill(null)
|
|
13
|
+
})
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
const smaInstance = main.instances[`relative_volume_${size}`]
|
|
@@ -17,7 +20,7 @@ export const relativeVolume = (main, index, size = 10) => {
|
|
|
17
20
|
try {
|
|
18
21
|
smaValue = smaInstance.getResult()
|
|
19
22
|
} catch (err) {
|
|
20
|
-
|
|
23
|
+
//do nothing
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
main.verticalOhlcv[`relative_volume_sma_${size}`][index] = smaValue;
|
package/src/oscillators/rsi.js
CHANGED
|
@@ -9,10 +9,16 @@ export const rsi = (main, index, size) => {
|
|
|
9
9
|
if(!main.instances.hasOwnProperty(`rsi_${size}`))
|
|
10
10
|
{
|
|
11
11
|
main.autoCrossPairsList.push({fast: `rsi_${size}`, slow: `rsi_sma_${size}`})
|
|
12
|
-
|
|
13
|
-
main.instances
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
|
|
13
|
+
Object.assign(main.instances, {
|
|
14
|
+
[`rsi_${size}`]: new FasterRSI(size),
|
|
15
|
+
[`rsi_sma_${size}`]: new FasterSMA(size)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
Object.assign(main.verticalOhlcv, {
|
|
19
|
+
[`rsi_${size}`]: new Array(main.len).fill(null),
|
|
20
|
+
[`rsi_sma_${size}`]: new Array(main.len).fill(null)
|
|
21
|
+
})
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
let currentRsi
|
|
@@ -5,10 +5,14 @@ export const volumeOscillator = (main, index, fastSize = 5, slowSize = 10) => {
|
|
|
5
5
|
const value = main.verticalOhlcv.volume[index]
|
|
6
6
|
|
|
7
7
|
if (!main.instances.hasOwnProperty(`volume_oscillator`)) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
|
|
9
|
+
Object.assign(main.instances, {
|
|
10
|
+
volume_oscillator: {
|
|
11
|
+
fastEMA: new FasterEMA(fastSize),
|
|
12
|
+
slowEMA: new FasterEMA(slowSize)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
12
16
|
main.verticalOhlcv[`volume_oscillator`] = new Array(main.len).fill(null);
|
|
13
17
|
main.autoCrossPairsList.push({ fast: `volume_oscillator`, slow: 0 });
|
|
14
18
|
}
|
|
@@ -3,54 +3,39 @@ import { classifySize } from '../utilities/classification.js';
|
|
|
3
3
|
|
|
4
4
|
export const candlesStudies = (main, index, size, classify = true, classificationLevels = {}) => {
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
6
|
const { changeLevel = 7, sizeLevel = 5 } = classificationLevels;
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
for (const field of fields) {
|
|
24
|
-
if (!main.verticalOhlcv.hasOwnProperty(field)) {
|
|
25
|
-
main.verticalOhlcv[field] = new Array(main.len).fill(null);
|
|
26
|
-
}
|
|
8
|
+
if(index === 0)
|
|
9
|
+
{
|
|
10
|
+
Object.assign(main.verticalOhlcv, {
|
|
11
|
+
candle_gap_size: new Array(main.len).fill(null),
|
|
12
|
+
candle_body_size: new Array(main.len).fill(null),
|
|
13
|
+
candle_top_size: new Array(main.len).fill(null),
|
|
14
|
+
candle_bottom_size: new Array(main.len).fill(null),
|
|
15
|
+
candle_shadow_size: new Array(main.len).fill(null),
|
|
16
|
+
candle_close_direction: new Array(main.len).fill(null),
|
|
17
|
+
candle_high_direction: new Array(main.len).fill(null),
|
|
18
|
+
candle_low_direction: new Array(main.len).fill(null),
|
|
19
|
+
candle_open_direction: new Array(main.len).fill(null),
|
|
20
|
+
})
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
// Ensure instances exist if classification is enabled
|
|
30
|
-
if (classify) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Create a namespace for candle studies if it doesn't exist
|
|
44
|
-
if (!main.instances.hasOwnProperty('candleStudies')) {
|
|
45
|
-
main.instances.candleStudies = {};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Create instances if they don't exist
|
|
49
|
-
for (const name of instanceNames) {
|
|
50
|
-
if (!main.instances.candleStudies.hasOwnProperty(name)) {
|
|
51
|
-
main.instances.candleStudies[name] = new FasterSMA(size);
|
|
24
|
+
if (classify && index === 0) {
|
|
25
|
+
|
|
26
|
+
Object.assign(main.instances, {
|
|
27
|
+
candleStudies: {
|
|
28
|
+
topInstance: new FasterSMA(size),
|
|
29
|
+
bottomInstance: new FasterSMA(size),
|
|
30
|
+
gapInstance: new FasterSMA(size),
|
|
31
|
+
bodySizeInstance: new FasterSMA(size),
|
|
32
|
+
shadowSizeInstance: new FasterSMA(size),
|
|
33
|
+
closeDirectionInstance: new FasterSMA(size),
|
|
34
|
+
highDirectionInstance: new FasterSMA(size),
|
|
35
|
+
lowDirectionInstance: new FasterSMA(size),
|
|
36
|
+
openDirectionInstance: new FasterSMA(size)
|
|
52
37
|
}
|
|
53
|
-
}
|
|
38
|
+
})
|
|
54
39
|
}
|
|
55
40
|
|
|
56
41
|
// If we do not have a previous candle, we cannot compute differences
|
|
@@ -127,45 +112,45 @@ export const candlesStudies = (main, index, size, classify = true, classificatio
|
|
|
127
112
|
// If there's not enough data, means remain null
|
|
128
113
|
}
|
|
129
114
|
|
|
130
|
-
main.verticalOhlcv
|
|
115
|
+
main.verticalOhlcv.candle_body_size[index] = isUp
|
|
131
116
|
? classifySize(candleBodySize, bodySizeMean, 2, changeLevel)
|
|
132
117
|
: -Math.abs(classifySize(candleBodySize, bodySizeMean, 2, changeLevel)) || 0;
|
|
133
118
|
|
|
134
|
-
main.verticalOhlcv
|
|
119
|
+
main.verticalOhlcv.candle_gap_size[index] = isUp
|
|
135
120
|
? classifySize(gapSize, gapMean, 2, changeLevel)
|
|
136
121
|
: -Math.abs(classifySize(gapSize, gapMean, 2, changeLevel)) || 0;
|
|
137
122
|
|
|
138
|
-
main.verticalOhlcv
|
|
123
|
+
main.verticalOhlcv.candle_close_direction[index] = isUp
|
|
139
124
|
? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
|
|
140
125
|
: -Math.abs(classifySize(closeDirection, closeDirectionMean, 2, changeLevel)) || 0;
|
|
141
126
|
|
|
142
|
-
main.verticalOhlcv
|
|
127
|
+
main.verticalOhlcv.candle_high_direction[index] = isUp
|
|
143
128
|
? classifySize(highDirection, highDirectionMean, 2, changeLevel)
|
|
144
129
|
: -Math.abs(classifySize(highDirection, highDirectionMean, 2, changeLevel)) || 0;
|
|
145
130
|
|
|
146
|
-
main.verticalOhlcv
|
|
131
|
+
main.verticalOhlcv.candle_low_direction[index] = isUp
|
|
147
132
|
? classifySize(lowDirection, lowDirectionMean, 2, changeLevel)
|
|
148
133
|
: -Math.abs(classifySize(lowDirection, lowDirectionMean, 2, changeLevel)) || 0;
|
|
149
134
|
|
|
150
|
-
main.verticalOhlcv
|
|
135
|
+
main.verticalOhlcv.candle_open_direction[index] = isUp
|
|
151
136
|
? classifySize(openDirection, openDirectionMean, 2, changeLevel)
|
|
152
137
|
: -Math.abs(classifySize(openDirection, openDirectionMean, 2, changeLevel)) || 0;
|
|
153
138
|
|
|
154
|
-
main.verticalOhlcv
|
|
155
|
-
main.verticalOhlcv
|
|
156
|
-
main.verticalOhlcv
|
|
139
|
+
main.verticalOhlcv.candle_top_size[index] = classifySize(topSize, topSizeMean, 0.5, sizeLevel);
|
|
140
|
+
main.verticalOhlcv.candle_bottom_size[index] = classifySize(bottomSize, bottomSizeMean, 0.5, sizeLevel);
|
|
141
|
+
main.verticalOhlcv.candle_shadow_size[index] = classifySize(shadowSize, shadowSizeMean, 0.5, sizeLevel);
|
|
157
142
|
} else {
|
|
158
143
|
// Without classification, just store raw values
|
|
159
|
-
main.verticalOhlcv
|
|
160
|
-
main.verticalOhlcv
|
|
161
|
-
main.verticalOhlcv
|
|
162
|
-
main.verticalOhlcv
|
|
163
|
-
main.verticalOhlcv
|
|
164
|
-
main.verticalOhlcv
|
|
165
|
-
|
|
166
|
-
main.verticalOhlcv
|
|
167
|
-
main.verticalOhlcv
|
|
168
|
-
main.verticalOhlcv
|
|
144
|
+
main.verticalOhlcv.candle_body_size[index] = isUp ? candleBodySize : -candleBodySize;
|
|
145
|
+
main.verticalOhlcv.candle_gap_size[index] = isUp ? gapSize : -gapSize;
|
|
146
|
+
main.verticalOhlcv.candle_close_direction[index] = isUp ? closeDirection : -closeDirection;
|
|
147
|
+
main.verticalOhlcv.candle_high_direction[index] = isUp ? highDirection : -highDirection;
|
|
148
|
+
main.verticalOhlcv.candle_low_direction[index] = isUp ? lowDirection : -lowDirection;
|
|
149
|
+
main.verticalOhlcv.candle_open_direction[index] = isUp ? openDirection : -openDirection;
|
|
150
|
+
|
|
151
|
+
main.verticalOhlcv.candle_top_size[index] = topSize;
|
|
152
|
+
main.verticalOhlcv.candle_bottom_size[index] = bottomSize;
|
|
153
|
+
main.verticalOhlcv.candle_shadow_size[index] = shadowSize;
|
|
169
154
|
}
|
|
170
155
|
|
|
171
156
|
return true;
|
|
@@ -3,8 +3,69 @@ const gt = (f, s) => f > s
|
|
|
3
3
|
const lt = (f, s) => f < s
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
export const crossPairs = (main, arr) => {
|
|
8
|
+
|
|
9
|
+
const {verticalOhlcv} = main
|
|
10
|
+
|
|
11
|
+
const x = {}
|
|
12
|
+
const c = {}
|
|
13
|
+
|
|
14
|
+
for (const { fast, slow, count } of arr) {
|
|
15
|
+
// Validate parameters
|
|
16
|
+
if (!fast || !slow) continue;
|
|
17
|
+
|
|
18
|
+
// Prepare slowNumArr if 'slow' is a number
|
|
19
|
+
if (typeof slow === 'number') {
|
|
20
|
+
|
|
21
|
+
main.verticalOhlcv[slow] = new Array(main.len).fill(slow)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const crossName = `${fast}_x_${slow}`
|
|
25
|
+
const countName = `${fast}_c_${slow}`
|
|
26
|
+
|
|
27
|
+
const splitCount = arr => (typeof count !== 'undefined') ? (arr.slice(-count)).filter(o => o === 1 || o === -1).length : 0
|
|
28
|
+
|
|
29
|
+
//cross already exists
|
|
30
|
+
if(verticalOhlcv[crossName])
|
|
31
|
+
{
|
|
32
|
+
const cross = verticalOhlcv[crossName]
|
|
33
|
+
x[crossName] = cross
|
|
34
|
+
|
|
35
|
+
if(typeof count !== 'undefined')
|
|
36
|
+
{
|
|
37
|
+
c[countName] = splitCount(cross)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
//cross created from 2 columns
|
|
42
|
+
else if ((verticalOhlcv[fast] || fast === 'price') && verticalOhlcv[slow]) {
|
|
43
|
+
|
|
44
|
+
const cross = (fast === 'price')
|
|
45
|
+
? findCrosses({
|
|
46
|
+
fast: verticalOhlcv.close,
|
|
47
|
+
high: verticalOhlcv.high,
|
|
48
|
+
low: verticalOhlcv.low,
|
|
49
|
+
slow: verticalOhlcv[slow]}
|
|
50
|
+
)
|
|
51
|
+
: findCrosses({fast: verticalOhlcv[fast], slow: verticalOhlcv[slow]})
|
|
52
|
+
|
|
53
|
+
x[crossName] = cross
|
|
54
|
+
|
|
55
|
+
if(typeof count !== 'undefined')
|
|
56
|
+
{
|
|
57
|
+
c[countName] = splitCount(cross)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {x, c}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
6
67
|
// Function to find crosses between two arrays: fast and slow
|
|
7
|
-
|
|
68
|
+
const findCrosses = ({fast, high, low, slow}) => {
|
|
8
69
|
// Map states based on comparison of fast and slow arrays
|
|
9
70
|
|
|
10
71
|
const dataLength = fast.length
|
|
@@ -80,131 +141,48 @@ export const findCrosses = ({fast, high, low, slow}) => {
|
|
|
80
141
|
|
|
81
142
|
|
|
82
143
|
const flatGroupsToNumArr = groups => {
|
|
83
|
-
|
|
84
|
-
const groupLengths = groups.map(g => g.length)
|
|
85
|
-
|
|
86
|
-
return groups.map((group, gIndex) => {
|
|
87
|
-
let initialValue = groupLengths[gIndex]
|
|
88
|
-
|
|
89
|
-
return group.map((s, i) => {
|
|
90
|
-
let v = initialValue - i
|
|
91
|
-
|
|
92
|
-
if(s === 0.5)
|
|
93
|
-
{
|
|
94
|
-
v = 0
|
|
95
|
-
}
|
|
96
|
-
if(s === 1)
|
|
97
|
-
{
|
|
98
|
-
v = v * 1
|
|
99
|
-
}
|
|
100
|
-
if(s === 0)
|
|
101
|
-
{
|
|
102
|
-
v = v * -1
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return v
|
|
106
|
-
}).reverse()
|
|
107
|
-
}).flat()
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Helper function to group consecutive values
|
|
112
|
-
export const groupConsecutiveValues = arr => {
|
|
113
|
-
if (arr.length === 0) return []
|
|
114
|
-
|
|
115
|
-
return arr.reduce((acc, currentValue, index, array) => {
|
|
116
|
-
if (index === 0) {
|
|
117
|
-
// Start the first group with the first element
|
|
118
|
-
acc.push([currentValue])
|
|
119
|
-
} else {
|
|
120
|
-
// Get the previous value
|
|
121
|
-
const prevValue = array[index - 1]
|
|
122
|
-
const currentGroup = acc[acc.length - 1]
|
|
123
|
-
|
|
124
|
-
// Check the direction of the previous and current values
|
|
125
|
-
if (currentValue !== prevValue) {
|
|
126
|
-
// Start a new group if the direction changes
|
|
127
|
-
acc.push([currentValue]);
|
|
128
|
-
} else {
|
|
129
|
-
// Otherwise, add to the current group
|
|
130
|
-
currentGroup.push(currentValue);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return acc
|
|
134
|
-
}, [])
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
export const crossPairs = (main, arr) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const {verticalOhlcv, len} = main
|
|
144
|
-
const slowNumArrCache = {};
|
|
145
|
-
const x = {}
|
|
146
|
-
const c = {}
|
|
147
|
-
|
|
148
|
-
for (const { fast, slow, count } of arr) {
|
|
149
|
-
// Validate parameters
|
|
150
|
-
if (!fast || !slow) continue;
|
|
144
|
+
const result = [];
|
|
151
145
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
for (const group of groups) {
|
|
147
|
+
const length = group.length;
|
|
148
|
+
|
|
149
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
150
|
+
const s = group[i];
|
|
151
|
+
const offset = length - i;
|
|
152
|
+
result.push(
|
|
153
|
+
s === 0.5 ? 0 : offset * (s === 1 ? 1 : -1)
|
|
154
|
+
);
|
|
156
155
|
}
|
|
156
|
+
}
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const splitCount = arr => (typeof count !== 'undefined') ? (arr.slice(-count)).filter(o => o === 1 || o === -1).length : 0
|
|
162
|
-
|
|
163
|
-
//cross already exists
|
|
164
|
-
if(verticalOhlcv[crossName])
|
|
165
|
-
{
|
|
166
|
-
const cross = verticalOhlcv[crossName]
|
|
167
|
-
x[crossName] = cross
|
|
168
|
-
|
|
169
|
-
if(typeof count !== 'undefined')
|
|
170
|
-
{
|
|
171
|
-
c[countName] = splitCount(cross)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
//cross created from 2 columns
|
|
176
|
-
else if ((verticalOhlcv[fast] || fast === 'price') && verticalOhlcv[slow]) {
|
|
177
|
-
|
|
178
|
-
const cross = (fast === 'price')
|
|
179
|
-
? findCrosses({
|
|
180
|
-
fast: verticalOhlcv.close,
|
|
181
|
-
high: verticalOhlcv.high,
|
|
182
|
-
low: verticalOhlcv.low,
|
|
183
|
-
slow: verticalOhlcv[slow]}
|
|
184
|
-
)
|
|
185
|
-
: findCrosses({fast: verticalOhlcv[fast], slow: verticalOhlcv[slow]})
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
186
160
|
|
|
187
|
-
x[crossName] = cross
|
|
188
161
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
162
|
+
// Helper function to group consecutive values
|
|
163
|
+
const groupConsecutiveValues = (arr) => {
|
|
164
|
+
const length = arr.length;
|
|
165
|
+
if (length === 0) return [];
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
else if (verticalOhlcv[fast] && slowNumArrCache[slow]) {
|
|
197
|
-
const cross = findCrosses({fast: verticalOhlcv[fast], slow: slowNumArrCache[slow]})
|
|
198
|
-
x[crossName] = cross
|
|
167
|
+
const result = [];
|
|
168
|
+
let currentGroup = [arr[0]];
|
|
199
169
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
170
|
+
for (let i = 1; i < length; i++) {
|
|
171
|
+
const currentValue = arr[i];
|
|
172
|
+
const prevValue = arr[i - 1];
|
|
203
173
|
|
|
174
|
+
if (currentValue === prevValue) {
|
|
175
|
+
// Same value, continue the current group
|
|
176
|
+
currentGroup.push(currentValue);
|
|
204
177
|
} else {
|
|
205
|
-
|
|
178
|
+
// Value changed, finalize the current group and start a new one
|
|
179
|
+
result.push(currentGroup);
|
|
180
|
+
currentGroup = [currentValue];
|
|
206
181
|
}
|
|
207
182
|
}
|
|
208
183
|
|
|
209
|
-
|
|
210
|
-
|
|
184
|
+
// Push the last group
|
|
185
|
+
result.push(currentGroup);
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
};
|
|
@@ -25,13 +25,13 @@ const indicatorFunctions = {
|
|
|
25
25
|
const validateFirstDate = (arr) => arr[0].hasOwnProperty('date') && typeof arr[0].date === 'string' && /^\d{4}-\d{2}-\d{2}/.test(arr[0].date)
|
|
26
26
|
|
|
27
27
|
export const defaultStudyOptions = {
|
|
28
|
-
midPriceOpenClose:
|
|
29
|
-
midPriceHighLow:
|
|
30
|
-
sessionIndex:
|
|
31
|
-
sessionIntradayIndex:
|
|
32
|
-
dayOfTheWeek:
|
|
33
|
-
dayOfTheMonth:
|
|
34
|
-
weekOfTheMonth:
|
|
28
|
+
midPriceOpenClose: true,
|
|
29
|
+
midPriceHighLow: true,
|
|
30
|
+
sessionIndex: true,
|
|
31
|
+
sessionIntradayIndex: true,
|
|
32
|
+
dayOfTheWeek: true,
|
|
33
|
+
dayOfTheMonth: true,
|
|
34
|
+
weekOfTheMonth: true
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const getDateInfo = (dateString) => {
|
|
@@ -49,8 +49,8 @@ const getDateInfo = (dateString) => {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export const parseOhlcvToVertical = (input, main) => {
|
|
53
|
-
const { len, studyOptions, inputParams } = main
|
|
52
|
+
export const parseOhlcvToVertical = (input, main, startIndex = 0) => {
|
|
53
|
+
const { len, studyOptions = {}, inputParams } = main
|
|
54
54
|
|
|
55
55
|
if (studyOptions) {
|
|
56
56
|
const studyOptionKeys = Object.keys(studyOptions)
|
|
@@ -79,46 +79,70 @@ export const parseOhlcvToVertical = (input, main) => {
|
|
|
79
79
|
const numberColsKeysSet = new Set(numberColsKeys)
|
|
80
80
|
const vert = main.verticalOhlcv
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
let isValidDate = false
|
|
83
|
+
if (startIndex === 0) {
|
|
84
|
+
// Fresh initialization
|
|
85
|
+
// Initialize arrays for numerical columns
|
|
86
|
+
for (let i = 0; i < numberColsKeys.length; i++) {
|
|
87
|
+
vert[numberColsKeys[i]] = new Array(len)
|
|
88
|
+
}
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
// Extract other keys and initialize their arrays
|
|
91
|
+
const inputKeys = Object.keys(input[0])
|
|
92
|
+
const otherKeys = []
|
|
93
|
+
for (let i = 0; i < inputKeys.length; i++) {
|
|
94
|
+
const key = inputKeys[i]
|
|
95
|
+
if (!numberColsKeysSet.has(key)) {
|
|
96
|
+
vert[key] = new Array(len)
|
|
97
|
+
otherKeys.push(key)
|
|
98
|
+
}
|
|
95
99
|
}
|
|
96
|
-
|
|
100
|
+
main.otherKeys = otherKeys
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
let prevDateStr
|
|
100
|
-
let sessionIndexCount = 0
|
|
101
|
-
let sessionIntradayIndexCount = 0
|
|
102
|
+
isValidDate = validateFirstDate(input)
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
if (isValidDate) {
|
|
105
|
+
if (sessionIndex) {
|
|
106
|
+
vert['session_index'] = new Array(len)
|
|
107
|
+
}
|
|
108
|
+
if (sessionIntradayIndex) {
|
|
109
|
+
vert['session_intraday_index'] = new Array(len)
|
|
110
|
+
}
|
|
111
|
+
if (dayOfTheWeek) {
|
|
112
|
+
vert['day_of_the_week'] = new Array(len)
|
|
113
|
+
}
|
|
114
|
+
if (dayOfTheMonth) {
|
|
115
|
+
vert['day_of_the_month'] = new Array(len)
|
|
116
|
+
}
|
|
117
|
+
if (weekOfTheMonth) {
|
|
118
|
+
vert['week_of_the_month'] = new Array(len)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
main.prevDateStr = input[0].date.slice(0, 10)
|
|
122
|
+
main.sessionIndexCount = 0
|
|
123
|
+
main.sessionIntradayIndexCount = 0
|
|
124
|
+
main.cachedDayInfo = getDateInfo(main.prevDateStr)
|
|
125
|
+
} else {
|
|
126
|
+
// If no valid date, ensure these are null
|
|
127
|
+
main.prevDateStr = null
|
|
128
|
+
main.sessionIndexCount = null
|
|
129
|
+
main.sessionIntradayIndexCount = null
|
|
130
|
+
main.cachedDayInfo = null
|
|
113
131
|
}
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
} else {
|
|
133
|
+
// startIndex > 0
|
|
134
|
+
// We assume arrays and keys are already set
|
|
135
|
+
if (!main.otherKeys) {
|
|
136
|
+
throw new Error("otherKeys not found in main. Ensure you run with startIndex=0 first to initialize.")
|
|
116
137
|
}
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
|
|
139
|
+
// Validate that state variables needed are present
|
|
140
|
+
if (!main.hasOwnProperty('prevDateStr')) {
|
|
141
|
+
throw new Error("prevDateStr missing in main. Make sure to store state after initial run.")
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
// If we had a valid date initially, we continue with date computations
|
|
145
|
+
isValidDate = main.prevDateStr !== null
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
// Pre-process indicator params
|
|
@@ -152,14 +176,19 @@ export const parseOhlcvToVertical = (input, main) => {
|
|
|
152
176
|
|
|
153
177
|
// References for other keys arrays
|
|
154
178
|
const otherArrays = {}
|
|
155
|
-
for (let i = 0; i < otherKeys.length; i++) {
|
|
156
|
-
const k = otherKeys[i]
|
|
179
|
+
for (let i = 0; i < main.otherKeys.length; i++) {
|
|
180
|
+
const k = main.otherKeys[i]
|
|
157
181
|
otherArrays[k] = vert[k]
|
|
158
182
|
}
|
|
159
183
|
|
|
160
|
-
//
|
|
161
|
-
let
|
|
162
|
-
|
|
184
|
+
// For date/session logic, use previously stored state if startIndex > 0
|
|
185
|
+
let prevDateStr = main.prevDateStr
|
|
186
|
+
let sessionIndexCount = main.sessionIndexCount
|
|
187
|
+
let sessionIntradayIndexCount = main.sessionIntradayIndexCount
|
|
188
|
+
let cachedDayInfo = main.cachedDayInfo
|
|
189
|
+
|
|
190
|
+
// Compute indicators and fill data from startIndex to len
|
|
191
|
+
for (let x = startIndex; x < len; x++) {
|
|
163
192
|
const current = input[x]
|
|
164
193
|
|
|
165
194
|
// Assign OHLCV
|
|
@@ -184,8 +213,8 @@ export const parseOhlcvToVertical = (input, main) => {
|
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
// Other keys
|
|
187
|
-
for (let i = 0; i < otherKeys.length; i++) {
|
|
188
|
-
const k = otherKeys[i]
|
|
216
|
+
for (let i = 0; i < main.otherKeys.length; i++) {
|
|
217
|
+
const k = main.otherKeys[i]
|
|
189
218
|
otherArrays[k][x] = current[k]
|
|
190
219
|
}
|
|
191
220
|
|
|
@@ -200,26 +229,32 @@ export const parseOhlcvToVertical = (input, main) => {
|
|
|
200
229
|
}
|
|
201
230
|
|
|
202
231
|
if (dayOfTheWeek) {
|
|
203
|
-
|
|
232
|
+
vert.day_of_the_week[x] = cachedDayInfo.day_of_the_week
|
|
204
233
|
}
|
|
205
234
|
|
|
206
235
|
if (dayOfTheMonth) {
|
|
207
|
-
|
|
236
|
+
vert.day_of_the_month[x] = cachedDayInfo.day_of_the_month
|
|
208
237
|
}
|
|
209
238
|
|
|
210
239
|
if (weekOfTheMonth) {
|
|
211
|
-
|
|
240
|
+
vert.week_of_the_month[x] = cachedDayInfo.week_of_the_month
|
|
212
241
|
}
|
|
213
242
|
|
|
214
243
|
if (sessionIndex) {
|
|
215
|
-
|
|
244
|
+
vert.session_index[x] = sessionIndexCount
|
|
216
245
|
}
|
|
217
246
|
|
|
218
247
|
if (sessionIntradayIndex) {
|
|
219
|
-
|
|
248
|
+
vert.session_intraday_index[x] = sessionIntradayIndexCount
|
|
220
249
|
}
|
|
221
250
|
|
|
222
251
|
sessionIntradayIndexCount++
|
|
223
252
|
}
|
|
224
253
|
}
|
|
254
|
+
|
|
255
|
+
// Store current state so that we can continue from here in subsequent calls
|
|
256
|
+
main.prevDateStr = prevDateStr
|
|
257
|
+
main.sessionIndexCount = sessionIndexCount
|
|
258
|
+
main.sessionIntradayIndexCount = sessionIntradayIndexCount
|
|
259
|
+
main.cachedDayInfo = cachedDayInfo
|
|
225
260
|
}
|