ohlcv-indicators 3.5.56 → 3.5.59
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 +39 -337
- package/package.json +2 -13
- package/src/core-functions/mainLoop.js +10 -42
- package/src/machine-learning/ml-utilities.js +1 -117
- package/src/machine-learning/scaler.js +78 -76
- package/src/studies/dateTime.js +29 -25
- package/src/studies/priceFeatures.js +78 -0
- package/src/utilities/dateUtilities.js +1 -1
- 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
|
@@ -4,12 +4,6 @@ export const oneHotEncode = (idx, size) => {
|
|
|
4
4
|
return vec
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export const logMlTraining = ({featureCols, flatFeaturesColLen, type, trainingSize}) => {
|
|
8
|
-
|
|
9
|
-
const oneHotTxt = (featureCols.length !== flatFeaturesColLen) ? ' (including flat one-hot encoded array values) ' : ''
|
|
10
|
-
console.log(`---\nInitialized "${type}" with ${flatFeaturesColLen} features ${oneHotTxt} and ${trainingSize} rows: \n${JSON.stringify(featureCols)}\n\n`)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
export const normalizeMinMax = (value, min, max, [validMin, validMax]) => {
|
|
14
8
|
const clamped = Math.min(Math.max(value, min), max)
|
|
15
9
|
return ((clamped - min) / (max - min)) * (validMax - validMin) + validMin
|
|
@@ -17,114 +11,4 @@ export const normalizeMinMax = (value, min, max, [validMin, validMax]) => {
|
|
|
17
11
|
|
|
18
12
|
export const normalizeZScore = (value, mean, std) => {
|
|
19
13
|
return std === 0 ? 0 : (value - mean) / std
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const findGroupsFunc = (findGroups, scaledGroups) => {
|
|
23
|
-
|
|
24
|
-
const output = []
|
|
25
|
-
|
|
26
|
-
if(findGroups.length > 0)
|
|
27
|
-
{
|
|
28
|
-
for(let g = 0; g < findGroups.length; g++)
|
|
29
|
-
{
|
|
30
|
-
const group = findGroups[g]
|
|
31
|
-
const {type, size = null, groupName = null} = group
|
|
32
|
-
|
|
33
|
-
let groupKey = (size !== null) ? `${type}_${size}` : `${type}_${groupName}`
|
|
34
|
-
|
|
35
|
-
if(!scaledGroups.hasOwnProperty(groupKey)) throw new Error(`Scaled group (${groupKey}) not found for regressor.options.findGroups[${g}]: ${JSON.stringify(group)}`)
|
|
36
|
-
output.push(...scaledGroups[groupKey])
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return output
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const computeFlatFeaturesLen = (featureCols, verticalOhlcv, index) => {
|
|
44
|
-
|
|
45
|
-
let flatFeaturesColLen = 0
|
|
46
|
-
|
|
47
|
-
for (const key of featureCols) {
|
|
48
|
-
|
|
49
|
-
if(!verticalOhlcv.hasOwnProperty(key)) throw new Error(`Property "${key}" not found in "verticalOhlcv".`)
|
|
50
|
-
|
|
51
|
-
const val = verticalOhlcv[key][index]
|
|
52
|
-
|
|
53
|
-
if(typeof val === 'undefined' || val === null || Number.isNaN(val)) throw new Error(`Invalid value of property "${key}".`)
|
|
54
|
-
|
|
55
|
-
if (key.startsWith('one_hot_') || key.startsWith('pca_')) {
|
|
56
|
-
|
|
57
|
-
const colSize = verticalOhlcv[key][index].length
|
|
58
|
-
|
|
59
|
-
flatFeaturesColLen += colSize
|
|
60
|
-
|
|
61
|
-
} else {
|
|
62
|
-
|
|
63
|
-
flatFeaturesColLen += 1
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return flatFeaturesColLen
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export const getFeaturedKeys = ({trainingCols, findGroups, verticalOhlcv, scaledGroups}) => {
|
|
73
|
-
|
|
74
|
-
const featureCols = [...trainingCols, ...(findGroupsFunc(findGroups, scaledGroups))]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (featureCols.length === 0) {
|
|
78
|
-
return []
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let keyNotFound = false
|
|
82
|
-
|
|
83
|
-
// sanity‐check that all features exist
|
|
84
|
-
for(let x = 0; x < featureCols.length; x++)
|
|
85
|
-
{
|
|
86
|
-
const featureKey = featureCols[x]
|
|
87
|
-
|
|
88
|
-
if(!verticalOhlcv.hasOwnProperty(featureKey)) {
|
|
89
|
-
keyNotFound = true
|
|
90
|
-
break
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if(keyNotFound) return []
|
|
95
|
-
|
|
96
|
-
return featureCols
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export const updateClassifierMetrics = ({ metrics, trueLabel, predictedLabel }) => {
|
|
100
|
-
// Increment overall counters
|
|
101
|
-
metrics.total = (metrics.total || 0) + 1
|
|
102
|
-
metrics.correct = (metrics.correct || 0)
|
|
103
|
-
|
|
104
|
-
// Initialize per‐label stats container if needed
|
|
105
|
-
metrics.labels = metrics.labels || {}
|
|
106
|
-
|
|
107
|
-
const labelKey = predictedLabel.toString()
|
|
108
|
-
// Ensure we have a slot for this label
|
|
109
|
-
if (!metrics.labels[labelKey]) {
|
|
110
|
-
metrics.labels[labelKey] = { total: 0, correct: 0, accuracy: 0 }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Update this label’s total predictions
|
|
114
|
-
metrics.labels[labelKey].total++
|
|
115
|
-
|
|
116
|
-
// If correct, bump both overall and per-label correct counters
|
|
117
|
-
if (predictedLabel === trueLabel) {
|
|
118
|
-
metrics.correct++
|
|
119
|
-
metrics.labels[labelKey].correct++
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Recompute overall accuracy
|
|
123
|
-
metrics.accuracy = metrics.correct / metrics.total
|
|
124
|
-
|
|
125
|
-
// Recompute accuracy for each label
|
|
126
|
-
for (const key in metrics.labels) {
|
|
127
|
-
const stats = metrics.labels[key]
|
|
128
|
-
stats.accuracy = stats.correct / stats.total
|
|
129
|
-
}
|
|
130
|
-
}
|
|
14
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { normalizeMinMax, normalizeZScore } from "./ml-utilities.js";
|
|
2
|
-
|
|
3
|
-
export const scaler = (main, index, size, colKeys, {type, group, range, lag, precomputed, secondaryLoop}) => {
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
|
+
|
|
5
|
+
//rowWiseScaler
|
|
6
|
+
|
|
7
|
+
export const scaler = (main, index, size, colKeys, {type, range, lookback, weights, euclideanWeights}) => {
|
|
8
|
+
|
|
9
|
+
//this function performs scaling row by row "row wise scaling"
|
|
6
10
|
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
9
|
-
const prefix = `${type}_${size}`;
|
|
11
|
+
const { verticalOhlcv, instances } = main
|
|
12
|
+
const prefix = `${type}_${size}`
|
|
10
13
|
|
|
11
14
|
if(index === 0) {
|
|
12
15
|
const { len, isAlreadyComputed } = main
|
|
@@ -15,8 +18,6 @@ export const scaler = (main, index, size, colKeys, {type, group, range, lag, pre
|
|
|
15
18
|
{
|
|
16
19
|
throw new Error(`In scaler method can not repeat params "size" and "options.type" together between indicators.`)
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
isAlreadyComputed.add(prefix)
|
|
20
21
|
|
|
21
22
|
if(!instances.hasOwnProperty('scaler')) {
|
|
22
23
|
instances.scaler = {}
|
|
@@ -25,7 +26,7 @@ export const scaler = (main, index, size, colKeys, {type, group, range, lag, pre
|
|
|
25
26
|
if(!instances.scaler.hasOwnProperty(prefix))
|
|
26
27
|
{
|
|
27
28
|
instances.scaler[prefix] = {
|
|
28
|
-
|
|
29
|
+
extectedFeatures: (lookback === 0) ? colKeys.length : colKeys.length + (lookback * colKeys.length)
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -39,103 +40,104 @@ export const scaler = (main, index, size, colKeys, {type, group, range, lag, pre
|
|
|
39
40
|
const key = `${prefix}_${target}`;
|
|
40
41
|
verticalOhlcv[key] = new Float64Array(len).fill(NaN)
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!main.scaledGroups[groupKey]) main.scaledGroups[groupKey] = [];
|
|
46
|
-
main.scaledGroups[groupKey].push(key);
|
|
43
|
+
if (!main.scaledGroups[prefix]) main.scaledGroups[prefix] = [];
|
|
44
|
+
|
|
45
|
+
main.scaledGroups[prefix].push(key);
|
|
47
46
|
|
|
48
|
-
if (
|
|
49
|
-
const
|
|
50
|
-
|
|
47
|
+
if (lookback > 0) {
|
|
48
|
+
const laggedKeys = Array.from({ length: lookback }).map((_, i) => `${key}_lag_${i + 1}`)
|
|
49
|
+
|
|
50
|
+
for(const lKey of laggedKeys) {
|
|
51
|
+
verticalOhlcv[lKey] = new Float64Array(len).fill(NaN)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main.scaledGroups[prefix].push(...laggedKeys)
|
|
51
55
|
features.push(key)
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
for(const weKey of Object.keys(weights)) {
|
|
60
|
+
if (!verticalOhlcv.hasOwnProperty(weKey)) {
|
|
61
|
+
throw new Error(`Target property "${weKey}" not found in verticalOhlcv`);
|
|
62
|
+
}
|
|
58
63
|
}
|
|
59
|
-
}
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
}
|
|
62
66
|
|
|
63
|
-
const {
|
|
67
|
+
const { extectedFeatures } = instances.scaler[prefix]
|
|
64
68
|
|
|
65
69
|
let hasInvalidVal = false
|
|
70
|
+
const row = {}
|
|
71
|
+
const rowValues = []
|
|
72
|
+
let countFeatures = 0
|
|
66
73
|
|
|
67
|
-
// update windows with current values
|
|
68
74
|
for (let x = 0; x < colKeys.length; x++) {
|
|
69
75
|
const target = colKeys[x]
|
|
70
76
|
const col = verticalOhlcv[target]
|
|
77
|
+
const currWeight = weights?.[target]?.[0] ?? 1
|
|
78
|
+
const currVal = col[index]
|
|
71
79
|
|
|
72
|
-
if(
|
|
73
|
-
{
|
|
74
|
-
hasInvalidVal = true
|
|
75
|
-
break
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const val = col[index]
|
|
79
|
-
|
|
80
|
-
if(Number.isNaN(val) || val === null)
|
|
80
|
+
if(isBadNumber(currVal))
|
|
81
81
|
{
|
|
82
82
|
hasInvalidVal = true
|
|
83
83
|
break
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
win.push(val)
|
|
90
|
-
|
|
91
|
-
if (win.length > (group ? size * groupKeyLen : size)) {
|
|
92
|
-
win.shift()
|
|
86
|
+
row[target] = {
|
|
87
|
+
val: currVal,
|
|
88
|
+
weight: (euclideanWeights) ? Math.sqrt(currWeight) : currWeight
|
|
93
89
|
}
|
|
94
90
|
|
|
95
|
-
|
|
91
|
+
rowValues.push(currVal)
|
|
92
|
+
countFeatures++
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const ready = index + 1 >= size
|
|
94
|
+
if(lookback > 0) {
|
|
95
|
+
for (let step = 1; step <= lookback; step++) {
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// scale values once enough data
|
|
104
|
-
for (let x = 0; x < colKeys.length; x++) {
|
|
105
|
-
const target = colKeys[x]
|
|
106
|
-
const col = verticalOhlcv[target]
|
|
97
|
+
const laggedWeight = weights?.[target]?.[step] ?? 1
|
|
98
|
+
const laggedVal = col[index - step]
|
|
107
99
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
if(isBadNumber(laggedVal))
|
|
101
|
+
{
|
|
102
|
+
hasInvalidVal = true
|
|
103
|
+
break
|
|
104
|
+
}
|
|
112
105
|
|
|
113
|
-
|
|
106
|
+
row[`${target}_lag_${step}`] = {
|
|
107
|
+
val: laggedVal,
|
|
108
|
+
weight: (euclideanWeights) ? Math.sqrt(laggedWeight) : laggedWeight
|
|
109
|
+
}
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
rowValues.push(laggedVal)
|
|
112
|
+
countFeatures++
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if(hasInvalidVal) break
|
|
118
116
|
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if(hasInvalidVal || countFeatures !== extectedFeatures) return
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
if (type === "minmax") {
|
|
122
|
+
const mn = Math.min(...rowValues);
|
|
123
|
+
const mx = Math.max(...rowValues);
|
|
124
|
+
|
|
125
|
+
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
126
|
+
const scaled = normalizeMinMax(val, mn, mx, range) * weight;
|
|
127
|
+
main.pushToMain({ index, key: `${prefix}_${key}`, value: scaled });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (type === "zscore") {
|
|
132
|
+
const n = rowValues.length;
|
|
133
|
+
const mean = rowValues.reduce((s, x) => s + x, 0) / n;
|
|
134
|
+
const variance = rowValues.reduce((s, x) => s + (x - mean) ** 2, 0) / n;
|
|
135
|
+
const std = Math.sqrt(variance);
|
|
136
|
+
|
|
137
|
+
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
138
|
+
const scaled = normalizeZScore(val, mean, std) * weight;
|
|
139
|
+
main.pushToMain({ index, key: `${prefix}_${key}`, value: scaled });
|
|
136
140
|
}
|
|
137
|
-
|
|
138
|
-
main.pushToMain({ index, key, value: scaled})
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
package/src/studies/dateTime.js
CHANGED
|
@@ -12,17 +12,12 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
12
12
|
const {len, dateType} = main
|
|
13
13
|
if(!dateType) throw Error('dateTime method found and invalid "date" in input ohlcv')
|
|
14
14
|
|
|
15
|
-
const startYear = verticalOhlcv.date[0].getUTCFullYear()
|
|
16
|
-
const currentYear = new Date().getUTCFullYear()
|
|
17
|
-
|
|
18
15
|
Object.assign(instances, {
|
|
19
16
|
dateTime: {
|
|
20
17
|
colKeys: [...precompute.colKeys, `${prefix}year`],
|
|
21
18
|
colKeySizes: {
|
|
22
|
-
...precompute.colKeySizes
|
|
23
|
-
[`${prefix}year`]: (currentYear - startYear) + 1
|
|
19
|
+
...precompute.colKeySizes
|
|
24
20
|
}
|
|
25
|
-
,startYear
|
|
26
21
|
}
|
|
27
22
|
})
|
|
28
23
|
|
|
@@ -45,11 +40,11 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
45
40
|
}
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
const {colKeySizes
|
|
43
|
+
const {colKeySizes} = instances.dateTime
|
|
49
44
|
|
|
50
45
|
const currDate = verticalOhlcv.date[index]
|
|
51
46
|
|
|
52
|
-
const dateInfo = getDateInfo(currDate, oneHot, colKeySizes
|
|
47
|
+
const dateInfo = getDateInfo(currDate, oneHot, colKeySizes)
|
|
53
48
|
|
|
54
49
|
for(const [key, value] of Object.entries(dateInfo))
|
|
55
50
|
{
|
|
@@ -59,25 +54,34 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
59
54
|
|
|
60
55
|
|
|
61
56
|
|
|
62
|
-
const getDateInfo = (date, oneHot, colKeySizes
|
|
57
|
+
const getDateInfo = (date, oneHot, colKeySizes) => {
|
|
63
58
|
|
|
64
|
-
const encode = (value, size) => (oneHot) ? oneHotEncode(value, size) : value
|
|
65
59
|
|
|
66
|
-
|
|
67
|
-
[`${prefix}year`]: date.getUTCFullYear(),
|
|
68
|
-
[`${prefix}month`]: date.getUTCMonth(),
|
|
69
|
-
[`${prefix}hour`]: date.getUTCHours(),
|
|
70
|
-
[`${prefix}minute`]: date.getUTCMinutes(),
|
|
71
|
-
[`${prefix}day_of_the_week`]: date.getUTCDay(),
|
|
72
|
-
[`${prefix}day_of_the_month`]: date.getUTCDate() - 1,
|
|
73
|
-
}
|
|
60
|
+
const year = date.getFullYear();
|
|
74
61
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
//start with 0 values
|
|
63
|
+
const month = date.getMonth();
|
|
64
|
+
const hour = date.getHours();
|
|
65
|
+
const minute = date.getMinutes();
|
|
66
|
+
const dayOfWeek = date.getDay();
|
|
67
|
+
const dayOfMonth = date.getDate() - 1; // keep same offset
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
if (!oneHot) {
|
|
70
|
+
return {
|
|
71
|
+
year: year,
|
|
72
|
+
month: month + 1, //iso month
|
|
73
|
+
hour: hour,
|
|
74
|
+
minute: minute,
|
|
75
|
+
day_of_the_week: ((dayOfWeek + 6) % 7) + 1, //iso date of the week
|
|
76
|
+
day_of_the_month: dayOfMonth + 1, //iso date of the month
|
|
77
|
+
};
|
|
78
|
+
}
|
|
83
79
|
|
|
80
|
+
return {
|
|
81
|
+
one_hot_month: oneHotEncode(month, colKeySizes.one_hot_month),
|
|
82
|
+
one_hot_hour: oneHotEncode(hour, colKeySizes.one_hot_hour),
|
|
83
|
+
one_hot_minute: oneHotEncode(minute, colKeySizes.one_hot_minute),
|
|
84
|
+
one_hot_day_of_the_week: oneHotEncode(dayOfWeek, colKeySizes.one_hot_day_of_the_week),
|
|
85
|
+
one_hot_day_of_the_month: oneHotEncode(dayOfMonth, colKeySizes.one_hot_day_of_the_month),
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
const getRet = (next, prev) => (next - prev) / prev
|
|
3
|
+
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
|
+
|
|
5
|
+
export const priceFeatures = (main, index, {lag, colKeys}) => {
|
|
6
|
+
const { verticalOhlcv } = main
|
|
7
|
+
|
|
8
|
+
if(index === 0) {
|
|
9
|
+
const {len} = main
|
|
10
|
+
|
|
11
|
+
const newCols = {
|
|
12
|
+
ret_change: new Float64Array(len).fill(NaN),
|
|
13
|
+
ret_upper_wick: new Float64Array(len).fill(NaN),
|
|
14
|
+
ret_lower_wick: new Float64Array(len).fill(NaN),
|
|
15
|
+
ret_gap: new Float64Array(len).fill(NaN),
|
|
16
|
+
ret_body: new Float64Array(len).fill(NaN),
|
|
17
|
+
ret_range: new Float64Array(len).fill(NaN)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for(const target of colKeys) {
|
|
21
|
+
if(!verticalOhlcv.hasOwnProperty(target)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Target property ${target} not found in verticalOhlcv for "priceFeatures".`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
newCols[`ret_${target}`] = new Float64Array(len).fill(NaN)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Object.assign(verticalOhlcv, newCols)
|
|
31
|
+
|
|
32
|
+
if(lag) {
|
|
33
|
+
main.lag(Object.keys(newCols), lag)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const prevClose = verticalOhlcv.close[index - 1]
|
|
38
|
+
|
|
39
|
+
if(typeof prevClose === 'undefined') return
|
|
40
|
+
|
|
41
|
+
const currOpen = verticalOhlcv.open[index]
|
|
42
|
+
const currHigh = verticalOhlcv.high[index]
|
|
43
|
+
const currLow = verticalOhlcv.low[index]
|
|
44
|
+
const currClose = verticalOhlcv.close[index]
|
|
45
|
+
|
|
46
|
+
const upperWickTop = Math.max(currOpen, currClose)
|
|
47
|
+
const lowerWickTop = Math.min(currOpen, currClose)
|
|
48
|
+
|
|
49
|
+
const row = {
|
|
50
|
+
ret_change: getRet(currClose, prevClose),
|
|
51
|
+
ret_upper_wick: getRet(currHigh, upperWickTop),
|
|
52
|
+
ret_lower_wick: getRet(lowerWickTop, currLow),
|
|
53
|
+
ret_gap: getRet(currOpen, prevClose),
|
|
54
|
+
ret_body: getRet(currClose, currOpen),
|
|
55
|
+
ret_range: getRet(currHigh, currLow)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let hasInvalidTarget = false
|
|
59
|
+
|
|
60
|
+
for(const target of colKeys) {
|
|
61
|
+
const targetVal = verticalOhlcv[target][index]
|
|
62
|
+
|
|
63
|
+
if(isBadNumber(targetVal)) {
|
|
64
|
+
hasInvalidTarget = true
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const targetRet = getRet(currClose, targetVal)
|
|
69
|
+
row[`ret_${target}`] = targetRet
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if(hasInvalidTarget) return
|
|
73
|
+
|
|
74
|
+
for(const [key, value] of Object.entries(row)) {
|
|
75
|
+
main.pushToMain({index, key, value})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export const countUniqueLabels = Y => {
|
|
2
|
-
// 1. Guard against empty or non-array input
|
|
3
|
-
if (!Array.isArray(Y) || Y.length === 0) {
|
|
4
|
-
return 1
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// 2. Detect if Y is 2D or 1D
|
|
8
|
-
const is2D = Array.isArray(Y[0])
|
|
9
|
-
const nCols = is2D ? Y[0].length : 1
|
|
10
|
-
|
|
11
|
-
let total = 0
|
|
12
|
-
|
|
13
|
-
// 3. For each column, collect uniques in a single pass
|
|
14
|
-
for (let col = 0; col < nCols; col++) {
|
|
15
|
-
const seen = new Set()
|
|
16
|
-
for (let row = 0; row < Y.length; row++) {
|
|
17
|
-
// pick the cell: Y[row][col] if 2D, or Y[row] if 1D
|
|
18
|
-
const cell = is2D ? Y[row][col] : Y[row]
|
|
19
|
-
seen.add(cell)
|
|
20
|
-
}
|
|
21
|
-
total += seen.size
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 4. Return “sum of uniques per column”
|
|
25
|
-
return total
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// X and Y can have multivariable or univariable rows
|
|
29
|
-
export const oversampleXY = (X, Y) => {
|
|
30
|
-
if (!Array.isArray(X) || !Array.isArray(Y)) {
|
|
31
|
-
throw new TypeError('Both X and Y must be arrays');
|
|
32
|
-
}
|
|
33
|
-
if (X.length !== Y.length) {
|
|
34
|
-
throw new Error('X and Y must have the same length');
|
|
35
|
-
}
|
|
36
|
-
if (Y.length === 0) {
|
|
37
|
-
return { X: [], Y: [] };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 2) Group X-values by label using a Map
|
|
41
|
-
const groups = Y.reduce((map, label, i) => {
|
|
42
|
-
if (!map.has(label)) map.set(label, []);
|
|
43
|
-
map.get(label).push(X[i]);
|
|
44
|
-
return map;
|
|
45
|
-
}, new Map());
|
|
46
|
-
|
|
47
|
-
// 3) Find the largest group size
|
|
48
|
-
let maxSize = 0;
|
|
49
|
-
for (const arr of groups.values()) {
|
|
50
|
-
if (arr.length > maxSize) maxSize = arr.length;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 4) Build the oversampled arrays
|
|
54
|
-
const outX = [];
|
|
55
|
-
const outY = [];
|
|
56
|
-
for (const [label, xArr] of groups.entries()) {
|
|
57
|
-
const sz = xArr.length;
|
|
58
|
-
for (let i = 0; i < maxSize; i++) {
|
|
59
|
-
outX.push(xArr[i % sz]);
|
|
60
|
-
outY.push(label);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return { X: outX, Y: outY };
|
|
65
|
-
};
|