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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "1.9.3",
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
- main.instances[`bollinger_bands`] = {
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
- main.verticalOhlcv[`bollinger_bands_upper`] = new Array(main.len).fill(null);
14
- main.verticalOhlcv[`bollinger_bands_middle`] = new Array(main.len).fill(null);
15
- main.verticalOhlcv[`bollinger_bands_lower`] = new Array(main.len).fill(null);
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
- main.verticalOhlcv[`bollinger_bands_range`] = new Array(main.len).fill(null);
19
- main.verticalOhlcv[`bollinger_bands_height`] = new Array(main.len).fill(null);
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
- result = { upper: null, middle: null, lower: null };
38
+ //do nothing
31
39
  }
32
40
 
41
+ if(!result) return true
42
+
33
43
  const { upper, middle, lower } = result;
34
- main.verticalOhlcv[`bollinger_bands_upper`][index] = upper;
35
- main.verticalOhlcv[`bollinger_bands_middle`][index] = middle;
36
- main.verticalOhlcv[`bollinger_bands_lower`][index] = lower;
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
- if (upper !== null && lower !== null) {
45
- range = (value - lower) / (upper - lower);
46
- height = upper - lower;
54
+ range = (value - lower) / (upper - lower);
55
+ height = upper - lower;
47
56
 
48
- heightInstance.update(height);
57
+ heightInstance.update(height);
49
58
 
50
- try {
51
- heightMean = heightInstance.getResult();
52
- } catch (err) {
53
- heightMean = null;
54
- }
59
+ try {
60
+ heightMean = heightInstance.getResult();
61
+ } catch (err) {
62
+ //do nothing
55
63
  }
56
64
 
57
- main.verticalOhlcv[`bollinger_bands_range`][index] = range;
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
- const { verticalOhlcv } = main;
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
- verticalOhlcv['donchian_channel_upper'] = new Array(main.len).fill(null);
15
- verticalOhlcv['donchian_channel_basis'] = new Array(main.len).fill(null);
16
- verticalOhlcv['donchian_channel_lower'] = new Array(main.len).fill(null);
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['macd_diff'] = new Array(main.len).fill(null);
18
- main.verticalOhlcv['macd_dea'] = new Array(main.len).fill(null);
19
- main.verticalOhlcv['macd_histogram'] = new Array(main.len).fill(null);
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
- macdResult = { macd: null, signal: null, histogram: null };
32
+ //do nothing
30
33
  }
31
34
 
32
- const { macd, signal, histogram } = macdResult;
33
35
 
34
- if (macd !== null) {
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
- main.verticalOhlcv[`relative_volume_${size}`] = new Array(main.len).fill(null);
10
- main.verticalOhlcv[`relative_volume_sma_${size}`] = new Array(main.len).fill(null);
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
- smaValue = null;
23
+ //do nothing
21
24
  }
22
25
 
23
26
  main.verticalOhlcv[`relative_volume_sma_${size}`][index] = smaValue;
@@ -18,7 +18,7 @@ export const sma = (main, index, size) => {
18
18
  currSma = main.instances[`sma_${size}`].getResult()
19
19
  } catch(err)
20
20
  {
21
- currSma = null
21
+ //do nothing
22
22
  }
23
23
 
24
24
  if(currSma)
@@ -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
- main.instances[`rsi_${size}`] = new FasterRSI(size)
13
- main.instances[`rsi_sma_${size}`] = new FasterSMA(size)
14
- main.verticalOhlcv[`rsi_${size}`] = new Array(main.len).fill(null)
15
- main.verticalOhlcv[`rsi_sma_${size}`] = new Array(main.len).fill(null)
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
- main.instances[`volume_oscillator`] = {
9
- fastEMA: new FasterEMA(fastSize),
10
- slowEMA: new FasterEMA(slowSize)
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
- // Ensure arrays exist in main.verticalOhlcv
11
- const fields = [
12
- 'candle_gap_size',
13
- 'candle_body_size',
14
- 'candle_top_size',
15
- 'candle_bottom_size',
16
- 'candle_shadow_size',
17
- 'candle_close_direction',
18
- 'candle_high_direction',
19
- 'candle_low_direction',
20
- 'candle_open_direction'
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
- const instanceNames = [
32
- 'topInstance',
33
- 'bottomInstance',
34
- 'gapInstance',
35
- 'bodySizeInstance',
36
- 'shadowSizeInstance',
37
- 'closeDirectionInstance',
38
- 'highDirectionInstance',
39
- 'lowDirectionInstance',
40
- 'openDirectionInstance'
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['candle_body_size'][index] = isUp
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['candle_gap_size'][index] = isUp
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['candle_close_direction'][index] = isUp
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['candle_high_direction'][index] = isUp
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['candle_low_direction'][index] = isUp
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['candle_open_direction'][index] = isUp
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['candle_top_size'][index] = classifySize(topSize, topSizeMean, 0.5, sizeLevel);
155
- main.verticalOhlcv['candle_bottom_size'][index] = classifySize(bottomSize, bottomSizeMean, 0.5, sizeLevel);
156
- main.verticalOhlcv['candle_shadow_size'][index] = classifySize(shadowSize, shadowSizeMean, 0.5, sizeLevel);
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['candle_body_size'][index] = isUp ? candleBodySize : -candleBodySize;
160
- main.verticalOhlcv['candle_gap_size'][index] = isUp ? gapSize : -gapSize;
161
- main.verticalOhlcv['candle_close_direction'][index] = isUp ? closeDirection : -closeDirection;
162
- main.verticalOhlcv['candle_high_direction'][index] = isUp ? highDirection : -highDirection;
163
- main.verticalOhlcv['candle_low_direction'][index] = isUp ? lowDirection : -lowDirection;
164
- main.verticalOhlcv['candle_open_direction'][index] = isUp ? openDirection : -openDirection;
165
-
166
- main.verticalOhlcv['candle_top_size'][index] = topSize;
167
- main.verticalOhlcv['candle_bottom_size'][index] = bottomSize;
168
- main.verticalOhlcv['candle_shadow_size'][index] = shadowSize;
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
- export const findCrosses = ({fast, high, low, slow}) => {
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
- // Prepare slowNumArr if 'slow' is a number
153
- if (typeof slow === 'number' && !slowNumArrCache[slow]) {
154
-
155
- slowNumArrCache[slow] = Array(len).fill(slow)
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
- const crossName = `${fast}_x_${slow}`
159
- const countName = `${fast}_c_${slow}`
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
- if(typeof count !== 'undefined')
190
- {
191
- c[countName] = splitCount(cross)
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
- //cross created from 1 column and an number
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
- if(typeof count !== 'undefined'){
201
- c[countName] = splitCount(cross)
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
- throw Error(`Missing ohlcv properties for ${fast} or ${slow}`);
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
- return {x, c}
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: false,
29
- midPriceHighLow: false,
30
- sessionIndex: false,
31
- sessionIntradayIndex: false,
32
- dayOfTheWeek: false,
33
- dayOfTheMonth: false,
34
- weekOfTheMonth: false
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
- // Initialize arrays for numerical columns
83
- for (let i = 0; i < numberColsKeys.length; i++) {
84
- vert[numberColsKeys[i]] = new Array(len)
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
- // Extract other keys and initialize their arrays
88
- const inputKeys = Object.keys(input[0])
89
- const otherKeys = []
90
- for (let i = 0; i < inputKeys.length; i++) {
91
- const key = inputKeys[i]
92
- if (!numberColsKeysSet.has(key)) {
93
- vert[key] = new Array(len)
94
- otherKeys.push(key)
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
- const isValidDate = validateFirstDate(input)
99
- let prevDateStr
100
- let sessionIndexCount = 0
101
- let sessionIntradayIndexCount = 0
102
+ isValidDate = validateFirstDate(input)
102
103
 
103
- // Initialize date-related arrays if necessary
104
- if (isValidDate) {
105
- if (sessionIndex) {
106
- main.verticalOhlcv['session_index'] = new Array(len)
107
- }
108
- if (sessionIntradayIndex) {
109
- main.verticalOhlcv['session_intraday_index'] = new Array(len)
110
- }
111
- if (dayOfTheWeek) {
112
- main.verticalOhlcv['day_of_the_week'] = new Array(len)
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
- if (dayOfTheMonth) {
115
- main.verticalOhlcv['day_of_the_month'] = new Array(len)
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
- if (weekOfTheMonth) {
118
- main.verticalOhlcv['week_of_the_month'] = new Array(len)
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
- prevDateStr = input[0].date.slice(0, 10)
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
- // Cached date info for the current date
161
- let cachedDayInfo = getDateInfo(prevDateStr) // initial date info if needed
162
- for (let x = 0; x < len; x++) {
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
- main.verticalOhlcv.day_of_the_week[x] = cachedDayInfo.day_of_the_week
232
+ vert.day_of_the_week[x] = cachedDayInfo.day_of_the_week
204
233
  }
205
234
 
206
235
  if (dayOfTheMonth) {
207
- main.verticalOhlcv.day_of_the_month[x] = cachedDayInfo.day_of_the_month
236
+ vert.day_of_the_month[x] = cachedDayInfo.day_of_the_month
208
237
  }
209
238
 
210
239
  if (weekOfTheMonth) {
211
- main.verticalOhlcv.week_of_the_month[x] = cachedDayInfo.week_of_the_month
240
+ vert.week_of_the_month[x] = cachedDayInfo.week_of_the_month
212
241
  }
213
242
 
214
243
  if (sessionIndex) {
215
- main.verticalOhlcv.session_index[x] = sessionIndexCount
244
+ vert.session_index[x] = sessionIndexCount
216
245
  }
217
246
 
218
247
  if (sessionIntradayIndex) {
219
- main.verticalOhlcv.session_intraday_index[x] = sessionIntradayIndexCount
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
  }