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.
- package/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +53 -329
- package/package.json +2 -13
- package/src/core-functions/mainLoop.js +10 -42
- package/src/machine-learning/ml-utilities.js +6 -114
- package/src/machine-learning/scaler.js +93 -75
- package/src/studies/dateTime.js +29 -25
- package/src/studies/priceFeatures.js +78 -0
- package/src/utilities/dateUtilities.js +1 -1
- package/src/utilities/validators.js +2 -2
- 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,227 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds a flattened lookback feature vector or returns null if any value is non‐finite.
|
|
3
|
-
*
|
|
4
|
-
* @param {string[]} featureCols – list of feature column keys
|
|
5
|
-
* @param {Object} instances – your instances object (with crossPairs)
|
|
6
|
-
* @param {number} flatFeaturesColLen – total number of flattened feature slots
|
|
7
|
-
* @param {string} type – a string used only for error reporting
|
|
8
|
-
* @param {number} index – current time index
|
|
9
|
-
* @param {number} lookbackAbs – number of lags to include
|
|
10
|
-
* @param {Object} verticalOhlcv – an object mapping each key to its time series array
|
|
11
|
-
* @returns {number[]\|null} – the feature vector or null if exiting early
|
|
12
|
-
*/
|
|
13
|
-
export const buildTrainX = ({
|
|
14
|
-
featureCols,
|
|
15
|
-
flatFeaturesColLen,
|
|
16
|
-
type,
|
|
17
|
-
index,
|
|
18
|
-
lookbackAbs,
|
|
19
|
-
main
|
|
20
|
-
}) => {
|
|
21
|
-
|
|
22
|
-
const {verticalOhlcv} = main
|
|
23
|
-
|
|
24
|
-
let shouldExit = false
|
|
25
|
-
|
|
26
|
-
// --- BUILD A FLATTENED LIST OF “COLUMN SLOTS” ---
|
|
27
|
-
const slots = [];
|
|
28
|
-
for (const key of featureCols) {
|
|
29
|
-
if (key.startsWith('one_hot_') || key.startsWith('pca_')) {
|
|
30
|
-
|
|
31
|
-
const colSize = verticalOhlcv[key][index].length
|
|
32
|
-
|
|
33
|
-
for (let bit = 0; bit < colSize; bit++) {
|
|
34
|
-
slots.push({ key, bit });
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
slots.push({ key });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (slots.length !== flatFeaturesColLen) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`slots (${slots.length}) ≠ flatFeaturesColLen (${flatFeaturesColLen}) in ${type} index ${index}`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// --- ALLOCATE AND FILL trainX ---
|
|
48
|
-
const trainX = new Array(flatFeaturesColLen * lookbackAbs).fill(NaN);
|
|
49
|
-
|
|
50
|
-
for (let lag = 0; lag < lookbackAbs; lag++) {
|
|
51
|
-
const tIdx = index - lag;
|
|
52
|
-
|
|
53
|
-
for (let s = 0; s < slots.length; s++) {
|
|
54
|
-
const { key, bit } = slots[s];
|
|
55
|
-
const cell = verticalOhlcv[key][tIdx];
|
|
56
|
-
const value = (bit != null) ? cell[bit] : cell;
|
|
57
|
-
|
|
58
|
-
if (!Number.isFinite(value)) {
|
|
59
|
-
shouldExit = true;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
trainX[lag * flatFeaturesColLen + s] = value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (shouldExit) break;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (shouldExit) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return trainX;
|
|
73
|
-
};
|
package/src/utilities/order.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { validateArray, validateString } from "./validators.js"
|
|
3
|
-
|
|
4
|
-
export const getOrderFromArray = (arr, methodName) => {
|
|
5
|
-
|
|
6
|
-
validateArray(arr, 'arr', 'getOrderFromArray')
|
|
7
|
-
validateString(methodName, 'methodName', 'getOrderFromArray')
|
|
8
|
-
|
|
9
|
-
const level2Methods = ['regressor', 'classifier', 'pca']
|
|
10
|
-
const baseKeys = ['open', 'high', 'low', 'close', 'volume', 'date']
|
|
11
|
-
|
|
12
|
-
// 1. Level-2: does this method live in your level-2 list?
|
|
13
|
-
const isSecondary = level2Methods.includes(methodName);
|
|
14
|
-
|
|
15
|
-
// 2. Level-0: is every field one of the primitives *and* not level-2?
|
|
16
|
-
const isPrimary = arr.every(v => baseKeys.includes(v)) && !isSecondary;
|
|
17
|
-
|
|
18
|
-
if(isPrimary)
|
|
19
|
-
{
|
|
20
|
-
return 0
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let order = 1
|
|
24
|
-
const level1Prefixes = ['_x_', 'zscore_', 'minmax_', 'lag_']
|
|
25
|
-
|
|
26
|
-
for(const k of level1Prefixes)
|
|
27
|
-
{
|
|
28
|
-
if(arr.some(v => v.includes(k)))
|
|
29
|
-
{
|
|
30
|
-
order++
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if(!isSecondary)
|
|
35
|
-
{
|
|
36
|
-
return order
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const level2Prefixes = ['prediction_', 'reg_', 'cla_', 'pca_']
|
|
40
|
-
order = 10
|
|
41
|
-
|
|
42
|
-
for(const k of level2Prefixes)
|
|
43
|
-
{
|
|
44
|
-
if(arr.some(v => v.includes(k)))
|
|
45
|
-
{
|
|
46
|
-
order++
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return order
|
|
51
|
-
}
|