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.
- package/AGENTS.md +113 -0
- package/README.md +456 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
- package/index.js +112 -84
- package/package.json +2 -5
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +35 -30
- package/src/core-functions/pushToMain.js +8 -27
- package/src/core-indicators/README.md +60 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +81 -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 +39 -31
- package/src/moving-averages/donchianChannel.js +62 -36
- package/src/moving-averages/heikenAshi.js +33 -52
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +19 -25
- package/src/moving-averages/relativeVolume.js +30 -24
- package/src/oscillators/mfi.js +56 -0
- 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 +24 -32
- package/src/studies/candleFeatures.js +40 -28
- package/src/studies/dateTime.js +54 -56
- package/src/studies/findCrosses.js +18 -16
- package/src/studies/lag.js +32 -20
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +31 -0
- package/src/utilities/numberUtilities.js +5 -3
- package/src/utilities/precisionMultiplier.js +11 -6
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +26 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +1230 -0
- package/webpack.config.js +0 -11
- package/src/moving-averages/vidya.js +0 -181
- 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,
|
|
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,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 =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
58
|
+
executionParams
|
|
60
59
|
} = main;
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
validateInputParams(
|
|
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
|
|
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)})
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
115
|
-
if (!areKeyValuesValid(
|
|
119
|
+
// Preserve the missing/NaN/null predicate, including Infinity behavior.
|
|
120
|
+
if (!areKeyValuesValid(validationColumns, index)) {
|
|
116
121
|
main.invalidValueIndex = index
|
|
117
122
|
}
|
|
118
123
|
}
|