ohlcv-indicators 3.5.55 → 3.5.59
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 +39 -337
- package/package.json +2 -13
- package/src/core-functions/mainLoop.js +10 -42
- package/src/machine-learning/ml-utilities.js +1 -117
- package/src/machine-learning/scaler.js +78 -76
- package/src/moving-averages/movingAverages.js +0 -2
- package/src/moving-averages/relativeVolume.js +0 -2
- package/src/moving-averages/vidya.js +53 -53
- package/src/studies/dateTime.js +29 -25
- package/src/studies/priceFeatures.js +78 -0
- package/src/utilities/dateUtilities.js +1 -1
- package/src/utilities/precisionMultiplier.js +36 -19
- package/src/machine-learning/balancing.js +0 -65
- package/src/machine-learning/classifier.js +0 -321
- package/src/machine-learning/ml-config.js +0 -219
- package/src/machine-learning/pca.js +0 -95
- package/src/machine-learning/regressor.js +0 -227
- package/src/machine-learning/trainX.js +0 -73
- package/src/utilities/order.js +0 -51
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
const getRet = (next, prev) => (next - prev) / prev
|
|
3
|
+
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
|
+
|
|
5
|
+
export const priceFeatures = (main, index, {lag, colKeys}) => {
|
|
6
|
+
const { verticalOhlcv } = main
|
|
7
|
+
|
|
8
|
+
if(index === 0) {
|
|
9
|
+
const {len} = main
|
|
10
|
+
|
|
11
|
+
const newCols = {
|
|
12
|
+
ret_change: new Float64Array(len).fill(NaN),
|
|
13
|
+
ret_upper_wick: new Float64Array(len).fill(NaN),
|
|
14
|
+
ret_lower_wick: new Float64Array(len).fill(NaN),
|
|
15
|
+
ret_gap: new Float64Array(len).fill(NaN),
|
|
16
|
+
ret_body: new Float64Array(len).fill(NaN),
|
|
17
|
+
ret_range: new Float64Array(len).fill(NaN)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for(const target of colKeys) {
|
|
21
|
+
if(!verticalOhlcv.hasOwnProperty(target)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Target property ${target} not found in verticalOhlcv for "priceFeatures".`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
newCols[`ret_${target}`] = new Float64Array(len).fill(NaN)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Object.assign(verticalOhlcv, newCols)
|
|
31
|
+
|
|
32
|
+
if(lag) {
|
|
33
|
+
main.lag(Object.keys(newCols), lag)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const prevClose = verticalOhlcv.close[index - 1]
|
|
38
|
+
|
|
39
|
+
if(typeof prevClose === 'undefined') return
|
|
40
|
+
|
|
41
|
+
const currOpen = verticalOhlcv.open[index]
|
|
42
|
+
const currHigh = verticalOhlcv.high[index]
|
|
43
|
+
const currLow = verticalOhlcv.low[index]
|
|
44
|
+
const currClose = verticalOhlcv.close[index]
|
|
45
|
+
|
|
46
|
+
const upperWickTop = Math.max(currOpen, currClose)
|
|
47
|
+
const lowerWickTop = Math.min(currOpen, currClose)
|
|
48
|
+
|
|
49
|
+
const row = {
|
|
50
|
+
ret_change: getRet(currClose, prevClose),
|
|
51
|
+
ret_upper_wick: getRet(currHigh, upperWickTop),
|
|
52
|
+
ret_lower_wick: getRet(lowerWickTop, currLow),
|
|
53
|
+
ret_gap: getRet(currOpen, prevClose),
|
|
54
|
+
ret_body: getRet(currClose, currOpen),
|
|
55
|
+
ret_range: getRet(currHigh, currLow)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let hasInvalidTarget = false
|
|
59
|
+
|
|
60
|
+
for(const target of colKeys) {
|
|
61
|
+
const targetVal = verticalOhlcv[target][index]
|
|
62
|
+
|
|
63
|
+
if(isBadNumber(targetVal)) {
|
|
64
|
+
hasInvalidTarget = true
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const targetRet = getRet(currClose, targetVal)
|
|
69
|
+
row[`ret_${target}`] = targetRet
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if(hasInvalidTarget) return
|
|
73
|
+
|
|
74
|
+
for(const [key, value] of Object.entries(row)) {
|
|
75
|
+
main.pushToMain({index, key, value})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
|
|
2
|
-
//calcPrecisionMultiplier is calculated once, using the open, high, low and close values of the first row
|
|
3
|
-
//the multiplier (min 10000
|
|
2
|
+
//calcPrecisionMultiplier is calculated once in the class constructor, using the open, high, low and close values of the first row
|
|
3
|
+
//the multiplier (min 10000) is store in constructor
|
|
4
4
|
|
|
5
5
|
export const calcPrecisionMultiplier = main => {
|
|
6
|
-
const { precision, firstRow, initialPriceBased
|
|
6
|
+
const { precision, firstRow, initialPriceBased } = main
|
|
7
7
|
if (precision === false) return 1
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const err = 'If "precision" is set to true all the "open", "high", "low" and "close" values must be numeric strings like "123.45" with no commas or currency symbols.'
|
|
9
|
+
let maxMultiplier = 1
|
|
12
10
|
|
|
13
11
|
for (const [key, strNum] of Object.entries(firstRow)) {
|
|
12
|
+
if (!initialPriceBased.has(key)) continue
|
|
14
13
|
|
|
15
|
-
if(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Property "${key}" had the value "${strNum}" type ${typeof strNum}.`)
|
|
14
|
+
if (typeof strNum !== 'string' || !/^\d+(\.\d+)?$/.test(strNum)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`If "precision" is set to true all the "open", "high", "low" and "close" values must be numeric strings like "123.45" with no commas or currency symbols. Property "${key}" had the value "${strNum}" type ${typeof strNum}.`
|
|
17
|
+
)
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
if (!priceBased.has(key)) continue
|
|
23
|
-
|
|
24
20
|
const [integerStr, decimals = ''] = strNum.split('.')
|
|
25
21
|
const integer = Number(integerStr)
|
|
26
|
-
const decimalPrecision = integer > 0 ? 4 : Math.max(4, decimals.length)
|
|
27
|
-
multipliers.push(Math.pow(10, decimalPrecision))
|
|
28
|
-
}
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
let decimalPrecision = 4
|
|
24
|
+
|
|
25
|
+
// Treat any non-zero integer (positive or negative) as "normal"
|
|
26
|
+
if (Math.abs(integer) === 0) {
|
|
27
|
+
const decimalsLen = decimals.length
|
|
28
|
+
const countDecimals = Math.max(4, decimalsLen)
|
|
29
|
+
|
|
30
|
+
// index of first non-zero decimal digit, -1 if none
|
|
31
|
+
const firstNonZeroIndex = decimals.search(/[1-9]/)
|
|
32
|
+
|
|
33
|
+
if (firstNonZeroIndex === -1) {
|
|
34
|
+
// 0, 0.0, 0.0000... -> nothing special to preserve
|
|
35
|
+
decimalPrecision = countDecimals
|
|
36
|
+
} else {
|
|
37
|
+
// Give extra headroom after the first non-zero digit
|
|
38
|
+
const extra = 5 // tweak as you like
|
|
39
|
+
const needed = firstNonZeroIndex + 1 + extra
|
|
40
|
+
decimalPrecision = Math.max(countDecimals, needed)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
|
|
32
|
-
|
|
44
|
+
// Optional: cap exponent to keep numbers sane
|
|
45
|
+
const multiplier = 10 ** decimalPrecision
|
|
33
46
|
|
|
34
|
-
|
|
47
|
+
if (multiplier > maxMultiplier) maxMultiplier = multiplier
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return maxMultiplier
|
|
35
51
|
}
|
|
36
52
|
|
|
53
|
+
|
|
37
54
|
//this function converts the strNum into a integer * multiplier
|
|
38
55
|
export const addPrecisionAsNumber = (strNum, multiplier) => {
|
|
39
56
|
if (typeof strNum !== 'string') throw new Error('addPrecisionExact expects a string when precision=true')
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export const countUniqueLabels = Y => {
|
|
2
|
-
// 1. Guard against empty or non-array input
|
|
3
|
-
if (!Array.isArray(Y) || Y.length === 0) {
|
|
4
|
-
return 1
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// 2. Detect if Y is 2D or 1D
|
|
8
|
-
const is2D = Array.isArray(Y[0])
|
|
9
|
-
const nCols = is2D ? Y[0].length : 1
|
|
10
|
-
|
|
11
|
-
let total = 0
|
|
12
|
-
|
|
13
|
-
// 3. For each column, collect uniques in a single pass
|
|
14
|
-
for (let col = 0; col < nCols; col++) {
|
|
15
|
-
const seen = new Set()
|
|
16
|
-
for (let row = 0; row < Y.length; row++) {
|
|
17
|
-
// pick the cell: Y[row][col] if 2D, or Y[row] if 1D
|
|
18
|
-
const cell = is2D ? Y[row][col] : Y[row]
|
|
19
|
-
seen.add(cell)
|
|
20
|
-
}
|
|
21
|
-
total += seen.size
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 4. Return “sum of uniques per column”
|
|
25
|
-
return total
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// X and Y can have multivariable or univariable rows
|
|
29
|
-
export const oversampleXY = (X, Y) => {
|
|
30
|
-
if (!Array.isArray(X) || !Array.isArray(Y)) {
|
|
31
|
-
throw new TypeError('Both X and Y must be arrays');
|
|
32
|
-
}
|
|
33
|
-
if (X.length !== Y.length) {
|
|
34
|
-
throw new Error('X and Y must have the same length');
|
|
35
|
-
}
|
|
36
|
-
if (Y.length === 0) {
|
|
37
|
-
return { X: [], Y: [] };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 2) Group X-values by label using a Map
|
|
41
|
-
const groups = Y.reduce((map, label, i) => {
|
|
42
|
-
if (!map.has(label)) map.set(label, []);
|
|
43
|
-
map.get(label).push(X[i]);
|
|
44
|
-
return map;
|
|
45
|
-
}, new Map());
|
|
46
|
-
|
|
47
|
-
// 3) Find the largest group size
|
|
48
|
-
let maxSize = 0;
|
|
49
|
-
for (const arr of groups.values()) {
|
|
50
|
-
if (arr.length > maxSize) maxSize = arr.length;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 4) Build the oversampled arrays
|
|
54
|
-
const outX = [];
|
|
55
|
-
const outY = [];
|
|
56
|
-
for (const [label, xArr] of groups.entries()) {
|
|
57
|
-
const sz = xArr.length;
|
|
58
|
-
for (let i = 0; i < maxSize; i++) {
|
|
59
|
-
outX.push(xArr[i % sz]);
|
|
60
|
-
outY.push(label);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return { X: outX, Y: outY };
|
|
65
|
-
};
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import { getFeaturedKeys, computeFlatFeaturesLen, logMlTraining, updateClassifierMetrics, findGroupsFunc } from "./ml-utilities.js"
|
|
2
|
-
import { buildTrainX } from "./trainX.js"
|
|
3
|
-
import { areKeyValuesValid } from "../core-functions/pushToMain.js"
|
|
4
|
-
import { countUniqueLabels, oversampleXY } from "./balancing.js"
|
|
5
|
-
|
|
6
|
-
const algo = 'classifier'
|
|
7
|
-
|
|
8
|
-
class Store {
|
|
9
|
-
constructor(args = {}) {
|
|
10
|
-
Object.entries(args).forEach(([k, v]) => { this[k] = v })
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// internal helpers
|
|
14
|
-
_unwrap = v => Array.isArray(v) ? v[0] : v
|
|
15
|
-
_num = (v, def = 0) => {
|
|
16
|
-
const n = Number(this._unwrap(v))
|
|
17
|
-
return Number.isFinite(n) ? n : def
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// set: supports value OR updater function
|
|
21
|
-
// if you pass a function, we feed it the current *numeric* value
|
|
22
|
-
set = (key, valueOrFn) => {
|
|
23
|
-
if (typeof valueOrFn === 'function') {
|
|
24
|
-
const next = valueOrFn(this._num(this[key], 0))
|
|
25
|
-
this[key] = next // <- change to [next] if you want to keep the array wrapper
|
|
26
|
-
return next
|
|
27
|
-
} else {
|
|
28
|
-
this[key] = valueOrFn // <- change to [valueOrFn] if you want to keep the array wrapper
|
|
29
|
-
return valueOrFn
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// convenience numerics
|
|
34
|
-
inc = (key, delta = 1) => this.set(key, v => v + delta)
|
|
35
|
-
add = (key, delta = 0) => this.inc(key, delta) // alias
|
|
36
|
-
getNum = (key, def = 0) => this._num(this[key], def)
|
|
37
|
-
|
|
38
|
-
get = (key) => this._unwrap(this[key])
|
|
39
|
-
getAll = () => {
|
|
40
|
-
|
|
41
|
-
const output = {}
|
|
42
|
-
|
|
43
|
-
for(const [key, valueOrFn] of Object.entries(this)) {
|
|
44
|
-
if(typeof valueOrFn === 'function') continue
|
|
45
|
-
output[key] = valueOrFn
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return {...output}
|
|
50
|
-
} // shallow clone of data-only fields
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const classifier = (
|
|
54
|
-
main,
|
|
55
|
-
index,
|
|
56
|
-
trainingSize,
|
|
57
|
-
{
|
|
58
|
-
yCallback,
|
|
59
|
-
trainingCols,
|
|
60
|
-
findGroups = [],
|
|
61
|
-
retrain,
|
|
62
|
-
type,
|
|
63
|
-
modelArgs,
|
|
64
|
-
horizon,
|
|
65
|
-
precompute,
|
|
66
|
-
predictions,
|
|
67
|
-
filterCallback,
|
|
68
|
-
modelConfig,
|
|
69
|
-
modelClass
|
|
70
|
-
}
|
|
71
|
-
) => {
|
|
72
|
-
const { lookbackAbs, prefix, flatY } = precompute
|
|
73
|
-
const { verticalOhlcv, len, instances, scaledGroups, invalidValueIndex, ML } = main
|
|
74
|
-
const allModels = ML.models
|
|
75
|
-
const startIndex = (invalidValueIndex + 1)
|
|
76
|
-
|
|
77
|
-
// ─── INITIALIZATION ───────────────────────────────────────────────
|
|
78
|
-
if(index === startIndex) {
|
|
79
|
-
|
|
80
|
-
// prepare instance storage
|
|
81
|
-
if (!instances.hasOwnProperty(algo)) instances[algo] = {}
|
|
82
|
-
if(!instances[algo].hasOwnProperty(prefix)) instances[algo][prefix] = {}
|
|
83
|
-
|
|
84
|
-
const expectedLoops = (flatY) ? predictions : 1 //
|
|
85
|
-
|
|
86
|
-
instances[algo][prefix] = {
|
|
87
|
-
store: new Store({
|
|
88
|
-
strategy: null,
|
|
89
|
-
total: 0,
|
|
90
|
-
win: 0,
|
|
91
|
-
loss: 0,
|
|
92
|
-
strategyGainLoss: 0,
|
|
93
|
-
idx: 0,
|
|
94
|
-
initialBalance: verticalOhlcv.close[index]
|
|
95
|
-
}),
|
|
96
|
-
expectedLoops,
|
|
97
|
-
isTrained: new Array(expectedLoops).fill(false),
|
|
98
|
-
uniqueLabels: new Array(expectedLoops).fill(0),
|
|
99
|
-
shouldRetrain: retrain,
|
|
100
|
-
featureCols: [],
|
|
101
|
-
flatFeaturesColLen: 0,
|
|
102
|
-
X: [],
|
|
103
|
-
Y: Array.from({ length: expectedLoops }, () => []),
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
for (let i = 0; i < predictions; i++) {
|
|
107
|
-
const predictionKey = `${prefix}_${i + 1}`
|
|
108
|
-
|
|
109
|
-
verticalOhlcv[predictionKey] = (type === 'KNN') ? new Array(len).fill(null) : new Float32Array(len).fill(NaN)
|
|
110
|
-
ML.metrics[predictionKey] = {accuracy: {}, total: 0, correct: 0, labels: {}}
|
|
111
|
-
|
|
112
|
-
if(!allModels.hasOwnProperty(predictionKey))
|
|
113
|
-
{
|
|
114
|
-
allModels[predictionKey] = []
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
else if(index < startIndex || index < lookbackAbs)
|
|
119
|
-
{
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
else if(filterCallback(verticalOhlcv, index, instances[algo][prefix].store) === false)
|
|
123
|
-
{
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
else if(index + 1 === len)
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
if(instances[algo][prefix].featureCols.length === 0)
|
|
130
|
-
{
|
|
131
|
-
const inputFeatures = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
132
|
-
|
|
133
|
-
throw new Error(`Some of the provided features in ${algo} "${prefix}" features where not found in "verticalOhlcv": ${JSON.stringify(inputFeatures)}`)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
//last execution
|
|
137
|
-
for(const featureKey of instances[algo][prefix].featureCols)
|
|
138
|
-
{
|
|
139
|
-
if(!verticalOhlcv.hasOwnProperty(featureKey)) throw new Error(`Feature "${featureKey}" not found in verticalOhlcv for ${prefix}.`)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if(instances[algo][prefix].X.length < trainingSize)
|
|
143
|
-
{
|
|
144
|
-
const requiredDatapoints = instances[algo][prefix].X.length - trainingSize
|
|
145
|
-
throw new Error(`The current "trainingSize" at ${algo} "${prefix}" requires at least ${requiredDatapoints} more datapoints. Try adding more input ohlcv rows or reducing the "trainingSize" by ${requiredDatapoints}.`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const dataSetInstance = instances[algo][prefix]
|
|
151
|
-
|
|
152
|
-
if(dataSetInstance.flatFeaturesColLen === 0)
|
|
153
|
-
{
|
|
154
|
-
dataSetInstance.featureCols = getFeaturedKeys({trainingCols, findGroups, verticalOhlcv, scaledGroups})
|
|
155
|
-
|
|
156
|
-
if(areKeyValuesValid(main, index, dataSetInstance.featureCols))
|
|
157
|
-
{
|
|
158
|
-
dataSetInstance.flatFeaturesColLen = computeFlatFeaturesLen(dataSetInstance.featureCols, verticalOhlcv, index)
|
|
159
|
-
|
|
160
|
-
logMlTraining({
|
|
161
|
-
featureCols: dataSetInstance.featureCols,
|
|
162
|
-
flatFeaturesColLen: dataSetInstance.flatFeaturesColLen,
|
|
163
|
-
type,
|
|
164
|
-
trainingSize
|
|
165
|
-
})
|
|
166
|
-
} else {
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if(dataSetInstance.flatFeaturesColLen === 0) return
|
|
172
|
-
|
|
173
|
-
const {
|
|
174
|
-
uniqueLabels,
|
|
175
|
-
expectedLoops,
|
|
176
|
-
featureCols,
|
|
177
|
-
flatFeaturesColLen,
|
|
178
|
-
shouldRetrain,
|
|
179
|
-
X: xRows
|
|
180
|
-
} = dataSetInstance
|
|
181
|
-
|
|
182
|
-
const trainX = buildTrainX({
|
|
183
|
-
featureCols,
|
|
184
|
-
flatFeaturesColLen,
|
|
185
|
-
type,
|
|
186
|
-
index,
|
|
187
|
-
lookbackAbs,
|
|
188
|
-
main
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
if(!trainX) return
|
|
192
|
-
|
|
193
|
-
//if univariable Y (flatY) a model is created for each prediction
|
|
194
|
-
const trainY = yCallback(index, verticalOhlcv, horizon, instances[algo][prefix].store)
|
|
195
|
-
|
|
196
|
-
if (trainY !== null && (!Array.isArray(trainY) || trainY.length !== predictions)) {
|
|
197
|
-
|
|
198
|
-
throw new Error(`trainY must be null or an array with ${predictions} items, got "${JSON.stringify(trainY)}" at index ${index} for ${type}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for(let loopIdx = 0; loopIdx < expectedLoops; loopIdx++)
|
|
202
|
-
{
|
|
203
|
-
const predictionKey = `${prefix}_${(loopIdx+1)}`
|
|
204
|
-
const yRows = dataSetInstance.Y[loopIdx]
|
|
205
|
-
const currTrainY = (flatY) ? (trainY === null) ? null : trainY[loopIdx] : trainY
|
|
206
|
-
const isTrained = dataSetInstance.isTrained[loopIdx]
|
|
207
|
-
|
|
208
|
-
const shouldPredict = allModels.hasOwnProperty(predictionKey) && isTrained && (shouldRetrain === false || allModels[predictionKey].length === horizon)
|
|
209
|
-
|
|
210
|
-
//predicts using previously saved models even if current currTrainY is not available
|
|
211
|
-
if(shouldPredict)
|
|
212
|
-
{
|
|
213
|
-
const futureRow = allModels[predictionKey][0].predict([trainX])[0]
|
|
214
|
-
|
|
215
|
-
if(flatY)
|
|
216
|
-
{
|
|
217
|
-
if(futureRow == null) throw new Error(`Prediction of ${prefix} at index ${index} was expecting a number.`)
|
|
218
|
-
|
|
219
|
-
main.pushToMain({index, key: predictionKey, value: futureRow})
|
|
220
|
-
|
|
221
|
-
if(currTrainY != null)
|
|
222
|
-
{
|
|
223
|
-
updateClassifierMetrics({
|
|
224
|
-
metrics: ML.metrics[predictionKey],
|
|
225
|
-
trueLabel: currTrainY,
|
|
226
|
-
predictedLabel: futureRow
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
|
|
232
|
-
if (!Array.isArray(futureRow) || futureRow.length !== predictions) {
|
|
233
|
-
throw new Error(
|
|
234
|
-
`Prediction output of ${algo} "${prefix}" at index ${index} was expecting an array of values.`
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
for(let preIdx = 0; preIdx < predictions; preIdx++) {
|
|
239
|
-
main.pushToMain({index, key: `${prefix}_${(preIdx+1)}`, value: futureRow[preIdx]})
|
|
240
|
-
|
|
241
|
-
if(Array.isArray(currTrainY) && currTrainY.length === predictions)
|
|
242
|
-
{
|
|
243
|
-
updateClassifierMetrics({
|
|
244
|
-
metrics: ML.metrics[`${prefix}_${(preIdx+1)}`],
|
|
245
|
-
trueLabel: currTrainY[preIdx],
|
|
246
|
-
predictedLabel: futureRow[preIdx]
|
|
247
|
-
})
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if((index + predictions + 1) > len) continue
|
|
255
|
-
if(currTrainY === null) continue // future not defined
|
|
256
|
-
|
|
257
|
-
if(flatY)
|
|
258
|
-
{
|
|
259
|
-
if (typeof currTrainY === 'undefined' || currTrainY == null)
|
|
260
|
-
{
|
|
261
|
-
throw new Error(`currTrainY must return number, string or boolean got ${typeof currTrainY} at index ${index}`)
|
|
262
|
-
}
|
|
263
|
-
} else {
|
|
264
|
-
|
|
265
|
-
if ((currTrainY.length !== predictions)) {
|
|
266
|
-
|
|
267
|
-
throw new Error(
|
|
268
|
-
`The number of label columns returned (${currTrainY.length}) doesn’t match the options.predictions (${predictions}) setting for the ${algo} "${prefix}" classifier. ` +
|
|
269
|
-
`Please update your yCallback function so its output array items align with options.predictions.`
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// enqueue
|
|
276
|
-
|
|
277
|
-
yRows.push(currTrainY)
|
|
278
|
-
if (yRows.length > trainingSize) yRows.shift()
|
|
279
|
-
|
|
280
|
-
if(loopIdx === 0)
|
|
281
|
-
{
|
|
282
|
-
xRows.push(trainX)
|
|
283
|
-
if (xRows.length > trainingSize) xRows.shift()
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const shouldTrainModel = shouldRetrain || !isTrained
|
|
287
|
-
|
|
288
|
-
if (shouldTrainModel && xRows.length === trainingSize && yRows.length === trainingSize) {
|
|
289
|
-
|
|
290
|
-
if(isTrained === false)
|
|
291
|
-
{
|
|
292
|
-
uniqueLabels[loopIdx] = countUniqueLabels(yRows)
|
|
293
|
-
|
|
294
|
-
if(uniqueLabels[loopIdx] < 1)
|
|
295
|
-
{
|
|
296
|
-
throw new Error(`Invalid number or labels in ${prefix}. Check the logic of your "yCallback" function.`)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const {train} = modelConfig
|
|
302
|
-
|
|
303
|
-
const {X: overX, Y: overY} = oversampleXY(xRows, yRows)
|
|
304
|
-
|
|
305
|
-
const trainedModel = train({modelClass, xRows: overX, yRows: overY, modelArgs, uniqueLabels: uniqueLabels[loopIdx]})
|
|
306
|
-
|
|
307
|
-
allModels[predictionKey].push(
|
|
308
|
-
trainedModel
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
if(allModels[predictionKey].length > horizon)
|
|
312
|
-
{
|
|
313
|
-
allModels[predictionKey].shift()
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
dataSetInstance.isTrained[loopIdx] = true
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return true
|
|
321
|
-
}
|