xy-scale 1.3.2 → 1.3.4
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/datasets/btc-1d.json +1 -0
- package/dist/xy-scale.min.js +1 -1
- package/package.json +4 -1
- package/src/datasets.js +18 -14
- package/src/scale.js +60 -94
- package/test/test.js +149 -55
- package/datasets/1d-spy.json +0 -1
package/dist/xy-scale.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var XY_Scale;(()=>{"use strict";var e={d:(
|
|
1
|
+
var XY_Scale;(()=>{"use strict";var e={d:(t,n)=>{for(var a in n)e.o(n,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:n[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{arrayToTimesteps:()=>u,parseProductionX:()=>s,parseTrainingXY:()=>o});const n=({arrObj:e,repeat:t={},minmaxRange:n=[0,1],groups:o={},customMinMaxRanges:s=null,excludes:u=new Set})=>{const i=[...e],l=i.length,c=i[0],p="object"==typeof s&&null!==s;if(0===l)return{scaledOutput:[],scaledConfig:{}};const m=Object.keys(c),f=m.map((e=>t.hasOwnProperty(e)?Math.max(t[e],1):1)),g=f.reduce(((e,t)=>e+t),0),h={arrObjLen:l,rangeMin:n[0],rangeMax:n[1],inputTypes:{},min:{},max:{},groupMinMax:{},repeat:t,groups:o,inputKeyNames:m,outputKeyNames:new Array(g),repeatedKeyNames:f};let d=0;for(let e=0;e<h.inputKeyNames.length;e++)for(let t=0;t<h.repeatedKeyNames[e];t++)h.outputKeyNames[d++]=h.inputKeyNames[e];a(h.groups);const x=["number","boolean"];for(const e of h.inputKeyNames){if(u.has(e)){h.inputTypes[e]="excluded";continue}const t=typeof c[e],n=r(e,h.groups);if(!x.includes(t))throw new Error(`Invalid input type "${t}" provided for key "${e}". Only accepting `);h.inputTypes[e]=t,p&&s.hasOwnProperty(e)?n?h.groupMinMax[n]=s[e]:(h.min[e]=s[e].min,h.max[e]=s[e].max):n?h.groupMinMax[n]={min:1/0,max:-1/0}:(h.min[e]=1/0,h.max[e]=-1/0)}for(const e of i)for(const t of h.inputKeyNames){if("excluded"===h.inputTypes[t])continue;let n=e[t];"boolean"===h.inputTypes[t]&&(e[t]=Number(n));const a=r(t,h.groups);(!1===p||p&&!s.hasOwnProperty(t))&&(a?(h.groupMinMax[a].min=Math.min(h.groupMinMax[a].min,n),h.groupMinMax[a].max=Math.max(h.groupMinMax[a].max,n)):(h.min[t]=Math.min(h.min[t],n),h.max[t]=Math.max(h.max[t],n)))}const M=new Array(l);for(let e=0;e<l;e++){const t=i[e],n=new Array(h.outputKeyNames.length);let a=0;for(let e=0;e<h.inputKeyNames.length;e++){const o=h.inputKeyNames[e],s=t[o];if("excluded"===h.inputTypes[o]){n[a++]=s;continue}const u=r(o,h.groups);let i,l;u?(i=h.groupMinMax[u].min,l=h.groupMinMax[u].max):(i=h.min[o],l=h.max[o]);const c=l!==i?h.rangeMin+(s-i)/(l-i)*(h.rangeMax-h.rangeMin):h.rangeMin,p=h.repeatedKeyNames[e];for(let e=0;e<p;e++)n[a++]=c}M[e]=n}return{scaledOutput:M,scaledConfig:h}},a=e=>{const t=new Set,n=[];for(const[a,r]of Object.entries(e))t.add(a),n.push(a),r.forEach((e=>{t.add(e),n.push(e)}));if(t.size!==n.length)throw new Error("Duplicate value found between properties in validateUniqueProperties function.")},r=(e,t)=>{for(const[n,a]of Object.entries(t))if(a.includes(e))return n;return null},o=({arrObj:e=[],trainingSplit:t=.8,repeat:a={},yCallbackFunc:r=e=>e,xCallbackFunc:o=e=>e,validateRows:s=()=>!0,groups:u={},shuffle:i=!1,minmaxRange:l=[],balancing:c="",state:p={},customMinMaxRanges:m={},excludes:f=[]})=>{let g=[],h=[];for(let t=0;t<e.length;t++){if(!s({objRow:e,index:t,state:p}))continue;const n=o({objRow:e,index:t,state:p}),a=r({objRow:e,index:t,state:p});null!=n&&null!=a&&(g.push(n),h.push(a))}if(i){const{shuffledX:e,shuffledY:t}=((e,t)=>{if(e.length!==t.length)throw new Error("X and Y arrays must have the same length");const n=Array.from({length:e.length},((e,t)=>t));for(let e=n.length-1;e>0;e--){const t=Math.floor(Math.random()*(e+1));[n[e],n[t]]=[n[t],n[e]]}return{shuffledX:n.map((t=>e[t])),shuffledY:n.map((e=>t[e]))}})(g,h);g=e,h=t}const d=new Set(f);let{scaledOutput:x,scaledConfig:M}=n({arrObj:g,repeat:a,groups:u,minmaxRange:l,customMinMaxRanges:m,excludes:d}),{scaledOutput:y,scaledConfig:b}=n({arrObj:h,repeat:a,groups:u,minmaxRange:l,customMinMaxRanges:m,excludes:d});const O=Math.floor(x.length*t);let w=x.slice(0,O),j=y.slice(0,O),R=x.slice(O),v=y.slice(O);if(c){let e;if("oversample"===c)e=((e,t)=>{const n={},a={};t.forEach(((r,o)=>{n[r]||(n[r]=0,a[r]=[]),n[r]++,a[r].push([e[o],t[o]])}));const r=Math.max(...Object.values(n)),o=[],s=[];return Object.keys(a).forEach((e=>{const t=a[e],n=t.length;for(let e=0;e<r;e++){const a=t[e%n];o.push(a[0]),s.push(a[1])}})),{X:o,Y:s}})(w,j),w=e.X,j=e.Y;else{if("undersample"!==c)throw Error('balancing argument only accepts "false", "oversample" and "undersample". Defaults to "false".');e=((e,t)=>{const n={},a={};t.forEach(((r,o)=>{n[r]||(n[r]=0,a[r]=[]),n[r]++,a[r].push([e[o],t[o]])}));const r=Math.min(...Object.values(n)),o=[],s=[];return Object.keys(a).forEach((e=>{const t=a[e];for(let e=0;e<r;e++){const n=t[e];o.push(n[0]),s.push(n[1])}})),{X:o,Y:s}})(w,j),w=e.X,j=e.Y}}return{trainX:w,trainY:j,testX:R,testY:v,configX:M,configY:b}},s=({arrObj:e=[],repeat:t={},xCallbackFunc:a=e=>e,validateRows:r=()=>!0,groups:o={},shuffle:s=!1,minmaxRange:u=[],state:i={},customMinMaxRanges:l,excludes:c=[]})=>{let p=[];for(let t=0;t<e.length;t++){if(!r(e[t]))continue;const n=a({objRow:e,index:t,state:i});null!=n&&!1!==n&&p.push(n)}s&&(p=(e=>{const t=[...e];for(let e=t.length-1;e>0;e--){const n=Math.floor(Math.random()*(e+1));[t[e],t[n]]=[t[n],t[e]]}return t})(p));const{scaledOutput:m,scaledConfig:f}=n({arrObj:p,repeat:t,groups:o,minmaxRange:u,customMinMaxRanges:l,excludes:new Set(c)});return{X:m,configX:f}},u=(e,t)=>{if(0===t)return e;if(t<0)throw new Error("timeSteps must be greater than 0");const n=[];for(let a=0;a<=e.length-t;a++)n.push(e.slice(a,a+t));return n};XY_Scale=t})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xy-scale",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"description": "This repository contains a JavaScript module designed to facilitate the preprocessing of datasets for machine learning applications. The primary functionality of the module is to scale feature data using normalization or standardization methods, and to parse training and production datasets into appropriate formats for training models.",
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@tensorflow/tfjs-node": "^4.22.0",
|
|
15
|
+
"ml-confusion-matrix": "^2.0.0",
|
|
16
|
+
"ml-knn": "^3.0.0",
|
|
17
|
+
"ohlcv-indicators": "^3.4.1",
|
|
15
18
|
"webpack": "^5.96.1",
|
|
16
19
|
"webpack-cli": "^5.1.4"
|
|
17
20
|
},
|
package/src/datasets.js
CHANGED
|
@@ -3,18 +3,19 @@ import { arrayShuffle, xyArrayShuffle } from "./utilities.js";
|
|
|
3
3
|
import { oversampleXY, undersampleXY } from "./balancing.js";
|
|
4
4
|
|
|
5
5
|
export const parseTrainingXY = ({
|
|
6
|
-
arrObj,
|
|
6
|
+
arrObj = [],
|
|
7
7
|
trainingSplit = 0.8,
|
|
8
|
-
repeat,
|
|
8
|
+
repeat = {},
|
|
9
9
|
yCallbackFunc = row => row,
|
|
10
10
|
xCallbackFunc = row => row,
|
|
11
11
|
validateRows = () => true,
|
|
12
|
-
groups,
|
|
12
|
+
groups = {},
|
|
13
13
|
shuffle = false,
|
|
14
|
-
minmaxRange,
|
|
14
|
+
minmaxRange = [],
|
|
15
15
|
balancing = '',
|
|
16
16
|
state = {},
|
|
17
|
-
customMinMaxRanges
|
|
17
|
+
customMinMaxRanges = {},
|
|
18
|
+
excludes = []
|
|
18
19
|
}) => {
|
|
19
20
|
let X = [];
|
|
20
21
|
let Y = [];
|
|
@@ -40,15 +41,17 @@ export const parseTrainingXY = ({
|
|
|
40
41
|
Y = shuffledY
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
const excludesSet = new Set(excludes)
|
|
45
|
+
|
|
43
46
|
let {
|
|
44
47
|
scaledOutput: scaledX,
|
|
45
48
|
scaledConfig: configX
|
|
46
|
-
} = scaleArrayObj({arrObj: X, repeat, groups, minmaxRange, customMinMaxRanges})
|
|
49
|
+
} = scaleArrayObj({arrObj: X, repeat, groups, minmaxRange, customMinMaxRanges, excludes: excludesSet})
|
|
47
50
|
|
|
48
51
|
let {
|
|
49
52
|
scaledOutput: scaledY,
|
|
50
53
|
scaledConfig: configY,
|
|
51
|
-
} = scaleArrayObj({arrObj: Y, repeat, groups, minmaxRange, customMinMaxRanges})
|
|
54
|
+
} = scaleArrayObj({arrObj: Y, repeat, groups, minmaxRange, customMinMaxRanges, excludes: excludesSet})
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
|
|
@@ -98,16 +101,16 @@ export const parseTrainingXY = ({
|
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
export const parseProductionX = ({
|
|
101
|
-
arrObj,
|
|
102
|
-
repeat,
|
|
104
|
+
arrObj = [],
|
|
105
|
+
repeat = {},
|
|
103
106
|
xCallbackFunc = row => row,
|
|
104
107
|
validateRows = () => true,
|
|
105
|
-
groups,
|
|
108
|
+
groups = {},
|
|
106
109
|
shuffle = false,
|
|
107
|
-
minmaxRange,
|
|
110
|
+
minmaxRange = [],
|
|
108
111
|
state = {},
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
customMinMaxRanges,
|
|
113
|
+
excludes = []
|
|
111
114
|
}) => {
|
|
112
115
|
let X = [];
|
|
113
116
|
|
|
@@ -127,11 +130,12 @@ export const parseProductionX = ({
|
|
|
127
130
|
X = arrayShuffle(X)
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
|
|
130
134
|
// Scale X
|
|
131
135
|
const {
|
|
132
136
|
scaledOutput: scaledX,
|
|
133
137
|
scaledConfig: configX
|
|
134
|
-
} = scaleArrayObj({arrObj: X, repeat, groups, minmaxRange,
|
|
138
|
+
} = scaleArrayObj({arrObj: X, repeat, groups, minmaxRange, customMinMaxRanges, excludes: new Set(excludes)})
|
|
135
139
|
|
|
136
140
|
|
|
137
141
|
// Split into training and testing sets
|
package/src/scale.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], groups = {},
|
|
1
|
+
export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], groups = {}, customMinMaxRanges = null, excludes = new Set() }) => {
|
|
2
2
|
|
|
3
3
|
const arrObjClone = [...arrObj]
|
|
4
4
|
const arrObjLen = arrObjClone.length;
|
|
@@ -11,51 +11,36 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
11
11
|
scaledConfig: {}
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
let config = {}
|
|
16
|
-
|
|
17
|
-
const isValidPrevConfig = prevConfig && validateConfig(prevConfig)
|
|
18
|
-
|
|
19
|
-
if(isValidPrevConfig)
|
|
20
|
-
{
|
|
21
|
-
validateCurrPrevConfig(prevConfig, {minmaxRange, repeat, groups, firstRow})
|
|
22
|
-
config = {...prevConfig}
|
|
23
|
-
}
|
|
24
|
-
else
|
|
25
|
-
{
|
|
26
|
-
const inputKeyNames = Object.keys(firstRow);
|
|
27
|
-
|
|
28
|
-
const repeatedKeyNames = inputKeyNames.map(key => {
|
|
29
|
-
return repeat.hasOwnProperty(key) ? Math.max(repeat[key], 1) : 1;
|
|
30
|
-
});
|
|
31
14
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
15
|
+
const inputKeyNames = Object.keys(firstRow);
|
|
16
|
+
|
|
17
|
+
const repeatedKeyNames = inputKeyNames.map(key => {
|
|
18
|
+
return repeat.hasOwnProperty(key) ? Math.max(repeat[key], 1) : 1;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const countRepeatedKeyNames = repeatedKeyNames.reduce((sum, rep) => sum + rep, 0);
|
|
22
|
+
|
|
23
|
+
const config = {
|
|
24
|
+
arrObjLen,
|
|
25
|
+
rangeMin: minmaxRange[0],
|
|
26
|
+
rangeMax: minmaxRange[1],
|
|
27
|
+
inputTypes: {},
|
|
28
|
+
min: {},
|
|
29
|
+
max: {},
|
|
30
|
+
groupMinMax: {},
|
|
31
|
+
repeat,
|
|
32
|
+
groups,
|
|
33
|
+
inputKeyNames,
|
|
34
|
+
outputKeyNames: new Array(countRepeatedKeyNames),
|
|
35
|
+
repeatedKeyNames
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let keyNamesIdx = 0;
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
40
|
+
for (let i = 0; i < config.inputKeyNames.length; i++) {
|
|
41
|
+
for (let w = 0; w < config.repeatedKeyNames[i]; w++) {
|
|
42
|
+
config.outputKeyNames[keyNamesIdx++] = config.inputKeyNames[i];
|
|
55
43
|
}
|
|
56
|
-
|
|
57
|
-
validateConfig(config)
|
|
58
|
-
|
|
59
44
|
}
|
|
60
45
|
|
|
61
46
|
validateUniqueProperties(config.groups);
|
|
@@ -64,6 +49,12 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
64
49
|
|
|
65
50
|
for (const key of config.inputKeyNames) {
|
|
66
51
|
|
|
52
|
+
if(excludes.has(key))
|
|
53
|
+
{
|
|
54
|
+
config.inputTypes[key] = 'excluded'
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
67
58
|
const firstType = typeof firstRow[key]
|
|
68
59
|
const thisGroup = findGroup(key, config.groups);
|
|
69
60
|
|
|
@@ -73,22 +64,6 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
73
64
|
throw new Error(`Invalid input type "${firstType}" provided for key "${key}". Only accepting `)
|
|
74
65
|
}
|
|
75
66
|
|
|
76
|
-
if(isValidPrevConfig)
|
|
77
|
-
{
|
|
78
|
-
if(!config.inputTypes.hasOwnProperty(key))
|
|
79
|
-
{
|
|
80
|
-
// If prevConfig is set, no new inputTypes can be introduced
|
|
81
|
-
throw new Error(`Error: A new unknown inputType property "${key}" found.`)
|
|
82
|
-
}
|
|
83
|
-
if(config.inputTypes[key] !== firstType)
|
|
84
|
-
{
|
|
85
|
-
//is prevConfig is set the types of "typeof firstRow[key]" and config.inputTypes[key] must be the same
|
|
86
|
-
throw new Error(`Error: Current inputType of property "${key}" is not the same as in the prevConfig inputType.`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
67
|
config.inputTypes[key] = firstType;
|
|
93
68
|
|
|
94
69
|
if(validCustomMinMaxRanges && customMinMaxRanges.hasOwnProperty(key))
|
|
@@ -116,6 +91,12 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
116
91
|
|
|
117
92
|
for (const obj of arrObjClone) {
|
|
118
93
|
for (const key of config.inputKeyNames) {
|
|
94
|
+
|
|
95
|
+
if (config.inputTypes[key] === 'excluded')
|
|
96
|
+
{
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
119
100
|
let value = obj[key];
|
|
120
101
|
|
|
121
102
|
if (config.inputTypes[key] === 'boolean') {
|
|
@@ -139,24 +120,32 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
139
120
|
}
|
|
140
121
|
|
|
141
122
|
const scaledOutput = new Array(arrObjLen);
|
|
123
|
+
|
|
124
|
+
|
|
142
125
|
for (let i = 0; i < arrObjLen; i++) {
|
|
143
126
|
const obj = arrObjClone[i];
|
|
144
127
|
const scaledRow = new Array(config.outputKeyNames.length);
|
|
145
128
|
let idx = 0;
|
|
146
129
|
|
|
147
130
|
for (let j = 0; j < config.inputKeyNames.length; j++) {
|
|
148
|
-
const key = config.inputKeyNames[j]
|
|
149
|
-
const value = obj[key]
|
|
131
|
+
const key = config.inputKeyNames[j]
|
|
132
|
+
const value = obj[key]
|
|
133
|
+
|
|
134
|
+
if (config.inputTypes[key] === 'excluded')
|
|
135
|
+
{
|
|
136
|
+
scaledRow[idx++] = value
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
150
139
|
|
|
151
140
|
const thisGroup = findGroup(key, config.groups);
|
|
152
|
-
let minValue, maxValue
|
|
141
|
+
let minValue, maxValue
|
|
153
142
|
|
|
154
143
|
if (thisGroup) {
|
|
155
|
-
minValue = config.groupMinMax[thisGroup].min
|
|
156
|
-
maxValue = config.groupMinMax[thisGroup].max
|
|
144
|
+
minValue = config.groupMinMax[thisGroup].min
|
|
145
|
+
maxValue = config.groupMinMax[thisGroup].max
|
|
157
146
|
} else {
|
|
158
|
-
minValue = config.min[key]
|
|
159
|
-
maxValue = config.max[key]
|
|
147
|
+
minValue = config.min[key]
|
|
148
|
+
maxValue = config.max[key]
|
|
160
149
|
}
|
|
161
150
|
|
|
162
151
|
const scaledValue =
|
|
@@ -164,14 +153,14 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
164
153
|
? config.rangeMin + ((value - minValue) / (maxValue - minValue)) * (config.rangeMax - config.rangeMin)
|
|
165
154
|
: config.rangeMin;
|
|
166
155
|
|
|
167
|
-
const rep = config.repeatedKeyNames[j]
|
|
156
|
+
const rep = config.repeatedKeyNames[j]
|
|
168
157
|
|
|
169
158
|
for (let w = 0; w < rep; w++) {
|
|
170
|
-
scaledRow[idx++] = scaledValue
|
|
159
|
+
scaledRow[idx++] = scaledValue
|
|
171
160
|
}
|
|
172
161
|
|
|
173
162
|
}
|
|
174
|
-
scaledOutput[i] = scaledRow
|
|
163
|
+
scaledOutput[i] = scaledRow
|
|
175
164
|
}
|
|
176
165
|
|
|
177
166
|
|
|
@@ -179,8 +168,8 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange = [0, 1], group
|
|
|
179
168
|
return {
|
|
180
169
|
scaledOutput,
|
|
181
170
|
scaledConfig: config
|
|
182
|
-
}
|
|
183
|
-
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
184
173
|
|
|
185
174
|
|
|
186
175
|
const validateUniqueProperties = obj => {
|
|
@@ -294,27 +283,4 @@ const validateConfig = config => {
|
|
|
294
283
|
}
|
|
295
284
|
|
|
296
285
|
return true;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const validateCurrPrevConfig = (prevConfig, {minmaxRange, repeat, groups, firstRow}) => {
|
|
300
|
-
|
|
301
|
-
if(prevConfig.rangeMin !== minmaxRange[0] || prevConfig.rangeMax !== minmaxRange[1])
|
|
302
|
-
{
|
|
303
|
-
throw new Error(`"prevConfig.minmaxRange" is not equal "minmaxRange".`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
//it is important o keep the same key order
|
|
307
|
-
if (JSON.stringify(prevConfig.inputKeyNames) !== JSON.stringify(Object.keys(firstRow))) {
|
|
308
|
-
throw new Error(`"prevConfig.inputKeyNames" structure does not match "Object.keys(firstRow)" structure. The order of keys is important.`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (JSON.stringify(prevConfig.repeat) !== JSON.stringify(repeat)) {
|
|
312
|
-
throw new Error(`"prevConfig.repeat" structure does not match "repeat" structure. The order of keys is important.`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (JSON.stringify(prevConfig.groups) !== JSON.stringify(groups)) {
|
|
316
|
-
throw new Error(`"prevConfig.groups" structure does not match "groups" structure. The order of keys is important.`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return true
|
|
320
286
|
}
|
package/test/test.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import OHLCV_INDICATORS from 'ohlcv-indicators'
|
|
2
|
+
import KNN from 'ml-knn'
|
|
3
|
+
import { ConfusionMatrix } from 'ml-confusion-matrix';
|
|
4
|
+
|
|
1
5
|
import { parseTrainingXY } from "../src/datasets.js"
|
|
2
6
|
import {arrayToTimesteps} from '../src/timeSteps.js'
|
|
3
7
|
import { loadFile } from "./fs.js"
|
|
@@ -5,45 +9,30 @@ import * as tf from '@tensorflow/tfjs-node'
|
|
|
5
9
|
|
|
6
10
|
const test = async () => {
|
|
7
11
|
|
|
8
|
-
const
|
|
9
|
-
.map(({open, high, low, close}) => ({open, high, low, close})) //file in /datasets/1d-spy.json
|
|
10
|
-
|
|
11
|
-
//callback function used to prepare X before scaling
|
|
12
|
-
const xCallbackFunc = ({ objRow, index }) => {
|
|
13
|
-
const curr = objRow[index]
|
|
14
|
-
const prev = objRow[index - 1]
|
|
15
|
-
|
|
16
|
-
//returning null or undefined will exclude current row X and Y from training
|
|
17
|
-
if(typeof prev === 'undefined') return null
|
|
18
|
-
|
|
19
|
-
const { open, high, low, close } = curr
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
open,
|
|
23
|
-
high,
|
|
24
|
-
low,
|
|
25
|
-
close,
|
|
26
|
-
change: open - prev.close,
|
|
27
|
-
top: high - Math.max(open, close),
|
|
28
|
-
bottom: low - Math.min(open, close),
|
|
29
|
-
body: open-close,
|
|
30
|
-
}
|
|
31
|
-
}
|
|
12
|
+
const ohlcv = (await loadFile({fileName: 'btc-1d.json', pathName: 'datasets'}))
|
|
32
13
|
|
|
33
|
-
|
|
34
|
-
const yCallbackFunc = ({ objRow, index }) => {
|
|
35
|
-
const curr = objRow[index];
|
|
36
|
-
const next = objRow[index + 1];
|
|
14
|
+
const indicators = new OHLCV_INDICATORS({input: ohlcv, ticker: 'BTC', precision: false})
|
|
37
15
|
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
indicators
|
|
17
|
+
.rsi(40)
|
|
18
|
+
.bollingerBands(20, 2)
|
|
19
|
+
.ema(50)
|
|
20
|
+
.sma(200)
|
|
21
|
+
.sma(300)
|
|
22
|
+
.scaler(200, ['open', 'high', 'low', 'close'], {group: true})
|
|
23
|
+
.crossPairs([
|
|
24
|
+
{fast: 'rsi_40', slow: 30},
|
|
25
|
+
{fast: 'price', slow: 'sma_200'},
|
|
26
|
+
{fast: 'price', slow: 'sma_300'},
|
|
27
|
+
{fast: 'price', slow: 'bollinger_bands_upper'}
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
const parsedOhlcv = indicators.getData()
|
|
31
|
+
|
|
32
|
+
const {scaledGroups} = indicators
|
|
33
|
+
|
|
34
|
+
console.log(scaledGroups)
|
|
40
35
|
|
|
41
|
-
return {
|
|
42
|
-
label_1: next.open > curr.close,
|
|
43
|
-
label_2: next.close > curr.close,
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
|
|
47
36
|
const {
|
|
48
37
|
trainX,
|
|
49
38
|
trainY,
|
|
@@ -52,27 +41,77 @@ const test = async () => {
|
|
|
52
41
|
configX,
|
|
53
42
|
keyNamesX,
|
|
54
43
|
} = parseTrainingXY({
|
|
55
|
-
arrObj:
|
|
56
|
-
trainingSplit: 0.
|
|
44
|
+
arrObj: parsedOhlcv,
|
|
45
|
+
trainingSplit: 0.50,
|
|
57
46
|
yCallbackFunc,
|
|
58
47
|
xCallbackFunc,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
validateRows: ({objRow, index}) => {
|
|
49
|
+
const curr = objRow[index]
|
|
50
|
+
const prev = objRow[index - 1]
|
|
51
|
+
|
|
52
|
+
if(typeof prev === 'undefined') return false
|
|
53
|
+
|
|
54
|
+
return curr.ema_50 > curr.sma_300 && curr.sma_200 > curr.sma_300 && (curr.price_x_sma_300 === -1 || curr.price_x_sma_300 === 1)
|
|
65
55
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
shuffle: false,
|
|
57
|
+
minmaxRange: [0, 1],
|
|
58
|
+
balancing: null,
|
|
59
|
+
groups: scaledGroups,
|
|
60
|
+
excludes: ['high']
|
|
71
61
|
});
|
|
72
62
|
|
|
63
|
+
//console.log(configX.outputKeyNames)
|
|
64
|
+
//console.log(configX)
|
|
65
|
+
|
|
66
|
+
console.log('trainX', trainX[0])
|
|
67
|
+
|
|
68
|
+
tensorflowExample({
|
|
69
|
+
trainX,
|
|
70
|
+
trainY,
|
|
71
|
+
testX,
|
|
72
|
+
testY,
|
|
73
|
+
configX,
|
|
74
|
+
keyNamesX,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
classifiersExample({
|
|
78
|
+
trainX,
|
|
79
|
+
trainY,
|
|
80
|
+
testX,
|
|
81
|
+
testY,
|
|
82
|
+
configX,
|
|
83
|
+
keyNamesX,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const classifiersExample = ({
|
|
90
|
+
trainX,
|
|
91
|
+
trainY,
|
|
92
|
+
testX,
|
|
93
|
+
testY,
|
|
94
|
+
configX,
|
|
95
|
+
keyNamesX,
|
|
96
|
+
}) => {
|
|
97
|
+
const model = new KNN(trainX, trainY)
|
|
73
98
|
|
|
74
|
-
|
|
99
|
+
const predictions = model.predict(testX)
|
|
100
|
+
const compare = ConfusionMatrix.fromLabels(testY.flat(), predictions.flat())
|
|
75
101
|
|
|
102
|
+
//console.log(testY.flat(), predictions.flat())
|
|
103
|
+
|
|
104
|
+
console.log(compare.getAccuracy())
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const tensorflowExample = ({
|
|
108
|
+
trainX,
|
|
109
|
+
trainY,
|
|
110
|
+
testX,
|
|
111
|
+
testY,
|
|
112
|
+
configX,
|
|
113
|
+
keyNamesX,
|
|
114
|
+
}) => {
|
|
76
115
|
|
|
77
116
|
const timeSteps = 10
|
|
78
117
|
const colsX = trainX[0].length
|
|
@@ -80,16 +119,71 @@ const test = async () => {
|
|
|
80
119
|
const timeSteppedTrainX = arrayToTimesteps(trainX, timeSteps)
|
|
81
120
|
const trimedTrainY = trainY.slice(timeSteps-1)
|
|
82
121
|
|
|
83
|
-
|
|
84
|
-
//console.log([trainX.length, timeSteps, timeSteppedTrainX[0][0].length])
|
|
85
|
-
|
|
86
122
|
const inputX = tf.tensor3d(timeSteppedTrainX, [timeSteppedTrainX.length, timeSteps, colsX])
|
|
87
123
|
const targetY = tf.tensor2d(trimedTrainY, [trimedTrainY.length, colsY])
|
|
88
124
|
|
|
89
|
-
console.log('trainX', trainX
|
|
125
|
+
//console.log('trainX', trainX)
|
|
90
126
|
//console.log('configX', configX)
|
|
91
127
|
//console.log('inputX', inputX)
|
|
92
|
-
//console.log('inputX', targetY)
|
|
128
|
+
//console.log('inputX', targetY)
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//callback function used to prepare X before scaling
|
|
133
|
+
const xCallbackFunc = ({ objRow, index }) => {
|
|
134
|
+
const curr = objRow[index]
|
|
135
|
+
const prev = objRow[index - 1]
|
|
136
|
+
|
|
137
|
+
//returning null or undefined will exclude current row X and Y from training
|
|
138
|
+
if(typeof prev === 'undefined') return null
|
|
139
|
+
|
|
140
|
+
//console.log(((curr.sma_300 - curr.low) / curr.low) * 100)
|
|
141
|
+
|
|
142
|
+
const output = {
|
|
143
|
+
high: curr.high,
|
|
144
|
+
ema50IsUp: curr.ema_50 > prev.ema_50,
|
|
145
|
+
ema50GtSma200: curr.ema_50 > curr.sma_200,
|
|
146
|
+
ema50GtSma300: curr.ema_50 > curr.sma_300,
|
|
147
|
+
sma200IsUp: curr.sma_200 > prev.sma_200,
|
|
148
|
+
sma200GtSma300: curr.sma_200 > prev.sma_300,
|
|
149
|
+
sma_300IsUp: curr.sma_300 > prev.sma_300
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for(const [key, value] of Object.entries(curr))
|
|
153
|
+
{
|
|
154
|
+
if(key.includes('minmax'))
|
|
155
|
+
{
|
|
156
|
+
output[key] = value
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return output
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//callback function used to prepare Y before scaling
|
|
164
|
+
const yCallbackFunc = ({ objRow, index }) => {
|
|
165
|
+
const curr = objRow[index]
|
|
166
|
+
const next = new Array(60).fill(0).map((_, i) => objRow[index + 1 + i])
|
|
167
|
+
|
|
168
|
+
//returning null or undefined will exclude current row X and Y from training
|
|
169
|
+
if (next.some(o => typeof o === 'undefined')) return null;
|
|
170
|
+
|
|
171
|
+
const priceTp = curr.sma_300 * 1.3
|
|
172
|
+
const entryPrice = curr.sma_300 * 0.96
|
|
173
|
+
const tp = next.some(o => o.close > entryPrice && (o.high > priceTp))
|
|
174
|
+
const sl = next.some(o => (o.low - entryPrice)/entryPrice < -0.10 && o.low < entryPrice)
|
|
175
|
+
|
|
176
|
+
const highestHigh = Math.max(...next.map(o => o.high))
|
|
177
|
+
const lowestLow = Math.min(...next.slice(0, 5).map(o => o.low))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if(lowestLow > entryPrice) return null
|
|
181
|
+
|
|
182
|
+
//console.log([curr.date, (lowestLow - entryPrice)/entryPrice])
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
result: Number(tp === true && sl === false)
|
|
186
|
+
}
|
|
93
187
|
}
|
|
94
188
|
|
|
95
189
|
test()
|