ohlcv-indicators 3.5.78 → 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 (40) 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 +93 -55
  6. package/package.json +1 -1
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +47 -22
  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/moving-averages/bollingerBands.js +11 -26
  18. package/src/moving-averages/donchianChannel.js +30 -29
  19. package/src/moving-averages/heikenAshi.js +23 -33
  20. package/src/moving-averages/macd.js +10 -26
  21. package/src/moving-averages/movingAverages.js +7 -23
  22. package/src/moving-averages/relativeVolume.js +23 -21
  23. package/src/oscillators/rsi.js +7 -26
  24. package/src/oscillators/stochastic.js +6 -19
  25. package/src/oscillators/volumeDelta.js +36 -28
  26. package/src/oscillators/volumeOscillator.js +29 -31
  27. package/src/studies/candleFeatures.js +12 -20
  28. package/src/studies/dateTime.js +22 -27
  29. package/src/studies/findCrosses.js +16 -7
  30. package/src/studies/lag.js +26 -13
  31. package/src/studies/mapCols.js +14 -15
  32. package/src/utilities/assignTypes.js +1 -1
  33. package/src/utilities/config.js +64 -0
  34. package/src/utilities/numberUtilities.js +2 -0
  35. package/src/utilities/validators.js +40 -1
  36. package/src/utilities/verticalToHorizontal.js +21 -16
  37. package/src/volatility/adx.js +7 -20
  38. package/src/volatility/atr.js +10 -23
  39. package/test/test.js +533 -0
  40. 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
@@ -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, registerMapCallback, resolveMapCallback, exportInputParams } 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,27 @@ export default class OHLCV_INDICATORS {
86
116
  }
87
117
 
88
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
+
89
134
  getData(options = {}) {
90
135
 
91
136
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
92
137
  this.compute()
93
138
 
94
- const {skipNull = true, dateFormat = 'milliseconds'} = options
139
+ const {skipNull = this.config.skipNull, dateFormat = this.config.dateFormat} = options
95
140
 
96
141
  validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
97
142
  validateObject(options, 'options', 'getData')
@@ -109,7 +154,7 @@ export default class OHLCV_INDICATORS {
109
154
 
110
155
  this.compute()
111
156
 
112
- const {dateFormat = 'milliseconds'} = options
157
+ const {dateFormat = this.config.dateFormat} = options
113
158
 
114
159
  validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
115
160
 
@@ -128,7 +173,13 @@ export default class OHLCV_INDICATORS {
128
173
  return this;
129
174
  }
130
175
 
131
- 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
+ }
132
183
 
133
184
  // Mark as “in progress”
134
185
  this.isComputed = false;
@@ -139,7 +190,12 @@ export default class OHLCV_INDICATORS {
139
190
  // Only run the full loop once (or when new data appears later,
140
191
  // if you extend this to reset isComputed elsewhere)
141
192
  if (this.len > 0) {
142
- mainLoop(this.input, this)
193
+ this.isComputing = true
194
+ try {
195
+ mainLoop(this.input, this)
196
+ } finally {
197
+ this.isComputing = false
198
+ }
143
199
 
144
200
  this.isComputed = true;
145
201
 
@@ -187,7 +243,7 @@ export default class OHLCV_INDICATORS {
187
243
 
188
244
  this.isAlreadyComputed.add(methodName)
189
245
 
190
- this.inputParams.push({key: methodName, params: [arr, {limit, oneHot}]})
246
+ this._registerIndicator({key: methodName, params: [arr, {limit, oneHot}]})
191
247
 
192
248
  return this
193
249
  }
@@ -201,7 +257,7 @@ export default class OHLCV_INDICATORS {
201
257
  validateArray(colKeys, 'colKeys', methodName)
202
258
  validateNumber(lookback, {min:1, max: this.len, allowDecimals: false}, 'lookback', methodName)
203
259
 
204
- this.inputParams.push({key: methodName, params: [colKeys, lookback]})
260
+ this._registerIndicator({key: methodName, params: [colKeys, lookback]})
205
261
 
206
262
  return this;
207
263
  }
@@ -223,7 +279,7 @@ export default class OHLCV_INDICATORS {
223
279
 
224
280
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
225
281
 
226
- this.inputParams.push({key: methodName, order: 0, params: [size, {lag}]})
282
+ this._registerIndicator({key: methodName, order: 0, params: [size, {lag}]})
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
  }
@@ -303,7 +359,7 @@ export default class OHLCV_INDICATORS {
303
359
  validateString(target, 'options.target', methodName)
304
360
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
305
361
 
306
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
362
+ this._registerIndicator({key: methodName, params: [methodName, size, {target, lag}]})
307
363
 
308
364
  return this
309
365
  }
@@ -321,7 +377,7 @@ export default class OHLCV_INDICATORS {
321
377
  validateString(target, 'options.target', methodName)
322
378
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
323
379
 
324
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
380
+ this._registerIndicator({key: methodName, params: [methodName, size, {target, lag}]})
325
381
 
326
382
  return this
327
383
  }
@@ -347,30 +403,7 @@ export default class OHLCV_INDICATORS {
347
403
 
348
404
  const order = 0
349
405
 
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}]})
406
+ this._registerIndicator({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull, retLogs}]})
374
407
 
375
408
  return this
376
409
  }
@@ -378,6 +411,8 @@ export default class OHLCV_INDICATORS {
378
411
  stochastic(kPeriod = 14, kSlowingPeriod = 3, dPeriod = 3, options = {}){
379
412
  const methodName = 'stochastic'
380
413
 
414
+ isAlreadyComputed(this)
415
+
381
416
  const {len: max} = this
382
417
 
383
418
  validateNumber(kPeriod, {min: 1, max, allowDecimals: false}, 'kPeriod', methodName)
@@ -390,7 +425,7 @@ export default class OHLCV_INDICATORS {
390
425
  validateNumber(lag, {min: 0, max, allowDecimals: false}, 'options.lag', methodName)
391
426
  validateBoolean(retLogs, 'retLogs', methodName)
392
427
 
393
- this.inputParams.push({key: methodName, params: [kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}]})
428
+ this._registerIndicator({key: methodName, params: [kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}]})
394
429
 
395
430
  return this
396
431
  }
@@ -414,7 +449,7 @@ export default class OHLCV_INDICATORS {
414
449
  const instanceKey = `${fast}_${slow}_${signal}${target === 'close' ? '' : `_${target}`}`
415
450
  const precomputed = {instanceKey}
416
451
 
417
- this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
452
+ this._registerIndicator({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
418
453
 
419
454
  return this
420
455
 
@@ -433,7 +468,7 @@ export default class OHLCV_INDICATORS {
433
468
 
434
469
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
435
470
 
436
- this.inputParams.push({key: methodName, params: [size, stdDev, {lag}]});
471
+ this._registerIndicator({key: methodName, params: [size, stdDev, {lag}]});
437
472
 
438
473
  return this;
439
474
  }
@@ -453,7 +488,7 @@ export default class OHLCV_INDICATORS {
453
488
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
454
489
  validateBoolean(retLogs, 'retLogs', methodName)
455
490
 
456
- this.inputParams.push({key: methodName, params: [size, {target, lag, retLogs}]})
491
+ this._registerIndicator({key: methodName, params: [size, {target, lag, retLogs}]})
457
492
 
458
493
  return this
459
494
  }
@@ -471,7 +506,7 @@ export default class OHLCV_INDICATORS {
471
506
 
472
507
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
473
508
 
474
- this.inputParams.push({ key: methodName, order: 0, params: [size, offset, {lag}] });
509
+ this._registerIndicator({ key: methodName, order: 0, params: [size, offset, {lag}] });
475
510
 
476
511
  return this;
477
512
  }
@@ -491,11 +526,12 @@ export default class OHLCV_INDICATORS {
491
526
  validateNumber(slowsize, {min: fastsize, max: this.len, allowDecimals: false}, 'slowsize', methodName)
492
527
  validateObject(options, 'options', methodName)
493
528
 
494
- const {lag = 0} = options
529
+ const {lag = 0, retLogs = false} = options
495
530
 
496
531
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
532
+ validateBoolean(retLogs, 'retLogs', methodName)
497
533
 
498
- this.inputParams.push({key: methodName, order: 0, params: [fastsize, slowsize, {lag}]})
534
+ this._registerIndicator({key: methodName, order: 0, params: [fastsize, slowsize, {lag, retLogs}]})
499
535
  return this
500
536
  }
501
537
  dateTime(options = {})
@@ -529,7 +565,7 @@ export default class OHLCV_INDICATORS {
529
565
  colKeys: Object.keys(colKeySizes)
530
566
  }
531
567
 
532
- this.inputParams.push({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
568
+ this._registerIndicator({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
533
569
  return this
534
570
  }
535
571
 
@@ -547,7 +583,7 @@ export default class OHLCV_INDICATORS {
547
583
  validateBoolean(retLogs, 'retLogs', methodName)
548
584
 
549
585
 
550
- this.inputParams.push({key: methodName, params: [{lag, colKeys, retLogs}]})
586
+ this._registerIndicator({key: methodName, params: [{lag, colKeys, retLogs}]})
551
587
 
552
588
  return this
553
589
  }
@@ -560,12 +596,14 @@ export default class OHLCV_INDICATORS {
560
596
 
561
597
  if(typeof callback === 'undefined' || callback === null)
562
598
  {
563
- callback = defaultMapColsCallback
599
+ callback = 'default'
564
600
  }
565
601
 
566
602
  validateObject(options, 'options', methodName)
567
603
 
568
- const {lag = 0, isPriceBased = false} = options
604
+ const {lag = 0, isPriceBased = false, callbackParams = {}} = options
605
+ resolveMapCallback(callback)
606
+ const savedCallbackParams = copyConfig(callbackParams)
569
607
 
570
608
  validateArray(newCols, 'newCols', methodName)
571
609
  validateNumber(lag, {min: 0, allowDecimals: false}, 'options.lag', methodName)
@@ -575,7 +613,7 @@ export default class OHLCV_INDICATORS {
575
613
  throw new Error(`Invalid param: If "mapCols.options.isPriceBased" is true, the "constructor.precision" param must be also true.`)
576
614
  }
577
615
 
578
- this.inputParams.push({key: methodName, params: [newCols, callback, {lag, isPriceBased}]})
616
+ this._registerIndicator({key: methodName, params: [newCols, callback, {lag, isPriceBased, callbackParams: savedCallbackParams}]})
579
617
 
580
618
  return this
581
619
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.5.78",
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";
@@ -35,7 +34,6 @@ const mainFunctions = {
35
34
  adx,
36
35
  ema: movingAverages,
37
36
  sma: movingAverages,
38
- vidya,
39
37
  macd,
40
38
  volumeDelta,
41
39
  relativeVolume,
@@ -56,17 +54,33 @@ export const mainLoop = (input, main) => {
56
54
  inputTypes,
57
55
  chunkProcess,
58
56
  precisionMultiplier,
59
- inputParams
57
+ executionParams
60
58
  } = main;
61
59
 
62
60
 
63
- 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
+ }
64
67
 
65
68
  for(const key of Object.keys(inputTypes))
66
69
  {
67
70
  verticalOhlcv[key] = buildArray(arrayTypes[key], len)
68
71
  }
69
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
+
70
84
  // Process each row in the input
71
85
  for (let chunkStart = 0; chunkStart < len; chunkStart += chunkProcess) {
72
86
 
@@ -75,25 +89,36 @@ export const mainLoop = (input, main) => {
75
89
  for (let index = chunkStart; index < chunkEnd; index++) {
76
90
  const curr = input[index]
77
91
 
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)})
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 })
93
100
  }
94
- else
101
+ } else {
102
+ for(const [key, formaterKey] of Object.entries(inputTypes))
95
103
  {
96
- 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
+ }
97
122
  }
98
123
  }
99
124
 
@@ -102,7 +127,7 @@ export const mainLoop = (input, main) => {
102
127
  main.pushToMain({index, key: 'mid_price', value: midPrice})
103
128
 
104
129
  // Process these indicators separately (ensuring their execution in the order of initialization)
105
- for (const { key, params} of inputParams)
130
+ for (const { key, params} of executionParams)
106
131
  {
107
132
  mainFunctions[key](main, index, ...params)
108
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) => {
@@ -0,0 +1,61 @@
1
+ # Core indicators
2
+
3
+ Local numeric replacements for the `Faster*` implementations used from
4
+ `trading-signals@5.0.4`. The existing constructor arguments, `update(value,
5
+ replace?)`, `isStable`, and `getResult()` interface remain available. The
6
+ numeric series also retain result/extrema and replacement semantics. These
7
+ are not replacements for the arbitrary-precision `Big` classes or a promise
8
+ of compatibility with other trading-signals versions.
9
+
10
+ ```js
11
+ import { FasterEMA } from './index.js'
12
+
13
+ const ema = new FasterEMA(14)
14
+ ema.update(price)
15
+ const value = ema.isStable ? ema.getResult() : NaN
16
+ ```
17
+
18
+ `getResult()` still throws `NotEnoughDataError` during warm-up for API
19
+ compatibility. All readiness checks are non-throwing; project handlers check
20
+ readiness before reading. EMA still returns its provisional result from
21
+ `update()` before it becomes stable, so do not substitute that return value
22
+ for a readiness-checked read.
23
+
24
+ ## Storage and calculation choices
25
+
26
+ - EMA uses scalar state and a readiness counter.
27
+ - SMA uses a bounded circular `Float64Array`. It sums oldest to newest to
28
+ preserve floating-point rounding and recovery after NaN/Infinity. It remains
29
+ O(period) per update rather than adopting a different running-sum formula.
30
+ - WSMA releases its seed SMA after initialization, then updates in O(1).
31
+ - RSI retains two prices instead of the entire input history.
32
+ - MACD uses a capped counter instead of retaining an unused price window.
33
+ - Bollinger uses a bounded numeric ring and two ordered passes, without
34
+ intermediate deviation arrays. Its size + 1 warm-up is intentional.
35
+ - Stochastic uses a bounded candle-reference ring, scans extrema without
36
+ temporary arrays, and preserves the original zero/NaN smoothing behavior.
37
+ - TR/ATR/DX/ADX retain the existing arithmetic and replacement behavior.
38
+
39
+ Scalar rings switch once to a bounded ordinary Array if a non-number is
40
+ provided. This preserves existing JavaScript coercion for custom mapped
41
+ columns without adding object storage for normal numeric input. NaN and
42
+ Infinity remain numeric values and do not trigger this fallback. Candle
43
+ references are retained where the reference implementation observes mutations
44
+ of previously supplied objects. Private buffer layouts intentionally differ.
45
+
46
+ ## Verification
47
+
48
+ `npm test` compares the core classes directly with the installed
49
+ trading-signals reference, checks bounded retained storage, and exercises all
50
+ public indicators, precision/log modes, lags, callbacks, and config replay.
51
+ The dependency is retained as a regression oracle; production source imports
52
+ only these local classes. Preserve the reference version in the lockfile.
53
+
54
+ `node test/test.js --baseline /path/to/original/index.js --benchmark` also
55
+ compares the full pipeline with a pre-change checkout. Benchmarks report
56
+ local timings and retained array slots/buffer bytes, not total heap usage or
57
+ performance guarantees. Results must remain exact; changing summation order,
58
+ warm-up, or quirks is a separate behavior change.
59
+
60
+ Adapted portions retain the upstream MIT notice in `indicator.js`, also
61
+ preserved in the generated distribution's license notice.