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.
- package/AGENTS.md +105 -0
- package/README.md +415 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
- package/index.js +93 -55
- package/package.json +1 -1
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +47 -22
- package/src/core-functions/pushToMain.js +1 -3
- package/src/core-indicators/README.md +61 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +82 -0
- package/src/core-indicators/movingAverages.js +88 -0
- package/src/core-indicators/rsi.js +41 -0
- package/src/core-indicators/volatility.js +107 -0
- package/src/moving-averages/bollingerBands.js +11 -26
- package/src/moving-averages/donchianChannel.js +30 -29
- package/src/moving-averages/heikenAshi.js +23 -33
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +7 -23
- package/src/moving-averages/relativeVolume.js +23 -21
- package/src/oscillators/rsi.js +7 -26
- package/src/oscillators/stochastic.js +6 -19
- package/src/oscillators/volumeDelta.js +36 -28
- package/src/oscillators/volumeOscillator.js +29 -31
- package/src/studies/candleFeatures.js +12 -20
- package/src/studies/dateTime.js +22 -27
- package/src/studies/findCrosses.js +16 -7
- package/src/studies/lag.js +26 -13
- package/src/studies/mapCols.js +14 -15
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +64 -0
- package/src/utilities/numberUtilities.js +2 -0
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +21 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +533 -0
- 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,
|
|
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(
|
|
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}) =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
-
|
|
57
|
+
executionParams
|
|
60
58
|
} = main;
|
|
61
59
|
|
|
62
60
|
|
|
63
|
-
validateInputParams(
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
101
|
+
} else {
|
|
102
|
+
for(const [key, formaterKey] of Object.entries(inputTypes))
|
|
95
103
|
{
|
|
96
|
-
|
|
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
|
|
130
|
+
for (const { key, params} of executionParams)
|
|
106
131
|
{
|
|
107
132
|
mainFunctions[key](main, index, ...params)
|
|
108
133
|
}
|
|
@@ -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.
|