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.
@@ -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 {groupKey, groupKeyLen} = precomputed
8
- const { verticalOhlcv, instances, invalidValueIndex } = main;
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
- windows: {}
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
- const winKey = group ? groupKey : target;
43
- instances.scaler[prefix].windows[winKey] = [];
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 (lag > 0) {
49
- const lags = Array.from({ length: lag }).map((_, i) => `${key}_lag_${i + 1}`)
50
- main.scaledGroups[groupKey].push(...lags)
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
- if(lag > 0)
56
- {
57
- main.lag(features, lag)
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
- if(secondaryLoop === true && index <= invalidValueIndex) true
65
+ }
62
66
 
63
- const { windows } = instances.scaler[prefix]
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(typeof col === 'undefined')
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
- const winKey = group ? groupKey : target
87
- const win = windows[winKey]
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
- if(hasInvalidVal) return
98
-
99
- const ready = index + 1 >= size
94
+ if(lookback > 0) {
95
+ for (let step = 1; step <= lookback; step++) {
100
96
 
101
- if(!ready) return
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
- if(typeof col === 'undefined')
109
- {
110
- break
111
- }
100
+ if(isBadNumber(laggedVal))
101
+ {
102
+ hasInvalidVal = true
103
+ break
104
+ }
112
105
 
113
- const val = col[index]
106
+ row[`${target}_lag_${step}`] = {
107
+ val: laggedVal,
108
+ weight: (euclideanWeights) ? Math.sqrt(laggedWeight) : laggedWeight
109
+ }
114
110
 
115
- if(Number.isNaN(val) || val === null)
116
- {
117
- break
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
- 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}"`)
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
 
@@ -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, startYear} = instances.dateTime
43
+ const {colKeySizes} = instances.dateTime
49
44
 
50
45
  const currDate = verticalOhlcv.date[index]
51
46
 
52
- const dateInfo = getDateInfo(currDate, oneHot, colKeySizes, prefix, startYear)
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, prefix, startYear) => {
57
+ const getDateInfo = (date, oneHot, colKeySizes) => {
63
58
 
64
- const encode = (value, size) => (oneHot) ? oneHotEncode(value, size) : value
65
59
 
66
- const output = {
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
- for(const [key, size] of Object.entries(colKeySizes))
76
- {
77
- const value = (key === `${prefix}year`) ? output[key] - startYear: output[key]
78
- output[key] = encode(value, size)
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
- return output
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,5 +1,5 @@
1
1
  export const dateOutputFormaters = {
2
- string: d => d.toISOString(),
2
+ string: d => d.toString(),
3
3
  milliseconds: d => d.getTime(),
4
4
  seconds: d => d.getTime() / 1000,
5
5
  object: d => d
@@ -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
- };