ohlcv-indicators 1.9.2 → 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.
@@ -1,47 +1,47 @@
1
- import { FasterEMA } from 'trading-signals'
2
- import { findCrosses } from "../studies/findCrosses.js";
1
+ import { FasterEMA } from 'trading-signals';
3
2
 
3
+ export const volumeOscillator = (main, index, fastSize = 5, slowSize = 10) => {
4
4
 
5
- export const volumeOscillator = (main, fastPeriod = 5, slowPeriod = 10) => {
6
- const { verticalOhlcv } = main;
7
- const { volume } = verticalOhlcv;
5
+ const value = main.verticalOhlcv.volume[index]
8
6
 
9
- return getVolumeOscillator(volume, fastPeriod, slowPeriod);
7
+ if (!main.instances.hasOwnProperty(`volume_oscillator`)) {
10
8
 
11
- }
9
+ Object.assign(main.instances, {
10
+ volume_oscillator: {
11
+ fastEMA: new FasterEMA(fastSize),
12
+ slowEMA: new FasterEMA(slowSize)
13
+ }
14
+ })
12
15
 
13
- export const getVolumeOscillator = (volume, fastPeriod, slowPeriod) => {
14
- const fastEMA = new FasterEMA(fastPeriod)
15
- const slowEMA = new FasterEMA(slowPeriod)
16
- const volume_oscillator = new Array(volume.length).fill(null)
17
-
18
- for (let i = 0; i < volume.length; i++) {
19
-
20
- fastEMA.update(volume[i])
21
- slowEMA.update(volume[i])
16
+ main.verticalOhlcv[`volume_oscillator`] = new Array(main.len).fill(null);
17
+ main.autoCrossPairsList.push({ fast: `volume_oscillator`, slow: 0 });
18
+ }
22
19
 
23
- let fastValue = null
24
- let slowValue = null
20
+ const { fastEMA, slowEMA } = main.instances[`volume_oscillator`];
25
21
 
26
- try {
27
- fastValue = fastEMA.getResult()
28
- } catch (err) {
29
- fastValue = null
30
- }
22
+ fastEMA.update(value);
23
+ slowEMA.update(value);
31
24
 
32
- try {
33
- slowValue = slowEMA.getResult()
34
- } catch (err) {
35
- slowValue = null
36
- }
25
+ let fastValue = null;
26
+ let slowValue = null;
37
27
 
38
- if (typeof fastValue === 'number' && typeof slowValue === 'number' && slowValue !== 0) {
28
+ try {
29
+ fastValue = fastEMA.getResult();
30
+ } catch (err) {
31
+ fastValue = null;
32
+ }
39
33
 
40
- volume_oscillator[i] = 100 * (fastValue - slowValue) / slowValue
41
- }
34
+ try {
35
+ slowValue = slowEMA.getResult();
36
+ } catch (err) {
37
+ slowValue = null;
42
38
  }
43
39
 
44
- const volume_oscillator_x_0 = findCrosses({fast: volume_oscillator, slow: new Array(volume.length).fill(0)})
40
+ if (typeof fastValue === 'number' && typeof slowValue === 'number' && slowValue !== 0) {
41
+ main.verticalOhlcv[`volume_oscillator`][index] = 100 * (fastValue - slowValue) / slowValue
42
+ } else {
43
+ main.verticalOhlcv[`volume_oscillator`][index] = null;
44
+ }
45
45
 
46
- return {volume_oscillator, volume_oscillator_x_0}
47
- }
46
+ return true;
47
+ };
@@ -1,184 +1,157 @@
1
- import {FasterSMA} from 'trading-signals'
2
- import { classifySize } from '../utilities/classification.js'
3
-
4
- export const candlesStudies = (main, period, classify, classificationLevels) => {
5
-
6
- const {inputOhlcv, len} = main
7
-
8
- const cols = getCandlesStudies(inputOhlcv, period, len, classify, classificationLevels)
9
- return cols
10
- }
11
-
12
- const getCandlesStudies = (inputOhlcv, period = 20, len, classify = true, classificationLevels = {}) => {
13
-
14
- const {changeLevel = 7, sizeLevel = 5} = classificationLevels
15
-
16
- //close > open
17
- let candle_body_size = new Array(len).fill(null) //scaled candle direction close - open
18
- let candle_top_size = new Array(len).fill(null) //scaled top size (0, 0.5 and 1)
19
- let candle_bottom_size = new Array(len).fill(null) //scaled bottom size(0, 0.5 and 1)
20
- let candle_gap_size = new Array(len).fill(null) //scaled gap between the current open and previous close (-1 to 1)
21
- let candle_shadow_size = new Array(len).fill(null) //scaled shadow size high - slow
22
- let candle_close_direction = new Array(len).fill(null) //scaled shadow size curr.close - prev.close
23
- let candle_high_direction = new Array(len).fill(null) //scaled shadow size curr.high - prev.high
24
- let candle_low_direction = new Array(len).fill(null) //scaled shadow size curr.low - prev.low
25
- let candle_open_direction = new Array(len).fill(null) //scaled shadow size curr.open - prev.open
26
-
27
- //instances
28
- let topInstance
29
- let bottomInstance
30
- let gapInstance
31
- let bodySizeInstance
32
- let shadowSizeInstance
33
- let closeDirectionInstance
34
- let highDirectionInstance
35
- let lowDirectionInstance
36
- let openDirectionInstance
37
-
38
- if(classify)
1
+ import { FasterSMA } from 'trading-signals';
2
+ import { classifySize } from '../utilities/classification.js';
3
+
4
+ export const candlesStudies = (main, index, size, classify = true, classificationLevels = {}) => {
5
+
6
+ const { changeLevel = 7, sizeLevel = 5 } = classificationLevels;
7
+
8
+ if(index === 0)
39
9
  {
40
- topInstance = new FasterSMA(period)
41
- bottomInstance = new FasterSMA(period)
42
- gapInstance = new FasterSMA(period)
43
- bodySizeInstance = new FasterSMA(period)
44
- shadowSizeInstance = new FasterSMA(period)
45
- closeDirectionInstance = new FasterSMA(period)
46
- highDirectionInstance = new FasterSMA(period)
47
- lowDirectionInstance = new FasterSMA(period)
48
- openDirectionInstance = new FasterSMA(period)
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
+ })
49
21
  }
50
22
 
51
- for(let x = 0; x < len; x++)
52
- {
53
- const curr = inputOhlcv[x]
54
- const prev = inputOhlcv[x-1]
55
-
56
- if(typeof prev === 'undefined') continue
57
-
58
- const {open, high, low, close} = curr
59
-
60
-
61
- //initial means
62
- let gapMean = null
63
- let bottomSizeMean = null
64
- let topSizeMean = null
65
- let bodySizeMean = null
66
- let shadowSizeMean = null
67
- let closeDirectionMean = null
68
- let highDirectionMean = null
69
- let lowDirectionMean = null
70
- let openDirectionMean = null
71
-
72
- //directions
73
- const isUp = close > open
74
- const candleBodySize = Math.abs(close - open)
75
- const shadowSize = Math.abs(curr.high - curr.low)
76
-
77
- //sizes
78
- const topSize = high - Math.max(open, close)
79
- const bottomSize = Math.min(open, close) - low
80
- const gapSize = Math.abs(prev.close - curr.open)
81
- const closeDirection = Math.abs(curr.close - prev.close)
82
- const highDirection = Math.abs(curr.high - prev.high)
83
- const lowDirection = Math.abs(curr.low - prev.low)
84
- const openDirection = Math.abs(curr.open - prev.open)
85
-
86
- if(classify)
87
- {
88
- //instances
89
- topInstance.update(Math.abs(topSize))
90
- bottomInstance.update(Math.abs(bottomSize))
91
- shadowSizeInstance.update(Math.abs(shadowSize))
92
-
93
- gapInstance.update(gapSize)
94
- bodySizeInstance.update(candleBodySize)
95
- closeDirectionInstance.update(closeDirection)
96
- highDirectionInstance.update(highDirection)
97
- lowDirectionInstance.update(lowDirection)
98
- openDirectionInstance.update(openDirection)
99
-
100
- try
101
- {
102
- topSizeMean = topInstance.getResult()
103
- bottomSizeMean = bottomInstance.getResult()
104
- gapMean = gapInstance.getResult()
105
- bodySizeMean = bodySizeInstance.getResult()
106
- shadowSizeMean = shadowSizeInstance.getResult()
107
- closeDirectionMean = closeDirectionInstance.getResult()
108
- highDirectionMean = highDirectionInstance.getResult()
109
- lowDirectionMean = lowDirectionInstance.getResult()
110
- openDirectionMean = openDirectionInstance.getResult()
111
- }
112
- catch(err)
113
- {
114
- bodySizeMean = null
115
- gapMean = null
116
- topSizeMean = null
117
- bottomSizeMean = null
118
- shadowSizeMean = null
119
- closeDirectionMean = null
120
- highDirectionMean = null
121
- lowDirectionMean = null
122
- openDirectionMean = null
23
+ // Ensure instances exist if classification is enabled
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)
123
37
  }
38
+ })
39
+ }
124
40
 
125
- //negatives and positives
126
- candle_body_size[x] = (isUp)
127
- ? classifySize(candleBodySize, bodySizeMean, 2, changeLevel)
41
+ // If we do not have a previous candle, we cannot compute differences
42
+ const prevOpen = main.verticalOhlcv.open[index-1]
43
+ const prevHigh = main.verticalOhlcv.high[index-1]
44
+ const prevLow = main.verticalOhlcv.low[index-1]
45
+ const prevClose = main.verticalOhlcv.close[index-1]
46
+
47
+ if ([prevOpen, prevHigh, prevLow, prevClose].some(v => typeof v === 'undefined')) return true;
48
+
49
+ const currOpen = main.verticalOhlcv.open[index]
50
+ const currHigh = main.verticalOhlcv.high[index]
51
+ const currLow = main.verticalOhlcv.low[index]
52
+ const currClose = main.verticalOhlcv.close[index]
53
+
54
+ // Determine candle direction and sizes
55
+ const isUp = currClose > currOpen;
56
+ const candleBodySize = Math.abs(currClose - currOpen);
57
+ const shadowSize = Math.abs(currHigh - currLow);
58
+
59
+ const topSize = currHigh - Math.max(currOpen, currClose);
60
+ const bottomSize = Math.min(currOpen, currClose) - currLow;
61
+ const gapSize = Math.abs(prevClose - currOpen);
62
+ const closeDirection = Math.abs(currClose - prevClose);
63
+ const highDirection = Math.abs(currHigh - prevHigh);
64
+ const lowDirection = Math.abs(currLow - prevLow);
65
+ const openDirection = Math.abs(currOpen - prevOpen);
66
+
67
+ let gapMean = null;
68
+ let bottomSizeMean = null;
69
+ let topSizeMean = null;
70
+ let bodySizeMean = null;
71
+ let shadowSizeMean = null;
72
+ let closeDirectionMean = null;
73
+ let highDirectionMean = null;
74
+ let lowDirectionMean = null;
75
+ let openDirectionMean = null;
76
+
77
+ if (classify) {
78
+ const {
79
+ topInstance,
80
+ bottomInstance,
81
+ gapInstance,
82
+ bodySizeInstance,
83
+ shadowSizeInstance,
84
+ closeDirectionInstance,
85
+ highDirectionInstance,
86
+ lowDirectionInstance,
87
+ openDirectionInstance
88
+ } = main.instances.candleStudies;
89
+
90
+ // Update instances with current absolute values
91
+ topInstance.update(Math.abs(topSize));
92
+ bottomInstance.update(Math.abs(bottomSize));
93
+ shadowSizeInstance.update(Math.abs(shadowSize));
94
+ gapInstance.update(gapSize);
95
+ bodySizeInstance.update(candleBodySize);
96
+ closeDirectionInstance.update(closeDirection);
97
+ highDirectionInstance.update(highDirection);
98
+ lowDirectionInstance.update(lowDirection);
99
+ openDirectionInstance.update(openDirection);
100
+
101
+ try {
102
+ topSizeMean = topInstance.getResult();
103
+ bottomSizeMean = bottomInstance.getResult();
104
+ gapMean = gapInstance.getResult();
105
+ bodySizeMean = bodySizeInstance.getResult();
106
+ shadowSizeMean = shadowSizeInstance.getResult();
107
+ closeDirectionMean = closeDirectionInstance.getResult();
108
+ highDirectionMean = highDirectionInstance.getResult();
109
+ lowDirectionMean = lowDirectionInstance.getResult();
110
+ openDirectionMean = openDirectionInstance.getResult();
111
+ } catch (err) {
112
+ // If there's not enough data, means remain null
113
+ }
114
+
115
+ main.verticalOhlcv.candle_body_size[index] = isUp
116
+ ? classifySize(candleBodySize, bodySizeMean, 2, changeLevel)
128
117
  : -Math.abs(classifySize(candleBodySize, bodySizeMean, 2, changeLevel)) || 0;
129
118
 
130
- candle_gap_size[x] = (isUp)
131
- ? classifySize(gapSize, gapMean, 2, changeLevel)
119
+ main.verticalOhlcv.candle_gap_size[index] = isUp
120
+ ? classifySize(gapSize, gapMean, 2, changeLevel)
132
121
  : -Math.abs(classifySize(gapSize, gapMean, 2, changeLevel)) || 0;
133
122
 
134
- candle_close_direction[x] = (isUp)
135
- ? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
123
+ main.verticalOhlcv.candle_close_direction[index] = isUp
124
+ ? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
136
125
  : -Math.abs(classifySize(closeDirection, closeDirectionMean, 2, changeLevel)) || 0;
137
126
 
138
- candle_high_direction[x] = (isUp)
139
- ? classifySize(highDirection, highDirectionMean, 2, changeLevel)
140
- : -Math.abs(classifySize(highDirection, highDirectionMean, 2, changeLevel)) || 0;
141
-
142
-
143
- candle_low_direction[x] = (isUp)
144
- ? classifySize(lowDirection, lowDirectionMean, 2, changeLevel)
127
+ main.verticalOhlcv.candle_high_direction[index] = isUp
128
+ ? classifySize(highDirection, highDirectionMean, 2, changeLevel)
129
+ : -Math.abs(classifySize(highDirection, highDirectionMean, 2, changeLevel)) || 0;
130
+
131
+ main.verticalOhlcv.candle_low_direction[index] = isUp
132
+ ? classifySize(lowDirection, lowDirectionMean, 2, changeLevel)
145
133
  : -Math.abs(classifySize(lowDirection, lowDirectionMean, 2, changeLevel)) || 0;
146
134
 
147
- candle_open_direction[x] = (isUp)
148
- ? classifySize(openDirection, openDirectionMean, 2, changeLevel)
135
+ main.verticalOhlcv.candle_open_direction[index] = isUp
136
+ ? classifySize(openDirection, openDirectionMean, 2, changeLevel)
149
137
  : -Math.abs(classifySize(openDirection, openDirectionMean, 2, changeLevel)) || 0;
150
138
 
151
-
152
- //positives only
153
- candle_top_size[x] = classifySize(topSize, topSizeMean, 0.5, sizeLevel)
154
- candle_bottom_size[x] = classifySize(bottomSize, bottomSizeMean, 0.5, sizeLevel)
155
- candle_shadow_size[x] = classifySize(shadowSize, shadowSizeMean, 0.5, sizeLevel)
156
- }
157
- else{
158
- //negatives and positives
159
- candle_body_size[x] = candleBodySize
160
- candle_gap_size[x] = gapSize
161
- candle_close_direction[x] = closeDirection
162
- candle_high_direction[x] = highDirection
163
- candle_low_direction[x] = lowDirection
164
-
165
- //positives only
166
- candle_top_size[x] = topSize
167
- candle_bottom_size[x] = bottomSize
168
- candle_shadow_size[x] = shadowSize
169
- }
170
-
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);
142
+ } else {
143
+ // Without classification, just store raw values
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;
171
154
  }
172
155
 
173
- return {
174
- candle_gap_size,
175
- candle_body_size,
176
- candle_top_size,
177
- candle_bottom_size,
178
- candle_shadow_size,
179
- candle_close_direction,
180
- candle_high_direction,
181
- candle_low_direction,
182
- candle_open_direction
183
- }
184
- }
156
+ return true;
157
+ };
@@ -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
+ };
@@ -0,0 +1,27 @@
1
+ export const lag = (main, index, colKeys = ['close'], lags = 1) => {
2
+
3
+ const { verticalOhlcv, len } = main;
4
+
5
+ // Ensure arrays exist for each lag key before we populate
6
+ for (let x = 0; x < colKeys.length; x++) {
7
+ for (let lag = 1; lag <= lags; lag++) {
8
+ const key = `${colKeys[x]}_lag_${lag}`;
9
+ // If the array does not exist, initialize it
10
+ if (!verticalOhlcv.hasOwnProperty(key)) {
11
+ verticalOhlcv[key] = new Array(len).fill(null);
12
+ }
13
+
14
+ // Compute the lagged index
15
+ const laggedIndex = index - lag;
16
+
17
+ // If laggedIndex is negative, we don't have a previous value
18
+ const value = (laggedIndex >= 0)
19
+ ? verticalOhlcv[colKeys[x]][laggedIndex]
20
+ : null;
21
+
22
+ verticalOhlcv[key][index] = value;
23
+ }
24
+ }
25
+
26
+ return true;
27
+ };