ohlcv-indicators 3.5.56 → 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/studies/dateTime.js +29 -25
- package/src/studies/priceFeatures.js +78 -0
- package/src/utilities/dateUtilities.js +1 -1
- 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
|
@@ -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
|
-
}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
export const defaultYCallback = (index, verticalOhlcv, horizon) => {
|
|
2
|
-
|
|
3
|
-
//this function will be executed inside a secondary loop after most of the indicates are calculated
|
|
4
|
-
//use the "verticalOhlcv" object to access desired indicators
|
|
5
|
-
|
|
6
|
-
if(horizon !== 2) throw new Error(`The default "yCallback" function in this classifier requires that "options.horizon" and "options.predictions" are set to "2".`)
|
|
7
|
-
|
|
8
|
-
const currClose = verticalOhlcv.close[index]
|
|
9
|
-
const nextCloseArr = Array.from({length: horizon}, (_, i) => verticalOhlcv.close[i + 1 + index])
|
|
10
|
-
|
|
11
|
-
if(nextCloseArr.some(v => typeof v === 'undefined')) return null
|
|
12
|
-
|
|
13
|
-
const [nextClose, nextNextClose] = nextCloseArr
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//the total length of item is this output must be equal to "options.yColumns" property
|
|
17
|
-
return [Number(nextClose > currClose), Number(nextNextClose > nextClose)]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const exportTrainedModels = main => {
|
|
21
|
-
|
|
22
|
-
const {ML, inputParams} = main
|
|
23
|
-
const allModels = ML.models
|
|
24
|
-
const output = {}
|
|
25
|
-
|
|
26
|
-
for (const { key, params } of inputParams) {
|
|
27
|
-
|
|
28
|
-
if(key !== 'classifier' && key !== 'regressor') continue
|
|
29
|
-
|
|
30
|
-
const [_, {type, predictions, precompute}] = params
|
|
31
|
-
const {flatY, prefix} = precompute
|
|
32
|
-
const expectedLoops = (flatY) ? predictions : 1
|
|
33
|
-
|
|
34
|
-
let exportModel
|
|
35
|
-
|
|
36
|
-
if(key === 'regressor')
|
|
37
|
-
{
|
|
38
|
-
exportModel = validRegressors[type].exportModel
|
|
39
|
-
}
|
|
40
|
-
else if (key === 'classifier')
|
|
41
|
-
{
|
|
42
|
-
exportModel = validClassifiers[type].exportModel
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for(let loopIdx = 0; loopIdx < expectedLoops; loopIdx++)
|
|
46
|
-
{
|
|
47
|
-
const modelKey = `${prefix}_${(loopIdx+1)}`
|
|
48
|
-
output[modelKey] = exportModel(allModels[modelKey])
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return output
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
export const validClassifiers = {
|
|
58
|
-
'KNN': {
|
|
59
|
-
shortName: 'knn',
|
|
60
|
-
flatY: true,
|
|
61
|
-
exportModel: m => m.toJSON(),
|
|
62
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => new modelClass(xRows, yRows, modelArgs),
|
|
63
|
-
unsupervised: false
|
|
64
|
-
},
|
|
65
|
-
'FeedForwardNeuralNetworks': {
|
|
66
|
-
shortName: 'feed_forward',
|
|
67
|
-
flatY: false,
|
|
68
|
-
exportModel: m => m.toJSON(),
|
|
69
|
-
train: ({modelClass, xRows, yRows, modelArgs, uniqueLabels}) => {
|
|
70
|
-
|
|
71
|
-
if(!modelArgs) modelArgs = {}
|
|
72
|
-
|
|
73
|
-
modelArgs = {
|
|
74
|
-
...modelArgs,
|
|
75
|
-
activation: (uniqueLabels === 2) ? 'logistic' : 'identity'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const m = new modelClass(modelArgs)
|
|
79
|
-
m.train(xRows, yRows)
|
|
80
|
-
return m
|
|
81
|
-
},
|
|
82
|
-
unsupervised: false
|
|
83
|
-
},
|
|
84
|
-
'GaussianNB': {
|
|
85
|
-
shortName: 'naive_bayes',
|
|
86
|
-
flatY: true,
|
|
87
|
-
exportModel: m => m.toJSON(),
|
|
88
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
89
|
-
const m = new modelClass(modelArgs)
|
|
90
|
-
m.train(xRows, yRows)
|
|
91
|
-
return m
|
|
92
|
-
},
|
|
93
|
-
unsupervised: false
|
|
94
|
-
},
|
|
95
|
-
'MultinomialNB': {
|
|
96
|
-
shortName: 'naive_bayes',
|
|
97
|
-
flatY: true,
|
|
98
|
-
exportModel: m => m.toJSON(),
|
|
99
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
100
|
-
const m = new modelClass(modelArgs)
|
|
101
|
-
m.train(xRows, yRows)
|
|
102
|
-
return m
|
|
103
|
-
},
|
|
104
|
-
unsupervised: false
|
|
105
|
-
},
|
|
106
|
-
'DecisionTreeClassifier': {
|
|
107
|
-
shortName: 'decision_tree',
|
|
108
|
-
flatY: true,
|
|
109
|
-
exportModel: m => m.toJSON(),
|
|
110
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
111
|
-
const m = new modelClass(modelArgs)
|
|
112
|
-
m.train(xRows, yRows)
|
|
113
|
-
return m
|
|
114
|
-
},
|
|
115
|
-
unsupervised: false
|
|
116
|
-
},
|
|
117
|
-
'RandomForestClassifier': {
|
|
118
|
-
shortName: 'random_forest',
|
|
119
|
-
flatY: true,
|
|
120
|
-
exportModel: m => m.toJSON(),
|
|
121
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
122
|
-
const m = new modelClass(modelArgs)
|
|
123
|
-
m.train(xRows, yRows)
|
|
124
|
-
return m
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
unsupervised: false
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export const validRegressors = {
|
|
131
|
-
'ARIMA': {
|
|
132
|
-
shortName: 'arima',
|
|
133
|
-
flatX: true,
|
|
134
|
-
flatY: false,
|
|
135
|
-
exportModel: m => m,
|
|
136
|
-
train: ({modelClass, xRows, modelArgs}) => {
|
|
137
|
-
|
|
138
|
-
if(!modelArgs) modelArgs = {}
|
|
139
|
-
|
|
140
|
-
modelArgs = { ...modelArgs, p: 2, d: 1, q: 2, P: 0, D: 0, Q: 0, S: 0, verbose: false }
|
|
141
|
-
|
|
142
|
-
const m = new modelClass(modelArgs)
|
|
143
|
-
m.train(xRows)
|
|
144
|
-
return m
|
|
145
|
-
},
|
|
146
|
-
unsupervised: true
|
|
147
|
-
},
|
|
148
|
-
'SimpleLinearRegression': {
|
|
149
|
-
shortName: 'linear',
|
|
150
|
-
flatX: true,
|
|
151
|
-
flatY: true,
|
|
152
|
-
|
|
153
|
-
exportModel: m => m.toJSON(),
|
|
154
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => new modelClass(xRows, yRows, modelArgs),
|
|
155
|
-
unsupervised: false
|
|
156
|
-
},
|
|
157
|
-
'PolynomialRegression': {
|
|
158
|
-
shortName: 'polynomial',
|
|
159
|
-
flatX: true,
|
|
160
|
-
flatY: true,
|
|
161
|
-
|
|
162
|
-
exportModel: m => m.toJSON(),
|
|
163
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => new modelClass(xRows, yRows, modelArgs),
|
|
164
|
-
unsupervised: false
|
|
165
|
-
},
|
|
166
|
-
'MultivariateLinearRegression': {
|
|
167
|
-
shortName: 'multivariable',
|
|
168
|
-
flatX: false,
|
|
169
|
-
flatY: false,
|
|
170
|
-
|
|
171
|
-
exportModel: m => m.toJSON(),
|
|
172
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => new modelClass(xRows, yRows, modelArgs),
|
|
173
|
-
unsupervised: false
|
|
174
|
-
},
|
|
175
|
-
'DecisionTreeRegression': {
|
|
176
|
-
shortName: 'decision_tree',
|
|
177
|
-
flatX: false,
|
|
178
|
-
flatY: true,
|
|
179
|
-
exportModel: m => m.toJSON(),
|
|
180
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
181
|
-
const m = new modelClass(modelArgs)
|
|
182
|
-
m.train(xRows, yRows)
|
|
183
|
-
return m
|
|
184
|
-
},
|
|
185
|
-
unsupervised: false
|
|
186
|
-
},
|
|
187
|
-
'RandomForestRegression': {
|
|
188
|
-
shortName: 'random_forest',
|
|
189
|
-
flatX: false,
|
|
190
|
-
flatY: true,
|
|
191
|
-
exportModel: m => m.toJSON(),
|
|
192
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
193
|
-
const m = new modelClass(modelArgs)
|
|
194
|
-
m.train(xRows, yRows)
|
|
195
|
-
return m
|
|
196
|
-
},
|
|
197
|
-
unsupervised: false
|
|
198
|
-
},
|
|
199
|
-
'FeedForwardNeuralNetworks': {
|
|
200
|
-
shortName: 'feed_forward',
|
|
201
|
-
flatX: false,
|
|
202
|
-
flatY: false,
|
|
203
|
-
exportModel: m => m.toJSON(),
|
|
204
|
-
train: ({modelClass, xRows, yRows, modelArgs}) => {
|
|
205
|
-
|
|
206
|
-
if(!modelArgs) modelArgs = {}
|
|
207
|
-
|
|
208
|
-
modelArgs = {
|
|
209
|
-
...modelArgs,
|
|
210
|
-
activation: 'identity'
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const m = new modelClass(modelArgs)
|
|
214
|
-
m.train(xRows, yRows)
|
|
215
|
-
return m
|
|
216
|
-
},
|
|
217
|
-
unsupervised: false
|
|
218
|
-
}
|
|
219
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { getFeaturedKeys, computeFlatFeaturesLen, findGroupsFunc } from "./ml-utilities.js";
|
|
2
|
-
import { buildTrainX } from "./trainX.js";
|
|
3
|
-
import { areKeyValuesValid } from "../core-functions/pushToMain.js";
|
|
4
|
-
|
|
5
|
-
const algo = 'pca'
|
|
6
|
-
|
|
7
|
-
export const pca = (main, index, {prefix, trainingSize, findGroups, trainingCols, lookbackAbs, modelArgs, filterCallback}) => {
|
|
8
|
-
|
|
9
|
-
const {verticalOhlcv, scaledGroups, len, instances, ML, invalidValueIndex} = main
|
|
10
|
-
|
|
11
|
-
const startIndex = (invalidValueIndex + 2)
|
|
12
|
-
const allModels = ML.models
|
|
13
|
-
|
|
14
|
-
if(index === startIndex)
|
|
15
|
-
{
|
|
16
|
-
if(!instances.hasOwnProperty(algo)) instances[algo] = {}
|
|
17
|
-
|
|
18
|
-
instances[algo][prefix] = {
|
|
19
|
-
flatFeaturesColLen: 0,
|
|
20
|
-
featureCols: [],
|
|
21
|
-
X: []
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
verticalOhlcv[prefix] = new Array(len).fill(null)
|
|
25
|
-
}
|
|
26
|
-
else if(index < startIndex || index < lookbackAbs)
|
|
27
|
-
{
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
else if(filterCallback(verticalOhlcv, index) === false)
|
|
31
|
-
{
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
else if(index + 1 === len)
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
if(instances[algo][prefix].featureCols.length === 0)
|
|
38
|
-
{
|
|
39
|
-
const inputFeatures = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
40
|
-
|
|
41
|
-
throw new Error(`Some of the provided features in ${prefix} features where not found in "verticalOhlcv": ${JSON.stringify(inputFeatures)}`)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
//last execution
|
|
45
|
-
for(const featureKey of instances[algo][prefix].featureCols)
|
|
46
|
-
{
|
|
47
|
-
if(!verticalOhlcv.hasOwnProperty(featureKey)) throw new Error(`Feature "${featureKey}" not found in verticalOhlcv for pca.`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if(instances[algo][prefix].X.length < trainingSize)
|
|
51
|
-
{
|
|
52
|
-
const requiredDatapoints = instances[algo][prefix].X.length - trainingSize
|
|
53
|
-
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}.`)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const dataSetInstance = instances[algo][prefix]
|
|
58
|
-
|
|
59
|
-
if(dataSetInstance.flatFeaturesColLen === 0)
|
|
60
|
-
{
|
|
61
|
-
dataSetInstance.featureCols = getFeaturedKeys({trainingCols, findGroups, verticalOhlcv, scaledGroups})
|
|
62
|
-
|
|
63
|
-
if(areKeyValuesValid(main, index, dataSetInstance.featureCols))
|
|
64
|
-
{
|
|
65
|
-
dataSetInstance.flatFeaturesColLen = computeFlatFeaturesLen(dataSetInstance.featureCols, verticalOhlcv, index)
|
|
66
|
-
} else {
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if(dataSetInstance.flatFeaturesColLen === 0) return
|
|
72
|
-
|
|
73
|
-
const {X: xRows, featureCols, flatFeaturesColLen} = dataSetInstance
|
|
74
|
-
|
|
75
|
-
const trainX = buildTrainX({featureCols, flatFeaturesColLen, type: algo, index, lookbackAbs, main})
|
|
76
|
-
|
|
77
|
-
if (trainX == null) return
|
|
78
|
-
|
|
79
|
-
if(allModels.hasOwnProperty(prefix))
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
const futureRow = allModels[prefix].predict([trainX]).to2DArray()
|
|
83
|
-
|
|
84
|
-
verticalOhlcv[prefix][index] = futureRow[0]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
xRows.push(trainX)
|
|
88
|
-
|
|
89
|
-
if (xRows.length > trainingSize) xRows.shift()
|
|
90
|
-
|
|
91
|
-
if(xRows.length === trainingSize)
|
|
92
|
-
{
|
|
93
|
-
allModels[prefix] = new ML.classes.PCA(xRows, modelArgs)
|
|
94
|
-
}
|
|
95
|
-
}
|