xy-scale 1.4.2 → 1.4.3
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/package.json +1 -1
- package/src/scale.js +125 -147
- package/test/test.js +10 -8
package/package.json
CHANGED
package/src/scale.js
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, customMinMaxRanges =
|
|
1
|
+
export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, customMinMaxRanges = {}, excludes = new Set() }) => {
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
|
+
1. excluded items can be repeated
|
|
5
|
+
2. excluded items can not be grouped
|
|
6
|
+
3. excluded items can not be in customMixMaxRanges
|
|
7
|
+
4. customMixMaxRanges items can not be grouped
|
|
8
|
+
6. customMixMaxRanges items can not be excluded
|
|
9
|
+
5. customMixMaxRanges can be repeated
|
|
10
|
+
7. hierarchy order: 1 excluded items, 2 customMixMaxRanges items , 3 grouped items, 4 any other item
|
|
11
|
+
6. customMixMaxRanges min and max must not be updated from values
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const arrObjClone = arrObj.map(row => ({ ...row })) //[...arrObj] modified june 2
|
|
4
15
|
const arrObjLen = arrObjClone.length;
|
|
5
16
|
const firstRow = arrObjClone[0]
|
|
6
|
-
const
|
|
17
|
+
const inputKeyNames = Object.keys(firstRow);
|
|
18
|
+
|
|
19
|
+
const groupSets = {}
|
|
7
20
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
scaledConfig: {}
|
|
12
|
-
};
|
|
21
|
+
for(const [k, g] of Object.entries(groups))
|
|
22
|
+
{
|
|
23
|
+
groupSets[k] = new Set(g)
|
|
13
24
|
}
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
|
|
26
|
+
validateUniqueProperties({excludes, groupSets, customMinMaxRanges, inputKeyNames, repeat})
|
|
16
27
|
|
|
17
28
|
const repeatedKeyNames = inputKeyNames.map(key => {
|
|
18
29
|
return repeat.hasOwnProperty(key) ? Math.max(repeat[key], 1) : 1;
|
|
@@ -29,7 +40,7 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, c
|
|
|
29
40
|
max: {},
|
|
30
41
|
groupMinMax: {},
|
|
31
42
|
repeat,
|
|
32
|
-
|
|
43
|
+
groupSets,
|
|
33
44
|
inputKeyNames,
|
|
34
45
|
outputKeyNames: new Array(countRepeatedKeyNames),
|
|
35
46
|
repeatedKeyNames
|
|
@@ -43,8 +54,6 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, c
|
|
|
43
54
|
}
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
validateUniqueProperties(config.groups);
|
|
47
|
-
|
|
48
57
|
const validInputTypes = ['number', 'boolean']
|
|
49
58
|
|
|
50
59
|
for (const key of config.inputKeyNames) {
|
|
@@ -56,36 +65,28 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, c
|
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
const firstType = typeof firstRow[key]
|
|
59
|
-
const thisGroup = findGroup(key, config.
|
|
68
|
+
const thisGroup = findGroup(key, config.groupSets);
|
|
60
69
|
|
|
61
70
|
|
|
62
71
|
if(!validInputTypes.includes(firstType))
|
|
63
72
|
{
|
|
64
|
-
throw new Error(`Invalid input type "${firstType}" provided for key "${key}". Only accepting `)
|
|
73
|
+
throw new Error(`Invalid input type "${firstType}" provided for key "${key}". Only accepting ${JSON.stringify(validInputTypes)}`)
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
config.inputTypes[key] = firstType;
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
//customMinMaxRanges can not be grouped
|
|
79
|
+
if(customMinMaxRanges.hasOwnProperty(key))
|
|
70
80
|
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
config.min[key] = customMinMaxRanges[key].min;
|
|
78
|
-
config.max[key] = customMinMaxRanges[key].max;
|
|
79
|
-
}
|
|
81
|
+
config.min[key] = customMinMaxRanges[key].min;
|
|
82
|
+
config.max[key] = customMinMaxRanges[key].max;
|
|
83
|
+
}
|
|
84
|
+
else if(thisGroup && !config.groupMinMax.hasOwnProperty(thisGroup)){
|
|
85
|
+
config.groupMinMax[thisGroup] = { min: Infinity, max: -Infinity };
|
|
80
86
|
}
|
|
81
87
|
else {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
config.min[key] = Infinity;
|
|
87
|
-
config.max[key] = -Infinity;
|
|
88
|
-
}
|
|
88
|
+
config.min[key] = Infinity;
|
|
89
|
+
config.max[key] = -Infinity;
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -101,11 +102,12 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, c
|
|
|
101
102
|
|
|
102
103
|
if (config.inputTypes[key] === 'boolean') {
|
|
103
104
|
obj[key] = Number(value);
|
|
105
|
+
value = obj[key]
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
const thisGroup = findGroup(key, config.
|
|
108
|
+
const thisGroup = findGroup(key, config.groupSets)
|
|
107
109
|
|
|
108
|
-
if(
|
|
110
|
+
if(!customMinMaxRanges.hasOwnProperty(key))
|
|
109
111
|
{
|
|
110
112
|
if (thisGroup) {
|
|
111
113
|
config.groupMinMax[thisGroup].min = Math.min(config.groupMinMax[thisGroup].min, value);
|
|
@@ -130,28 +132,31 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, c
|
|
|
130
132
|
for (let j = 0; j < config.inputKeyNames.length; j++) {
|
|
131
133
|
const key = config.inputKeyNames[j]
|
|
132
134
|
const value = obj[key]
|
|
135
|
+
let scaledValue
|
|
133
136
|
|
|
134
137
|
if (config.inputTypes[key] === 'excluded')
|
|
135
138
|
{
|
|
136
|
-
|
|
137
|
-
continue
|
|
139
|
+
scaledValue = value
|
|
138
140
|
}
|
|
141
|
+
else
|
|
142
|
+
{
|
|
143
|
+
const thisGroup = findGroup(key, config.groupSets);
|
|
144
|
+
let minValue, maxValue
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
if (thisGroup) {
|
|
147
|
+
minValue = config.groupMinMax[thisGroup].min
|
|
148
|
+
maxValue = config.groupMinMax[thisGroup].max
|
|
149
|
+
} else {
|
|
150
|
+
minValue = config.min[key]
|
|
151
|
+
maxValue = config.max[key]
|
|
152
|
+
}
|
|
142
153
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
minValue = config.min[key]
|
|
148
|
-
maxValue = config.max[key]
|
|
154
|
+
scaledValue =
|
|
155
|
+
maxValue !== minValue
|
|
156
|
+
? config.rangeMin + ((value - minValue) / (maxValue - minValue)) * (config.rangeMax - config.rangeMin)
|
|
157
|
+
: config.rangeMin;
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
const scaledValue =
|
|
152
|
-
maxValue !== minValue
|
|
153
|
-
? config.rangeMin + ((value - minValue) / (maxValue - minValue)) * (config.rangeMax - config.rangeMin)
|
|
154
|
-
: config.rangeMin;
|
|
155
160
|
|
|
156
161
|
const rep = config.repeatedKeyNames[j]
|
|
157
162
|
|
|
@@ -172,115 +177,88 @@ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, c
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
|
|
175
|
-
const validateUniqueProperties =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
});
|
|
180
|
+
const validateUniqueProperties = ({
|
|
181
|
+
excludes,
|
|
182
|
+
groupSets,
|
|
183
|
+
customMinMaxRanges,
|
|
184
|
+
repeat,
|
|
185
|
+
inputKeyNames
|
|
186
|
+
}) => {
|
|
187
|
+
// 0. Ensure every 'repeat' key actually exists in inputKeyNames
|
|
188
|
+
for (const key of Object.keys(repeat)) {
|
|
189
|
+
if (!inputKeyNames.includes(key)) {
|
|
190
|
+
throw new Error(`"repeat" references missing key "${key}".`);
|
|
187
191
|
}
|
|
192
|
+
}
|
|
188
193
|
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
// 1. Ensure every excluded key actually exists in inputKeyNames
|
|
195
|
+
for (const key of excludes) {
|
|
196
|
+
if (!inputKeyNames.includes(key)) {
|
|
197
|
+
throw new Error(`Excluded property "${key}" does not exist in inputKeyNames.`);
|
|
191
198
|
}
|
|
192
|
-
}
|
|
199
|
+
}
|
|
193
200
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
201
|
+
// 2. Ensure every customMinMaxRanges key exists in inputKeyNames
|
|
202
|
+
for (const key of Object.keys(customMinMaxRanges)) {
|
|
203
|
+
if (!inputKeyNames.includes(key)) {
|
|
204
|
+
throw new Error(`customMinMaxRanges property "${key}" does not exist in inputKeyNames.`);
|
|
199
205
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"max",
|
|
214
|
-
"groupMinMax",
|
|
215
|
-
"repeat",
|
|
216
|
-
"groups",
|
|
217
|
-
"inputKeyNames",
|
|
218
|
-
"outputKeyNames",
|
|
219
|
-
"repeatedKeyNames"
|
|
220
|
-
];
|
|
221
|
-
|
|
222
|
-
// Check for missing keys
|
|
223
|
-
for (const key of requiredKeys) {
|
|
224
|
-
if (!config.hasOwnProperty(key)) {
|
|
225
|
-
throw new Error(`Missing key "${key}" in config.`);
|
|
226
|
-
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 3. Ensure every group member exists in inputKeyNames, and detect if a key appears in more than one group
|
|
209
|
+
const seenInGroup = new Set();
|
|
210
|
+
for (const [groupName, members] of Object.entries(groupSets)) {
|
|
211
|
+
for (const key of members) {
|
|
212
|
+
if (!inputKeyNames.includes(key)) {
|
|
213
|
+
throw new Error(`Group "${groupName}" references missing key "${key}".`);
|
|
214
|
+
}
|
|
215
|
+
if (seenInGroup.has(key)) {
|
|
216
|
+
throw new Error(`Property "${key}" appears in more than one group.`);
|
|
217
|
+
}
|
|
218
|
+
seenInGroup.add(key);
|
|
227
219
|
}
|
|
220
|
+
}
|
|
228
221
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
min,
|
|
234
|
-
max,
|
|
235
|
-
groupMinMax,
|
|
236
|
-
repeat,
|
|
237
|
-
groups,
|
|
238
|
-
inputKeyNames,
|
|
239
|
-
outputKeyNames,
|
|
240
|
-
repeatedKeyNames
|
|
241
|
-
} = config;
|
|
242
|
-
|
|
243
|
-
// Validate rangeMin and rangeMax are numbers and in proper order
|
|
244
|
-
if (typeof rangeMin !== 'number' || typeof rangeMax !== 'number') {
|
|
245
|
-
throw new Error("rangeMin and rangeMax must be numbers.");
|
|
222
|
+
// 4a. A key cannot be both in excludes and in customMinMaxRanges
|
|
223
|
+
for (const key of excludes) {
|
|
224
|
+
if (customMinMaxRanges.hasOwnProperty(key)) {
|
|
225
|
+
throw new Error(`Property "${key}" is in both "excludes" and "customMinMaxRanges".`);
|
|
246
226
|
}
|
|
247
|
-
|
|
248
|
-
throw new Error("rangeMin must be less than rangeMax.");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Helper to check if a value is a plain object (and not null or an array)
|
|
252
|
-
const isPlainObject = (obj) => typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
|
227
|
+
}
|
|
253
228
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
throw new Error("min must be an object.");
|
|
229
|
+
// 4b. A key in excludes cannot be grouped
|
|
230
|
+
for (const key of excludes) {
|
|
231
|
+
if (findGroup(key, groupSets)) {
|
|
232
|
+
throw new Error(`Property "${key}" is in "excludes" and also appears in a group.`);
|
|
259
233
|
}
|
|
260
|
-
|
|
261
|
-
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 4c. A key in customMinMaxRanges cannot be grouped
|
|
237
|
+
for (const key of Object.keys(customMinMaxRanges)) {
|
|
238
|
+
if (findGroup(key, groupSets)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Property "${key}" is in "customMinMaxRanges" and also appears in a group.`
|
|
241
|
+
);
|
|
262
242
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
if(!Array.isArray(inputKeyNames))
|
|
273
|
-
{
|
|
274
|
-
throw new Error("inputKeyNames must be an array.");
|
|
275
|
-
}
|
|
276
|
-
if(!Array.isArray(outputKeyNames))
|
|
277
|
-
{
|
|
278
|
-
throw new Error("outputKeyNames must be an array.");
|
|
279
|
-
}
|
|
280
|
-
if(!Array.isArray(repeatedKeyNames))
|
|
281
|
-
{
|
|
282
|
-
throw new Error("repeatedKeyNames must be an array.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 5. Ensure customMinMaxRanges[key].min <= customMinMaxRanges[key].max
|
|
246
|
+
for (const [key, { min, max }] of Object.entries(customMinMaxRanges)) {
|
|
247
|
+
if (min > max) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`customMinMaxRanges["${key}"].min (${min}) > max (${max}).`
|
|
250
|
+
);
|
|
283
251
|
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
284
254
|
|
|
285
|
-
|
|
286
|
-
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
const findGroup = (key, groupSets) => {
|
|
258
|
+
for (const [groupK, groupV] of Object.entries(groupSets)) {
|
|
259
|
+
if (groupV.has(key)) {
|
|
260
|
+
return groupK;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
};
|
package/test/test.js
CHANGED
|
@@ -14,14 +14,14 @@ const test = async () => {
|
|
|
14
14
|
const indicators = new OHLCV_INDICATORS({input: ohlcv, ticker: 'BTC', precision: false})
|
|
15
15
|
|
|
16
16
|
indicators
|
|
17
|
-
.rsi(
|
|
17
|
+
.rsi(14)
|
|
18
18
|
.bollingerBands(20, 2)
|
|
19
19
|
.ema(50)
|
|
20
20
|
.sma(200)
|
|
21
21
|
.sma(300)
|
|
22
22
|
.scaler(200, ['open', 'high', 'low', 'close'], {group: true})
|
|
23
23
|
.crossPairs([
|
|
24
|
-
{fast: '
|
|
24
|
+
{fast: 'rsi_14', slow: 30},
|
|
25
25
|
{fast: 'price', slow: 'sma_200'},
|
|
26
26
|
{fast: 'price', slow: 'sma_300'},
|
|
27
27
|
{fast: 'price', slow: 'bollinger_bands_upper'}
|
|
@@ -33,8 +33,6 @@ const test = async () => {
|
|
|
33
33
|
|
|
34
34
|
const {scaledGroups} = indicators
|
|
35
35
|
|
|
36
|
-
//console.log(scaledGroups)
|
|
37
|
-
|
|
38
36
|
const {
|
|
39
37
|
trainX,
|
|
40
38
|
trainY,
|
|
@@ -59,15 +57,17 @@ const test = async () => {
|
|
|
59
57
|
minmaxRange: [0, 1],
|
|
60
58
|
balancing: null,
|
|
61
59
|
groups: scaledGroups,
|
|
62
|
-
excludes: ['
|
|
63
|
-
|
|
60
|
+
excludes: ['high'],
|
|
61
|
+
repeat: {
|
|
62
|
+
high2: 100
|
|
63
|
+
}
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
console.log(configX.outputKeyNames)
|
|
67
67
|
console.log(configX.inputTypes)
|
|
68
68
|
//console.log(configX)
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
console.log('trainX', [trainX[0], trainX.at(-1)])
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
|
|
@@ -153,7 +153,9 @@ const xCallbackFunc = ({ objRow, index }) => {
|
|
|
153
153
|
ema50GtSma300: curr.ema_50 > curr.sma_300,
|
|
154
154
|
sma200IsUp: curr.sma_200 > prev.sma_200,
|
|
155
155
|
sma200GtSma300: curr.sma_200 > prev.sma_300,
|
|
156
|
-
sma_300IsUp: curr.sma_300 > prev.sma_300
|
|
156
|
+
sma_300IsUp: curr.sma_300 > prev.sma_300,
|
|
157
|
+
rsi_14: curr.rsi_14,
|
|
158
|
+
rsi_sma_14: curr.rsi_sma_14,
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
for(const [key, value] of Object.entries(curr))
|