ohlcv-indicators 3.5.78 → 3.5.80

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.
Files changed (43) hide show
  1. package/AGENTS.md +113 -0
  2. package/README.md +456 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
  5. package/index.js +112 -84
  6. package/package.json +2 -5
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +35 -30
  9. package/src/core-functions/pushToMain.js +8 -27
  10. package/src/core-indicators/README.md +60 -0
  11. package/src/core-indicators/composites.js +136 -0
  12. package/src/core-indicators/index.js +5 -0
  13. package/src/core-indicators/indicator.js +81 -0
  14. package/src/core-indicators/movingAverages.js +88 -0
  15. package/src/core-indicators/rsi.js +41 -0
  16. package/src/core-indicators/volatility.js +107 -0
  17. package/src/moving-averages/bollingerBands.js +39 -31
  18. package/src/moving-averages/donchianChannel.js +62 -36
  19. package/src/moving-averages/heikenAshi.js +33 -52
  20. package/src/moving-averages/macd.js +10 -26
  21. package/src/moving-averages/movingAverages.js +19 -25
  22. package/src/moving-averages/relativeVolume.js +30 -24
  23. package/src/oscillators/mfi.js +56 -0
  24. package/src/oscillators/rsi.js +7 -26
  25. package/src/oscillators/stochastic.js +6 -19
  26. package/src/oscillators/volumeDelta.js +36 -28
  27. package/src/oscillators/volumeOscillator.js +24 -32
  28. package/src/studies/candleFeatures.js +40 -28
  29. package/src/studies/dateTime.js +54 -56
  30. package/src/studies/findCrosses.js +18 -16
  31. package/src/studies/lag.js +32 -20
  32. package/src/utilities/assignTypes.js +1 -1
  33. package/src/utilities/config.js +31 -0
  34. package/src/utilities/numberUtilities.js +5 -3
  35. package/src/utilities/precisionMultiplier.js +11 -6
  36. package/src/utilities/validators.js +40 -1
  37. package/src/utilities/verticalToHorizontal.js +26 -16
  38. package/src/volatility/adx.js +7 -20
  39. package/src/volatility/atr.js +10 -23
  40. package/test/test.js +1230 -0
  41. package/webpack.config.js +0 -11
  42. package/src/moving-averages/vidya.js +0 -181
  43. package/src/studies/mapCols.js +0 -46
@@ -0,0 +1,22 @@
1
+ /*!
2
+ * MIT License
3
+ * Copyright (c) 2020 Benny Neugebauer
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
package/index.js CHANGED
@@ -10,11 +10,10 @@ import {
10
10
  validateString
11
11
  } from './src/utilities/validators.js'
12
12
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
13
- import { pushToMain } from './src/core-functions/pushToMain.js'
14
13
  import { assignTypes } from './src/utilities/assignTypes.js'
15
14
  import { dateOutputFormaters } from './src/utilities/dateUtilities.js'
16
- import { defaultMapColsCallback } from './src/studies/mapCols.js'
17
15
  import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
16
+ import { CONFIG_VERSION, copyConfig, freezeConfig } from './src/utilities/config.js'
18
17
 
19
18
  /**
20
19
  * Class OHLCV_INDICATORS
@@ -25,14 +24,41 @@ import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
25
24
  */
26
25
 
27
26
  export default class OHLCV_INDICATORS {
28
- constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, precision = false, useFullNames = false}) {
27
+ constructor({input, ticker = null, chunkProcess = 2000, config = {}}) {
28
+
29
+ if (config === null || Array.isArray(config) || typeof config !== 'object') {
30
+ throw new TypeError('Constructor "config" must be a plain object.')
31
+ }
32
+ const savedConfig = copyConfig(config)
33
+ const configKeys = ['schemaVersion', 'precision', 'useFullNames', 'inputParams', 'dateFormat', 'skipNull', 'timeZone']
34
+ for (const key of Object.keys(savedConfig)) {
35
+ if (!configKeys.includes(key)) throw new Error(`Unknown configuration option "${key}".`)
36
+ }
37
+ const {
38
+ schemaVersion = CONFIG_VERSION,
39
+ precision = false,
40
+ useFullNames = false,
41
+ inputParams = null,
42
+ dateFormat = 'milliseconds',
43
+ skipNull = true,
44
+ timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
45
+ } = savedConfig
46
+
47
+ if (schemaVersion !== CONFIG_VERSION) throw new Error(`Unsupported configuration schemaVersion: ${schemaVersion}.`)
48
+ validateBoolean(skipNull, 'config.skipNull', 'constructor')
49
+ validateString(dateFormat, 'config.dateFormat', 'constructor')
50
+ validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'config.dateFormat', 'constructor')
51
+ validateString(timeZone, 'config.timeZone', 'constructor')
52
+ const resolvedTimeZone = new Intl.DateTimeFormat('en-US', { timeZone }).resolvedOptions().timeZone
53
+ this.config = Object.freeze({ schemaVersion, precision, useFullNames, dateFormat, skipNull, timeZone: resolvedTimeZone })
54
+ this.timeZone = resolvedTimeZone
29
55
 
30
56
  validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
31
57
  if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
32
58
 
33
59
  validateNumber(chunkProcess, {min: 100, max: 50000, allowDecimals: false}, 'chunkProcess', 'constructor')
34
60
  validateBoolean(precision, 'precision', 'contructor')
35
- validateBoolean(precision, 'useFullNames', 'contructor')
61
+ validateBoolean(useFullNames, 'config.useFullNames', 'constructor')
36
62
 
37
63
  this.chunkProcess = chunkProcess
38
64
 
@@ -56,6 +82,8 @@ export default class OHLCV_INDICATORS {
56
82
 
57
83
  this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
58
84
  this.isComputed = false
85
+ this.isComputing = false
86
+ this.executionParams = []
59
87
 
60
88
  this.instances = {}
61
89
  this.verticalOhlcv = {}
@@ -67,17 +95,19 @@ export default class OHLCV_INDICATORS {
67
95
  this.scaledGroups = {}
68
96
  this.isAlreadyComputed = new Set()
69
97
 
70
- this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
98
+ this.pushToMain = ({index, key, value}) => {
99
+ this.verticalOhlcv[key][index] = value
100
+ }
71
101
 
72
102
  if(inputParams !== null)
73
103
  {
74
104
  validateInputParams(inputParams, this.len)
75
- this.inputParams = inputParams.sort((a, b) => a.order - b.order)
105
+ this.inputParams = freezeConfig(copyConfig(inputParams))
76
106
  this.compute()
77
107
  }
78
108
  else
79
109
  {
80
- this.inputParams = []
110
+ this.inputParams = Object.freeze([])
81
111
  }
82
112
 
83
113
 
@@ -86,12 +116,22 @@ export default class OHLCV_INDICATORS {
86
116
  }
87
117
 
88
118
 
119
+ exportConfig() {
120
+ return { ...this.config, inputParams: copyConfig(this.inputParams) }
121
+ }
122
+
123
+ _registerIndicator(job) {
124
+ isAlreadyComputed(this)
125
+ this.inputParams = Object.freeze([...this.inputParams, freezeConfig(copyConfig(job))])
126
+ return this
127
+ }
128
+
89
129
  getData(options = {}) {
90
130
 
91
131
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
92
132
  this.compute()
93
133
 
94
- const {skipNull = true, dateFormat = 'milliseconds'} = options
134
+ const {skipNull = this.config.skipNull, dateFormat = this.config.dateFormat} = options
95
135
 
96
136
  validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
97
137
  validateObject(options, 'options', 'getData')
@@ -109,7 +149,7 @@ export default class OHLCV_INDICATORS {
109
149
 
110
150
  this.compute()
111
151
 
112
- const {dateFormat = 'milliseconds'} = options
152
+ const {dateFormat = this.config.dateFormat} = options
113
153
 
114
154
  validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
115
155
 
@@ -128,7 +168,10 @@ export default class OHLCV_INDICATORS {
128
168
  return this;
129
169
  }
130
170
 
131
- this.inputParams = this.inputParams.sort((a, b) => a.order - b.order)
171
+ if (this.isComputing) throw new Error('Computation is already in progress.')
172
+
173
+ this.executionParams = copyConfig(this.inputParams)
174
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
132
175
 
133
176
  // Mark as “in progress”
134
177
  this.isComputed = false;
@@ -139,7 +182,12 @@ export default class OHLCV_INDICATORS {
139
182
  // Only run the full loop once (or when new data appears later,
140
183
  // if you extend this to reset isComputed elsewhere)
141
184
  if (this.len > 0) {
142
- mainLoop(this.input, this)
185
+ this.isComputing = true
186
+ try {
187
+ mainLoop(this.input, this)
188
+ } finally {
189
+ this.isComputing = false
190
+ }
143
191
 
144
192
  this.isComputed = true;
145
193
 
@@ -187,7 +235,7 @@ export default class OHLCV_INDICATORS {
187
235
 
188
236
  this.isAlreadyComputed.add(methodName)
189
237
 
190
- this.inputParams.push({key: methodName, params: [arr, {limit, oneHot}]})
238
+ this._registerIndicator({key: methodName, params: [arr, {limit, oneHot}]})
191
239
 
192
240
  return this
193
241
  }
@@ -199,9 +247,16 @@ export default class OHLCV_INDICATORS {
199
247
 
200
248
  isAlreadyComputed(this)
201
249
  validateArray(colKeys, 'colKeys', methodName)
250
+
251
+ const invalidItem = colKeys.find(v => typeof v !== 'string' || v === '')
252
+
253
+ if(invalidItem !== undefined) {
254
+ throw new Error(`Invalid item "${invalidItem}" (column name) in "colKeys" array param of "${methodName}()".`)
255
+ }
256
+
202
257
  validateNumber(lookback, {min:1, max: this.len, allowDecimals: false}, 'lookback', methodName)
203
258
 
204
- this.inputParams.push({key: methodName, params: [colKeys, lookback]})
259
+ this._registerIndicator({key: methodName, params: [colKeys, lookback]})
205
260
 
206
261
  return this;
207
262
  }
@@ -219,11 +274,12 @@ export default class OHLCV_INDICATORS {
219
274
  validateNumber(size, {min:1, max: this.len, allowDecimals: false}, 'size', methodName)
220
275
  validateObject(options, 'options', methodName)
221
276
 
222
- const { lag = 0} = options;
277
+ const { lag = 0, retLogs = false } = options;
223
278
 
224
279
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
280
+ validateBoolean(retLogs, 'options.retLogs', methodName)
225
281
 
226
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag}]})
282
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag, retLogs}]})
227
283
 
228
284
  return this
229
285
  }
@@ -244,7 +300,7 @@ export default class OHLCV_INDICATORS {
244
300
 
245
301
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
246
302
 
247
- this.inputParams.push({key: methodName, order: 0, params: [{lag}]})
303
+ this._registerIndicator({key: methodName, order: 0, params: [{lag}]})
248
304
 
249
305
  return this
250
306
  }
@@ -264,7 +320,7 @@ export default class OHLCV_INDICATORS {
264
320
  validateBoolean(retLogs, 'options.retLogs', methodName)
265
321
 
266
322
 
267
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag, retLogs}]})
323
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag, retLogs}]})
268
324
 
269
325
  return this
270
326
  }
@@ -284,7 +340,7 @@ export default class OHLCV_INDICATORS {
284
340
  validateBoolean(retLogs, 'options.retLogs', methodName)
285
341
 
286
342
 
287
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag, retLogs}]})
343
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag, retLogs}]})
288
344
 
289
345
  return this
290
346
  }
@@ -298,12 +354,13 @@ export default class OHLCV_INDICATORS {
298
354
  validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
299
355
  validateObject(options, 'options', methodName)
300
356
 
301
- const {target = 'close', lag = 0} = options
357
+ const {target = 'close', lag = 0, retLogs = false} = options
302
358
 
303
359
  validateString(target, 'options.target', methodName)
304
360
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
361
+ validateBoolean(retLogs, 'options.retLogs', methodName)
305
362
 
306
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
363
+ this._registerIndicator({key: methodName, params: [methodName, size, {target, lag, retLogs}]})
307
364
 
308
365
  return this
309
366
  }
@@ -316,12 +373,13 @@ export default class OHLCV_INDICATORS {
316
373
  validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
317
374
  validateObject(options, 'options', methodName)
318
375
 
319
- const {target = 'close', lag = 0} = options
376
+ const {target = 'close', lag = 0, retLogs = false} = options
320
377
 
321
378
  validateString(target, 'options.target', methodName)
322
379
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
380
+ validateBoolean(retLogs, 'options.retLogs', methodName)
323
381
 
324
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
382
+ this._registerIndicator({key: methodName, params: [methodName, size, {target, lag, retLogs}]})
325
383
 
326
384
  return this
327
385
  }
@@ -347,30 +405,7 @@ export default class OHLCV_INDICATORS {
347
405
 
348
406
  const order = 0
349
407
 
350
- this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull, retLogs}]})
351
-
352
- return this
353
- }
354
-
355
- vidya(size = 14, momentum = 20, options = {}) {
356
-
357
- const methodName = 'vidya'
358
-
359
- isAlreadyComputed(this)
360
-
361
- validateNumber(size, {min: 2, max: this.len, allowDecimals: false}, 'size', methodName)
362
- validateNumber(momentum, {min: 1, max: this.len, allowDecimals: false}, 'momentum', methodName)
363
- validateObject(options, 'options', methodName)
364
-
365
- const {target = 'close', lag = 0, atrLength = 200, bandDistance = 2, liquidityLookback = 20} = options
366
-
367
- validateString(target, 'options.target', methodName)
368
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
369
- validateNumber(bandDistance, {min: 0.01, max: 10, allowDecimals: true}, 'options.bandDistance', methodName)
370
- validateNumber(atrLength, {min: 2, max: this.len, allowDecimals: false}, 'options.atrLength', methodName)
371
- validateNumber(liquidityLookback, {min: 1, max: this.len, allowDecimals: false}, 'options.liquidityLookback', methodName)
372
-
373
- this.inputParams.push({key: methodName, params: [size, momentum, {target, lag}]})
408
+ this._registerIndicator({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull, retLogs}]})
374
409
 
375
410
  return this
376
411
  }
@@ -378,6 +413,8 @@ export default class OHLCV_INDICATORS {
378
413
  stochastic(kPeriod = 14, kSlowingPeriod = 3, dPeriod = 3, options = {}){
379
414
  const methodName = 'stochastic'
380
415
 
416
+ isAlreadyComputed(this)
417
+
381
418
  const {len: max} = this
382
419
 
383
420
  validateNumber(kPeriod, {min: 1, max, allowDecimals: false}, 'kPeriod', methodName)
@@ -390,7 +427,7 @@ export default class OHLCV_INDICATORS {
390
427
  validateNumber(lag, {min: 0, max, allowDecimals: false}, 'options.lag', methodName)
391
428
  validateBoolean(retLogs, 'retLogs', methodName)
392
429
 
393
- this.inputParams.push({key: methodName, params: [kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}]})
430
+ this._registerIndicator({key: methodName, params: [kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}]})
394
431
 
395
432
  return this
396
433
  }
@@ -414,7 +451,7 @@ export default class OHLCV_INDICATORS {
414
451
  const instanceKey = `${fast}_${slow}_${signal}${target === 'close' ? '' : `_${target}`}`
415
452
  const precomputed = {instanceKey}
416
453
 
417
- this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
454
+ this._registerIndicator({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
418
455
 
419
456
  return this
420
457
 
@@ -429,11 +466,12 @@ export default class OHLCV_INDICATORS {
429
466
  validateNumber(stdDev, {min: 0.01, max: 50, allowDecimals: true}, 'stdDev', methodName)
430
467
  validateObject(options, 'options', methodName)
431
468
 
432
- const { lag = 0} = options
469
+ const { lag = 0, retLogs = false } = options
433
470
 
434
471
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
472
+ validateBoolean(retLogs, 'options.retLogs', methodName)
435
473
 
436
- this.inputParams.push({key: methodName, params: [size, stdDev, {lag}]});
474
+ this._registerIndicator({key: methodName, params: [size, stdDev, {lag, retLogs}]});
437
475
 
438
476
  return this;
439
477
  }
@@ -453,10 +491,25 @@ export default class OHLCV_INDICATORS {
453
491
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
454
492
  validateBoolean(retLogs, 'retLogs', methodName)
455
493
 
456
- this.inputParams.push({key: methodName, params: [size, {target, lag, retLogs}]})
494
+ this._registerIndicator({key: methodName, params: [size, {target, lag, retLogs}]})
495
+
496
+ return this
497
+ }
498
+ mfi(size = 14, options = {}) {
499
+ const methodName = 'mfi'
500
+
501
+ isAlreadyComputed(this)
502
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
503
+ validateObject(options, 'options', methodName)
504
+
505
+ const {lag = 0, retLogs = false} = options
506
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
507
+ validateBoolean(retLogs, 'options.retLogs', methodName)
457
508
 
509
+ this._registerIndicator({key: methodName, params: [size, {lag, retLogs}]})
458
510
  return this
459
511
  }
512
+
460
513
  donchianChannels(size = 20, offset = 0, options = {}) {
461
514
 
462
515
  const methodName = 'donchianChannels'
@@ -467,11 +520,12 @@ export default class OHLCV_INDICATORS {
467
520
  validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
468
521
 
469
522
  validateObject(options, 'options', methodName)
470
- const { lag = 0} = options;
523
+ const { lag = 0, retLogs = false } = options;
471
524
 
472
525
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
526
+ validateBoolean(retLogs, 'options.retLogs', methodName)
473
527
 
474
- this.inputParams.push({ key: methodName, order: 0, params: [size, offset, {lag}] });
528
+ this._registerIndicator({ key: methodName, order: 0, params: [size, offset, {lag, retLogs}] });
475
529
 
476
530
  return this;
477
531
  }
@@ -491,11 +545,12 @@ export default class OHLCV_INDICATORS {
491
545
  validateNumber(slowsize, {min: fastsize, max: this.len, allowDecimals: false}, 'slowsize', methodName)
492
546
  validateObject(options, 'options', methodName)
493
547
 
494
- const {lag = 0} = options
548
+ const {lag = 0, retLogs = false} = options
495
549
 
496
550
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
551
+ validateBoolean(retLogs, 'retLogs', methodName)
497
552
 
498
- this.inputParams.push({key: methodName, order: 0, params: [fastsize, slowsize, {lag}]})
553
+ this._registerIndicator({key: methodName, order: 0, params: [fastsize, slowsize, {lag, retLogs}]})
499
554
  return this
500
555
  }
501
556
  dateTime(options = {})
@@ -529,7 +584,7 @@ export default class OHLCV_INDICATORS {
529
584
  colKeys: Object.keys(colKeySizes)
530
585
  }
531
586
 
532
- this.inputParams.push({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
587
+ this._registerIndicator({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
533
588
  return this
534
589
  }
535
590
 
@@ -547,36 +602,9 @@ export default class OHLCV_INDICATORS {
547
602
  validateBoolean(retLogs, 'retLogs', methodName)
548
603
 
549
604
 
550
- this.inputParams.push({key: methodName, params: [{lag, colKeys, retLogs}]})
605
+ this._registerIndicator({key: methodName, params: [{lag, colKeys, retLogs}]})
551
606
 
552
607
  return this
553
608
  }
554
609
 
555
- mapCols(newCols = ['change'], callback = null, options = {}) {
556
-
557
- isAlreadyComputed(this)
558
-
559
- const methodName = 'mapCols'
560
-
561
- if(typeof callback === 'undefined' || callback === null)
562
- {
563
- callback = defaultMapColsCallback
564
- }
565
-
566
- validateObject(options, 'options', methodName)
567
-
568
- const {lag = 0, isPriceBased = false} = options
569
-
570
- validateArray(newCols, 'newCols', methodName)
571
- validateNumber(lag, {min: 0, allowDecimals: false}, 'options.lag', methodName)
572
- validateBoolean(isPriceBased, 'isPriceBased', 'mapCols')
573
-
574
- if(this.precision === false && isPriceBased) {
575
- throw new Error(`Invalid param: If "mapCols.options.isPriceBased" is true, the "constructor.precision" param must be also true.`)
576
- }
577
-
578
- this.inputParams.push({key: methodName, params: [newCols, callback, {lag, isPriceBased}]})
579
-
580
- return this
581
- }
582
610
  }
package/package.json CHANGED
@@ -1,20 +1,17 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.5.78",
3
+ "version": "3.5.80",
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",
7
7
  "scripts": {
8
8
  "test": "node test/test.js",
9
+ "test-benchmark": "npm test -- --benchmark --rows 20000 --repeats 9",
9
10
  "build": "npx webpack",
10
11
  "cross": "node src/studies/findCrosses2.js"
11
12
  },
12
13
  "author": "https://jaimelias.com/",
13
14
  "license": "ISC",
14
- "dependencies": {
15
- "ohlcv-indicators": "^3.5.72",
16
- "trading-signals": "^5.0.4"
17
- },
18
15
  "devDependencies": {
19
16
  "@babel/core": "^7.25.2",
20
17
  "@babel/plugin-proposal-class-properties": "^7.18.6",
@@ -0,0 +1,40 @@
1
+ import { buildArray } from '../utilities/assignTypes.js';
2
+ import { validateNumber } from '../utilities/validators.js';
3
+
4
+ // Called once by each indicator, inside its index === 0 block.
5
+ export const initializeColumns = (main, columns, { lag = 0 } = {}) => {
6
+ const keyNames = [];
7
+ const lagKeys = [];
8
+
9
+ for (const {
10
+ key,
11
+ type = 'Float64Array',
12
+ fill,
13
+ enabled = true,
14
+ priceBased = false,
15
+ includeInLag = true
16
+ } of columns) {
17
+ if (!enabled) continue;
18
+
19
+ if (Object.prototype.hasOwnProperty.call(main.verticalOhlcv, key)) {
20
+ throw new Error(`Output column "${key}" already exists in verticalOhlcv.`);
21
+ }
22
+
23
+ // Integer arrays cannot represent the default missing value, NaN.
24
+ if (/^(?:Int|Uint)/.test(type) && fill === undefined) {
25
+ throw new Error(`Integer output column "${key}" requires an explicit fill value.`);
26
+ }
27
+
28
+ main.verticalOhlcv[key] = buildArray(type, main.len, fill);
29
+ if (priceBased) main.priceBased.add(key);
30
+ keyNames.push(key);
31
+ if (includeInLag) lagKeys.push(key);
32
+ }
33
+
34
+ if (lag > 0 && lagKeys.length > 0) {
35
+ validateNumber(lag, { min: 1, max: main.len }, 'lag', 'initializeColumns');
36
+ main.executionParams.push({ key: 'lag', params: [lagKeys, lag] });
37
+ }
38
+
39
+ return keyNames;
40
+ };
@@ -1,10 +1,10 @@
1
1
  import { areKeyValuesValid } from "./pushToMain.js";
2
2
  import { validateInputParams } from "../utilities/validators.js";
3
3
  import { rsi } from "../oscillators/rsi.js";
4
+ import { mfi } from "../oscillators/mfi.js";
4
5
  import { stochastic } from "../oscillators/stochastic.js";
5
6
  import { movingAverages } from "../moving-averages/movingAverages.js";
6
7
  import { heikenAshi } from "../moving-averages/heikenAshi.js";
7
- import { vidya } from "../moving-averages/vidya.js";
8
8
  import { macd } from "../moving-averages/macd.js";
9
9
  import { relativeVolume } from "../moving-averages/relativeVolume.js";
10
10
  import { donchianChannels } from "../moving-averages/donchianChannel.js";
@@ -16,12 +16,12 @@ import { crossPairs } from "../studies/findCrosses.js";
16
16
  import { dateTime } from "../studies/dateTime.js";
17
17
  import { atr } from "../volatility/atr.js";
18
18
  import { adx } from "../volatility/adx.js";
19
- import { mapCols } from "../studies/mapCols.js";
20
19
 
21
20
  import { buildArray } from "../utilities/assignTypes.js";
22
21
  import { inputNumberFormatter } from "../utilities/numberUtilities.js";
23
22
  import { dateFormaters } from "../utilities/dateUtilities.js";
24
23
  import { candleFeatures } from "../studies/candleFeatures.js";
24
+ import { getPrecisionDecimals } from "../utilities/precisionMultiplier.js";
25
25
 
26
26
 
27
27
  // Map indicator keys to their respective functions
@@ -30,19 +30,18 @@ const mainFunctions = {
30
30
  dateTime,
31
31
  heikenAshi,
32
32
  rsi,
33
+ mfi,
33
34
  stochastic,
34
35
  atr,
35
36
  adx,
36
37
  ema: movingAverages,
37
38
  sma: movingAverages,
38
- vidya,
39
39
  macd,
40
40
  volumeDelta,
41
41
  relativeVolume,
42
42
  donchianChannels,
43
43
  bollingerBands,
44
44
  volumeOscillator,
45
- mapCols,
46
45
  crossPairs,
47
46
  lag
48
47
  };
@@ -56,17 +55,33 @@ export const mainLoop = (input, main) => {
56
55
  inputTypes,
57
56
  chunkProcess,
58
57
  precisionMultiplier,
59
- inputParams
58
+ executionParams
60
59
  } = main;
61
60
 
62
61
 
63
- validateInputParams(inputParams, len)
62
+ validateInputParams(executionParams, len)
63
+ for (const { key } of executionParams) {
64
+ if (!Object.prototype.hasOwnProperty.call(mainFunctions, key)) {
65
+ throw new Error(`Unknown indicator "${key}" in configuration.`)
66
+ }
67
+ }
64
68
 
65
69
  for(const key of Object.keys(inputTypes))
66
70
  {
67
71
  verticalOhlcv[key] = buildArray(arrayTypes[key], len)
68
72
  }
69
73
 
74
+ const inputColumns = Object.entries(inputTypes).map(([key, formatterKey]) => {
75
+ const numeric = inputNumberFormatter.hasOwnProperty(formatterKey)
76
+ const formatter = numeric
77
+ ? inputNumberFormatter[formatterKey]
78
+ : dateFormaters.hasOwnProperty(formatterKey) ? dateFormaters[formatterKey] : null
79
+ return { key, formatter, numeric }
80
+ })
81
+ const precisionDecimals = typeof precisionMultiplier === 'number'
82
+ ? getPrecisionDecimals(precisionMultiplier) : undefined
83
+ let validationColumns = null
84
+
70
85
  // Process each row in the input
71
86
  for (let chunkStart = 0; chunkStart < len; chunkStart += chunkProcess) {
72
87
 
@@ -75,26 +90,13 @@ export const mainLoop = (input, main) => {
75
90
  for (let index = chunkStart; index < chunkEnd; index++) {
76
91
  const curr = input[index]
77
92
 
78
- for(const [key, formaterKey] of Object.entries(inputTypes))
79
- {
80
- let value = curr[key]
81
-
82
- if(typeof value === 'undefined') continue
83
-
84
- if(inputNumberFormatter.hasOwnProperty(formaterKey))
85
- {
86
- const formatedValue = inputNumberFormatter[formaterKey](value, precisionMultiplier)
87
-
88
- main.pushToMain({index, key, value: formatedValue})
89
- }
90
- else if(dateFormaters.hasOwnProperty(formaterKey))
91
- {
92
- main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
93
- }
94
- else
95
- {
96
- main.pushToMain({index, key, value})
97
- }
93
+ for (const { key, formatter, numeric } of inputColumns) {
94
+ const value = curr[key]
95
+ if (typeof value === 'undefined') continue
96
+
97
+ const formattedValue = formatter === null ? value
98
+ : numeric ? formatter(value, precisionMultiplier, precisionDecimals) : formatter(value)
99
+ main.pushToMain({ index, key, value: formattedValue })
98
100
  }
99
101
 
100
102
  const midPrice = (verticalOhlcv.open[index] + verticalOhlcv.close[index]) / 2
@@ -102,17 +104,20 @@ export const mainLoop = (input, main) => {
102
104
  main.pushToMain({index, key: 'mid_price', value: midPrice})
103
105
 
104
106
  // Process these indicators separately (ensuring their execution in the order of initialization)
105
- for (const { key, params} of inputParams)
107
+ for (const { key, params} of executionParams)
106
108
  {
107
109
  mainFunctions[key](main, index, ...params)
108
110
  }
109
111
 
110
112
  input[index] = null //flusing data
111
113
 
112
- const keyNames = Object.keys(verticalOhlcv)
114
+ // All built-in outputs (including generated lags) exist after row zero.
115
+ if (validationColumns === null) {
116
+ validationColumns = Object.values(verticalOhlcv)
117
+ }
113
118
 
114
- // if any value at this index is missing/NaN/null, mark it invalid
115
- if (!areKeyValuesValid(main, index, keyNames)) {
119
+ // Preserve the missing/NaN/null predicate, including Infinity behavior.
120
+ if (!areKeyValuesValid(validationColumns, index)) {
116
121
  main.invalidValueIndex = index
117
122
  }
118
123
  }