ohlcv-indicators 1.9.3 → 1.9.5

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.
@@ -6,13 +6,19 @@ export const rsi = (main, index, size) => {
6
6
 
7
7
  const value = main.verticalOhlcv.close[index]
8
8
 
9
- if(!main.instances.hasOwnProperty(`rsi_${size}`))
9
+ if(index === 0)
10
10
  {
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)
11
+ main.crossPairsList.push({fast: `rsi_${size}`, slow: `rsi_sma_${size}`})
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
@@ -4,13 +4,17 @@ export const volumeOscillator = (main, index, fastSize = 5, slowSize = 10) => {
4
4
 
5
5
  const value = main.verticalOhlcv.volume[index]
6
6
 
7
- if (!main.instances.hasOwnProperty(`volume_oscillator`)) {
8
- main.instances[`volume_oscillator`] = {
9
- fastEMA: new FasterEMA(fastSize),
10
- slowEMA: new FasterEMA(slowSize)
11
- };
7
+ if (index === 0) {
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
- main.autoCrossPairsList.push({ fast: `volume_oscillator`, slow: 0 });
17
+ main.crossPairsList.push({ fast: `volume_oscillator`, slow: 0 });
14
18
  }
15
19
 
16
20
  const { fastEMA, slowEMA } = main.instances[`volume_oscillator`];
@@ -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;
@@ -1,210 +1,185 @@
1
- const eq = (f, s) => f === s
2
- const gt = (f, s) => f > s
3
- const lt = (f, s) => f < s
1
+ const eq = (fast, slow) => fast === slow
2
+ const gt = (fast, slow) => fast > slow
3
+ const lt = (fast, slow) => fast < slow
4
4
 
5
5
 
6
- // Function to find crosses between two arrays: fast and slow
7
- export const findCrosses = ({fast, high, low, slow}) => {
8
- // Map states based on comparison of fast and slow arrays
6
+ //this class counts how many intervals have passed between crosses: fast crossed slow line, high crossed slow line or low crossed slow line.
7
+ //interval === 0 for no crosses yet or the lines are equal
8
+ //interval >= 1 for an upwards crosses
9
+ //interval <= -1 from downward crosses
9
10
 
10
- const dataLength = fast.length
11
+ class CrossInstance {
11
12
 
12
- if(dataLength ==! slow.length)
13
+ constructor()
13
14
  {
14
- throw Error('fast and slow do not have the same length')
15
- }
16
-
17
- const state = new Array(dataLength).fill(0.5)
15
+ this.interval = 0
16
+ this.prevState = 0.5
17
+ this.prevFast = null
18
+ this.prevSlow = null
19
+ this.prevHigh = null
20
+ this.prevLow = null
21
+ this.areHighAndLowUndefined = false
22
+ this.index = 0
23
+ this.crossIndexes = {
24
+ up: [],
25
+ down: []
26
+ }
18
27
 
19
- for(let x = 0; x < dataLength; x++)
28
+ }
29
+ update({fast, high, slow, low})
20
30
  {
21
- const prevState = state?.[x-1] || 0.5
22
- const f = fast[x]
23
- const s = slow[x]
24
- const hi = high?.[x]
25
- const lo = low?.[x]
31
+ let currState = this.prevState
26
32
 
27
- if(high || low)
33
+ if(!this.areHighAndLowUndefined)
28
34
  {
29
- if(typeof hi === 'undefined' || typeof lo === 'undefined') continue
35
+ if(typeof high === 'undefined' || typeof low === 'undefined')
36
+ {
37
+ this.areHighAndLowUndefined = true
38
+ }
30
39
  }
31
40
 
32
- const prevS = slow[x - 1]
33
- const prevHi = high?.[x-1]
34
- const prevLo = low?.[x-1]
35
-
36
- if(high || low)
37
- {
38
- if(typeof prevHi === 'undefined' || typeof prevLo === 'undefined' || typeof prevS === 'undefined') continue
41
+ if(fast === null || slow === null){
42
+ currState = 0.5
39
43
  }
40
-
41
- let value
42
-
43
- if(f === null || s === null) value = 0.5
44
-
45
- else if(eq(f, s)) {
46
- value = prevState
44
+ else if(eq(fast, slow)) {
45
+ currState = 0.5
47
46
  }
48
- else if(gt(f, s)) {
47
+ else if(gt(fast, slow)) {
49
48
 
50
- if(lt(lo, s) && gt(prevLo, prevS))
49
+ if(this.areHighAndLowUndefined)
51
50
  {
52
- value = 0
51
+ currState = 1
53
52
  }
54
53
  else
55
54
  {
56
- value = 1
55
+ if ([low, slow, this.prevLow, this.prevSlow].every(v => v !== null) && lt(low, slow) && gt(this.prevLow, this.prevSlow))
56
+ {
57
+ currState = 0
58
+ }
59
+ else
60
+ {
61
+ currState = 1
62
+ }
57
63
  }
58
64
  }
59
- else if(lt(f, s)) {
65
+ else if(lt(fast, slow)) {
60
66
 
61
- if(gt(hi, s) && lt(prevHi, prevS))
67
+ if(this.areHighAndLowUndefined)
62
68
  {
63
- value = 1
69
+ currState = 0
64
70
  }
65
71
  else
66
72
  {
67
- value = 0
73
+ if([high, slow, this.prevHigh, this.prevSlow].every(v => v !== null) && gt(high, slow) && lt(this.prevHigh, this.prevSlow))
74
+ {
75
+ currState = 1
76
+ }
77
+ else
78
+ {
79
+ currState = 0
80
+ }
68
81
  }
69
82
  }
70
-
71
- state[x] = value
72
- }
73
-
74
83
 
75
-
76
- const groups = groupConsecutiveValues(state)
77
-
78
- return flatGroupsToNumArr(groups)
79
- }
80
-
81
-
82
- 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)
84
+ if(currState === 0.5)
85
+ {
86
+ this.interval = 0
87
+ }
88
+ else if(currState === 1)
89
+ {
90
+ if(this.prevState <= 0.5)
91
+ {
92
+ this.interval = 1
93
+ }
94
+ else
93
95
  {
94
- v = 0
96
+ this.interval++
95
97
  }
96
- if(s === 1)
98
+ }
99
+ else if(currState === 0)
100
+ {
101
+ if(this.prevState >= 0.5)
97
102
  {
98
- v = v * 1
103
+ this.interval = -1
99
104
  }
100
- if(s === 0)
105
+ else
101
106
  {
102
- v = v * -1
107
+ this.interval--
103
108
  }
104
-
105
- return v
106
- }).reverse()
107
- }).flat()
108
- }
109
+ }
109
110
 
110
111
 
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
- }
112
+ if(this.interval === 1) this.crossIndexes.up.push(this.index)
113
+ if(this.interval === -1) this.crossIndexes.down.push(this.index)
136
114
 
115
+ //save prev state
116
+ this.index++
117
+ this.prevState = currState
118
+ this.prevFast = fast
119
+ this.prevSlow = slow
120
+ this.prevHigh = high
121
+ this.prevLow = low
122
+ }
137
123
 
124
+ getResult()
125
+ {
126
+ return this.interval
127
+ }
138
128
 
139
- export const crossPairs = (main, arr) => {
129
+ }
140
130
 
131
+ export const crossPairs = (main, index, arr) => {
141
132
 
133
+ const {len} = main
142
134
 
143
- const {verticalOhlcv, len} = main
144
- const slowNumArrCache = {};
145
- const x = {}
146
- const c = {}
135
+ for (const { fast, slow } of arr)
136
+ {
147
137
 
148
- for (const { fast, slow, count } of arr) {
149
- // Validate parameters
150
- if (!fast || !slow) continue;
151
-
152
- // Prepare slowNumArr if 'slow' is a number
153
- if (typeof slow === 'number' && !slowNumArrCache[slow]) {
138
+
154
139
 
155
- slowNumArrCache[slow] = Array(len).fill(slow)
156
- }
157
-
158
- const crossName = `${fast}_x_${slow}`
159
- const countName = `${fast}_c_${slow}`
140
+ if (!fast || !slow) continue
160
141
 
161
- const splitCount = arr => (typeof count !== 'undefined') ? (arr.slice(-count)).filter(o => o === 1 || o === -1).length : 0
142
+ let crossName = `${fast}_x_${slow}`
162
143
 
163
- //cross already exists
164
- if(verticalOhlcv[crossName])
144
+ if(index === 0)
165
145
  {
166
- const cross = verticalOhlcv[crossName]
167
- x[crossName] = cross
168
-
169
- if(typeof count !== 'undefined')
146
+ if(typeof slow === 'number')
170
147
  {
171
- c[countName] = splitCount(cross)
148
+ main.verticalOhlcv[slow] = new Array(len).fill(slow)
172
149
  }
173
150
 
174
- }
175
- //cross created from 2 columns
176
- else if ((verticalOhlcv[fast] || fast === 'price') && verticalOhlcv[slow]) {
151
+ if(fast !== 'price' && !main.verticalOhlcv.hasOwnProperty(fast)) throw Error(`fast "${fast} not found in crossPairs"`)
152
+ if(!main.verticalOhlcv.hasOwnProperty(slow)) throw Error(`slow "${slow} not found in crossPairs"`)
177
153
 
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]})
186
154
 
187
- x[crossName] = cross
155
+ main.instances[crossName] = new CrossInstance()
156
+ main.verticalOhlcv[crossName] = new Array(len).fill(null)
157
+ }
188
158
 
189
- if(typeof count !== 'undefined')
190
- {
191
- c[countName] = splitCount(cross)
192
- }
159
+ let fastValue
160
+ let closeValue
161
+ let highValue
162
+ let lowValue
163
+ let slowValue
193
164
 
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
165
+ if(fast === 'price')
166
+ {
167
+ closeValue = main.verticalOhlcv.close[index]
168
+ highValue = main.verticalOhlcv.high[index]
169
+ lowValue = main.verticalOhlcv.low[index]
170
+ slowValue = main.verticalOhlcv[slow][index]
199
171
 
200
- if(typeof count !== 'undefined'){
201
- c[countName] = splitCount(cross)
202
- }
172
+ main.instances[crossName].update({fast: closeValue, slow: slowValue, high: highValue, low: lowValue})
203
173
 
204
- } else {
205
- throw Error(`Missing ohlcv properties for ${fast} or ${slow}`);
174
+ } else
175
+ {
176
+ fastValue = main.verticalOhlcv[fast][index]
177
+ slowValue = main.verticalOhlcv[slow][index]
178
+
179
+ main.instances[crossName].update({fast: fastValue, slow: slowValue})
206
180
  }
181
+
182
+ main.verticalOhlcv[crossName][index] = main.instances[crossName].getResult()
207
183
  }
208
184
 
209
- return {x, c}
210
185
  }
@@ -1,27 +1,34 @@
1
- export const lag = (main, index, colKeys = ['close'], lags = 1) => {
1
+ export const lag = (main, index, params) => {
2
2
 
3
- const { verticalOhlcv, len } = main;
3
+ for(let p = 0; p < params.length; p++)
4
+ {
5
+ const [colKeys, lags] = params[p]
4
6
 
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
- }
7
+ // Ensure arrays exist for each lag key before we populate
8
+ for (let x = 0; x < colKeys.length; x++) {
9
+ for (let lag = 1; lag <= lags; lag++) {
10
+ const key = `${colKeys[x]}_lag_${lag}`;
11
+ // If the array does not exist, initialize it
12
+ if (index === 0) {
13
+ main.verticalOhlcv[key] = new Array(main.len).fill(null)
14
+ }
13
15
 
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;
16
+ // Compute the lagged index
17
+ const laggedIndex = index - lag;
18
+
19
+ // If laggedIndex is negative, we don't have a previous value
20
+ const value = (laggedIndex >= 0)
21
+ ? main.verticalOhlcv[colKeys[x]][laggedIndex]
22
+ : null;
23
+
24
+ main.verticalOhlcv[key][index] = value;
25
+ }
23
26
  }
24
27
  }
25
28
 
29
+
30
+
31
+
32
+
26
33
  return true;
27
34
  };