ohlcv-indicators 3.5.77 → 3.5.79

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 (42) hide show
  1. package/AGENTS.md +105 -0
  2. package/README.md +415 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
  5. package/index.js +96 -127
  6. package/package.json +1 -1
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +49 -26
  9. package/src/core-functions/pushToMain.js +1 -3
  10. package/src/core-indicators/README.md +61 -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 +82 -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/machine-learning/ml-utilities.js +0 -9
  18. package/src/moving-averages/bollingerBands.js +11 -26
  19. package/src/moving-averages/donchianChannel.js +30 -29
  20. package/src/moving-averages/heikenAshi.js +23 -33
  21. package/src/moving-averages/macd.js +10 -26
  22. package/src/moving-averages/movingAverages.js +7 -23
  23. package/src/moving-averages/relativeVolume.js +23 -21
  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 +29 -31
  28. package/src/studies/{priceFeatures.js → candleFeatures.js} +15 -23
  29. package/src/studies/dateTime.js +22 -27
  30. package/src/studies/findCrosses.js +16 -7
  31. package/src/studies/lag.js +26 -13
  32. package/src/studies/mapCols.js +14 -15
  33. package/src/utilities/assignTypes.js +1 -1
  34. package/src/utilities/config.js +64 -0
  35. package/src/utilities/numberUtilities.js +2 -0
  36. package/src/utilities/validators.js +37 -21
  37. package/src/utilities/verticalToHorizontal.js +21 -16
  38. package/src/volatility/adx.js +7 -20
  39. package/src/volatility/atr.js +10 -23
  40. package/test/test.js +533 -0
  41. package/src/machine-learning/scaler.js +0 -165
  42. package/src/moving-averages/vidya.js +0 -181
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Portions adapted from trading-signals 5.0.4.
3
+ * MIT License
4
+ * Copyright (c) 2020 Benny Neugebauer
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
package/index.js CHANGED
@@ -6,16 +6,14 @@ import {
6
6
  validateArrayOptions,
7
7
  validateBoolean,
8
8
  validateNumber,
9
- validateArrayOfRanges,
10
9
  validateInputParams,
11
10
  validateString
12
11
  } from './src/utilities/validators.js'
13
12
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
14
- import { pushToMain } from './src/core-functions/pushToMain.js'
15
13
  import { assignTypes } from './src/utilities/assignTypes.js'
16
14
  import { dateOutputFormaters } from './src/utilities/dateUtilities.js'
17
- import { defaultMapColsCallback } from './src/studies/mapCols.js'
18
15
  import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
16
+ import { CONFIG_VERSION, copyConfig, freezeConfig, registerMapCallback, resolveMapCallback, exportInputParams } from './src/utilities/config.js'
19
17
 
20
18
  /**
21
19
  * Class OHLCV_INDICATORS
@@ -26,14 +24,41 @@ import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
26
24
  */
27
25
 
28
26
  export default class OHLCV_INDICATORS {
29
- 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
30
55
 
31
56
  validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
32
57
  if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
33
58
 
34
59
  validateNumber(chunkProcess, {min: 100, max: 50000, allowDecimals: false}, 'chunkProcess', 'constructor')
35
60
  validateBoolean(precision, 'precision', 'contructor')
36
- validateBoolean(precision, 'useFullNames', 'contructor')
61
+ validateBoolean(useFullNames, 'config.useFullNames', 'constructor')
37
62
 
38
63
  this.chunkProcess = chunkProcess
39
64
 
@@ -57,6 +82,8 @@ export default class OHLCV_INDICATORS {
57
82
 
58
83
  this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
59
84
  this.isComputed = false
85
+ this.isComputing = false
86
+ this.executionParams = []
60
87
 
61
88
  this.instances = {}
62
89
  this.verticalOhlcv = {}
@@ -68,17 +95,19 @@ export default class OHLCV_INDICATORS {
68
95
  this.scaledGroups = {}
69
96
  this.isAlreadyComputed = new Set()
70
97
 
71
- this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
98
+ this.pushToMain = ({index, key, value}) => {
99
+ this.verticalOhlcv[key][index] = value
100
+ }
72
101
 
73
102
  if(inputParams !== null)
74
103
  {
75
104
  validateInputParams(inputParams, this.len)
76
- this.inputParams = inputParams.sort((a, b) => a.order - b.order)
105
+ this.inputParams = freezeConfig(copyConfig(inputParams))
77
106
  this.compute()
78
107
  }
79
108
  else
80
109
  {
81
- this.inputParams = []
110
+ this.inputParams = Object.freeze([])
82
111
  }
83
112
 
84
113
 
@@ -87,12 +116,27 @@ export default class OHLCV_INDICATORS {
87
116
  }
88
117
 
89
118
 
119
+ static registerMapCallback(name, callback) {
120
+ registerMapCallback(name, callback)
121
+ return this
122
+ }
123
+
124
+ exportConfig() {
125
+ return { ...this.config, inputParams: exportInputParams(this.inputParams) }
126
+ }
127
+
128
+ _registerIndicator(job) {
129
+ isAlreadyComputed(this)
130
+ this.inputParams = Object.freeze([...this.inputParams, freezeConfig(copyConfig(job, true))])
131
+ return this
132
+ }
133
+
90
134
  getData(options = {}) {
91
135
 
92
136
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
93
137
  this.compute()
94
138
 
95
- const {skipNull = true, dateFormat = 'milliseconds'} = options
139
+ const {skipNull = this.config.skipNull, dateFormat = this.config.dateFormat} = options
96
140
 
97
141
  validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
98
142
  validateObject(options, 'options', 'getData')
@@ -110,7 +154,7 @@ export default class OHLCV_INDICATORS {
110
154
 
111
155
  this.compute()
112
156
 
113
- const {dateFormat = 'milliseconds'} = options
157
+ const {dateFormat = this.config.dateFormat} = options
114
158
 
115
159
  validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
116
160
 
@@ -129,7 +173,13 @@ export default class OHLCV_INDICATORS {
129
173
  return this;
130
174
  }
131
175
 
132
- this.inputParams = this.inputParams.sort((a, b) => a.order - b.order)
176
+ if (this.isComputing) throw new Error('Computation is already in progress.')
177
+
178
+ this.executionParams = copyConfig(this.inputParams, true)
179
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
180
+ for (const job of this.executionParams) {
181
+ if (job.key === 'mapCols') job.params[1] = resolveMapCallback(job.params[1])
182
+ }
133
183
 
134
184
  // Mark as “in progress”
135
185
  this.isComputed = false;
@@ -140,7 +190,12 @@ export default class OHLCV_INDICATORS {
140
190
  // Only run the full loop once (or when new data appears later,
141
191
  // if you extend this to reset isComputed elsewhere)
142
192
  if (this.len > 0) {
143
- mainLoop(this.input, this)
193
+ this.isComputing = true
194
+ try {
195
+ mainLoop(this.input, this)
196
+ } finally {
197
+ this.isComputing = false
198
+ }
144
199
 
145
200
  this.isComputed = true;
146
201
 
@@ -188,7 +243,7 @@ export default class OHLCV_INDICATORS {
188
243
 
189
244
  this.isAlreadyComputed.add(methodName)
190
245
 
191
- this.inputParams.push({key: methodName, params: [arr, {limit, oneHot}]})
246
+ this._registerIndicator({key: methodName, params: [arr, {limit, oneHot}]})
192
247
 
193
248
  return this
194
249
  }
@@ -202,7 +257,7 @@ export default class OHLCV_INDICATORS {
202
257
  validateArray(colKeys, 'colKeys', methodName)
203
258
  validateNumber(lookback, {min:1, max: this.len, allowDecimals: false}, 'lookback', methodName)
204
259
 
205
- this.inputParams.push({key: methodName, params: [colKeys, lookback]})
260
+ this._registerIndicator({key: methodName, params: [colKeys, lookback]})
206
261
 
207
262
  return this;
208
263
  }
@@ -224,7 +279,7 @@ export default class OHLCV_INDICATORS {
224
279
 
225
280
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
226
281
 
227
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag}]})
282
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag}]})
228
283
 
229
284
  return this
230
285
  }
@@ -245,7 +300,7 @@ export default class OHLCV_INDICATORS {
245
300
 
246
301
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
247
302
 
248
- this.inputParams.push({key: methodName, order: 0, params: [{lag}]})
303
+ this._registerIndicator({key: methodName, order: 0, params: [{lag}]})
249
304
 
250
305
  return this
251
306
  }
@@ -265,7 +320,7 @@ export default class OHLCV_INDICATORS {
265
320
  validateBoolean(retLogs, 'options.retLogs', methodName)
266
321
 
267
322
 
268
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag, retLogs}]})
323
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag, retLogs}]})
269
324
 
270
325
  return this
271
326
  }
@@ -285,7 +340,7 @@ export default class OHLCV_INDICATORS {
285
340
  validateBoolean(retLogs, 'options.retLogs', methodName)
286
341
 
287
342
 
288
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag, retLogs}]})
343
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag, retLogs}]})
289
344
 
290
345
  return this
291
346
  }
@@ -304,7 +359,7 @@ export default class OHLCV_INDICATORS {
304
359
  validateString(target, 'options.target', methodName)
305
360
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
306
361
 
307
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
362
+ this._registerIndicator({key: methodName, params: [methodName, size, {target, lag}]})
308
363
 
309
364
  return this
310
365
  }
@@ -322,7 +377,7 @@ export default class OHLCV_INDICATORS {
322
377
  validateString(target, 'options.target', methodName)
323
378
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
324
379
 
325
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
380
+ this._registerIndicator({key: methodName, params: [methodName, size, {target, lag}]})
326
381
 
327
382
  return this
328
383
  }
@@ -348,30 +403,7 @@ export default class OHLCV_INDICATORS {
348
403
 
349
404
  const order = 0
350
405
 
351
- this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull, retLogs}]})
352
-
353
- return this
354
- }
355
-
356
- vidya(size = 14, momentum = 20, options = {}) {
357
-
358
- const methodName = 'vidya'
359
-
360
- isAlreadyComputed(this)
361
-
362
- validateNumber(size, {min: 2, max: this.len, allowDecimals: false}, 'size', methodName)
363
- validateNumber(momentum, {min: 1, max: this.len, allowDecimals: false}, 'momentum', methodName)
364
- validateObject(options, 'options', methodName)
365
-
366
- const {target = 'close', lag = 0, atrLength = 200, bandDistance = 2, liquidityLookback = 20} = options
367
-
368
- validateString(target, 'options.target', methodName)
369
- validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
370
- validateNumber(bandDistance, {min: 0.01, max: 10, allowDecimals: true}, 'options.bandDistance', methodName)
371
- validateNumber(atrLength, {min: 2, max: this.len, allowDecimals: false}, 'options.atrLength', methodName)
372
- validateNumber(liquidityLookback, {min: 1, max: this.len, allowDecimals: false}, 'options.liquidityLookback', methodName)
373
-
374
- this.inputParams.push({key: methodName, params: [size, momentum, {target, lag}]})
406
+ this._registerIndicator({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull, retLogs}]})
375
407
 
376
408
  return this
377
409
  }
@@ -379,6 +411,8 @@ export default class OHLCV_INDICATORS {
379
411
  stochastic(kPeriod = 14, kSlowingPeriod = 3, dPeriod = 3, options = {}){
380
412
  const methodName = 'stochastic'
381
413
 
414
+ isAlreadyComputed(this)
415
+
382
416
  const {len: max} = this
383
417
 
384
418
  validateNumber(kPeriod, {min: 1, max, allowDecimals: false}, 'kPeriod', methodName)
@@ -391,7 +425,7 @@ export default class OHLCV_INDICATORS {
391
425
  validateNumber(lag, {min: 0, max, allowDecimals: false}, 'options.lag', methodName)
392
426
  validateBoolean(retLogs, 'retLogs', methodName)
393
427
 
394
- this.inputParams.push({key: methodName, params: [kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}]})
428
+ this._registerIndicator({key: methodName, params: [kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}]})
395
429
 
396
430
  return this
397
431
  }
@@ -415,7 +449,7 @@ export default class OHLCV_INDICATORS {
415
449
  const instanceKey = `${fast}_${slow}_${signal}${target === 'close' ? '' : `_${target}`}`
416
450
  const precomputed = {instanceKey}
417
451
 
418
- this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
452
+ this._registerIndicator({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
419
453
 
420
454
  return this
421
455
 
@@ -434,7 +468,7 @@ export default class OHLCV_INDICATORS {
434
468
 
435
469
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
436
470
 
437
- this.inputParams.push({key: methodName, params: [size, stdDev, {lag}]});
471
+ this._registerIndicator({key: methodName, params: [size, stdDev, {lag}]});
438
472
 
439
473
  return this;
440
474
  }
@@ -454,7 +488,7 @@ export default class OHLCV_INDICATORS {
454
488
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
455
489
  validateBoolean(retLogs, 'retLogs', methodName)
456
490
 
457
- this.inputParams.push({key: methodName, params: [size, {target, lag, retLogs}]})
491
+ this._registerIndicator({key: methodName, params: [size, {target, lag, retLogs}]})
458
492
 
459
493
  return this
460
494
  }
@@ -472,7 +506,7 @@ export default class OHLCV_INDICATORS {
472
506
 
473
507
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
474
508
 
475
- this.inputParams.push({ key: methodName, order: 0, params: [size, offset, {lag}] });
509
+ this._registerIndicator({ key: methodName, order: 0, params: [size, offset, {lag}] });
476
510
 
477
511
  return this;
478
512
  }
@@ -492,11 +526,12 @@ export default class OHLCV_INDICATORS {
492
526
  validateNumber(slowsize, {min: fastsize, max: this.len, allowDecimals: false}, 'slowsize', methodName)
493
527
  validateObject(options, 'options', methodName)
494
528
 
495
- const {lag = 0} = options
529
+ const {lag = 0, retLogs = false} = options
496
530
 
497
531
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
532
+ validateBoolean(retLogs, 'retLogs', methodName)
498
533
 
499
- this.inputParams.push({key: methodName, order: 0, params: [fastsize, slowsize, {lag}]})
534
+ this._registerIndicator({key: methodName, order: 0, params: [fastsize, slowsize, {lag, retLogs}]})
500
535
  return this
501
536
  }
502
537
  dateTime(options = {})
@@ -530,12 +565,12 @@ export default class OHLCV_INDICATORS {
530
565
  colKeys: Object.keys(colKeySizes)
531
566
  }
532
567
 
533
- this.inputParams.push({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
568
+ this._registerIndicator({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
534
569
  return this
535
570
  }
536
571
 
537
- priceFeatures(options = {}) {
538
- let methodName = 'priceFeatures'
572
+ candleFeatures(options = {}) {
573
+ let methodName = 'candleFeatures'
539
574
 
540
575
  isAlreadyComputed(this)
541
576
 
@@ -548,76 +583,8 @@ export default class OHLCV_INDICATORS {
548
583
  validateBoolean(retLogs, 'retLogs', methodName)
549
584
 
550
585
 
551
- this.inputParams.push({key: methodName, params: [{lag, colKeys, retLogs}]})
552
-
553
- return this
554
- }
555
-
556
- scaler(type = 'zscore', size, options = {})
557
- {
558
- let methodName = 'scaler'
559
-
560
- isAlreadyComputed(this)
561
-
562
- validateArrayOptions(['minmax', 'zscore', 'byfeature'], type, 'options.type', methodName)
563
- validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
564
-
565
- const longName = `${methodName}(${type}, ${size}, options = {})`
566
-
567
- validateObject(options, 'options', longName)
568
-
569
- const {minMaxRange = null, lag = false, colKeys = [], weights = {}, euclideanWeights = false, byFeatureRange = null, offset = 0} = options
570
-
571
- validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
572
-
573
-
574
- validateArray(colKeys, 'options.colKeys', longName)
575
- validateBoolean(lag, 'options.lag', longName)
576
-
577
-
578
- validateObject(weights, 'options.weights', longName)
579
-
580
- const lookback = lag ? size - 1 : 0
581
-
582
- if(colKeys.length === 0) {
583
- throw new Error(`The property "options.colKeys" must be an array with target keys in ${longName}.`)
584
- }
585
-
586
-
587
-
588
- for(const [key, arr] of Object.entries(weights)) {
589
- validateArray(arr, `options.weights.${key}`)
590
-
591
- if(!lag && arr.size > 0) {
592
- throw new Error(`If "options.lag" is set to false "options.weights.${key}" can only contain 1 item (float) in ${longName}.`)
593
- }
594
-
595
- if(arr.length > size) {
596
- throw new Error(`The length of the property "options.weights.${key}" can not be longer than "size" in ${longName}.`)
597
- }
598
-
599
- for(let x = 0; x < arr.length; x++) {
600
- const val = arr[x]
601
- validateNumber(val, {min: 0.01, max: 10, allowDecimals: true}, `options.weights.${key}[${x}]`, longName)
602
- }
603
-
604
- }
605
-
606
- if (type !== 'byfeature') {
607
-
608
- if(!byFeatureRange == null) {
609
- throw new Error('"options.byFeatureRange" is only valid when "type" is "byfeature".')
610
- }
611
- }
612
- else {
613
- validateArrayOfRanges(byFeatureRange, 'options.byFeatureRange', longName)
614
- }
615
-
616
- if(['minmax', 'byfeature'].includes(type)) {
617
- validateArrayOfRanges(minMaxRange, 'options.minMaxRange', longName)
618
- }
586
+ this._registerIndicator({key: methodName, params: [{lag, colKeys, retLogs}]})
619
587
 
620
- this.inputParams.push({key: methodName, params: [size, colKeys, {type, minMaxRange, lookback, weights, euclideanWeights, byFeatureRange, offset}]})
621
588
  return this
622
589
  }
623
590
 
@@ -629,12 +596,14 @@ export default class OHLCV_INDICATORS {
629
596
 
630
597
  if(typeof callback === 'undefined' || callback === null)
631
598
  {
632
- callback = defaultMapColsCallback
599
+ callback = 'default'
633
600
  }
634
601
 
635
602
  validateObject(options, 'options', methodName)
636
603
 
637
- const {lag = 0, isPriceBased = false} = options
604
+ const {lag = 0, isPriceBased = false, callbackParams = {}} = options
605
+ resolveMapCallback(callback)
606
+ const savedCallbackParams = copyConfig(callbackParams)
638
607
 
639
608
  validateArray(newCols, 'newCols', methodName)
640
609
  validateNumber(lag, {min: 0, allowDecimals: false}, 'options.lag', methodName)
@@ -644,8 +613,8 @@ export default class OHLCV_INDICATORS {
644
613
  throw new Error(`Invalid param: If "mapCols.options.isPriceBased" is true, the "constructor.precision" param must be also true.`)
645
614
  }
646
615
 
647
- this.inputParams.push({key: methodName, params: [newCols, callback, {lag, isPriceBased}]})
616
+ this._registerIndicator({key: methodName, params: [newCols, callback, {lag, isPriceBased, callbackParams: savedCallbackParams}]})
648
617
 
649
618
  return this
650
619
  }
651
- }
620
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.5.77",
3
+ "version": "3.5.79",
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",
@@ -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
+ };
@@ -4,7 +4,6 @@ import { rsi } from "../oscillators/rsi.js";
4
4
  import { stochastic } from "../oscillators/stochastic.js";
5
5
  import { movingAverages } from "../moving-averages/movingAverages.js";
6
6
  import { heikenAshi } from "../moving-averages/heikenAshi.js";
7
- import { vidya } from "../moving-averages/vidya.js";
8
7
  import { macd } from "../moving-averages/macd.js";
9
8
  import { relativeVolume } from "../moving-averages/relativeVolume.js";
10
9
  import { donchianChannels } from "../moving-averages/donchianChannel.js";
@@ -14,7 +13,6 @@ import { volumeDelta } from "../oscillators/volumeDelta.js";
14
13
  import { lag } from "../studies/lag.js";
15
14
  import { crossPairs } from "../studies/findCrosses.js";
16
15
  import { dateTime } from "../studies/dateTime.js";
17
- import { scaler } from "../machine-learning/scaler.js";
18
16
  import { atr } from "../volatility/atr.js";
19
17
  import { adx } from "../volatility/adx.js";
20
18
  import { mapCols } from "../studies/mapCols.js";
@@ -22,12 +20,12 @@ import { mapCols } from "../studies/mapCols.js";
22
20
  import { buildArray } from "../utilities/assignTypes.js";
23
21
  import { inputNumberFormatter } from "../utilities/numberUtilities.js";
24
22
  import { dateFormaters } from "../utilities/dateUtilities.js";
25
- import { priceFeatures } from "../studies/priceFeatures.js";
23
+ import { candleFeatures } from "../studies/candleFeatures.js";
26
24
 
27
25
 
28
26
  // Map indicator keys to their respective functions
29
27
  const mainFunctions = {
30
- priceFeatures,
28
+ candleFeatures,
31
29
  dateTime,
32
30
  heikenAshi,
33
31
  rsi,
@@ -36,7 +34,6 @@ const mainFunctions = {
36
34
  adx,
37
35
  ema: movingAverages,
38
36
  sma: movingAverages,
39
- vidya,
40
37
  macd,
41
38
  volumeDelta,
42
39
  relativeVolume,
@@ -44,7 +41,6 @@ const mainFunctions = {
44
41
  bollingerBands,
45
42
  volumeOscillator,
46
43
  mapCols,
47
- scaler,
48
44
  crossPairs,
49
45
  lag
50
46
  };
@@ -58,17 +54,33 @@ export const mainLoop = (input, main) => {
58
54
  inputTypes,
59
55
  chunkProcess,
60
56
  precisionMultiplier,
61
- inputParams
57
+ executionParams
62
58
  } = main;
63
59
 
64
60
 
65
- validateInputParams(inputParams, len)
61
+ validateInputParams(executionParams, len)
62
+ for (const { key } of executionParams) {
63
+ if (!Object.prototype.hasOwnProperty.call(mainFunctions, key)) {
64
+ throw new Error(`Unknown indicator "${key}" in configuration.`)
65
+ }
66
+ }
66
67
 
67
68
  for(const key of Object.keys(inputTypes))
68
69
  {
69
70
  verticalOhlcv[key] = buildArray(arrayTypes[key], len)
70
71
  }
71
72
 
73
+ // Callbacks can modify inputTypes through main; retain dynamic lookup for them.
74
+ const inputColumns = executionParams.some(({ key }) => key === 'mapCols')
75
+ ? null
76
+ : Object.entries(inputTypes).map(([key, formatterKey]) => {
77
+ const numeric = inputNumberFormatter.hasOwnProperty(formatterKey)
78
+ const formatter = numeric
79
+ ? inputNumberFormatter[formatterKey]
80
+ : dateFormaters.hasOwnProperty(formatterKey) ? dateFormaters[formatterKey] : null
81
+ return { key, formatter, numeric }
82
+ })
83
+
72
84
  // Process each row in the input
73
85
  for (let chunkStart = 0; chunkStart < len; chunkStart += chunkProcess) {
74
86
 
@@ -77,25 +89,36 @@ export const mainLoop = (input, main) => {
77
89
  for (let index = chunkStart; index < chunkEnd; index++) {
78
90
  const curr = input[index]
79
91
 
80
- for(const [key, formaterKey] of Object.entries(inputTypes))
81
- {
82
- let value = curr[key]
83
-
84
- if(typeof value === 'undefined') continue
85
-
86
- if(inputNumberFormatter.hasOwnProperty(formaterKey))
87
- {
88
- const formatedValue = inputNumberFormatter[formaterKey](value, precisionMultiplier)
89
-
90
- main.pushToMain({index, key, value: formatedValue})
91
- }
92
- else if(dateFormaters.hasOwnProperty(formaterKey))
93
- {
94
- main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
92
+ if (inputColumns !== null) {
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) : formatter(value)
99
+ main.pushToMain({ index, key, value: formattedValue })
95
100
  }
96
- else
101
+ } else {
102
+ for(const [key, formaterKey] of Object.entries(inputTypes))
97
103
  {
98
- main.pushToMain({index, key, value})
104
+ let value = curr[key]
105
+
106
+ if(typeof value === 'undefined') continue
107
+
108
+ if(inputNumberFormatter.hasOwnProperty(formaterKey))
109
+ {
110
+ const formatedValue = inputNumberFormatter[formaterKey](value, precisionMultiplier)
111
+
112
+ main.pushToMain({index, key, value: formatedValue})
113
+ }
114
+ else if(dateFormaters.hasOwnProperty(formaterKey))
115
+ {
116
+ main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
117
+ }
118
+ else
119
+ {
120
+ main.pushToMain({index, key, value})
121
+ }
99
122
  }
100
123
  }
101
124
 
@@ -104,7 +127,7 @@ export const mainLoop = (input, main) => {
104
127
  main.pushToMain({index, key: 'mid_price', value: midPrice})
105
128
 
106
129
  // Process these indicators separately (ensuring their execution in the order of initialization)
107
- for (const { key, params} of inputParams)
130
+ for (const { key, params} of executionParams)
108
131
  {
109
132
  mainFunctions[key](main, index, ...params)
110
133
  }
@@ -2,9 +2,7 @@ export const pushToMain = ({main, index, key, value}) => {
2
2
 
3
3
  const {verticalOhlcv} = main
4
4
 
5
- verticalOhlcv[key][index] = value
6
-
7
- return true
5
+ verticalOhlcv[key][index] = value
8
6
  }
9
7
 
10
8
  export const areKeyValuesValid = (main, index, keyNames) => {