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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/scale.js +125 -147
  3. package/test/test.js +10 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xy-scale",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "main": "./index.js",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/scale.js CHANGED
@@ -1,18 +1,29 @@
1
- export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, customMinMaxRanges = null, excludes = new Set() }) => {
1
+ export const scaleArrayObj = ({ arrObj, repeat = {}, minmaxRange, groups = {}, customMinMaxRanges = {}, excludes = new Set() }) => {
2
2
 
3
- const arrObjClone = [...arrObj]
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 validCustomMinMaxRanges = typeof customMinMaxRanges === 'object' && customMinMaxRanges !== null
17
+ const inputKeyNames = Object.keys(firstRow);
18
+
19
+ const groupSets = {}
7
20
 
8
- if (arrObjLen === 0) {
9
- return {
10
- scaledOutput: [],
11
- scaledConfig: {}
12
- };
21
+ for(const [k, g] of Object.entries(groups))
22
+ {
23
+ groupSets[k] = new Set(g)
13
24
  }
14
-
15
- const inputKeyNames = Object.keys(firstRow);
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
- groups,
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.groups);
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
- if(validCustomMinMaxRanges && customMinMaxRanges.hasOwnProperty(key))
78
+ //customMinMaxRanges can not be grouped
79
+ if(customMinMaxRanges.hasOwnProperty(key))
70
80
  {
71
- if (thisGroup)
72
- {
73
- config.groupMinMax[thisGroup] = customMinMaxRanges[key]
74
- }
75
- else
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
- if (thisGroup) {
83
- config.groupMinMax[thisGroup] = { min: Infinity, max: -Infinity };
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.groups);
108
+ const thisGroup = findGroup(key, config.groupSets)
107
109
 
108
- if(validCustomMinMaxRanges === false || (validCustomMinMaxRanges && !customMinMaxRanges.hasOwnProperty(key)))
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
- scaledRow[idx++] = value
137
- continue
139
+ scaledValue = value
138
140
  }
141
+ else
142
+ {
143
+ const thisGroup = findGroup(key, config.groupSets);
144
+ let minValue, maxValue
139
145
 
140
- const thisGroup = findGroup(key, config.groups);
141
- let minValue, maxValue
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
- if (thisGroup) {
144
- minValue = config.groupMinMax[thisGroup].min
145
- maxValue = config.groupMinMax[thisGroup].max
146
- } else {
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 = obj => {
176
- const uniqueValues = new Set();
177
- const allValues = [];
178
-
179
- for (const [key, arr] of Object.entries(obj)) {
180
- uniqueValues.add(key);
181
- allValues.push(key);
182
-
183
- arr.forEach(v => {
184
- uniqueValues.add(v);
185
- allValues.push(v);
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
- if (uniqueValues.size !== allValues.length) {
190
- throw new Error('Duplicate value found between properties in validateUniqueProperties function.');
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
- const findGroup = (key, groups) => {
195
- for (const [groupK, groupV] of Object.entries(groups)) {
196
- if (groupV.includes(key)) {
197
- return groupK;
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
- return null;
201
- };
202
-
203
-
204
- const validateConfig = config => {
205
-
206
- if(!config) return false
207
-
208
- const requiredKeys = [
209
- "rangeMin",
210
- "rangeMax",
211
- "inputTypes",
212
- "min",
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
- const {
230
- rangeMin,
231
- rangeMax,
232
- inputTypes,
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
- if (rangeMin >= rangeMax) {
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
- if (!isPlainObject(inputTypes)) {
255
- throw new Error("inputTypes must be an object.");
256
- }
257
- if (!isPlainObject(min)) {
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
- if (!isPlainObject(max)) {
261
- throw new Error("max must be an object.");
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
- if (!isPlainObject(groupMinMax)) {
264
- throw new Error("groupMinMax must be an object.");
265
- }
266
- if (!isPlainObject(repeat)) {
267
- throw new Error("repeat must be an object.");
268
- }
269
- if (!isPlainObject(groups)) {
270
- throw new Error("groups must be an object.");
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
- return true;
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(40)
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: 'rsi_40', slow: 30},
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: ['high2'],
63
- correlation: {corrExcludes: ['price_x_sma_300', 'price_x_sma_200']}
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
- //console.log('trainX', trainX[0])
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))