ohlcv-indicators 3.3.5 → 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,10 +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'
3
+ import {
4
+ isAlreadyComputed,
5
+ validateArray,
6
+ validateObject,
7
+ validateArrayOptions,
8
+ validateBoolean,
9
+ validateNumber,
10
+ validateArrayOfRanges
11
+ } from './src/utilities/validators.js'
4
12
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
5
13
  import { pushToMain } from './src/core-functions/pushToMain.js'
6
14
  import { assignTypes } from './src/utilities/assignTypes.js'
7
15
  import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
16
+ import { buildArray } from './src/utilities/assignTypes.js'
8
17
 
9
18
  /**
10
19
  * Class OHLCV_INDICATORS
@@ -40,6 +49,7 @@ export default class OHLCV_INDICATORS {
40
49
  this.crossPairsList = []
41
50
  this.verticalOhlcv = {}
42
51
  this.verticalOhlcvKeyNames = []
52
+ this.verticalOhlcvTempCols = new Set()
43
53
 
44
54
  this.utilities = {
45
55
  correlation
@@ -52,7 +62,7 @@ export default class OHLCV_INDICATORS {
52
62
 
53
63
  if(inputParams !== null)
54
64
  {
55
- validateArray(inputParams, 'inputParams', 'constructor')
65
+ validateInputParams(inputParams, this.len)
56
66
  this.inputParams = inputParams
57
67
  this.compute()
58
68
  }
@@ -75,21 +85,28 @@ export default class OHLCV_INDICATORS {
75
85
  invalidValueIndex,
76
86
  len,
77
87
  verticalOhlcv,
78
- priceBased
79
- } = this;
80
- const result = {};
81
- const shouldSlice = invalidValueIndex >= 0 && skipNull;
82
- 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
83
95
 
84
96
  for (const [key, arr] of Object.entries(verticalOhlcv)) {
85
- // If slicing is needed, create a sliced copy, otherwise re-use the array.
86
- let newArr = shouldSlice ? arr.slice(-sliceLength) : arr;
87
-
88
- // If this key is price based and precision is enabled, map over the array.
89
- if (precision && priceBased.has(key)) {
90
- newArr = newArr.map(v => (v == null ? null : v / precisionMultiplier));
91
- }
92
- 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
+
93
110
  }
94
111
 
95
112
  return result;
@@ -195,7 +212,7 @@ export default class OHLCV_INDICATORS {
195
212
 
196
213
  const { lag = 0} = options;
197
214
 
198
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
215
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
199
216
 
200
217
  this.inputParams.push({key: methodName, params: [size, {lag}]})
201
218
 
@@ -213,7 +230,7 @@ export default class OHLCV_INDICATORS {
213
230
 
214
231
  const {target = 'close', lag = 0} = options
215
232
 
216
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
233
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
217
234
 
218
235
  this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
219
236
 
@@ -230,7 +247,7 @@ export default class OHLCV_INDICATORS {
230
247
 
231
248
  const {target = 'close', lag = 0} = options
232
249
 
233
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
250
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
234
251
 
235
252
  this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
236
253
 
@@ -251,7 +268,7 @@ export default class OHLCV_INDICATORS {
251
268
 
252
269
  const {target = 'close', lag = 0} = options
253
270
 
254
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
271
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
255
272
 
256
273
  this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag}]})
257
274
 
@@ -271,9 +288,9 @@ export default class OHLCV_INDICATORS {
271
288
  const {target = 'close', height = false, range = [], lag = 0} = options
272
289
 
273
290
 
274
- validateArray(range, 'range', methodName)
275
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
276
- 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)
277
294
 
278
295
  this.inputParams.push({key: methodName, params: [size, stdDev, {target, height, range, lag}]});
279
296
 
@@ -291,7 +308,7 @@ export default class OHLCV_INDICATORS {
291
308
 
292
309
  const {target = 'close', lag = 0} = options
293
310
 
294
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
311
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
295
312
 
296
313
  this.inputParams.push({key: methodName, params: [size, {target, lag}]})
297
314
 
@@ -310,9 +327,9 @@ export default class OHLCV_INDICATORS {
310
327
  validateObject(options, 'options', methodName)
311
328
  const { height = false, range = [], lag = 0} = options;
312
329
 
313
- validateArray(range, 'range', methodName)
314
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
315
- 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)
316
333
 
317
334
  this.inputParams.push({ key: methodName, params: [size, offset, { height, range, lag }] });
318
335
 
@@ -336,7 +353,7 @@ export default class OHLCV_INDICATORS {
336
353
 
337
354
  const {lag = 0} = options
338
355
 
339
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
356
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
340
357
 
341
358
  this.inputParams.push({key: methodName, params: [fastSize, slowSize, {lag}]})
342
359
  return this
@@ -352,7 +369,7 @@ export default class OHLCV_INDICATORS {
352
369
 
353
370
  const {lag = 0} = options
354
371
 
355
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
372
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
356
373
 
357
374
  this.inputParams.push({key: methodName, params: [{lag}]})
358
375
  return this
@@ -369,9 +386,9 @@ export default class OHLCV_INDICATORS {
369
386
 
370
387
  const {group = false, range = [0, 1], lag = 0, type = 'minmax'} = options
371
388
 
372
- validateBoolean(group, 'group', methodName)
373
- validateArray(range, 'range', methodName)
374
- 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)
375
392
 
376
393
  this.inputParams.push({key: methodName, params: [size, colKeys, type, group, range, lag]})
377
394
  return this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.3.5",
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",
@@ -33,7 +33,7 @@ const indicatorFunctions = {
33
33
  export const mainLoop = (input, main) => {
34
34
  const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes } = main;
35
35
 
36
- validateInputParams({inputParams, len})
36
+ validateInputParams(inputParams, len)
37
37
 
38
38
  for(const key of Object.keys(inputTypes))
39
39
  {
@@ -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}`;
@@ -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 = [
@@ -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
 
@@ -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,11 +7,14 @@ 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
  {
@@ -1,4 +1,9 @@
1
- export const validateInputParams = ({inputParams, len}) => {
1
+ export const validateInputParams = (inputParams, len) => {
2
+
3
+ if(!Array.isArray(inputParams))
4
+ {
5
+ throw new Error('Property "inputParams" must be an "Array" or "Set".')
6
+ }
2
7
 
3
8
  for (const { params } of inputParams) {
4
9
  for (const v of params) {
@@ -87,3 +92,25 @@ export const validateNumber = (value, options, paramName, callerName) => {
87
92
  return true
88
93
  }
89
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
+ }
@@ -4,27 +4,26 @@ export const verticalToHorizontal = (
4
4
  startIndex = 0
5
5
  ) => {
6
6
 
7
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames} = main
7
+ const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames, verticalOhlcvTempCols} = main
8
8
 
9
9
  if (verticalOhlcvKeyNames.length === 0) return []
10
10
 
11
11
  const skipNullIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0
12
12
  const maxStartIndex = Math.max(skipNullIndex, startIndex)
13
13
  const diffLen = len - maxStartIndex
14
- const result = new Array(diffLen)
14
+ const result = Array.from({ length: diffLen }, () => ({}))
15
15
 
16
- for (let i = maxStartIndex; i < len; i++) {
17
- const row = {};
18
- for (let j = 0; j < verticalOhlcvKeyNames.length; j++) {
19
- const key = verticalOhlcvKeyNames[j]
20
- const value = verticalOhlcv[key][i]
16
+ for(const [key, arr] of Object.entries(verticalOhlcv)){
17
+ if(verticalOhlcvTempCols.has(key)) continue
18
+ const shouldApplyPrecision = priceBased.has(key) && precision
21
19
 
22
- row[key] = (precision && priceBased.has(key)) ? value / precisionMultiplier : value
23
-
20
+ for (let i = maxStartIndex; i < len; i++)
21
+ {
22
+ result[i - maxStartIndex][key] = (shouldApplyPrecision)
23
+ ? arr[i] / precisionMultiplier
24
+ : arr[i]
24
25
  }
25
-
26
- result[i - maxStartIndex] = row
27
26
  }
28
-
27
+
29
28
  return result
30
- };
29
+ }