ohlcv-indicators 3.5.56 → 3.5.60

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.
@@ -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
- }