ohlcv-indicators 3.5.56 → 3.5.60
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 +53 -329
- package/package.json +2 -13
- package/src/core-functions/mainLoop.js +10 -42
- package/src/machine-learning/ml-utilities.js +6 -114
- package/src/machine-learning/scaler.js +93 -75
- package/src/studies/dateTime.js +29 -25
- package/src/studies/priceFeatures.js +78 -0
- package/src/utilities/dateUtilities.js +1 -1
- package/src/utilities/validators.js +2 -2
- 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
|
|
@@ -19,112 +13,10 @@ export const normalizeZScore = (value, mean, std) => {
|
|
|
19
13
|
return std === 0 ? 0 : (value - mean) / std
|
|
20
14
|
}
|
|
21
15
|
|
|
22
|
-
export const
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
}
|
|
16
|
+
export const scaleByFeatureRange = (x, min, max) => {
|
|
17
|
+
const center = (min + max) / 2
|
|
18
|
+
const halfRange = (max - min) / 2
|
|
19
|
+
const scaled = ((x - center) / halfRange) * 2
|
|
67
20
|
|
|
68
|
-
return
|
|
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
|
-
}
|
|
21
|
+
return Math.max(-2, Math.min(2, scaled))
|
|
22
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { normalizeMinMax, normalizeZScore } from "./ml-utilities.js";
|
|
2
|
-
|
|
3
|
-
export const scaler = (main, index, size, colKeys, {type, group, range, lag, precomputed, secondaryLoop}) => {
|
|
1
|
+
import { normalizeMinMax, normalizeZScore, scaleByFeatureRange } from "./ml-utilities.js";
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
|
+
|
|
5
|
+
//rowWiseScaler
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export const scaler = (main, index, size, colKeys, {type, minMaxRange, lookback, weights, euclideanWeights, byFeatureRange}) => {
|
|
8
|
+
|
|
9
|
+
//this function performs scaling row by row "row wise scaling"
|
|
10
|
+
|
|
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,120 @@ 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(
|
|
80
|
+
if(isBadNumber(currVal))
|
|
73
81
|
{
|
|
74
82
|
hasInvalidVal = true
|
|
75
|
-
break
|
|
83
|
+
break
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
|
|
86
|
+
row[target] = {
|
|
87
|
+
val: currVal,
|
|
88
|
+
weight: (euclideanWeights) ? Math.sqrt(currWeight) : currWeight
|
|
89
|
+
}
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
rowValues.push(currVal)
|
|
92
|
+
countFeatures++
|
|
93
|
+
|
|
94
|
+
if(lookback > 0) {
|
|
95
|
+
for (let step = 1; step <= lookback; step++) {
|
|
96
|
+
|
|
97
|
+
const laggedWeight = weights?.[target]?.[step] ?? 1
|
|
98
|
+
const laggedVal = col[index - step]
|
|
99
|
+
|
|
100
|
+
if(isBadNumber(laggedVal))
|
|
101
|
+
{
|
|
102
|
+
hasInvalidVal = true
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
row[`${target}_lag_${step}`] = {
|
|
107
|
+
val: laggedVal,
|
|
108
|
+
weight: (euclideanWeights) ? Math.sqrt(laggedWeight) : laggedWeight
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
rowValues.push(laggedVal)
|
|
112
|
+
countFeatures++
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if(hasInvalidVal) break
|
|
84
116
|
}
|
|
117
|
+
}
|
|
85
118
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
119
|
+
if(hasInvalidVal || countFeatures !== extectedFeatures) return
|
|
120
|
+
|
|
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, minMaxRange)
|
|
127
|
+
const weigthed = scaled === 0 ? 0 : scaled * weight
|
|
128
|
+
main.pushToMain({ index, key: `${prefix}_${key}`, value: weigthed });
|
|
93
129
|
}
|
|
130
|
+
}
|
|
94
131
|
|
|
132
|
+
if (type === "zscore") {
|
|
133
|
+
const n = rowValues.length;
|
|
134
|
+
const mean = rowValues.reduce((s, x) => s + x, 0) / n;
|
|
135
|
+
const variance = rowValues.reduce((s, x) => s + (x - mean) ** 2, 0) / n;
|
|
136
|
+
const std = Math.sqrt(variance);
|
|
137
|
+
|
|
138
|
+
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
139
|
+
const scaled = normalizeZScore(val, mean, std)
|
|
140
|
+
const weigthed = scaled === 0 ? 0 : scaled * weight
|
|
141
|
+
main.pushToMain({ index, key: `${prefix}_${key}`, value: weigthed });
|
|
142
|
+
}
|
|
95
143
|
}
|
|
96
144
|
|
|
97
|
-
if(
|
|
98
|
-
|
|
99
|
-
const ready = index + 1 >= size
|
|
145
|
+
if(type === "byfeature") {
|
|
100
146
|
|
|
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]
|
|
147
|
+
const [mn, mx] = byFeatureRange
|
|
107
148
|
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
break
|
|
111
|
-
}
|
|
149
|
+
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
112
150
|
|
|
113
|
-
|
|
151
|
+
const scaled = normalizeMinMax(val, mn, mx, minMaxRange)
|
|
152
|
+
const weigthed = scaled === 0 ? 0 : scaled * weight
|
|
114
153
|
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
break
|
|
154
|
+
main.pushToMain({ index, key: `${prefix}_${key}`, value: weigthed });
|
|
118
155
|
}
|
|
119
156
|
|
|
120
|
-
const key = `${prefix}_${target}`
|
|
121
|
-
let scaled = null
|
|
122
|
-
const winKey = group ? groupKey : target
|
|
123
|
-
const win = windows[winKey]
|
|
124
|
-
|
|
125
|
-
if (type === 'minmax') {
|
|
126
|
-
const mn = Math.min(...win)
|
|
127
|
-
const mx = Math.max(...win)
|
|
128
|
-
scaled = normalizeMinMax(val, mn, mx, range)
|
|
129
|
-
} else if (type === 'zscore') {
|
|
130
|
-
const mean = win.reduce((sum, x) => sum + x, 0) / win.length;
|
|
131
|
-
const variance = win.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / win.length
|
|
132
|
-
const std = Math.sqrt(variance);
|
|
133
|
-
scaled = normalizeZScore(val, mean, std)
|
|
134
|
-
} else {
|
|
135
|
-
throw new Error(`Unknown scaler type "${type}"`)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
main.pushToMain({ index, key, value: scaled})
|
|
139
157
|
}
|
|
140
158
|
}
|
|
141
159
|
|
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
|
+
}
|
|
@@ -123,8 +123,8 @@ export const validateArrayOfRanges = (range, paramName, callerName) => {
|
|
|
123
123
|
|
|
124
124
|
const [min, max] = range
|
|
125
125
|
|
|
126
|
-
validateNumber(min, {min: -100, max, allowDecimals: false},
|
|
127
|
-
validateNumber(max, {min: min, max: 100, allowDecimals: false},
|
|
126
|
+
validateNumber(min, {min: -100, max, allowDecimals: false}, `${paramName}[0] (min)`, callerName)
|
|
127
|
+
validateNumber(max, {min: min, max: 100, allowDecimals: false}, `${paramName}[1] (max)`, callerName)
|
|
128
128
|
|
|
129
129
|
if(min === max)
|
|
130
130
|
{
|
|
@@ -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
|
-
};
|