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