ohlcv-indicators 3.5.4 → 3.5.42
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/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +533 -67
- package/package.json +11 -2
- package/src/core-functions/mainLoop.js +70 -16
- package/src/core-functions/pushToMain.js +28 -7
- package/src/machine-learning/classifier.js +267 -0
- package/src/machine-learning/ml-config.js +219 -0
- package/src/machine-learning/ml-utilities.js +148 -0
- package/src/machine-learning/pca.js +72 -79
- package/src/machine-learning/regressor.js +227 -0
- package/src/machine-learning/scaler.js +97 -64
- package/src/machine-learning/trainX.js +73 -0
- package/src/moving-averages/bollingerBands.js +30 -31
- package/src/moving-averages/donchianChannel.js +6 -4
- package/src/moving-averages/macd.js +3 -7
- package/src/moving-averages/movingAverages.js +30 -33
- package/src/moving-averages/relativeVolume.js +2 -0
- package/src/moving-averages/vidya.js +181 -0
- package/src/oscillators/rsi.js +26 -15
- package/src/oscillators/stochastic.js +74 -0
- package/src/oscillators/volumeOscillator.js +3 -2
- package/src/studies/dateTime.js +58 -65
- package/src/studies/findCrosses.js +88 -50
- package/src/studies/lag.js +37 -70
- package/src/studies/mapCols.js +47 -0
- package/src/utilities/assignTypes.js +28 -4
- package/src/utilities/numberUtilities.js +0 -26
- package/src/utilities/order.js +51 -0
- package/src/utilities/precisionMultiplier.js +3 -4
- package/src/utilities/validators.js +7 -0
- package/src/utilities/verticalToHorizontal.js +2 -2
- package/src/volatility/atr.js +2 -7
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const oneHotEncode = (idx, size) => {
|
|
2
|
+
const vec = new Uint8Array(size)
|
|
3
|
+
vec[idx] = 1
|
|
4
|
+
return vec
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const logMlTraining = ({featureCols, flatFeaturesColLen, type, trainingSize}) => {
|
|
8
|
+
|
|
9
|
+
const oneHotTxt = (featureCols.length !== flatFeaturesColLen) ? ' (including flat one-hot encoded array values) ' : ''
|
|
10
|
+
console.log(`---\nInitialized "${type}" with ${flatFeaturesColLen} features ${oneHotTxt} and ${trainingSize} rows: \n${JSON.stringify(featureCols)}\n\n`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const normalizeMinMax = (value, min, max, [validMin, validMax]) => {
|
|
14
|
+
const clamped = Math.min(Math.max(value, min), max)
|
|
15
|
+
return ((clamped - min) / (max - min)) * (validMax - validMin) + validMin
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const normalizeZScore = (value, mean, std) => {
|
|
19
|
+
return std === 0 ? 0 : (value - mean) / std
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const findGroupsFunc = (findGroups, scaledGroups) => {
|
|
23
|
+
|
|
24
|
+
const output = []
|
|
25
|
+
|
|
26
|
+
if(findGroups.length > 0)
|
|
27
|
+
{
|
|
28
|
+
for(let g = 0; g < findGroups.length; g++)
|
|
29
|
+
{
|
|
30
|
+
const group = findGroups[g]
|
|
31
|
+
const {type, size = null, groupName = null} = group
|
|
32
|
+
|
|
33
|
+
let groupKey = (size !== null) ? `${type}_${size}` : `${type}_${groupName}`
|
|
34
|
+
|
|
35
|
+
if(!scaledGroups.hasOwnProperty(groupKey)) throw new Error(`Scaled group (${groupKey}) not found for regressor.options.findGroups[${g}]: ${JSON.stringify(group)}`)
|
|
36
|
+
output.push(...scaledGroups[groupKey])
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return output
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const computeFlatFeaturesLen = (featureCols, verticalOhlcv, index) => {
|
|
44
|
+
|
|
45
|
+
let flatFeaturesColLen = 0
|
|
46
|
+
|
|
47
|
+
for (const key of featureCols) {
|
|
48
|
+
|
|
49
|
+
if(!verticalOhlcv.hasOwnProperty(key)) throw new Error(`Property "${key}" not found in "verticalOhlcv".`)
|
|
50
|
+
|
|
51
|
+
const val = verticalOhlcv[key][index]
|
|
52
|
+
|
|
53
|
+
if(typeof val === 'undefined' || val === null || Number.isNaN(val)) throw new Error(`Invalid value of property "${key}".`)
|
|
54
|
+
|
|
55
|
+
if (key.startsWith('one_hot_') || key.startsWith('pca_')) {
|
|
56
|
+
|
|
57
|
+
const colSize = verticalOhlcv[key][index].length
|
|
58
|
+
|
|
59
|
+
flatFeaturesColLen += colSize
|
|
60
|
+
|
|
61
|
+
} else {
|
|
62
|
+
|
|
63
|
+
flatFeaturesColLen += 1
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return flatFeaturesColLen
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export const countUniqueLabels = Y => {
|
|
73
|
+
// If Y[0] isn't an array, treat Y as a single-column 2D array
|
|
74
|
+
const data = Array.isArray(Y[0]) ? Y : Y.map(v => [v]);
|
|
75
|
+
|
|
76
|
+
// Number of columns in our (possibly wrapped) data
|
|
77
|
+
const nCols = data[0].length;
|
|
78
|
+
|
|
79
|
+
// Sum up the unique values in each column
|
|
80
|
+
const totalLabels = Array.from({ length: nCols }).reduce((sum, _, col) => {
|
|
81
|
+
const uniques = new Set(data.map(row => row[col]));
|
|
82
|
+
return sum + uniques.size;
|
|
83
|
+
}, 0);
|
|
84
|
+
|
|
85
|
+
// Return count + 1 (same as before)
|
|
86
|
+
return totalLabels + 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
export const getFeaturedKeys = ({trainingCols, findGroups, verticalOhlcv, scaledGroups}) => {
|
|
91
|
+
|
|
92
|
+
const featureCols = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if (featureCols.length === 0) {
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let keyNotFound = false
|
|
100
|
+
|
|
101
|
+
// sanity‐check that all features exist
|
|
102
|
+
for(let x = 0; x < featureCols.length; x++)
|
|
103
|
+
{
|
|
104
|
+
const featureKey = featureCols[x]
|
|
105
|
+
|
|
106
|
+
if(!verticalOhlcv.hasOwnProperty(featureKey)) {
|
|
107
|
+
keyNotFound = true
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if(keyNotFound) return []
|
|
113
|
+
|
|
114
|
+
return featureCols
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const updateClassifierMetrics = ({ metrics, trueLabel, predictedLabel }) => {
|
|
118
|
+
// Increment overall counters
|
|
119
|
+
metrics.total = (metrics.total || 0) + 1
|
|
120
|
+
metrics.correct = (metrics.correct || 0)
|
|
121
|
+
|
|
122
|
+
// Initialize per‐label stats container if needed
|
|
123
|
+
metrics.labels = metrics.labels || {}
|
|
124
|
+
|
|
125
|
+
const labelKey = predictedLabel.toString()
|
|
126
|
+
// Ensure we have a slot for this label
|
|
127
|
+
if (!metrics.labels[labelKey]) {
|
|
128
|
+
metrics.labels[labelKey] = { total: 0, correct: 0, accuracy: 0 }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Update this label’s total predictions
|
|
132
|
+
metrics.labels[labelKey].total++
|
|
133
|
+
|
|
134
|
+
// If correct, bump both overall and per-label correct counters
|
|
135
|
+
if (predictedLabel === trueLabel) {
|
|
136
|
+
metrics.correct++
|
|
137
|
+
metrics.labels[labelKey].correct++
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Recompute overall accuracy
|
|
141
|
+
metrics.accuracy = metrics.correct / metrics.total
|
|
142
|
+
|
|
143
|
+
// Recompute accuracy for each label
|
|
144
|
+
for (const key in metrics.labels) {
|
|
145
|
+
const stats = metrics.labels[key]
|
|
146
|
+
stats.accuracy = stats.correct / stats.total
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1,103 +1,96 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getFeaturedKeys, computeFlatFeaturesLen, findGroupsFunc } from "./ml-utilities.js";
|
|
2
|
+
import { buildTrainX } from "./trainX.js";
|
|
3
|
+
import { areKeyValuesValid } from "../core-functions/pushToMain.js";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const algo = 'pca'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
const startIndex = invalidValueIndex + 1
|
|
7
|
+
export const pca = (main, index, {prefix, trainingSize, findGroups, trainingCols, lookbackAbs, modelArgs, filterCallback}) => {
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
const {key: indicatorName, params} = rowParams
|
|
12
|
-
|
|
13
|
-
if(indicatorName !== 'scaler') continue
|
|
14
|
-
|
|
15
|
-
const [size, keyNames, options] = params
|
|
16
|
-
|
|
17
|
-
const {type, group, lag, pca: pcaOptions} = options
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
showSource = false,
|
|
21
|
-
storeModel = false,
|
|
22
|
-
isCovarianceMatrix = false,
|
|
23
|
-
method = 'NIPALS', //SVD, covarianceMatrix or NIPALS
|
|
24
|
-
center = true,
|
|
25
|
-
scale = false,
|
|
26
|
-
nCompNIPALS = 2,
|
|
27
|
-
ignoreZeroVariance = false
|
|
28
|
-
} = pcaOptions
|
|
29
|
-
|
|
30
|
-
if(!group || pcaOptions === null) continue
|
|
31
|
-
|
|
32
|
-
validateBoolean(showSource, 'showSource', 'scaler.options.pca.showSource')
|
|
33
|
-
validateBoolean(storeModel, 'storeModel', 'scaler.options.pca.storeModel')
|
|
34
|
-
validateBoolean(isCovarianceMatrix, 'isCovarianceMatrix', 'scaler.options.pca.isCovarianceMatrix')
|
|
35
|
-
validateBoolean(center, 'center', 'scaler.options.pca.center')
|
|
36
|
-
validateBoolean(scale, 'scale', 'scaler.options.pca.scale')
|
|
37
|
-
validateBoolean(ignoreZeroVariance, 'ignoreZeroVariance', 'scaler.options.pca.ignoreZeroVariance')
|
|
38
|
-
validateArrayOptions(['SVD', 'NIPALS', 'covarianceMatrix'], method, 'scaler.options.pca.method')
|
|
39
|
-
validateNumber(nCompNIPALS, {min: 1}, 'nCompNIPALS', 'scaler.options.pca.nCompNIPALS')
|
|
40
|
-
|
|
41
|
-
const scaledKeyNames = keyNames.map(v => `${type}_${size}_${v}`)
|
|
42
|
-
const laggedKeys = []
|
|
43
|
-
|
|
44
|
-
for (const k of scaledKeyNames) {
|
|
45
|
-
for (let step = 1; step <= lag; step++) {
|
|
46
|
-
laggedKeys.push(`${k}_lag_${step}`)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
9
|
+
const {verticalOhlcv, scaledGroups, len, instances, ML, invalidValueIndex} = main
|
|
49
10
|
|
|
50
|
-
|
|
11
|
+
const startIndex = (invalidValueIndex + 2)
|
|
12
|
+
const allModels = ML.models
|
|
51
13
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
for (let i = startIndex; i < len; i++) {
|
|
14
|
+
if(index === startIndex)
|
|
15
|
+
{
|
|
16
|
+
if(!instances.hasOwnProperty(algo)) instances[algo] = {}
|
|
57
17
|
|
|
58
|
-
|
|
18
|
+
instances[algo][prefix] = {
|
|
19
|
+
flatFeaturesColLen: 0,
|
|
20
|
+
featureCols: [],
|
|
21
|
+
X: []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
verticalOhlcv[prefix] = new Array(len).fill(null)
|
|
25
|
+
main.notNumberKeys.add(prefix)
|
|
26
|
+
}
|
|
27
|
+
else if(index < startIndex || index < lookbackAbs)
|
|
28
|
+
{
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
else if(filterCallback(verticalOhlcv, index) === false)
|
|
32
|
+
{
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
else if(index + 1 === len)
|
|
36
|
+
{
|
|
59
37
|
|
|
60
|
-
|
|
38
|
+
if(instances[algo][prefix].featureCols.length === 0)
|
|
39
|
+
{
|
|
40
|
+
const inputFeatures = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
61
41
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
42
|
+
throw new Error(`Some of the provided features in ${prefix} features where not found in "verticalOhlcv": ${JSON.stringify(inputFeatures)}`)
|
|
43
|
+
}
|
|
65
44
|
|
|
66
|
-
|
|
45
|
+
//last execution
|
|
46
|
+
for(const featureKey of instances[algo][prefix].featureCols)
|
|
47
|
+
{
|
|
48
|
+
if(!verticalOhlcv.hasOwnProperty(featureKey)) throw new Error(`Feature "${featureKey}" not found in verticalOhlcv for pca.`)
|
|
67
49
|
}
|
|
68
50
|
|
|
69
|
-
if(
|
|
51
|
+
if(instances[algo][prefix].X.length < trainingSize)
|
|
70
52
|
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
53
|
+
const requiredDatapoints = instances[algo][prefix].X.length - trainingSize
|
|
54
|
+
throw new Error(`The current "trainingSize" at "${prefix}" requires at least ${requiredDatapoints} more datapoints. Try adding more input ohlcv rows or reducing the "trainingSize" by ${requiredDatapoints}.`)
|
|
74
55
|
}
|
|
56
|
+
}
|
|
75
57
|
|
|
76
|
-
|
|
58
|
+
const dataSetInstance = instances[algo][prefix]
|
|
77
59
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
center,
|
|
82
|
-
scale,
|
|
83
|
-
nCompNIPALS,
|
|
84
|
-
ignoreZeroVariance
|
|
85
|
-
})
|
|
60
|
+
if(dataSetInstance.flatFeaturesColLen === 0)
|
|
61
|
+
{
|
|
62
|
+
dataSetInstance.featureCols = getFeaturedKeys({trainingCols, findGroups, verticalOhlcv, scaledGroups})
|
|
86
63
|
|
|
87
|
-
if(
|
|
64
|
+
if(areKeyValuesValid(main, index, dataSetInstance.featureCols))
|
|
88
65
|
{
|
|
89
|
-
|
|
66
|
+
dataSetInstance.flatFeaturesColLen = computeFlatFeaturesLen(dataSetInstance.featureCols, verticalOhlcv, index)
|
|
67
|
+
} else {
|
|
68
|
+
return
|
|
90
69
|
}
|
|
91
|
-
|
|
92
|
-
const predictions = pca.predict(tempArr).to2DArray()
|
|
70
|
+
}
|
|
93
71
|
|
|
94
|
-
|
|
72
|
+
if(dataSetInstance.flatFeaturesColLen === 0) return
|
|
73
|
+
|
|
74
|
+
const {X: xRows, featureCols, flatFeaturesColLen} = dataSetInstance
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
76
|
+
const trainX = buildTrainX({featureCols, flatFeaturesColLen, type: algo, index, lookbackAbs, main})
|
|
77
|
+
|
|
78
|
+
if (trainX == null) return
|
|
100
79
|
|
|
80
|
+
if(allModels.hasOwnProperty(prefix))
|
|
81
|
+
{
|
|
82
|
+
|
|
83
|
+
const futureRow = allModels[prefix].predict([trainX]).to2DArray()
|
|
84
|
+
|
|
85
|
+
verticalOhlcv[prefix][index] = futureRow[0]
|
|
101
86
|
}
|
|
102
87
|
|
|
88
|
+
xRows.push(trainX)
|
|
89
|
+
|
|
90
|
+
if (xRows.length > trainingSize) xRows.shift()
|
|
91
|
+
|
|
92
|
+
if(xRows.length === trainingSize)
|
|
93
|
+
{
|
|
94
|
+
allModels[prefix] = new ML.classes.PCA(xRows, modelArgs)
|
|
95
|
+
}
|
|
103
96
|
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { getFeaturedKeys, computeFlatFeaturesLen, logMlTraining, findGroupsFunc } from './ml-utilities.js'
|
|
2
|
+
import { buildTrainX } from './trainX.js'
|
|
3
|
+
import { areKeyValuesValid } from '../core-functions/pushToMain.js'
|
|
4
|
+
|
|
5
|
+
const algo = 'regressor'
|
|
6
|
+
|
|
7
|
+
export const regressor = (main, index, trainingSize, {target, predictions, retrain, trainingCols, findGroups, type, modelArgs, precompute, filterCallback, modelConfig, modelClass}) => {
|
|
8
|
+
|
|
9
|
+
const {lookbackAbs, prefix, flatX, flatY} = precompute
|
|
10
|
+
const {verticalOhlcv, len, instances, scaledGroups, invalidValueIndex, ML} = main
|
|
11
|
+
const allModels = ML.models
|
|
12
|
+
const startIndex = (invalidValueIndex + 1)
|
|
13
|
+
|
|
14
|
+
if(index === startIndex)
|
|
15
|
+
{
|
|
16
|
+
if (!instances.hasOwnProperty(algo)) instances.regressor = {}
|
|
17
|
+
if(!instances.regressor.hasOwnProperty(prefix)) instances.regressor[prefix] = {}
|
|
18
|
+
|
|
19
|
+
const expectedLoops = (flatY) ? predictions : 1 //
|
|
20
|
+
|
|
21
|
+
instances.regressor[prefix] = {
|
|
22
|
+
expectedLoops,
|
|
23
|
+
trainingSize,
|
|
24
|
+
isTrained: new Array(expectedLoops).fill(false),
|
|
25
|
+
shouldRetrain: retrain,
|
|
26
|
+
featureCols: [],
|
|
27
|
+
flatFeaturesColLen: 0,
|
|
28
|
+
X: [],
|
|
29
|
+
Y: Array.from({ length: expectedLoops }, () => []),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for(let x = 0; x < predictions; x++)
|
|
33
|
+
{
|
|
34
|
+
const predictionKey = `${prefix}_${(x+1)}`
|
|
35
|
+
verticalOhlcv[predictionKey] = new Float64Array(len).fill(NaN)
|
|
36
|
+
|
|
37
|
+
if(!allModels.hasOwnProperty(predictionKey))
|
|
38
|
+
{
|
|
39
|
+
allModels[predictionKey] = []
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if(index < startIndex || index < lookbackAbs)
|
|
44
|
+
{
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
else if(filterCallback(verticalOhlcv, index) === false)
|
|
48
|
+
{
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
else if(index + 1 === len)
|
|
52
|
+
{
|
|
53
|
+
if(instances.regressor[prefix].featureCols.length === 0)
|
|
54
|
+
{
|
|
55
|
+
const inputFeatures = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
56
|
+
|
|
57
|
+
throw new Error(`Some of the provided features in ${prefix} features where not found in "verticalOhlcv": ${JSON.stringify(inputFeatures)}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//last execution
|
|
61
|
+
for(const featureKey of instances.regressor[prefix].featureCols)
|
|
62
|
+
{
|
|
63
|
+
if(!verticalOhlcv.hasOwnProperty(featureKey)) throw new Error(`Feature "${featureKey}" not found in verticalOhlcv for ${prefix}.`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if(instances.regressor[prefix].X.length < trainingSize)
|
|
67
|
+
{
|
|
68
|
+
const requiredDatapoints = instances.regressor[prefix].X.length - trainingSize
|
|
69
|
+
throw new Error(`The current "trainingSize" at "${prefix}" requires at least ${requiredDatapoints} more datapoints. Try adding more input ohlcv rows or reducing the "trainingSize" by ${requiredDatapoints}.`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const dataSetInstance = instances.regressor[prefix]
|
|
74
|
+
|
|
75
|
+
if(dataSetInstance.flatFeaturesColLen === 0)
|
|
76
|
+
{
|
|
77
|
+
dataSetInstance.featureCols = getFeaturedKeys({trainingCols, findGroups, verticalOhlcv, scaledGroups})
|
|
78
|
+
|
|
79
|
+
if(areKeyValuesValid(main, index, dataSetInstance.featureCols))
|
|
80
|
+
{
|
|
81
|
+
dataSetInstance.flatFeaturesColLen = computeFlatFeaturesLen(dataSetInstance.featureCols, verticalOhlcv, index)
|
|
82
|
+
|
|
83
|
+
logMlTraining({
|
|
84
|
+
featureCols: dataSetInstance.featureCols,
|
|
85
|
+
flatFeaturesColLen: dataSetInstance.flatFeaturesColLen,
|
|
86
|
+
type,
|
|
87
|
+
trainingSize
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if(dataSetInstance.flatFeaturesColLen === 0) return
|
|
96
|
+
|
|
97
|
+
if(!verticalOhlcv.hasOwnProperty(target))
|
|
98
|
+
{
|
|
99
|
+
throw new Error(`Target property "${target}" not found in verticalOhlcv for regressor.`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const {
|
|
103
|
+
expectedLoops,
|
|
104
|
+
X: xRows,
|
|
105
|
+
Y,
|
|
106
|
+
flatFeaturesColLen,
|
|
107
|
+
featureCols,
|
|
108
|
+
shouldRetrain
|
|
109
|
+
} = dataSetInstance
|
|
110
|
+
|
|
111
|
+
if(!featureCols.includes(target))
|
|
112
|
+
{
|
|
113
|
+
throw new Error(`Target property "${target}" not found in ${type} features: ${JSON.stringify(featureCols)}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const trainX = (flatX) ? verticalOhlcv[target][index] : buildTrainX({featureCols, flatFeaturesColLen, type, index, lookbackAbs, main})
|
|
117
|
+
|
|
118
|
+
if (flatX) {
|
|
119
|
+
if (!Number.isFinite(trainX)) return; // or `trainX == null`
|
|
120
|
+
} else {
|
|
121
|
+
if (trainX == null) return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let trainY = []
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < predictions; i++) {
|
|
127
|
+
const v = verticalOhlcv[target][index + i + 1]
|
|
128
|
+
|
|
129
|
+
if (!Number.isFinite(v)) {
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
trainY.push(v)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const {train, unsupervised} = modelConfig
|
|
137
|
+
|
|
138
|
+
//if univariable Y (flatY) a model is created for each prediction
|
|
139
|
+
for(let loopIdx = 0; loopIdx < expectedLoops; loopIdx++)
|
|
140
|
+
{
|
|
141
|
+
const predictionKey = `${prefix}_${(loopIdx+1)}`
|
|
142
|
+
const yRows = Y[loopIdx]
|
|
143
|
+
const currTrainY = (flatY) ? (typeof trainY[loopIdx] === 'undefined') ? null : trainY[loopIdx] : trainY
|
|
144
|
+
const isTrained = dataSetInstance.isTrained[loopIdx]
|
|
145
|
+
|
|
146
|
+
const shouldPredict = allModels.hasOwnProperty(predictionKey) && isTrained && allModels[predictionKey].length === expectedLoops
|
|
147
|
+
|
|
148
|
+
//predicts using previously saved models even if current currTrainY is not available
|
|
149
|
+
if(shouldPredict)
|
|
150
|
+
{
|
|
151
|
+
const currModel = allModels[predictionKey][0]
|
|
152
|
+
const futureRow = (unsupervised) ? currModel.predict(predictions)[0] : currModel.predict([trainX])[0]
|
|
153
|
+
|
|
154
|
+
if(flatY)
|
|
155
|
+
{
|
|
156
|
+
if(Number.isNaN(futureRow)) throw new Error(`Prediction of ${type} at index ${index} was expecting a number.`)
|
|
157
|
+
|
|
158
|
+
main.pushToMain({index, key: predictionKey, value: futureRow})
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
|
|
162
|
+
if (!Array.isArray(futureRow) || futureRow.length !== predictions) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Prediction output of "${type}" at index ${index} was expecting an array of values.`
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for(let preIdx = 0; preIdx < predictions; preIdx++) {
|
|
169
|
+
main.pushToMain({index, key: `${prefix}_${(preIdx+1)}`, value: futureRow[preIdx]})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if((index + predictions + 1) > len) continue
|
|
175
|
+
if(trainY.length < predictions) continue
|
|
176
|
+
if(currTrainY === null) continue
|
|
177
|
+
|
|
178
|
+
if(flatY)
|
|
179
|
+
{
|
|
180
|
+
if (typeof currTrainY !== 'number' || Number.isNaN(currTrainY))
|
|
181
|
+
{
|
|
182
|
+
throw new Error(`currTrainY must return number, got ${typeof currTrainY} at index ${index}`)
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
if (!Array.isArray(currTrainY)) {
|
|
186
|
+
throw new Error(`currTrainY must return an array, got ${typeof currTrainY} at index ${index}`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if ((currTrainY.length !== predictions)) {
|
|
190
|
+
|
|
191
|
+
throw new Error(`currTrainY length (${currTrainY.length}) ≠ "options.predictions" (${predictions}) for regressor "${type}"`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// enqueue
|
|
196
|
+
yRows.push(currTrainY)
|
|
197
|
+
if (yRows.length > trainingSize) yRows.shift()
|
|
198
|
+
|
|
199
|
+
if(loopIdx === 0)
|
|
200
|
+
{
|
|
201
|
+
xRows.push(trainX)
|
|
202
|
+
if (xRows.length > trainingSize) xRows.shift()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const shouldTrainModel = shouldRetrain || !isTrained
|
|
206
|
+
|
|
207
|
+
if (shouldTrainModel && xRows.length === trainingSize && yRows.length === trainingSize) {
|
|
208
|
+
|
|
209
|
+
const trainedModel = train({modelClass, xRows, yRows, modelArgs, uniqueLabels: 0})
|
|
210
|
+
|
|
211
|
+
if(unsupervised)
|
|
212
|
+
{
|
|
213
|
+
allModels[predictionKey] = [trainedModel]
|
|
214
|
+
} else {
|
|
215
|
+
allModels[predictionKey].push(trainedModel)
|
|
216
|
+
|
|
217
|
+
if(allModels[predictionKey].length > predictions)
|
|
218
|
+
{
|
|
219
|
+
allModels[predictionKey].shift()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
dataSetInstance.isTrained[loopIdx] = true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
}
|