ohlcv-indicators 3.5.41 → 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 +71 -80
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.42",
|
|
4
4
|
"description": "A JavaScript library featuring optimized trading indicators for OHLCV arrays [{open, high, low, close, value}]. This library is optimized for backtesting and trading bot development.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"author": "https://jaimelias.com/",
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"ml-pca": "^4.1.1",
|
|
16
15
|
"trading-signals": "^5.0.4",
|
|
17
16
|
"xy-scale": "^1.4.1"
|
|
18
17
|
},
|
|
@@ -20,8 +19,18 @@
|
|
|
20
19
|
"@babel/core": "^7.25.2",
|
|
21
20
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
22
21
|
"@babel/preset-env": "^7.25.3",
|
|
22
|
+
"arima": "^0.2.5",
|
|
23
23
|
"babel-loader": "^9.1.3",
|
|
24
24
|
"dotenv": "^16.4.5",
|
|
25
|
+
"merge-multi-timeframe": "^1.1.6",
|
|
26
|
+
"ml-cart": "^2.1.1",
|
|
27
|
+
"ml-fnn": "^5.0.0",
|
|
28
|
+
"ml-knn": "^3.0.0",
|
|
29
|
+
"ml-naivebayes": "^4.0.0",
|
|
30
|
+
"ml-pca": "^4.1.1",
|
|
31
|
+
"ml-random-forest": "^2.1.0",
|
|
32
|
+
"ml-regression-multivariate-linear": "^2.0.4",
|
|
33
|
+
"ml-regression-simple-linear": "^3.0.1",
|
|
25
34
|
"terser-webpack-plugin": "^5.3.14",
|
|
26
35
|
"webpack": "^5.93.0",
|
|
27
36
|
"webpack-cli": "^5.1.4"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { areKeyValuesValid } from "./pushToMain.js";
|
|
1
2
|
import { validateInputParams } from "../utilities/validators.js";
|
|
2
3
|
import { rsi } from "../oscillators/rsi.js";
|
|
4
|
+
import { stochastic } from "../oscillators/stochastic.js";
|
|
3
5
|
import { movingAverages } from "../moving-averages/movingAverages.js";
|
|
6
|
+
import { vidya } from "../moving-averages/vidya.js";
|
|
4
7
|
import { macd } from "../moving-averages/macd.js";
|
|
5
8
|
import { relativeVolume } from "../moving-averages/relativeVolume.js";
|
|
6
9
|
import { donchianChannels } from "../moving-averages/donchianChannel.js";
|
|
@@ -11,31 +14,48 @@ import { crossPairs } from "../studies/findCrosses.js";
|
|
|
11
14
|
import { dateTime } from "../studies/dateTime.js";
|
|
12
15
|
import { scaler } from "../machine-learning/scaler.js";
|
|
13
16
|
import { atr } from "../volatility/atr.js";
|
|
17
|
+
import { mapCols } from "../studies/mapCols.js";
|
|
14
18
|
|
|
15
19
|
import { buildArray } from "../utilities/assignTypes.js";
|
|
16
20
|
import { numberFormater } from "../utilities/numberUtilities.js";
|
|
17
21
|
import { dateFormaters } from "../utilities/dateUtilities.js";
|
|
18
22
|
|
|
19
23
|
import { pca } from "../machine-learning/pca.js";
|
|
24
|
+
import { regressor } from "../machine-learning/regressor.js";
|
|
25
|
+
import { classifier } from "../machine-learning/classifier.js";
|
|
20
26
|
|
|
21
27
|
// Map indicator keys to their respective functions
|
|
22
|
-
const
|
|
28
|
+
const mainFunctions = {
|
|
23
29
|
dateTime,
|
|
24
30
|
rsi,
|
|
31
|
+
stochastic,
|
|
25
32
|
atr,
|
|
26
33
|
ema: movingAverages,
|
|
27
34
|
sma: movingAverages,
|
|
35
|
+
vidya,
|
|
28
36
|
macd,
|
|
29
37
|
relativeVolume,
|
|
30
38
|
donchianChannels,
|
|
31
39
|
bollingerBands,
|
|
32
40
|
volumeOscillator,
|
|
33
|
-
|
|
41
|
+
mapCols,
|
|
42
|
+
scaler,
|
|
43
|
+
crossPairs,
|
|
44
|
+
lag
|
|
34
45
|
};
|
|
35
46
|
|
|
36
|
-
|
|
37
47
|
export const mainLoop = (input, main) => {
|
|
38
|
-
const { len,
|
|
48
|
+
const { len,
|
|
49
|
+
inputParams,
|
|
50
|
+
precisionMultiplier,
|
|
51
|
+
arrayTypes,
|
|
52
|
+
verticalOhlcv,
|
|
53
|
+
verticalOhlcvKeyNames,
|
|
54
|
+
inputTypes,
|
|
55
|
+
chunkProcess,
|
|
56
|
+
notNumberKeys,
|
|
57
|
+
processSecondaryLoop,
|
|
58
|
+
} = main;
|
|
39
59
|
|
|
40
60
|
validateInputParams(inputParams, len)
|
|
41
61
|
|
|
@@ -43,7 +63,6 @@ export const mainLoop = (input, main) => {
|
|
|
43
63
|
{
|
|
44
64
|
verticalOhlcv[key] = buildArray(arrayTypes[key], len)
|
|
45
65
|
}
|
|
46
|
-
|
|
47
66
|
// Process each row in the input
|
|
48
67
|
for (let chunkStart = 0; chunkStart < len; chunkStart += chunkProcess) {
|
|
49
68
|
|
|
@@ -60,7 +79,7 @@ export const mainLoop = (input, main) => {
|
|
|
60
79
|
{
|
|
61
80
|
let formatedValue = numberFormater[formaterKey](value)
|
|
62
81
|
|
|
63
|
-
if(precisionMultiplier > 1 &&
|
|
82
|
+
if(precisionMultiplier > 1 && !notNumberKeys.has(key))
|
|
64
83
|
{
|
|
65
84
|
formatedValue = formatedValue * precisionMultiplier
|
|
66
85
|
}
|
|
@@ -76,22 +95,57 @@ export const mainLoop = (input, main) => {
|
|
|
76
95
|
main.pushToMain({index, key, value})
|
|
77
96
|
}
|
|
78
97
|
}
|
|
79
|
-
|
|
80
|
-
// Run all indicator functions except for the ones processed later
|
|
81
|
-
for (const { key, params } of inputParams) {
|
|
82
|
-
if (key === 'lag' || key === 'crossPairs') continue;
|
|
83
|
-
// resolve fn on-demand, no per-item object allocation here
|
|
84
|
-
indicatorFunctions[key](main, index, ...params)
|
|
85
|
-
}
|
|
86
98
|
|
|
87
99
|
// Process these indicators separately (ensuring their execution order)
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
for (const { key, params, order } of inputParams)
|
|
101
|
+
{
|
|
102
|
+
for(let orderIdx = 0; orderIdx <= 9; orderIdx++)
|
|
103
|
+
{
|
|
104
|
+
if(typeof order !== 'number') throw new Error(`order property of ${key} not found: ${params}`)
|
|
105
|
+
if(orderIdx !== order) continue
|
|
106
|
+
mainFunctions[key](main, index, ...params)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
input[index] = null //flusing data
|
|
111
|
+
|
|
112
|
+
const keyNames = Object.keys(verticalOhlcv)
|
|
113
|
+
|
|
114
|
+
// if any value at this index is missing/NaN/null, mark it invalid
|
|
115
|
+
if (!areKeyValuesValid(main, index, keyNames)) {
|
|
116
|
+
main.invalidValueIndex = index
|
|
117
|
+
}
|
|
91
118
|
}
|
|
92
119
|
}
|
|
93
120
|
|
|
94
|
-
|
|
121
|
+
const isSecondaryLoopNeeded = inputParams.some(({ key }) => key === 'regressor' || key === 'classifier' || key === 'pca')
|
|
122
|
+
|
|
123
|
+
if(isSecondaryLoopNeeded || processSecondaryLoop)
|
|
124
|
+
{
|
|
125
|
+
secondaryLoop(main)
|
|
126
|
+
}
|
|
95
127
|
|
|
96
128
|
verticalOhlcvKeyNames.push(...Object.keys(verticalOhlcv))
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const secondaryLoop = (main) => {
|
|
133
|
+
const {inputParams, len, invalidValueIndex} = main
|
|
134
|
+
|
|
135
|
+
const startIndex = invalidValueIndex + 1
|
|
136
|
+
|
|
137
|
+
for(let index = startIndex; index < len; index++)
|
|
138
|
+
{
|
|
139
|
+
for (const { key, params, order } of inputParams)
|
|
140
|
+
{
|
|
141
|
+
for(let orderIdx = 10; orderIdx <= 20; orderIdx++) {
|
|
142
|
+
if(typeof order !== 'number') throw new Error(`order property of ${key} not found: ${params}`)
|
|
143
|
+
if(orderIdx !== order) continue
|
|
144
|
+
if(key === 'pca') pca(main, index, ...params)
|
|
145
|
+
if(key === 'classifier') classifier(main, index, ...params)
|
|
146
|
+
if(key === 'regressor') regressor(main, index, ...params)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
97
151
|
}
|
|
@@ -2,14 +2,35 @@ export const pushToMain = ({main, index, key, value}) => {
|
|
|
2
2
|
|
|
3
3
|
const {verticalOhlcv} = main
|
|
4
4
|
|
|
5
|
-
if(value === null || typeof value === 'undefined' || Number.isNaN(value))
|
|
6
|
-
{
|
|
7
|
-
main.invalidValueIndex = index
|
|
8
|
-
verticalOhlcv[key][index] = value
|
|
9
|
-
return false
|
|
10
|
-
}
|
|
11
|
-
|
|
12
5
|
verticalOhlcv[key][index] = value
|
|
13
6
|
|
|
14
7
|
return true
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const areKeyValuesValid = (main, index, keyNames) => {
|
|
11
|
+
|
|
12
|
+
if(keyNames.length === 0) return false
|
|
13
|
+
|
|
14
|
+
const {verticalOhlcv} = main
|
|
15
|
+
|
|
16
|
+
let output = true
|
|
17
|
+
|
|
18
|
+
for(let x = 0 ; x < keyNames.length; x++)
|
|
19
|
+
{
|
|
20
|
+
const key = keyNames[x]
|
|
21
|
+
if(!verticalOhlcv.hasOwnProperty(key)) {
|
|
22
|
+
output = false
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const val = verticalOhlcv[key][index]
|
|
27
|
+
|
|
28
|
+
if(typeof val === 'undefined' || val === null || Number.isNaN(val))
|
|
29
|
+
{
|
|
30
|
+
output = false
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return output
|
|
15
36
|
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { getFeaturedKeys, computeFlatFeaturesLen, countUniqueLabels, logMlTraining, updateClassifierMetrics, findGroupsFunc } from "./ml-utilities.js"
|
|
2
|
+
import { buildTrainX } from "./trainX.js"
|
|
3
|
+
import { areKeyValuesValid } from "../core-functions/pushToMain.js"
|
|
4
|
+
|
|
5
|
+
const algo = 'classifier'
|
|
6
|
+
|
|
7
|
+
export const classifier = (
|
|
8
|
+
main,
|
|
9
|
+
index,
|
|
10
|
+
trainingSize,
|
|
11
|
+
{
|
|
12
|
+
yCallback,
|
|
13
|
+
trainingCols,
|
|
14
|
+
findGroups = [],
|
|
15
|
+
retrain,
|
|
16
|
+
type,
|
|
17
|
+
modelArgs,
|
|
18
|
+
horizon,
|
|
19
|
+
precompute,
|
|
20
|
+
predictions,
|
|
21
|
+
filterCallback,
|
|
22
|
+
modelConfig,
|
|
23
|
+
modelClass
|
|
24
|
+
}
|
|
25
|
+
) => {
|
|
26
|
+
const { lookbackAbs, prefix, flatY } = precompute
|
|
27
|
+
const { verticalOhlcv, len, instances, scaledGroups, invalidValueIndex, ML } = main
|
|
28
|
+
const allModels = ML.models
|
|
29
|
+
const startIndex = (invalidValueIndex + 1)
|
|
30
|
+
|
|
31
|
+
// ─── INITIALIZATION ───────────────────────────────────────────────
|
|
32
|
+
if(index === startIndex) {
|
|
33
|
+
|
|
34
|
+
// prepare instance storage
|
|
35
|
+
if (!instances.hasOwnProperty(algo)) instances[algo] = {}
|
|
36
|
+
if(!instances[algo].hasOwnProperty(prefix)) instances[algo][prefix] = {}
|
|
37
|
+
|
|
38
|
+
const expectedLoops = (flatY) ? predictions : 1 //
|
|
39
|
+
|
|
40
|
+
instances[algo][prefix] = {
|
|
41
|
+
|
|
42
|
+
expectedLoops,
|
|
43
|
+
isTrained: new Array(expectedLoops).fill(false),
|
|
44
|
+
uniqueLabels: new Array(expectedLoops).fill(0),
|
|
45
|
+
shouldRetrain: retrain,
|
|
46
|
+
featureCols: [],
|
|
47
|
+
flatFeaturesColLen: 0,
|
|
48
|
+
X: [],
|
|
49
|
+
Y: Array.from({ length: expectedLoops }, () => []),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < predictions; i++) {
|
|
53
|
+
const predictionKey = `${prefix}_${i + 1}`
|
|
54
|
+
|
|
55
|
+
verticalOhlcv[predictionKey] = (type === 'KNN') ? new Array(len).fill(null) : new Float32Array(len).fill(NaN)
|
|
56
|
+
ML.metrics[predictionKey] = {accuracy: {}, total: 0, correct: 0, labels: {}}
|
|
57
|
+
|
|
58
|
+
if(!allModels.hasOwnProperty(predictionKey))
|
|
59
|
+
{
|
|
60
|
+
allModels[predictionKey] = []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if(index < startIndex || index < lookbackAbs)
|
|
65
|
+
{
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
else if(filterCallback(verticalOhlcv, index) === false)
|
|
69
|
+
{
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
else if(index + 1 === len)
|
|
73
|
+
{
|
|
74
|
+
|
|
75
|
+
if(instances[algo][prefix].featureCols.length === 0)
|
|
76
|
+
{
|
|
77
|
+
const inputFeatures = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
78
|
+
|
|
79
|
+
throw new Error(`Some of the provided features in ${algo} "${prefix}" features where not found in "verticalOhlcv": ${JSON.stringify(inputFeatures)}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//last execution
|
|
83
|
+
for(const featureKey of instances[algo][prefix].featureCols)
|
|
84
|
+
{
|
|
85
|
+
if(!verticalOhlcv.hasOwnProperty(featureKey)) throw new Error(`Feature "${featureKey}" not found in verticalOhlcv for ${prefix}.`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if(instances[algo][prefix].X.length < trainingSize)
|
|
89
|
+
{
|
|
90
|
+
const requiredDatapoints = instances[algo][prefix].X.length - trainingSize
|
|
91
|
+
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}.`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const dataSetInstance = instances[algo][prefix]
|
|
97
|
+
|
|
98
|
+
if(dataSetInstance.flatFeaturesColLen === 0)
|
|
99
|
+
{
|
|
100
|
+
dataSetInstance.featureCols = getFeaturedKeys({trainingCols, findGroups, verticalOhlcv, scaledGroups})
|
|
101
|
+
|
|
102
|
+
if(areKeyValuesValid(main, index, dataSetInstance.featureCols))
|
|
103
|
+
{
|
|
104
|
+
dataSetInstance.flatFeaturesColLen = computeFlatFeaturesLen(dataSetInstance.featureCols, verticalOhlcv, index)
|
|
105
|
+
|
|
106
|
+
logMlTraining({
|
|
107
|
+
featureCols: dataSetInstance.featureCols,
|
|
108
|
+
flatFeaturesColLen: dataSetInstance.flatFeaturesColLen,
|
|
109
|
+
type,
|
|
110
|
+
trainingSize
|
|
111
|
+
})
|
|
112
|
+
} else {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if(dataSetInstance.flatFeaturesColLen === 0) return
|
|
118
|
+
|
|
119
|
+
const {
|
|
120
|
+
uniqueLabels,
|
|
121
|
+
expectedLoops,
|
|
122
|
+
featureCols,
|
|
123
|
+
flatFeaturesColLen,
|
|
124
|
+
shouldRetrain,
|
|
125
|
+
X: xRows
|
|
126
|
+
} = dataSetInstance
|
|
127
|
+
|
|
128
|
+
const trainX = buildTrainX({
|
|
129
|
+
featureCols,
|
|
130
|
+
flatFeaturesColLen,
|
|
131
|
+
type,
|
|
132
|
+
index,
|
|
133
|
+
lookbackAbs,
|
|
134
|
+
main
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
if(!trainX) return
|
|
138
|
+
|
|
139
|
+
//if univariable Y (flatY) a model is created for each prediction
|
|
140
|
+
const trainY = yCallback(index, verticalOhlcv, horizon)
|
|
141
|
+
|
|
142
|
+
if (trainY !== null && (!Array.isArray(trainY) || trainY.length !== predictions)) {
|
|
143
|
+
|
|
144
|
+
throw new Error(`trainY must be null or an array with ${predictions} items, got "${JSON.stringify(trainY)}" at index ${index} for ${type}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for(let loopIdx = 0; loopIdx < expectedLoops; loopIdx++)
|
|
148
|
+
{
|
|
149
|
+
const predictionKey = `${prefix}_${(loopIdx+1)}`
|
|
150
|
+
const yRows = dataSetInstance.Y[loopIdx]
|
|
151
|
+
const currTrainY = (flatY) ? (trainY === null) ? null : trainY[loopIdx] : trainY
|
|
152
|
+
const isTrained = dataSetInstance.isTrained[loopIdx]
|
|
153
|
+
|
|
154
|
+
const shouldPredict = allModels.hasOwnProperty(predictionKey) && isTrained && (shouldRetrain === false || allModels[predictionKey].length === horizon)
|
|
155
|
+
|
|
156
|
+
//predicts using previously saved models even if current currTrainY is not available
|
|
157
|
+
if(shouldPredict)
|
|
158
|
+
{
|
|
159
|
+
const futureRow = allModels[predictionKey][0].predict([trainX])[0]
|
|
160
|
+
|
|
161
|
+
if(flatY)
|
|
162
|
+
{
|
|
163
|
+
if(futureRow == null) throw new Error(`Prediction of ${prefix} at index ${index} was expecting a number.`)
|
|
164
|
+
|
|
165
|
+
main.pushToMain({index, key: predictionKey, value: futureRow})
|
|
166
|
+
|
|
167
|
+
if(currTrainY != null)
|
|
168
|
+
{
|
|
169
|
+
updateClassifierMetrics({
|
|
170
|
+
metrics: ML.metrics[predictionKey],
|
|
171
|
+
trueLabel: currTrainY,
|
|
172
|
+
predictedLabel: futureRow
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
|
|
178
|
+
if (!Array.isArray(futureRow) || futureRow.length !== predictions) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Prediction output of ${algo} "${prefix}" at index ${index} was expecting an array of values.`
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for(let preIdx = 0; preIdx < predictions; preIdx++) {
|
|
185
|
+
main.pushToMain({index, key: `${prefix}_${(preIdx+1)}`, value: futureRow[preIdx]})
|
|
186
|
+
|
|
187
|
+
if(Array.isArray(currTrainY) && currTrainY.length === predictions)
|
|
188
|
+
{
|
|
189
|
+
updateClassifierMetrics({
|
|
190
|
+
metrics: ML.metrics[`${prefix}_${(preIdx+1)}`],
|
|
191
|
+
trueLabel: currTrainY[preIdx],
|
|
192
|
+
predictedLabel: futureRow[preIdx]
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if((index + predictions + 1) > len) continue
|
|
201
|
+
if(currTrainY === null) continue // future not defined
|
|
202
|
+
|
|
203
|
+
if(flatY)
|
|
204
|
+
{
|
|
205
|
+
if (typeof currTrainY === 'undefined' || currTrainY == null)
|
|
206
|
+
{
|
|
207
|
+
throw new Error(`currTrainY must return number, string or boolean got ${typeof currTrainY} at index ${index}`)
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
|
|
211
|
+
if ((currTrainY.length !== predictions)) {
|
|
212
|
+
|
|
213
|
+
throw new Error(
|
|
214
|
+
`The number of label columns returned (${currTrainY.length}) doesn’t match the options.predictions (${predictions}) setting for the ${algo} "${prefix}" classifier. ` +
|
|
215
|
+
`Please update your yCallback function so its output array items align with options.predictions.`
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// enqueue
|
|
222
|
+
|
|
223
|
+
yRows.push(currTrainY)
|
|
224
|
+
if (yRows.length > trainingSize) yRows.shift()
|
|
225
|
+
|
|
226
|
+
if(loopIdx === 0)
|
|
227
|
+
{
|
|
228
|
+
xRows.push(trainX)
|
|
229
|
+
if (xRows.length > trainingSize) xRows.shift()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const shouldTrainModel = shouldRetrain || !isTrained
|
|
233
|
+
|
|
234
|
+
if (shouldTrainModel && xRows.length === trainingSize && yRows.length === trainingSize) {
|
|
235
|
+
|
|
236
|
+
if(isTrained === false)
|
|
237
|
+
{
|
|
238
|
+
uniqueLabels[loopIdx] = countUniqueLabels(yRows)
|
|
239
|
+
|
|
240
|
+
if(uniqueLabels[loopIdx] < 2)
|
|
241
|
+
{
|
|
242
|
+
throw new Error(`Invalid number or labels in ${prefix}. Check the logic of your "yCallback" function.`)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
const {train} = modelConfig
|
|
248
|
+
|
|
249
|
+
console.log(yRows)
|
|
250
|
+
|
|
251
|
+
const trainedModel = train({modelClass, xRows, yRows, modelArgs, uniqueLabels: uniqueLabels[loopIdx]})
|
|
252
|
+
|
|
253
|
+
allModels[predictionKey].push(
|
|
254
|
+
trainedModel
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if(allModels[predictionKey].length > horizon)
|
|
258
|
+
{
|
|
259
|
+
allModels[predictionKey].shift()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
dataSetInstance.isTrained[loopIdx] = true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return true
|
|
267
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
}
|