ohlcv-indicators 3.5.41 → 3.5.43

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/index.js CHANGED
@@ -8,7 +8,8 @@ import {
8
8
  validateBoolean,
9
9
  validateNumber,
10
10
  validateArrayOfRanges,
11
- validateInputParams
11
+ validateInputParams,
12
+ validateString
12
13
  } from './src/utilities/validators.js'
13
14
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
14
15
  import { pushToMain } from './src/core-functions/pushToMain.js'
@@ -16,33 +17,45 @@ import { assignTypes } from './src/utilities/assignTypes.js'
16
17
  import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
17
18
  import { buildArray } from './src/utilities/assignTypes.js'
18
19
  import { dateOutputFormaters } from './src/utilities/dateUtilities.js'
20
+ import { defaultMapColsCallback } from './src/studies/mapCols.js'
21
+ import { getOrderFromArray } from './src/utilities/order.js'
19
22
 
20
- //precomputed
21
- import { precomputeMovingAverages } from './src/moving-averages/movingAverages.js'
23
+ import {
24
+ defaultYCallback,
25
+ validRegressors,
26
+ validClassifiers,
27
+ exportTrainedModels
28
+ } from './src/machine-learning/ml-config.js'
29
+
30
+ import { normalizeMinMax } from './src/machine-learning/ml-utilities.js'
22
31
 
23
32
  /**
24
33
  * Class OHLCV_INDICATORS
25
34
  *
26
35
  * This class provides methods for calculating and managing technical indicators
27
36
  * on financial OHLCV (Open, High, Low, Close, Volume) data. It enables users
28
- * to parallel compute various technical indicators in 1 single loop.
37
+ * to parallel compute various technical indicators in 1 single loop for standard indicators and a second loop for ML regressors and classifier.
29
38
  * OHLCV datasets.
30
39
  */
31
40
 
32
41
  export default class OHLCV_INDICATORS {
33
- constructor({input, ticker = null, precision = true, inputParams = null, chunkProcess = 2000}) {
42
+ constructor({input, ticker = null, precision = true, inputParams = null, chunkProcess = 2000, ML = {}}) {
34
43
 
35
44
  validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
36
45
  if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
37
46
 
38
47
  validateBoolean(precision, 'precision', 'constructor')
39
48
  validateNumber(chunkProcess, {min: 100, max: 50000, allowDecimals: false}, 'chunkProcess', 'constructor')
49
+ validateObject(ML, 'ML', 'constructor')
40
50
 
41
-
51
+ this.notNumberKeys = new Set()
42
52
  this.chunkProcess = chunkProcess
43
- this.firstRow = input[0]
44
53
 
45
- const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
54
+ this.input = [...input]
55
+ this.len = this.input.length
56
+ this.firstRow = this.input[0]
57
+
58
+ const {inputTypes, arrayTypes} = assignTypes(this)
46
59
 
47
60
  this.inputTypes = inputTypes
48
61
  this.arrayTypes = arrayTypes
@@ -50,11 +63,8 @@ export default class OHLCV_INDICATORS {
50
63
 
51
64
  this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
52
65
  this.isComputed = false
53
- this.input = input
54
- this.priceBased = new Set(['open', 'high', 'low', 'close'])
55
- this.len = input.length
66
+
56
67
  this.instances = {}
57
- this.crossPairsList = []
58
68
  this.verticalOhlcv = {}
59
69
  this.verticalOhlcvKeyNames = []
60
70
  this.verticalOhlcvTempCols = new Set()
@@ -67,8 +77,19 @@ export default class OHLCV_INDICATORS {
67
77
  this.precision = precision
68
78
  this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
69
79
  this.scaledGroups = {}
70
- this.scaledLabels = new Set()
71
- this.models = {}
80
+ this.isAlreadyComputed = new Set()
81
+
82
+ const {classes: mlClasses} = ML
83
+
84
+ if(typeof mlClasses !== 'undefined') validateObject(mlClasses, 'ML.classes', 'constructor')
85
+
86
+ this.ML = {
87
+ models: {},
88
+ classes: mlClasses,
89
+ metrics: {},
90
+ featureCols: {}
91
+ }
92
+ this.processSecondaryLoop = false
72
93
 
73
94
  this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
74
95
 
@@ -78,6 +99,7 @@ export default class OHLCV_INDICATORS {
78
99
  this.inputParams = inputParams
79
100
  this.compute()
80
101
  }
102
+ else
81
103
  {
82
104
  this.inputParams = []
83
105
  }
@@ -99,14 +121,15 @@ export default class OHLCV_INDICATORS {
99
121
  const {
100
122
  precisionMultiplier,
101
123
  precision,
102
- invalidValueIndex,
103
124
  len,
125
+ invalidValueIndex,
104
126
  verticalOhlcv,
105
- priceBased,
106
127
  arrayTypes,
107
- verticalOhlcvTempCols
128
+ verticalOhlcvTempCols,
129
+ notNumberKeys
108
130
  } = this
109
131
  const result = {}
132
+
110
133
  const startIndex = skipNull ? invalidValueIndex + 1 : 0
111
134
  const newLen = len - startIndex
112
135
 
@@ -114,7 +137,7 @@ export default class OHLCV_INDICATORS {
114
137
 
115
138
  if(verticalOhlcvTempCols.has(key)) continue
116
139
 
117
- const shouldApplyPrecision = priceBased.has(key) && precision
140
+ const shouldApplyPrecision = precision && !notNumberKeys.has(key)
118
141
  result[key] = buildArray(arrayTypes[key], newLen)
119
142
 
120
143
  for (let x = startIndex; x < len; x++)
@@ -162,7 +185,8 @@ export default class OHLCV_INDICATORS {
162
185
  this.compute()
163
186
 
164
187
  const {dateFormat = 'string'} = options
165
- validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
188
+
189
+ validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getData')
166
190
 
167
191
  return verticalToHorizontal({
168
192
  skipNull: false,
@@ -188,13 +212,14 @@ export default class OHLCV_INDICATORS {
188
212
  // Only run the full loop once (or when new data appears later,
189
213
  // if you extend this to reset isComputed elsewhere)
190
214
  if (this.len > 0) {
191
- mainLoop(this.input, this);
192
- this.isComputed = true;
215
+ mainLoop(this.input, this)
216
+
217
+ this.isComputed = true;
193
218
 
194
- //flushing after mainLoop
195
- this.input = []
196
- this.instances = {}
197
- this.firstRow = []
219
+ //flushing after mainLoop
220
+ this.input = []
221
+ this.instances = {}
222
+ this.firstRow = []
198
223
  }
199
224
 
200
225
  return this;
@@ -202,16 +227,42 @@ export default class OHLCV_INDICATORS {
202
227
 
203
228
 
204
229
 
205
- crossPairs(arr = [])
230
+ crossPairs(arr = [], options = {})
206
231
  {
207
232
  const methodName = 'crossPairs'
208
233
 
209
234
  isAlreadyComputed(this)
210
-
211
235
  validateArray(arr, 'arr', methodName)
236
+ validateObject(options, 'options', methodName)
237
+
238
+ const {limit = 125, oneHot = false} = options
239
+
240
+ validateNumber(limit, {min: 2, max: this.len}, 'options.limit', methodName)
241
+ validateBoolean(oneHot, 'options.oneHot', methodName)
242
+
243
+ const orderArr = []
244
+
245
+ for (const {fast = '', slow = ''} of arr) {
246
+
247
+ if (fast === '' || slow === '') {
248
+ throw new Error(
249
+ `Invalid crossPairs[${fast}_${slow}]: Object property “fast” must be a non-null column name and “slow” must be a non-null column name or integer.`
250
+ );
251
+ }
252
+
253
+ orderArr.push(fast.toString(), slow.toString())
254
+ }
255
+
256
+ if(this.isAlreadyComputed.has(methodName))
257
+ {
258
+ throw new Error(`You can only call the "${methodName}" method once.`)
259
+ }
260
+
261
+ this.isAlreadyComputed.add(methodName)
262
+
263
+ const order = getOrderFromArray(orderArr, methodName)
212
264
 
213
- this.crossPairsList = [...this.crossPairsList, ...arr]
214
- this.inputParams.push({key: methodName, params: [this.crossPairsList]})
265
+ this.inputParams.push({key: methodName, order, params: [arr, {limit, oneHot}]})
215
266
 
216
267
  return this
217
268
  }
@@ -222,19 +273,11 @@ export default class OHLCV_INDICATORS {
222
273
  const methodName = 'lag'
223
274
 
224
275
  isAlreadyComputed(this)
225
-
226
276
  validateArray(colKeys, 'colKeys', methodName)
227
277
  validateNumber(lookback, {min:1, max: this.len, allowDecimals: false}, 'lookback', methodName)
228
278
 
229
- this.inputParams.push({key: methodName, params: [colKeys, lookback]})
230
-
231
- for(const key of colKeys)
232
- {
233
- if(this.priceBased.has(key))
234
- {
235
- this.priceBased.add(key);
236
- }
237
- }
279
+ const order = getOrderFromArray(colKeys, methodName)
280
+ this.inputParams.push({key: methodName, order, params: [colKeys, lookback]})
238
281
 
239
282
  return this;
240
283
  }
@@ -256,7 +299,7 @@ export default class OHLCV_INDICATORS {
256
299
 
257
300
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
258
301
 
259
- this.inputParams.push({key: methodName, params: [size, {lag}]})
302
+ this.inputParams.push({key: methodName, order: 0, params: [size, {lag}]})
260
303
 
261
304
  return this
262
305
  }
@@ -278,7 +321,7 @@ export default class OHLCV_INDICATORS {
278
321
  if(upper !== null) validateNumber(upper, {min: 0.001, max: 100, allowDecimals: true}, 'options.upper', 'atr')
279
322
  if(lower !== null) validateNumber(lower, {min: 0.001, max: 100, allowDecimals: true}, 'options.lower', 'atr')
280
323
 
281
- this.inputParams.push({key: methodName, params: [size, {lag, percentage, upper, lower}]})
324
+ this.inputParams.push({key: methodName, order: 0, params: [size, {lag, percentage, upper, lower}]})
282
325
 
283
326
  return this
284
327
  }
@@ -294,11 +337,12 @@ export default class OHLCV_INDICATORS {
294
337
 
295
338
  const {target = 'close', lag = 0} = options
296
339
 
340
+ validateString(target, 'options.target', methodName)
297
341
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
298
342
 
299
- const precomputed = precomputeMovingAverages({main: this, size, target, lag, methodName})
343
+ const order = getOrderFromArray([target], methodName)
300
344
 
301
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag, precomputed}]})
345
+ this.inputParams.push({key: methodName, order, params: [methodName, size, {target, lag}]})
302
346
 
303
347
  return this
304
348
  }
@@ -313,16 +357,75 @@ export default class OHLCV_INDICATORS {
313
357
 
314
358
  const {target = 'close', lag = 0} = options
315
359
 
360
+ validateString(target, 'options.target', methodName)
316
361
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
317
362
 
363
+ const order = getOrderFromArray([target], methodName)
364
+
365
+ this.inputParams.push({key: methodName, order, params: [methodName, size, {target, lag}]})
366
+
367
+ return this
368
+ }
369
+
370
+ vidya(size = 14, smoothing = 20, options = {}) {
371
+
372
+ const methodName = 'vidya'
373
+
374
+ isAlreadyComputed(this)
375
+
376
+ validateNumber(size, {min: 2, max: this.len, allowDecimals: false}, 'size', methodName)
377
+ validateNumber(smoothing, {min: size, max: this.len, allowDecimals: false}, 'smoothing', methodName)
378
+ validateObject(options, 'options', methodName)
318
379
 
319
- const precomputed = precomputeMovingAverages({main: this, size, target, lag, methodName})
380
+ const {target = 'close', lag = 0} = options
320
381
 
321
- this.inputParams.push({key: methodName, params: [methodName, size, {target, lag, precomputed}]})
382
+ validateString(target, 'options.target', methodName)
383
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
384
+
385
+ const order = getOrderFromArray([target], methodName)
386
+
387
+ this.inputParams.push({key: methodName, order, params: [size, smoothing, {target, lag}]})
322
388
 
323
389
  return this
324
390
  }
325
391
 
392
+ stochastic(kPeriod = 14, kSlowingPeriod = 3, dPeriod = 3, options = {}){
393
+ const methodName = 'stochastic'
394
+
395
+ const {len: max} = this
396
+
397
+ validateNumber(kPeriod, {min: 1, max, allowDecimals: false}, 'kPeriod', methodName)
398
+ validateNumber(kSlowingPeriod, {min: 1, max, allowDecimals: false}, 'kSlowingPeriod', methodName)
399
+ validateNumber(dPeriod, {min: 1, max, allowDecimals: false}, 'dPeriod', methodName)
400
+ validateObject(options, 'options', methodName)
401
+
402
+ let parser = v => v
403
+ let prefix = ''
404
+
405
+ const {lag = 0} = options
406
+
407
+ validateNumber(lag, {min: 0, max, allowDecimals: false}, 'options.lag', methodName)
408
+
409
+ let minmax = options.minmax
410
+
411
+ if(Array.isArray(minmax) || (typeof minmax === 'boolean' && minmax === true))
412
+ {
413
+ if(Array.isArray(minmax)){
414
+ validateArrayOfRanges(minmax, 'options.minmax', methodName)
415
+ parser = value => (minmax !== null) ? normalizeMinMax(value, 0, 100, minmax) : value
416
+ } else
417
+ {
418
+ validateBoolean(minmax, 'options.minmax', methodName)
419
+ minmax = [0, 1]
420
+ parser = value => (minmax !== null) ? normalizeMinMax(value, 0, 100, minmax) : value
421
+ }
422
+ prefix = 'minmax_'
423
+ }
424
+
425
+ this.inputParams.push({key: methodName, order: 0, params: [kPeriod, kSlowingPeriod, dPeriod, {minmax, prefix, parser, lag}]})
426
+
427
+ return this
428
+ }
326
429
 
327
430
  macd(fast = 12, slow = 26, signal = 9, options = {}) {
328
431
 
@@ -337,12 +440,15 @@ export default class OHLCV_INDICATORS {
337
440
 
338
441
  const {target = 'close', lag = 0} = options
339
442
 
443
+ validateString(target, 'options.target', methodName)
340
444
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
341
445
 
342
446
  const instanceKey = `${fast}_${slow}_${signal}${target === 'close' ? '' : `_${target}`}`
343
447
  const precomputed = {instanceKey}
344
448
 
345
- this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
449
+ const order = getOrderFromArray([target], methodName)
450
+
451
+ this.inputParams.push({key: methodName, order, params: [fast, slow, signal, {target, lag, precomputed}]})
346
452
 
347
453
  return this
348
454
 
@@ -359,13 +465,15 @@ export default class OHLCV_INDICATORS {
359
465
 
360
466
  const {target = 'close', height = false, range = [], lag = 0, decimals = null} = options
361
467
 
362
-
468
+ validateString(target, 'options.target', methodName)
363
469
  validateArray(range, 'options.range', methodName)
364
470
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
365
471
  validateBoolean(height, 'options.height', methodName)
366
472
  if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
367
-
368
- this.inputParams.push({key: methodName, params: [size, stdDev, {target, height, range, lag, decimals}]});
473
+
474
+ const order = getOrderFromArray([target], methodName)
475
+
476
+ this.inputParams.push({key: methodName, order, params: [size, stdDev, {target, height, range, lag, decimals}]});
369
477
 
370
478
  return this;
371
479
  }
@@ -380,10 +488,30 @@ export default class OHLCV_INDICATORS {
380
488
  validateObject(options, 'options', methodName)
381
489
 
382
490
  const {target = 'close', lag = 0} = options
383
-
491
+ let minmax = options.minmax
492
+ validateString(target, 'options.target', methodName)
384
493
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
385
494
 
386
- this.inputParams.push({key: methodName, params: [size, {target, lag}]})
495
+ let parser = v => v
496
+ let prefix = ''
497
+
498
+ if(Array.isArray(minmax) || (typeof minmax === 'boolean' && minmax === true))
499
+ {
500
+ if(Array.isArray(minmax)){
501
+ validateArrayOfRanges(minmax, 'options.minmax', methodName)
502
+ parser = value => (minmax !== null) ? normalizeMinMax(value, 0, 100, minmax) : value
503
+ } else
504
+ {
505
+ validateBoolean(minmax, 'options.minmax', methodName)
506
+ minmax = [0, 1]
507
+ parser = value => (minmax !== null) ? normalizeMinMax(value, 0, 100, minmax) : value
508
+ }
509
+ prefix = 'minmax_'
510
+ }
511
+
512
+ const order = getOrderFromArray([target], methodName)
513
+
514
+ this.inputParams.push({key: methodName, order, params: [size, {target, lag, parser, prefix, minmax}]})
387
515
 
388
516
  return this
389
517
  }
@@ -405,13 +533,13 @@ export default class OHLCV_INDICATORS {
405
533
  validateBoolean(height, 'options.height', methodName)
406
534
  if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
407
535
 
408
- this.inputParams.push({ key: methodName, params: [size, offset, { height, range, lag, decimals}] });
536
+ this.inputParams.push({ key: methodName, order: 0, params: [size, offset, { height, range, lag, decimals}] });
409
537
 
410
538
  return this;
411
539
  }
412
540
 
413
541
 
414
- volumeOscillator(fastSize = 5, slowSize = 10, options = {})
542
+ volumeOscillator(fastsize = 5, slowsize = 10, options = {})
415
543
  {
416
544
  const methodName = 'volumeOscillator'
417
545
 
@@ -421,15 +549,15 @@ export default class OHLCV_INDICATORS {
421
549
 
422
550
  isAlreadyComputed(this)
423
551
 
424
- validateNumber(fastSize, {min: 1, max: this.len, allowDecimals: false}, 'fastSize', methodName)
425
- validateNumber(slowSize, {min: fastSize, max: this.len, allowDecimals: false}, 'slowSize', methodName)
552
+ validateNumber(fastsize, {min: 1, max: this.len, allowDecimals: false}, 'fastsize', methodName)
553
+ validateNumber(slowsize, {min: fastsize, max: this.len, allowDecimals: false}, 'slowsize', methodName)
426
554
  validateObject(options, 'options', methodName)
427
555
 
428
556
  const {lag = 0} = options
429
557
 
430
558
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
431
559
 
432
- this.inputParams.push({key: methodName, params: [fastSize, slowSize, {lag}]})
560
+ this.inputParams.push({key: methodName, order: 0, params: [fastsize, slowsize, {lag}]})
433
561
  return this
434
562
  }
435
563
  dateTime(options = {})
@@ -441,37 +569,375 @@ export default class OHLCV_INDICATORS {
441
569
 
442
570
  validateObject(options, 'options', methodName)
443
571
 
444
- const {lag = 0} = options
572
+ const {lag = 0, oneHot = false} = options
445
573
 
446
574
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
575
+ validateBoolean(oneHot, 'options.oneHot', methodName)
576
+
577
+ const prefix = (oneHot) ? 'one_hot_' : ''
578
+
579
+
580
+ const colKeySizes = {
581
+ [`${prefix}month`]: 12,
582
+ [`${prefix}day_of_the_month`]: 31,
583
+ [`${prefix}day_of_the_week`]: 7,
584
+ [`${prefix}hour`]: 24,
585
+ [`${prefix}minute`]: 60
586
+ }
587
+
588
+ const precompute = {
589
+ prefix,
590
+ colKeySizes,
591
+ colKeys: Object.keys(colKeySizes)
592
+ }
447
593
 
448
- this.inputParams.push({key: methodName, params: [{lag}]})
594
+ this.inputParams.push({key: methodName, order: 0, params: [{lag, oneHot, precompute}]})
449
595
  return this
450
596
  }
451
597
 
452
- scaler(size, colKeys = [], options = {})
598
+ scaler(type = 'zscore', size, options = {})
453
599
  {
454
- const methodName = 'scaler'
600
+ let methodName = 'scaler'
455
601
 
456
602
  isAlreadyComputed(this)
457
603
 
458
604
  validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
459
- validateArray(colKeys, 'colKeys', methodName)
605
+
606
+ validateObject(options, 'options', methodName)
460
607
 
461
- const {group = false, range = [0, 1], lag = 0, type = 'zscore', decimals = null, pca = null} = options
608
+ const {group = false, range = [0, 1], lag = 0, colKeys = []} = options
462
609
 
610
+ validateArray(colKeys, 'options.colKeys', methodName)
463
611
  validateBoolean(group, 'options.group', methodName)
464
- if(pca !== null) validateObject(pca, 'options.pca', methodName)
465
612
  validateArrayOfRanges(range, 'options.range', methodName)
466
613
  validateArrayOptions(['minmax', 'zscore'], type, 'options.type', methodName)
467
614
 
468
- if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
469
-
470
- const groupKey = (group) ? `${type}_${size}_group_${colKeys.join('_')}` : ''
615
+ const groupKey = `${type}_${size}`
471
616
  const groupKeyLen = colKeys.length
472
617
  const precomputed = {groupKey, groupKeyLen}
473
618
 
474
- this.inputParams.push({key: methodName, params: [size, colKeys, {type, group, range, lag, precomputed, decimals, pca}]})
619
+ let secondaryLoop = false
620
+
621
+ const order = getOrderFromArray(colKeys, methodName)
622
+
623
+ if(order >= 10)
624
+ {
625
+ secondaryLoop = true
626
+ }
627
+
628
+ this.inputParams.push({key: methodName, order, params: [size, colKeys, {type, group, range, lag, precomputed, secondaryLoop}]})
629
+ return this
630
+ }
631
+
632
+ classifier(trainingSize = 200, options = {})
633
+ {
634
+ isAlreadyComputed(this)
635
+
636
+ const methodName = 'classifier'
637
+ validateNumber(trainingSize, {max: this.len, allowDecimals: false}, 'trainingSize', methodName)
638
+ validateObject(options, 'options', methodName)
639
+
640
+ const {
641
+ retrain = false,
642
+ trainingCols = [],
643
+ type = 'KNN',
644
+ lookback = 0,
645
+ findGroups = [],
646
+ predictions = 2,
647
+ horizon = 2,
648
+ modelArgs = undefined,
649
+ filterCallback = () => true
650
+ } = options
651
+
652
+ if (modelArgs !== undefined && (typeof modelArgs !== 'object' || modelArgs === null || Array.isArray(modelArgs))) throw new TypeError(`"modelArgs" must be either undefined or a plain object in ${type}`);
653
+
654
+ const yCallback = options.yCallback ?? defaultYCallback
655
+
656
+ if(typeof yCallback !== 'function')
657
+ {
658
+ throw new Error(`"yCallback" must be a function in the following format:\n\n---\n\nconst yCallback = ${defaultYCallback.toString()}\n\n---\n\n`)
659
+ }
660
+
661
+ validateArrayOptions(Object.keys(validClassifiers), type, 'options.type', methodName)
662
+
663
+ if (!this.ML.classes.hasOwnProperty(type)) {
664
+ throw new Error(
665
+ `"${type}" isn’t available because its library wasn’t imported into OHLCV_INDICATORS.ML.`
666
+ )
667
+ }
668
+
669
+ validateBoolean(retrain, 'options.retrain', methodName)
670
+ validateArray(trainingCols, 'options.trainingCols', methodName)
671
+ validateNumber(horizon, {min: 1, max: this.len, allowDecimals: false}, 'options.horizon', methodName)
672
+ validateNumber(lookback, {max: 0, allowDecimals: false}, 'options.lookback', methodName)
673
+ validateNumber(predictions, {min: 1, allowDecimals: false}, 'options.predictions', methodName)
674
+
675
+ const orderArr = [...trainingCols]
676
+
677
+ if(validateArray(findGroups, 'options.findGroups', 'scaler') && findGroups.length > 0)
678
+ {
679
+ for (const { type = '', size, groupName = '' } of findGroups) {
680
+
681
+ if (!type || (!size && !groupName) || (size && groupName)) {
682
+ throw new Error(`If "options.findGroups" array is set, each item must be an object that includes the "type" (mandatory) and either "size" or "groupName" (choose 1) used to locate previously scaled (minmax or zscore) groups.`);
683
+ }
684
+
685
+ orderArr.push(type.toString(), groupName.toString());
686
+ }
687
+ } else {
688
+ if(trainingCols.length === 0)
689
+ {
690
+ throw new Error(`"trainingCols" array is empty in ${methodName}`)
691
+ }
692
+ }
693
+
694
+ const {shortName, flatY} = validClassifiers[type]
695
+
696
+ const prefix = `cla_${shortName}_${trainingSize}_prediction`
697
+
698
+ if(this.isAlreadyComputed.has(prefix))
699
+ {
700
+ throw new Error(
701
+ `Each classifier must have a unique pair of “type” and trainingSize.\n` +
702
+ `This rule ensures that your output columns are labeled unambiguously.\n` +
703
+ `You provided a duplicate: type="${type}" with trainingSize=${trainingSize}.`
704
+ );
705
+ }
706
+
707
+ const precompute = {
708
+ lookbackAbs: Math.abs(lookback) + 1,
709
+ flatY,
710
+ prefix
711
+ }
712
+
713
+ this.isAlreadyComputed.add(prefix)
714
+
715
+ const order = getOrderFromArray(orderArr, methodName)
716
+
717
+ const modelConfig = validClassifiers[type]
718
+ const modelClass = this.ML.classes[type]
719
+
720
+ this.inputParams.push({key: methodName, order, params: [trainingSize, {yCallback, predictions, lookback, retrain, trainingCols, findGroups, horizon, type, modelArgs, precompute, filterCallback, modelConfig, modelClass}]})
721
+
722
+
723
+ return this
724
+ }
725
+
726
+ regressor(trainingSize = 200, options = {})
727
+ {
728
+ isAlreadyComputed(this)
729
+
730
+ const methodName = 'regressor'
731
+
732
+
733
+ validateNumber(trainingSize, {max: this.len, allowDecimals: false}, 'trainingSize', methodName)
734
+ validateObject(options, 'options', methodName)
735
+
736
+ const {
737
+ retrain = false,
738
+ target = 'close',
739
+ predictions = 1,
740
+ trainingCols = [],
741
+ type = 'SimpleLinearRegression',
742
+ lookback = 0,
743
+ findGroups = [],
744
+ modelArgs = undefined,
745
+ filterCallback = () => true
746
+ } = options
747
+
748
+ validateArrayOptions(Object.keys(validRegressors), type, 'type', methodName)
749
+
750
+ if (!this.ML.classes.hasOwnProperty(type)) {
751
+ throw new Error(
752
+ `"${type}" isn’t available because its library wasn’t imported into OHLCV_INDICATORS.ML.`
753
+ )
754
+ }
755
+
756
+ //regressor
757
+ validateBoolean(retrain, 'options.retrain', methodName)
758
+ validateString(target, 'options.target', methodName)
759
+ validateNumber(predictions, {min: 1, allowDecimals: false}, 'predictions', methodName)
760
+ validateNumber(lookback, {max: 0, allowDecimals: false}, 'lookback', methodName)
761
+ validateArray(trainingCols, 'options.trainingCols', methodName)
762
+
763
+ const {shortName, flatX, flatY} = validRegressors[type]
764
+ const prefix = `reg_${shortName}_${trainingSize}_${target}_prediction`
765
+ const orderArr = [...trainingCols]
766
+
767
+ if(validateArray(findGroups, 'options.findGroups', 'scaler') && findGroups.length > 0)
768
+ {
769
+ for (const { type = '', size, groupName = '' } of findGroups) {
770
+
771
+ if (!type || (!size && !groupName) || (size && groupName)) {
772
+ throw new Error(`If "options.findGroups" array is set, each item must be an object that includes the "type" (mandatory) and either "size" or "groupName" (choose 1) used to locate previously scaled (minmax or zscore) groups.`);
773
+ }
774
+
775
+ orderArr.push(type.toString(), groupName.toString());
776
+ }
777
+
778
+ } else {
779
+ if(trainingCols.length === 0 && flatX === false)
780
+ {
781
+ throw new Error(`"trainingCols" array is empty in ${methodName} ${type}`)
782
+ }
783
+ }
784
+
785
+ if(flatX) {
786
+ if(trainingCols.length > 0)
787
+ {
788
+ throw new Error(`If regressor type is ${type} then leave "options.trainingCols" array empty.`)
789
+ }
790
+ if(lookback > 0)
791
+ {
792
+ throw new Error(`If regressor type is ${type} then "options.lookback" must be 0.`)
793
+ }
794
+ if(findGroups.length > 0)
795
+ {
796
+ throw new Error(`If regressor type is ${type} then "options.findGroups" must be null.`)
797
+ }
798
+ else {
799
+ trainingCols.push(target)
800
+ }
801
+ }
802
+
803
+ if(this.isAlreadyComputed.has(prefix))
804
+ {
805
+ throw new Error(
806
+ `Each regressor must have a unique "type", "trainingSize" and "target".\n` +
807
+ `This rule ensures that your output columns are labeled unambiguously.\n` +
808
+ `You provided a duplicate: type="${type}", trainingSize=${trainingSize} target=${target}.`
809
+ );
810
+ }
811
+
812
+ const precompute = {
813
+ lookbackAbs: Math.abs(lookback) + 1,
814
+ flatX,
815
+ flatY,
816
+ prefix
817
+ }
818
+
819
+ this.isAlreadyComputed.add(prefix)
820
+
821
+
822
+ const order = getOrderFromArray(orderArr, methodName)
823
+
824
+ const modelConfig = validRegressors[type]
825
+ const modelClass = this.ML.classes[type]
826
+
827
+ this.inputParams.push({key: methodName, order, params: [trainingSize, {target, predictions, retrain, lookback, trainingCols, findGroups, type, modelArgs, precompute, filterCallback, modelConfig, modelClass}]})
828
+
829
+ return this
830
+ }
831
+
832
+ pca(suffix, trainingSize, options = {})
833
+ {
834
+ const methodName = 'pca'
835
+ validateNumber(trainingSize, {min: 1, max: this.len}, 'trainingSize', methodName)
836
+ validateString(suffix, 'suffix', methodName)
837
+ validateObject(options, 'options', methodName)
838
+
839
+ const {
840
+ findGroups = [],
841
+ trainingCols = [],
842
+ lookback = 0,
843
+ filterCallback = () => true
844
+ } = options
845
+
846
+
847
+ validateArray(trainingCols, 'options.trainigCols', methodName)
848
+ validateNumber(lookback, {min: 0, max: this.len, allowDecimals: false}, 'options.lookback', methodName)
849
+ if(typeof options.modelArgs !== 'undefined') {
850
+ validateObject(options.modelArgs, 'options.modelArgs', methodName)
851
+ } else {
852
+ options.modelArgs = {}
853
+ }
854
+
855
+ //modelArgs
856
+
857
+ const modelArgs = {
858
+ showSource: false,
859
+ storeModel: false,
860
+ isCovarianceMatrix: false,
861
+ method: 'NIPALS', //SVD, covarianceMatrix or NIPALS
862
+ center: true,
863
+ scale: false,
864
+ nCompNIPALS: 5,
865
+ ignoreZeroVariance: false,
866
+ ...options.modelArgs
867
+ }
868
+
869
+ validateBoolean(modelArgs.showSource, 'options.modelArgs.showSource', methodName)
870
+ validateBoolean(modelArgs.storeModel, 'options.modelArgs.storeModel', methodName)
871
+ validateBoolean(modelArgs.isCovarianceMatrix, 'options.modelArgs.isCovarianceMatrix', methodName)
872
+ validateBoolean(modelArgs.center, 'options.modelArgs.center', methodName)
873
+ validateBoolean(modelArgs.scale, 'options.modelArgs.scale', methodName)
874
+ validateBoolean(modelArgs.ignoreZeroVariance, 'options.modelArgs.ignoreZeroVariance', methodName)
875
+ validateArrayOptions(['SVD', 'NIPALS', 'covarianceMatrix'], modelArgs.method, 'options.modelArgs.method', methodName)
876
+ validateNumber(modelArgs.nCompNIPALS, {min: 1}, 'options.modelArgs.nCompNIPALS', methodName)
877
+
878
+
879
+
880
+
881
+ const orderArr = []
882
+
883
+ if(validateArray(findGroups, 'options.findGroups', 'pca') && findGroups.length > 0)
884
+ {
885
+
886
+ for (const { type = '', size, groupName = '' } of findGroups) {
887
+
888
+ if (!type || (!size && !groupName) || (size && groupName)) {
889
+ throw new Error(`If "options.findGroups" array is set, each item must be an object that includes the "type" (mandatory) and either "size" or "groupName" (choose 1) used to locate previously scaled (minmax or zscore) groups.`);
890
+ }
891
+
892
+ orderArr.push(type.toString(), groupName.toString())
893
+ }
894
+ }
895
+
896
+ const order = getOrderFromArray(orderArr, methodName)
897
+ const prefix = `pca_${suffix}_${trainingSize}`
898
+
899
+ if(this.isAlreadyComputed.has(prefix)) {
900
+ throw new Error(
901
+ `Each regressor must have a unique "suffix" param.\n` +
902
+ `This rule ensures that your output columns are labeled unambiguously.\n` +
903
+ `You provided a duplicate: type="${suffix}".`
904
+ )
905
+ }
906
+
907
+ const lookbackAbs = Math.abs(lookback) + 1
908
+ this.isAlreadyComputed.add(prefix)
909
+
910
+ this.inputParams.push({key: methodName, order, params: [{prefix, trainingSize, findGroups, trainingCols, lookbackAbs, modelArgs, filterCallback}]})
911
+
912
+ return this
913
+ }
914
+
915
+ exportTrainedModels()
916
+ {
917
+ return exportTrainedModels(this)
918
+ }
919
+
920
+ mapCols(newCols = ['change'], callback = null, options = {}) {
921
+
922
+ isAlreadyComputed(this)
923
+
924
+ const methodName = 'mapCols'
925
+
926
+ if(typeof callback === 'undefined' || callback === null)
927
+ {
928
+ callback = defaultMapColsCallback
929
+ }
930
+
931
+ validateObject(options, 'options', methodName)
932
+
933
+ const {lag = 0} = options
934
+
935
+ validateArray(newCols, 'newCols', methodName)
936
+ validateNumber(lag, {min: 0, allowDecimals: false}, 'options.lag', methodName)
937
+ const order = getOrderFromArray(newCols, methodName)
938
+
939
+ this.inputParams.push({key: methodName, order, params: [newCols, callback, {lag}]})
940
+
475
941
  return this
476
942
  }
477
943
  }