ohlcv-indicators 3.5.55 → 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/moving-averages/movingAverages.js +0 -2
- package/src/moving-averages/relativeVolume.js +0 -2
- package/src/moving-averages/vidya.js +53 -53
- package/src/studies/dateTime.js +29 -25
- package/src/studies/priceFeatures.js +78 -0
- package/src/utilities/dateUtilities.js +1 -1
- package/src/utilities/precisionMultiplier.js +36 -19
- 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
|
|
|
@@ -8,8 +8,6 @@ const indicatorClasses = {
|
|
|
8
8
|
|
|
9
9
|
export const movingAverages = (main, index, methodName, size, { target, lag }) => {
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
11
|
const { verticalOhlcv, instances, priceBased } = main
|
|
14
12
|
const suffix = (target !== 'close') ? `_${target}` : ''
|
|
15
13
|
const keyName = `${methodName}_${size}${suffix}`
|
|
@@ -9,11 +9,11 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
9
9
|
// Initialization on the first bar
|
|
10
10
|
if (index === 0) {
|
|
11
11
|
if (!(target in verticalOhlcv)) {
|
|
12
|
-
throw new Error(`VIDYA could not find target "${target}"`)
|
|
12
|
+
throw new Error(`VIDYA could not find target "${target}"`)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const prefix = `vidya_${indicatorKey}
|
|
16
|
-
const keyBase = target === 'close' ? prefix : `${prefix}_${target}
|
|
15
|
+
const prefix = `vidya_${indicatorKey}`
|
|
16
|
+
const keyBase = target === 'close' ? prefix : `${prefix}_${target}`
|
|
17
17
|
const keys = {
|
|
18
18
|
vidya: keyBase,
|
|
19
19
|
upper: `${prefix}_upper`,
|
|
@@ -21,7 +21,7 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
21
21
|
delta_vol: `${prefix}_delta_vol`,
|
|
22
22
|
cross: `${prefix}_cross`,
|
|
23
23
|
liquidity: `${prefix}_liquidity`,
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
const priceBasedVidyaKeys = [keyBase, keys.upper, keys.lower, keys.liquidity]
|
|
27
27
|
|
|
@@ -63,13 +63,13 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
// Series inputs
|
|
66
|
-
const price = verticalOhlcv[target][index]
|
|
67
|
-
const high = verticalOhlcv.high[index]
|
|
68
|
-
const low = verticalOhlcv.low[index]
|
|
69
|
-
const close = verticalOhlcv.close[index]
|
|
70
|
-
const open = verticalOhlcv.open[index]
|
|
71
|
-
const vol = verticalOhlcv.volume[index]
|
|
72
|
-
const prevCross = index > 0 ? verticalOhlcv[keys.cross][index - 1] : 0
|
|
66
|
+
const price = verticalOhlcv[target][index]
|
|
67
|
+
const high = verticalOhlcv.high[index]
|
|
68
|
+
const low = verticalOhlcv.low[index]
|
|
69
|
+
const close = verticalOhlcv.close[index]
|
|
70
|
+
const open = verticalOhlcv.open[index]
|
|
71
|
+
const vol = verticalOhlcv.volume[index]
|
|
72
|
+
const prevCross = index > 0 ? verticalOhlcv[keys.cross][index - 1] : 0
|
|
73
73
|
|
|
74
74
|
const highestHigh = (inst.liquidity.high.length === liquidityLookback) ? Math.max(...inst.liquidity.high) : NaN
|
|
75
75
|
const lowestLow = (inst.liquidity.low.length === liquidityLookback) ? Math.min(...inst.liquidity.low) : NaN
|
|
@@ -81,77 +81,77 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
81
81
|
high - low,
|
|
82
82
|
Math.abs(high - prevClose),
|
|
83
83
|
Math.abs(low - prevClose)
|
|
84
|
-
)
|
|
85
|
-
inst.atrBuffer.push(tr)
|
|
86
|
-
if (inst.atrBuffer.length > atrLength) inst.atrBuffer.shift()
|
|
84
|
+
)
|
|
85
|
+
inst.atrBuffer.push(tr)
|
|
86
|
+
if (inst.atrBuffer.length > atrLength) inst.atrBuffer.shift()
|
|
87
87
|
}
|
|
88
88
|
const atr = inst.atrBuffer.length === atrLength
|
|
89
89
|
? inst.atrBuffer.reduce((a, b) => a + b, 0) / atrLength
|
|
90
|
-
: NaN
|
|
90
|
+
: NaN
|
|
91
91
|
|
|
92
92
|
// Compute raw VIDYA
|
|
93
|
-
let rawVidya = NaN
|
|
94
|
-
let mom = NaN
|
|
95
|
-
if (index > 0) mom = price - verticalOhlcv[target][index - 1]
|
|
96
|
-
inst.momentumBuffer.push(mom)
|
|
97
|
-
if (inst.momentumBuffer.length > momentum) inst.momentumBuffer.shift()
|
|
93
|
+
let rawVidya = NaN
|
|
94
|
+
let mom = NaN
|
|
95
|
+
if (index > 0) mom = price - verticalOhlcv[target][index - 1]
|
|
96
|
+
inst.momentumBuffer.push(mom)
|
|
97
|
+
if (inst.momentumBuffer.length > momentum) inst.momentumBuffer.shift()
|
|
98
98
|
if (inst.momentumBuffer.length === momentum) {
|
|
99
|
-
let sumPos = 0, sumNeg = 0
|
|
99
|
+
let sumPos = 0, sumNeg = 0
|
|
100
100
|
inst.momentumBuffer.forEach(m => m >= 0 ? sumPos += m : sumNeg += -m);
|
|
101
|
-
const denom = sumPos + sumNeg
|
|
101
|
+
const denom = sumPos + sumNeg
|
|
102
102
|
const cmo = denom === 0 ? 0 : Math.abs((sumPos - sumNeg) / denom) * 100;
|
|
103
|
-
const alpha = 2 / (size + 1)
|
|
104
|
-
const a = alpha * (cmo / 100)
|
|
103
|
+
const alpha = 2 / (size + 1)
|
|
104
|
+
const a = alpha * (cmo / 100)
|
|
105
105
|
rawVidya = isNaN(inst.prevVidya) ? price : (a * price + (1 - a) * inst.prevVidya);
|
|
106
|
-
inst.prevVidya = rawVidya
|
|
106
|
+
inst.prevVidya = rawVidya
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Smooth VIDYA using fixed 15-bar SMA
|
|
110
|
-
inst.sma.update(rawVidya)
|
|
111
|
-
let smoothedVidya = NaN
|
|
112
|
-
try { smoothedVidya = inst.sma.getResult()
|
|
110
|
+
inst.sma.update(rawVidya)
|
|
111
|
+
let smoothedVidya = NaN
|
|
112
|
+
try { smoothedVidya = inst.sma.getResult() } catch {}
|
|
113
113
|
|
|
114
114
|
// Bands
|
|
115
115
|
const upper = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
116
116
|
? smoothedVidya + atr * bandDistance
|
|
117
|
-
: NaN
|
|
117
|
+
: NaN
|
|
118
118
|
const lower = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
119
119
|
? smoothedVidya - atr * bandDistance
|
|
120
|
-
: NaN
|
|
120
|
+
: NaN
|
|
121
121
|
|
|
122
122
|
// Detect crosses
|
|
123
|
-
const prevPrice = index > 0 ? verticalOhlcv[target][index - 1] : NaN
|
|
124
|
-
const prevUpper = index > 0 ? verticalOhlcv[keys.upper][index - 1] : NaN
|
|
125
|
-
const prevLower = index > 0 ? verticalOhlcv[keys.lower][index - 1] : NaN
|
|
126
|
-
const crossUp = index > 0 && prevPrice <= prevUpper && price > upper
|
|
127
|
-
const crossDown = index > 0 && prevPrice >= prevLower && price < lower
|
|
123
|
+
const prevPrice = index > 0 ? verticalOhlcv[target][index - 1] : NaN
|
|
124
|
+
const prevUpper = index > 0 ? verticalOhlcv[keys.upper][index - 1] : NaN
|
|
125
|
+
const prevLower = index > 0 ? verticalOhlcv[keys.lower][index - 1] : NaN
|
|
126
|
+
const crossUp = index > 0 && prevPrice <= prevUpper && price > upper
|
|
127
|
+
const crossDown = index > 0 && prevPrice >= prevLower && price < lower
|
|
128
128
|
|
|
129
129
|
// Update trend state
|
|
130
|
-
if (crossUp) inst.isTrendUp = true
|
|
131
|
-
if (crossDown) inst.isTrendUp = false
|
|
130
|
+
if (crossUp) inst.isTrendUp = true
|
|
131
|
+
if (crossDown) inst.isTrendUp = false
|
|
132
132
|
|
|
133
133
|
// Cumulative cross counter: positive when uptrend, negative when downtrend
|
|
134
134
|
let cross = 0;
|
|
135
135
|
if (index > 0) {
|
|
136
136
|
if (inst.isTrendUp) {
|
|
137
|
-
cross = prevCross > 0 ? prevCross + 1 : 1
|
|
137
|
+
cross = prevCross > 0 ? prevCross + 1 : 1
|
|
138
138
|
} else {
|
|
139
|
-
cross = prevCross < 0 ? prevCross - 1 : -1
|
|
139
|
+
cross = prevCross < 0 ? prevCross - 1 : -1
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// Volume accumulation
|
|
144
144
|
if (crossUp || crossDown) {
|
|
145
|
-
inst.upVol = inst.downVol = 0
|
|
145
|
+
inst.upVol = inst.downVol = 0
|
|
146
146
|
} else {
|
|
147
|
-
inst.upVol += (close > open ? vol : 0)
|
|
148
|
-
inst.downVol += (close < open ? vol : 0)
|
|
147
|
+
inst.upVol += (close > open ? vol : 0)
|
|
148
|
+
inst.downVol += (close < open ? vol : 0)
|
|
149
149
|
}
|
|
150
|
-
const avgVol = (inst.upVol + inst.downVol) / 2 || 1
|
|
151
|
-
const deltaPct = (inst.upVol - inst.downVol) / avgVol
|
|
150
|
+
const avgVol = (inst.upVol + inst.downVol) / 2 || 1
|
|
151
|
+
const deltaPct = (inst.upVol - inst.downVol) / avgVol
|
|
152
152
|
|
|
153
153
|
// Trend-based boundary (reset on crossover)
|
|
154
|
-
const trendValue = (inst.isTrendUp) ? lower : upper
|
|
154
|
+
const trendValue = (inst.isTrendUp) ? lower : upper
|
|
155
155
|
let liquidity = NaN
|
|
156
156
|
|
|
157
157
|
if(cross > 0)
|
|
@@ -164,12 +164,12 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// Push outputs
|
|
167
|
-
main.pushToMain({ index, key: keys.vidya, value: trendValue })
|
|
168
|
-
main.pushToMain({ index, key: keys.upper, value: upper })
|
|
169
|
-
main.pushToMain({ index, key: keys.lower, value: lower })
|
|
170
|
-
main.pushToMain({ index, key: keys.delta_vol, value: deltaPct })
|
|
171
|
-
main.pushToMain({ index, key: keys.cross, value: cross })
|
|
172
|
-
main.pushToMain({ index, key: keys.liquidity, value: liquidity })
|
|
167
|
+
main.pushToMain({ index, key: keys.vidya, value: trendValue })
|
|
168
|
+
main.pushToMain({ index, key: keys.upper, value: upper })
|
|
169
|
+
main.pushToMain({ index, key: keys.lower, value: lower })
|
|
170
|
+
main.pushToMain({ index, key: keys.delta_vol, value: deltaPct })
|
|
171
|
+
main.pushToMain({ index, key: keys.cross, value: cross })
|
|
172
|
+
main.pushToMain({ index, key: keys.liquidity, value: liquidity })
|
|
173
173
|
|
|
174
174
|
inst.liquidity.high.push(high)
|
|
175
175
|
inst.liquidity.low.push(low)
|
|
@@ -177,5 +177,5 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
177
177
|
if(inst.liquidity.high.length > liquidityLookback) inst.liquidity.high.shift()
|
|
178
178
|
if(inst.liquidity.low.length > liquidityLookback) inst.liquidity.low.shift()
|
|
179
179
|
|
|
180
|
-
return true
|
|
181
|
-
}
|
|
180
|
+
return true
|
|
181
|
+
}
|
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
|
+
};
|