ohlcv-indicators 3.3.4 → 3.3.6

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/index.js CHANGED
@@ -1,12 +1,19 @@
1
1
  import { mainLoop } from './src/core-functions/mainLoop.js'
2
2
  import { correlation } from './src/studies/correlation.js'
3
- import { isAlreadyComputed, validateArray, validateObject, validateArrayOptions, validateBoolean, validateNumber } from './src/utilities/validators.js'
4
- import { divideByMultiplier } from './src/utilities/numberUtilities.js'
3
+ import {
4
+ isAlreadyComputed,
5
+ validateArray,
6
+ validateObject,
7
+ validateArrayOptions,
8
+ validateBoolean,
9
+ validateNumber,
10
+ validateArrayOfRanges
11
+ } from './src/utilities/validators.js'
5
12
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
6
13
  import { pushToMain } from './src/core-functions/pushToMain.js'
7
14
  import { assignTypes } from './src/utilities/assignTypes.js'
8
15
  import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
9
- import { selectDateFormatter, dateFormaters } from './src/utilities/dateUtilities.js'
16
+ import { buildArray } from './src/utilities/assignTypes.js'
10
17
 
11
18
  /**
12
19
  * Class OHLCV_INDICATORS
@@ -27,40 +34,35 @@ export default class OHLCV_INDICATORS {
27
34
 
28
35
  this.firstRow = input[0]
29
36
 
30
- this.inputCols = Object.keys(this.firstRow)
31
-
32
37
  const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
33
-
38
+
34
39
  this.inputTypes = inputTypes
35
40
  this.arrayTypes = arrayTypes
36
-
37
-
38
41
  if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
39
42
 
40
43
  this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
41
44
  this.isComputed = false
42
45
  this.input = input
43
- this.priceBased = ['open', 'high', 'low', 'close']
46
+ this.priceBased = new Set(['open', 'high', 'low', 'close'])
44
47
  this.len = input.length
45
48
  this.instances = {}
46
49
  this.crossPairsList = []
47
50
  this.verticalOhlcv = {}
51
+ this.verticalOhlcvKeyNames = []
52
+ this.verticalOhlcvTempCols = new Set()
48
53
 
49
- this.studies = {}
50
54
  this.utilities = {
51
55
  correlation
52
56
  }
53
57
 
54
58
  this.invalidValueIndex = -1
55
-
56
59
  this.precision = precision
57
60
  this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
58
61
  this.ScaledGroups = {}
59
62
 
60
-
61
63
  if(inputParams !== null)
62
64
  {
63
- validateArray(inputParams, 'inputParams', 'constructor')
65
+ validateInputParams(inputParams, this.len)
64
66
  this.inputParams = inputParams
65
67
  this.compute()
66
68
  }
@@ -70,7 +72,6 @@ export default class OHLCV_INDICATORS {
70
72
 
71
73
  this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
72
74
 
73
-
74
75
  return this
75
76
  }
76
77
 
@@ -84,21 +85,28 @@ export default class OHLCV_INDICATORS {
84
85
  invalidValueIndex,
85
86
  len,
86
87
  verticalOhlcv,
87
- priceBased
88
- } = this;
89
- const result = {};
90
- const shouldSlice = invalidValueIndex >= 0 && skipNull;
91
- const sliceLength = len - (invalidValueIndex + 1);
88
+ priceBased,
89
+ arrayTypes,
90
+ verticalOhlcvTempCols
91
+ } = this
92
+ const result = {}
93
+ const startIndex = skipNull ? invalidValueIndex + 1 : 0
94
+ const newLen = len - startIndex
92
95
 
93
96
  for (const [key, arr] of Object.entries(verticalOhlcv)) {
94
- // If slicing is needed, create a sliced copy, otherwise re-use the array.
95
- let newArr = shouldSlice ? arr.slice(-sliceLength) : arr;
96
-
97
- // If this key is price based and precision is enabled, map over the array.
98
- if (precision && priceBased.includes(key)) {
99
- newArr = newArr.map(v => (v == null ? null : v / precisionMultiplier));
100
- }
101
- result[key] = newArr;
97
+
98
+ if(verticalOhlcvTempCols.has(key)) continue
99
+
100
+ const shouldApplyPrecision = priceBased.has(key) && precision
101
+ result[key] = buildArray(arrayTypes[key], newLen)
102
+
103
+ for (let x = startIndex; x < len; x++)
104
+ {
105
+ result[key][x] = (shouldApplyPrecision)
106
+ ? arr[x] / precisionMultiplier
107
+ : arr[x]
108
+ }
109
+
102
110
  }
103
111
 
104
112
  return result;
@@ -110,9 +118,7 @@ export default class OHLCV_INDICATORS {
110
118
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
111
119
  this.compute()
112
120
 
113
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex} = this
114
-
115
- return verticalToHorizontal(verticalOhlcv, skipNull, precision, precisionMultiplier, priceBased, invalidValueIndex)
121
+ return verticalToHorizontal(skipNull, this, 0)
116
122
  }
117
123
 
118
124
 
@@ -120,16 +126,7 @@ export default class OHLCV_INDICATORS {
120
126
 
121
127
  this.compute()
122
128
 
123
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, len} = this
124
- const keyNames = Object.keys(verticalOhlcv)
125
- const row = {}
126
-
127
- for(const key of keyNames)
128
- {
129
- row[key] = verticalOhlcv[key][len - 1]
130
- }
131
-
132
- return (precision) ? divideByMultiplier({row, precisionMultiplier, priceBased}) : row
129
+ return verticalToHorizontal(false, this, this.len - 1)[0]
133
130
  }
134
131
 
135
132
  compute() {
@@ -150,6 +147,11 @@ export default class OHLCV_INDICATORS {
150
147
  if (this.len > 0) {
151
148
  mainLoop(this.input, this);
152
149
  this.isComputed = true;
150
+
151
+ //flushing after mainLoop
152
+ this.input = []
153
+ this.instances = {}
154
+ this.firstRow = []
153
155
  }
154
156
 
155
157
  return this;
@@ -184,13 +186,11 @@ export default class OHLCV_INDICATORS {
184
186
 
185
187
  this.inputParams.push({key: methodName, params: [colKeys, lookback]})
186
188
 
187
- for(let x = 0; x < colKeys.length; x++)
189
+ for(const key of colKeys)
188
190
  {
189
- const key = colKeys[x]
190
-
191
- if(this.priceBased.find(v => key === v))
191
+ if(this.priceBased.has(key))
192
192
  {
193
- this.priceBased.push(key)
193
+ this.priceBased.add(key);
194
194
  }
195
195
  }
196
196
 
@@ -201,7 +201,7 @@ export default class OHLCV_INDICATORS {
201
201
 
202
202
  const methodName = 'relativeVolume'
203
203
 
204
- if(!this.inputCols.includes('volume')) {
204
+ if(!this.inputTypes.hasOwnProperty('volume')) {
205
205
  throw new Error('If "relativeVolume" is called the input ohlcv must contain valid volume properties.')
206
206
  }
207
207
 
@@ -212,7 +212,7 @@ export default class OHLCV_INDICATORS {
212
212
 
213
213
  const { lag = 0} = options;
214
214
 
215
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
215
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
216
216
 
217
217
  this.inputParams.push({key: methodName, params: [size, {lag}]})
218
218
 
@@ -230,7 +230,7 @@ export default class OHLCV_INDICATORS {
230
230
 
231
231
  const {target = 'close', lag = 0} = options
232
232
 
233
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
233
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
234
234
 
235
235
  this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
236
236
 
@@ -247,7 +247,7 @@ export default class OHLCV_INDICATORS {
247
247
 
248
248
  const {target = 'close', lag = 0} = options
249
249
 
250
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
250
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
251
251
 
252
252
  this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
253
253
 
@@ -268,7 +268,7 @@ export default class OHLCV_INDICATORS {
268
268
 
269
269
  const {target = 'close', lag = 0} = options
270
270
 
271
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
271
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
272
272
 
273
273
  this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag}]})
274
274
 
@@ -288,9 +288,9 @@ export default class OHLCV_INDICATORS {
288
288
  const {target = 'close', height = false, range = [], lag = 0} = options
289
289
 
290
290
 
291
- validateArray(range, 'range', methodName)
292
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
293
- validateBoolean(height, 'height', methodName)
291
+ validateArray(range, 'options.range', methodName)
292
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
293
+ validateBoolean(height, 'options.height', methodName)
294
294
 
295
295
  this.inputParams.push({key: methodName, params: [size, stdDev, {target, height, range, lag}]});
296
296
 
@@ -308,7 +308,7 @@ export default class OHLCV_INDICATORS {
308
308
 
309
309
  const {target = 'close', lag = 0} = options
310
310
 
311
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
311
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
312
312
 
313
313
  this.inputParams.push({key: methodName, params: [size, {target, lag}]})
314
314
 
@@ -327,9 +327,9 @@ export default class OHLCV_INDICATORS {
327
327
  validateObject(options, 'options', methodName)
328
328
  const { height = false, range = [], lag = 0} = options;
329
329
 
330
- validateArray(range, 'range', methodName)
331
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
332
- validateBoolean(height, 'height', methodName)
330
+ validateArray(range, 'options.range', methodName)
331
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
332
+ validateBoolean(height, 'options.height', methodName)
333
333
 
334
334
  this.inputParams.push({ key: methodName, params: [size, offset, { height, range, lag }] });
335
335
 
@@ -341,7 +341,7 @@ export default class OHLCV_INDICATORS {
341
341
  {
342
342
  const methodName = 'volumeOscillator'
343
343
 
344
- if(!this.inputCols.includes('volume')) {
344
+ if(!this.inputTypes.hasOwnProperty('volume')) {
345
345
  throw new Error('If "volumeOscillator" is called the input ohlcv must contain valid volume properties.')
346
346
  }
347
347
 
@@ -353,7 +353,7 @@ export default class OHLCV_INDICATORS {
353
353
 
354
354
  const {lag = 0} = options
355
355
 
356
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
356
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
357
357
 
358
358
  this.inputParams.push({key: methodName, params: [fastSize, slowSize, {lag}]})
359
359
  return this
@@ -369,7 +369,7 @@ export default class OHLCV_INDICATORS {
369
369
 
370
370
  const {lag = 0} = options
371
371
 
372
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
372
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
373
373
 
374
374
  this.inputParams.push({key: methodName, params: [{lag}]})
375
375
  return this
@@ -386,9 +386,9 @@ export default class OHLCV_INDICATORS {
386
386
 
387
387
  const {group = false, range = [0, 1], lag = 0, type = 'minmax'} = options
388
388
 
389
- validateBoolean(group, 'group', methodName)
390
- validateArray(range, 'range', methodName)
391
- validateArrayOptions(['minmax', 'zscore'], type, 'type', methodName)
389
+ validateBoolean(group, 'options.group', methodName)
390
+ validateArrayOfRanges(range, 'options.range', methodName)
391
+ validateArrayOptions(['minmax', 'zscore'], type, 'options.type', methodName)
392
392
 
393
393
  this.inputParams.push({key: methodName, params: [size, colKeys, type, group, range, lag]})
394
394
  return this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
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",
@@ -29,41 +29,22 @@ const indicatorFunctions = {
29
29
  Scaler
30
30
  };
31
31
 
32
- // Processes the inputParams into a list of indicator calls
33
- const processIndicatorCalls = inputParams =>
34
- Array.isArray(inputParams)
35
- ? inputParams.map(({ key, params }) => ({
36
- key,
37
- fn: indicatorFunctions[key],
38
- args: params
39
- }))
40
- : [];
41
32
 
42
33
  export const mainLoop = (input, main) => {
43
- const { len, inputParams, priceBased, precisionMultiplier, arrayTypes } = main;
34
+ const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes } = main;
44
35
 
45
- validateInputParams(main)
36
+ validateInputParams(inputParams, len)
46
37
 
47
- for(const key of Object.keys(main.inputTypes))
38
+ for(const key of Object.keys(inputTypes))
48
39
  {
49
- main.verticalOhlcv[key] = buildArray(arrayTypes[key], len)
40
+ verticalOhlcv[key] = buildArray(arrayTypes[key], len)
50
41
  }
51
42
 
52
- // Prepare the list of indicator function calls
53
- const indicatorCalls = processIndicatorCalls(inputParams);
54
-
55
43
  // Process each row in the input
56
- for (let x = 0; x < len; x++) {
57
- const curr = input[x]
58
-
59
- processThisRow({x, main, curr, indicatorCalls, priceBased, precisionMultiplier})
60
- }
61
- }
62
-
63
- const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMultiplier}) => {
64
-
65
-
66
- for(const [key, formaterKey] of Object.entries(main.inputTypes))
44
+ for (let index = 0; index < len; index++) {
45
+ const curr = input[index]
46
+
47
+ for(const [key, formaterKey] of Object.entries(inputTypes))
67
48
  {
68
49
  const value = curr[key]
69
50
 
@@ -71,30 +52,34 @@ const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMul
71
52
  {
72
53
  let formatedValue = numberFormater[formaterKey](value)
73
54
 
74
- if(precisionMultiplier > 1 && priceBased.includes(key))
55
+ if(precisionMultiplier > 1 && priceBased.has(key))
75
56
  {
76
57
  formatedValue = formatedValue * precisionMultiplier
77
58
  }
78
59
 
79
- main.pushToMain({index: x, key, value: formatedValue})
60
+ main.pushToMain({index, key, value: formatedValue})
80
61
  }
81
62
  else if(dateFormaters.hasOwnProperty(formaterKey))
82
63
  {
83
- main.pushToMain({index: x, key, value: dateFormaters[formaterKey](value)})
64
+ main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
84
65
  }
85
66
  else
86
67
  {
87
- main.pushToMain({index: x, key, value})
68
+ main.pushToMain({index, key, value})
88
69
  }
89
70
  }
71
+
72
+ // Run all indicator functions except for the ones processed later
73
+ for (const { key, params } of inputParams) {
74
+ if (key === 'lag' || key === 'crossPairs') continue;
75
+ // resolve fn on-demand, no per-item object allocation here
76
+ indicatorFunctions[key](main, index, ...params)
77
+ }
78
+
79
+ // Process these indicators separately (ensuring their execution order)
80
+ lag(main, index)
81
+ crossPairs(main, index)
82
+ }
90
83
 
91
- // Run all indicator functions except for the ones processed later
92
- for (const { key, fn, args } of indicatorCalls) {
93
- if (['crossPairs', 'lag'].includes(key)) continue;
94
- fn(main, x, ...args)
95
- }
96
-
97
- // Process these indicators separately (ensuring their execution order)
98
- lag(main, x)
99
- crossPairs(main, x)
100
- }
84
+ verticalOhlcvKeyNames.push(...Object.keys(verticalOhlcv))
85
+ }
@@ -17,7 +17,12 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
17
17
  throw new Error(`bollingerBands could not find target "${target}"`);
18
18
  }
19
19
 
20
- const numberOfIndicators = inputParams.filter(o => o.key === 'bollingerBands').length;
20
+ let numberOfIndicators = 0
21
+
22
+ for (const o of inputParams) {
23
+ if (o.key === 'bollingerBands') numberOfIndicators++;
24
+ }
25
+
21
26
  prefix = numberOfIndicators > 1
22
27
  ? `bollinger_bands_${indicatorKey}`
23
28
  : `bollinger_bands${suffix}`;
@@ -45,7 +50,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
45
50
 
46
51
  // Set up additional arrays for each range property.
47
52
  for (const rangeKey of range) {
48
- if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
53
+ if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
49
54
  throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
50
55
  }
51
56
  keyNames.push(`${prefix}_range_${rangeKey}`)
@@ -60,7 +65,9 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
60
65
  main.lag(keyNames, lag)
61
66
  }
62
67
 
63
- priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`);
68
+ [`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`].forEach(v => {
69
+ priceBased.add(v)
70
+ })
64
71
 
65
72
  for(const key of keyNames)
66
73
  {
@@ -6,7 +6,12 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
6
6
  // Initialization: create output arrays and indicator instance on the first call.
7
7
  if (index === 0) {
8
8
  const { inputParams, priceBased, arrayTypes} = main;
9
- const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length;
9
+ let numberOfIndicators = 0
10
+
11
+ for (const o of inputParams) {
12
+ if (o.key === 'donchianChannels') numberOfIndicators++;
13
+ }
14
+
10
15
  const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel';
11
16
 
12
17
  const keyNames = [
@@ -27,7 +32,7 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
27
32
 
28
33
  // Set up additional arrays for each range property.
29
34
  for (const rangeKey of range) {
30
- if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
35
+ if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
31
36
  throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
32
37
  }
33
38
  keyNames.push(`${prefix}_range_${rangeKey}`)
@@ -42,7 +47,10 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
42
47
  main.lag(keyNames, lag)
43
48
  }
44
49
 
45
- priceBased.push(`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`);
50
+
51
+ [`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`].forEach(v => {
52
+ priceBased.add(v)
53
+ })
46
54
 
47
55
  instances.donchian_channel.settings[indicatorKey] = {
48
56
  maxDeque: [], // will hold indices for highs in descending order
@@ -17,7 +17,12 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
17
17
  throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`);
18
18
  }
19
19
 
20
- const numberOfIndicators = inputParams.filter(o => o.key === 'macd').length;
20
+ let numberOfIndicators = 0
21
+
22
+ for (const o of inputParams) {
23
+ if (o.key === 'macd') numberOfIndicators++;
24
+ }
25
+
21
26
  // Choose a display prefix based on the number of indicators.
22
27
  const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd';
23
28
 
@@ -47,7 +52,9 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
47
52
  [histogramKey]: new Float64Array(len).fill(NaN),
48
53
  });
49
54
 
50
- priceBased.push(diffKey, deaKey, histogramKey)
55
+ [diffKey, deaKey, histogramKey].forEach(v => {
56
+ priceBased.add(v)
57
+ })
51
58
 
52
59
  const keyNames = [diffKey, deaKey, histogramKey]
53
60
 
@@ -28,7 +28,8 @@ export const movingAverages = (main, index, indicatorName, size, { target, lag }
28
28
  };
29
29
 
30
30
  verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
31
- priceBased.push(keyName);
31
+
32
+ priceBased.add(keyName);
32
33
 
33
34
  if(lag > 0)
34
35
  {
@@ -132,19 +132,25 @@ class CrossInstance {
132
132
 
133
133
  export const crossPairs = (main, index) => {
134
134
 
135
- const {verticalOhlcv, instances, len, arrayTypes} = main
135
+ const {verticalOhlcv, instances, len, arrayTypes, verticalOhlcvTempCols} = main
136
136
 
137
137
  if(index === 0)
138
138
  {
139
139
  const {inputParams} = main
140
- const findParams = inputParams.filter(o => o.key === 'crossPairs')
141
140
 
142
- if(typeof findParams !== 'object') return
143
-
144
- instances.crossPairs = {crossPairsList: findParams.flatMap(o => o.params.flat())}
141
+ instances.crossPairsList = inputParams.reduce((acc, { key, params }) => {
142
+ if (key === 'crossPairs') {
143
+ for (const group of params) {
144
+ for (const pair of group) {
145
+ acc.push(pair)
146
+ }
147
+ }
148
+ }
149
+ return acc;
150
+ }, [])
145
151
  }
146
152
 
147
- const {crossPairsList} = instances.crossPairs
153
+ const {crossPairsList} = instances
148
154
 
149
155
  for (const { fast, slow } of crossPairsList)
150
156
  {
@@ -156,7 +162,8 @@ export const crossPairs = (main, index) => {
156
162
  {
157
163
  if(typeof slow === 'number')
158
164
  {
159
- verticalOhlcv[slow] = new Int32Array(len).fill(slow)
165
+ verticalOhlcvTempCols.add(slow.toString())
166
+ verticalOhlcv[slow.toString()] = new Int32Array(len).fill(slow)
160
167
  }
161
168
 
162
169
  if(fast !== 'price' && !verticalOhlcv.hasOwnProperty(fast)) throw Error(`fast "${fast} not found in crossPairs"`)
@@ -7,20 +7,23 @@ export const lag = (main, index) => {
7
7
  if(index === 0)
8
8
  {
9
9
  const {inputParams} = main
10
- const findParams = inputParams.filter(o => o.key === 'lag')
11
10
 
12
- if(typeof findParams !== 'object') return
11
+ const params = []
13
12
 
14
- const params = findParams.map(o => o.params)
13
+ for (const o of inputParams) {
14
+ if (o.key === 'lag') {
15
+ params.push(o.params)
16
+ }
17
+ }
15
18
 
16
19
  for (const [colKeys, lookback] of params)
17
20
  {
18
21
  for (const colKey of colKeys)
19
22
  {
20
- if(priceBased.includes(colKey))
23
+ if(priceBased.has(colKey))
21
24
  {
22
25
  for (let step = 1; step <= lookback; step++) {
23
- priceBased.push(`${colKey}_lag_${step}`)
26
+ priceBased.add(`${colKey}_lag_${step}`)
24
27
  }
25
28
  }
26
29
  }
@@ -37,7 +37,7 @@ export const Scaler = (
37
37
  if (!verticalOhlcv.hasOwnProperty(target)) {
38
38
  throw new Error(`Target property "${target}" not found in verticalOhlcv`);
39
39
  }
40
- if (!priceBased.includes(target)) {
40
+ if (!priceBased.has(target)) {
41
41
  throw new Error(`Column "${target}" is not priceBased and cannot be scaled.`);
42
42
  }
43
43
 
@@ -1,7 +1,7 @@
1
1
  import { classifyNum } from "./numberUtilities.js";
2
2
  import { selectDateFormatter } from "./dateUtilities.js";
3
3
 
4
- const numberKeys = ['open', 'high', 'low', 'close']
4
+ const numberKeys = new Set(['open', 'high', 'low', 'close'])
5
5
 
6
6
  export const buildArray = (arrayType, len) => {
7
7
  if(arrayType === 'Array') return new Array(len).fill(null)
@@ -24,7 +24,7 @@ export const assignTypes = firstRow => {
24
24
  inputTypes[colName] = classifyNum(cellValue, true)
25
25
  arrayTypes[colName] = 'Int32Array'
26
26
  }
27
- else if(numberKeys.includes(colName))
27
+ else if(numberKeys.has(colName))
28
28
  {
29
29
  inputTypes[colName] = classifyNum(cellValue, true)
30
30
  arrayTypes[colName] = 'Float64Array'
@@ -15,7 +15,7 @@ export const divideByMultiplier = ({row, precisionMultiplier, priceBased}) => {
15
15
 
16
16
  let newValue = value
17
17
 
18
- if(priceBased.includes(key))
18
+ if(priceBased.has(key))
19
19
  {
20
20
  newValue = newValue / precisionMultiplier
21
21
  }
@@ -4,7 +4,7 @@ export const calcPrecisionMultiplier = (main, firstRow) => {
4
4
  if(precision === false) return 1
5
5
 
6
6
  let output = 1
7
- const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.includes(k)).map(([_, v]) => v)
7
+ const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.has(k)).map(([_, v]) => v)
8
8
 
9
9
 
10
10
  for(const value of priceBasedValues)
@@ -1,6 +1,9 @@
1
- export const validateInputParams = main => {
1
+ export const validateInputParams = (inputParams, len) => {
2
2
 
3
- const {inputParams, len} = main
3
+ if(!Array.isArray(inputParams))
4
+ {
5
+ throw new Error('Property "inputParams" must be an "Array" or "Set".')
6
+ }
4
7
 
5
8
  for (const { params } of inputParams) {
6
9
  for (const v of params) {
@@ -89,3 +92,25 @@ export const validateNumber = (value, options, paramName, callerName) => {
89
92
  return true
90
93
  }
91
94
 
95
+ export const validateArrayOfRanges = (range, paramName, callerName) => {
96
+
97
+ validateArray(range, paramName, callerName)
98
+
99
+ if (range.length !== 2) {
100
+ throw new Error(
101
+ `Invalid "${paramName}" array length: expected 2 items, but got ${range.length} in "${callerName}.${paramName}".`
102
+ );
103
+ }
104
+
105
+ const [min, max] = range
106
+
107
+ validateNumber(min, {min: -100, max, allowDecimals: false}, 'min', callerName)
108
+ validateNumber(max, {min: min, max: 100, allowDecimals: false}, 'max', callerName)
109
+
110
+ if(min === max)
111
+ {
112
+ throw new Error(`Invalid "min" can not be equal to "max" property in "${callerName}.${paramName}".`)
113
+ }
114
+
115
+ return true
116
+ }