ohlcv-indicators 3.5.52 → 3.5.54
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 +48 -65
- package/package.json +1 -1
- package/src/core-functions/mainLoop.js +8 -3
- package/src/core-functions/pushToMain.js +1 -2
- package/src/machine-learning/classifier.js +56 -3
- package/src/moving-averages/bollingerBands.js +11 -8
- package/src/moving-averages/donchianChannel.js +8 -6
- package/src/moving-averages/heikenAshi.js +117 -93
- package/src/moving-averages/macd.js +5 -1
- package/src/moving-averages/movingAverages.js +5 -1
- package/src/moving-averages/vidya.js +7 -1
- package/src/oscillators/volumeDelta.js +74 -0
- package/src/studies/findCrosses.js +31 -5
- package/src/studies/lag.js +17 -12
- package/src/utilities/assignTypes.js +4 -6
- package/src/utilities/numberUtilities.js +22 -39
- package/src/utilities/precisionMultiplier.js +91 -16
- package/src/utilities/validators.js +1 -1
- package/src/utilities/verticalToHorizontal.js +16 -3
- package/src/studies/correlation.js +0 -98
|
@@ -1,113 +1,137 @@
|
|
|
1
1
|
import { FasterEMA } from 'trading-signals';
|
|
2
2
|
|
|
3
|
-
export const heikenAshi = (main, index, smoothLength, afterSmoothLength, {lag}) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag = 0, bothNull = false } = {}) => {
|
|
4
|
+
const { verticalOhlcv, instances, len, priceBased } = main;
|
|
5
|
+
const prefix = (bothNull) ? 'heiken_ashi' : `heiken_ashi_${smoothLength}_${afterSmoothLength}`;
|
|
6
|
+
const keys = ['open', 'high', 'low', 'close'];
|
|
7
|
+
|
|
8
|
+
// ---- INIT ----
|
|
9
|
+
if (index === 0) {
|
|
10
|
+
// Initialize state
|
|
11
|
+
instances[prefix] = {
|
|
12
|
+
// Only allocate EMAs when smoothing is requested
|
|
13
|
+
emaPre: !bothNull
|
|
14
|
+
? Object.fromEntries(keys.map(k => [k, new FasterEMA(smoothLength)]))
|
|
15
|
+
: null,
|
|
16
|
+
emaPost: !bothNull
|
|
17
|
+
? Object.fromEntries(keys.map(k => [k, new FasterEMA(afterSmoothLength)]))
|
|
18
|
+
: null,
|
|
19
|
+
prevHaOpen: NaN,
|
|
20
|
+
prevHaClose: NaN,
|
|
21
|
+
isTrendUp: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const keyNames = keys.map(k => `${prefix}_${k}`);
|
|
25
|
+
const verticalOhlcvSetup = Object.fromEntries(
|
|
26
|
+
[...keyNames, `${prefix}_cross`].map(v => [v, new Float64Array(len).fill(NaN)])
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
for(const k of keyNames) {
|
|
30
|
+
priceBased.add(k)
|
|
31
|
+
}
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
Object.assign(verticalOhlcv, { ...verticalOhlcvSetup });
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
main.lag(keyNames, lag)
|
|
29
|
-
}
|
|
35
|
+
if (lag > 0) {
|
|
36
|
+
main.lag(keyNames, lag);
|
|
30
37
|
}
|
|
38
|
+
}
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
// ---- FETCH RAW INPUTS ----
|
|
41
|
+
const open = verticalOhlcv.open[index];
|
|
42
|
+
const high = verticalOhlcv.high[index];
|
|
43
|
+
const low = verticalOhlcv.low[index];
|
|
44
|
+
const close = verticalOhlcv.close[index];
|
|
45
|
+
const inst = instances[prefix];
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
inst.emaPre.open.update(open)
|
|
41
|
-
inst.emaPre.high.update(high)
|
|
42
|
-
inst.emaPre.low.update(low)
|
|
43
|
-
inst.emaPre.close.update(close)
|
|
47
|
+
let sOpen, sHigh, sLow, sClose;
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
if (!bothNull) {
|
|
50
|
+
// ---- PRE-SMOOTHING (EMA) ----
|
|
51
|
+
inst.emaPre.open.update(open);
|
|
52
|
+
inst.emaPre.high.update(high);
|
|
53
|
+
inst.emaPre.low.update(low);
|
|
54
|
+
inst.emaPre.close.update(close);
|
|
46
55
|
|
|
47
56
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
sOpen = inst.emaPre.open.getResult();
|
|
58
|
+
sHigh = inst.emaPre.high.getResult();
|
|
59
|
+
sLow = inst.emaPre.low.getResult();
|
|
60
|
+
sClose = inst.emaPre.close.getResult();
|
|
52
61
|
} catch {
|
|
53
|
-
|
|
62
|
+
return true; // skip if not enough data
|
|
54
63
|
}
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
} else {
|
|
65
|
+
// No pre-smoothing: use raw OHLC
|
|
66
|
+
sOpen = open;
|
|
67
|
+
sHigh = high;
|
|
68
|
+
sLow = low;
|
|
69
|
+
sClose = close;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---- HEIKEN ASHI CORE (from s*) ----
|
|
73
|
+
const haClose = (sOpen + sHigh + sLow + sClose) / 4;
|
|
74
|
+
const haOpen = (Number.isNaN(inst.prevHaOpen) || Number.isNaN(inst.prevHaClose))
|
|
75
|
+
? (sOpen + sClose) / 2
|
|
76
|
+
: (inst.prevHaOpen + inst.prevHaClose) / 2;
|
|
77
|
+
const haHigh = Math.max(sHigh, haOpen, haClose);
|
|
78
|
+
const haLow = Math.min(sLow, haOpen, haClose);
|
|
79
|
+
|
|
80
|
+
// Store for next round
|
|
81
|
+
inst.prevHaOpen = haOpen;
|
|
82
|
+
inst.prevHaClose = haClose;
|
|
83
|
+
|
|
84
|
+
let smOpen, smHigh, smLow, smClose;
|
|
85
|
+
|
|
86
|
+
if (!bothNull) {
|
|
66
87
|
// ---- POST-SMOOTHING (EMA) ----
|
|
67
|
-
inst.emaPost.open.update(haOpen)
|
|
68
|
-
inst.emaPost.high.update(haHigh)
|
|
69
|
-
inst.emaPost.low.update(haLow)
|
|
70
|
-
inst.emaPost.close.update(haClose)
|
|
71
|
-
|
|
72
|
-
let smOpen, smHigh, smLow, smClose
|
|
88
|
+
inst.emaPost.open.update(haOpen);
|
|
89
|
+
inst.emaPost.high.update(haHigh);
|
|
90
|
+
inst.emaPost.low.update(haLow);
|
|
91
|
+
inst.emaPost.close.update(haClose);
|
|
73
92
|
|
|
74
93
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
smOpen = inst.emaPost.open.getResult();
|
|
95
|
+
smHigh = inst.emaPost.high.getResult();
|
|
96
|
+
smLow = inst.emaPost.low.getResult();
|
|
97
|
+
smClose = inst.emaPost.close.getResult();
|
|
79
98
|
} catch {
|
|
80
|
-
|
|
99
|
+
return true; // skip if not enough data
|
|
81
100
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
} else {
|
|
102
|
+
// No post-smoothing: final values are plain HA
|
|
103
|
+
smOpen = haOpen;
|
|
104
|
+
smHigh = haHigh;
|
|
105
|
+
smLow = haLow;
|
|
106
|
+
smClose = haClose;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---- TREND/CROSS LOGIC (works for both modes) ----
|
|
110
|
+
const prevCross = index > 0 ? verticalOhlcv[`${prefix}_cross`][index - 1] : 0;
|
|
111
|
+
const prevHaOpenArr = index > 0 ? verticalOhlcv[`${prefix}_open`][index - 1] : NaN;
|
|
112
|
+
const prevHaCloseArr = index > 0 ? verticalOhlcv[`${prefix}_close`][index - 1] : NaN;
|
|
113
|
+
|
|
114
|
+
const crossUp = !Number.isNaN(prevHaCloseArr) && prevHaCloseArr <= prevHaOpenArr && smClose > smOpen;
|
|
115
|
+
const crossDown = !Number.isNaN(prevHaCloseArr) && prevHaCloseArr >= prevHaOpenArr && smClose < smOpen;
|
|
116
|
+
|
|
117
|
+
if (crossUp) inst.isTrendUp = true;
|
|
118
|
+
if (crossDown) inst.isTrendUp = false;
|
|
119
|
+
|
|
120
|
+
let cross = 0;
|
|
121
|
+
if (index > 0) {
|
|
122
|
+
if (inst.isTrendUp) {
|
|
123
|
+
cross = prevCross > 0 ? prevCross + 1 : 1;
|
|
124
|
+
} else {
|
|
125
|
+
cross = prevCross < 0 ? prevCross - 1 : -1;
|
|
102
126
|
}
|
|
127
|
+
}
|
|
103
128
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
// ---- PUSH OUTPUTS ----
|
|
130
|
+
main.pushToMain({ index, key: `${prefix}_open`, value: smOpen });
|
|
131
|
+
main.pushToMain({ index, key: `${prefix}_high`, value: smHigh });
|
|
132
|
+
main.pushToMain({ index, key: `${prefix}_low`, value: smLow });
|
|
133
|
+
main.pushToMain({ index, key: `${prefix}_close`, value: smClose });
|
|
134
|
+
main.pushToMain({ index, key: `${prefix}_cross`, value: cross });
|
|
111
135
|
|
|
112
|
-
return true
|
|
113
|
-
}
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
@@ -6,7 +6,7 @@ export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
const { verticalOhlcv, instances } = main
|
|
9
|
+
const { verticalOhlcv, instances, priceBased } = main
|
|
10
10
|
const {instanceKey} = precomputed
|
|
11
11
|
|
|
12
12
|
// Initialization on the first index.
|
|
@@ -52,6 +52,10 @@ export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}
|
|
|
52
52
|
|
|
53
53
|
const keyNames = [diffKey, deaKey, histogramKey]
|
|
54
54
|
|
|
55
|
+
for(const k of keyNames) {
|
|
56
|
+
priceBased.add(k)
|
|
57
|
+
}
|
|
58
|
+
|
|
55
59
|
if(lag > 0)
|
|
56
60
|
{
|
|
57
61
|
main.lag(keyNames, lag)
|
|
@@ -10,7 +10,7 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
const { verticalOhlcv, instances } = main
|
|
13
|
+
const { verticalOhlcv, instances, priceBased } = main
|
|
14
14
|
const suffix = (target !== 'close') ? `_${target}` : ''
|
|
15
15
|
const keyName = `${methodName}_${size}${suffix}`
|
|
16
16
|
|
|
@@ -29,6 +29,10 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
|
|
|
29
29
|
|
|
30
30
|
verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
|
|
31
31
|
|
|
32
|
+
if(priceBased.has(target)){
|
|
33
|
+
priceBased.add(keyName)
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
if(lag > 0)
|
|
33
37
|
{
|
|
34
38
|
main.lag([keyName], lag)
|
|
@@ -2,7 +2,7 @@ import { FasterSMA } from 'trading-signals';
|
|
|
2
2
|
// Variable Index Dynamic Average (VIDYA) indicator with volume stats
|
|
3
3
|
|
|
4
4
|
export const vidya = (main, index, size, momentum, options = {}) => {
|
|
5
|
-
const { verticalOhlcv, instances, len } = main;
|
|
5
|
+
const { verticalOhlcv, instances, len, priceBased } = main;
|
|
6
6
|
const { target, lag, atrLength = 200, bandDistance = 2, liquidityLookback = 5 } = options
|
|
7
7
|
const indicatorKey = `${size}_${momentum}`;
|
|
8
8
|
|
|
@@ -23,6 +23,12 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
23
23
|
liquidity: `${prefix}_liquidity`,
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const priceBasedVidyaKeys = [keyBase, keys.upper, keys.lower, keys.liquidity]
|
|
27
|
+
|
|
28
|
+
for(const k of priceBasedVidyaKeys) {
|
|
29
|
+
priceBased.add(k)
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
// Prepare instances container
|
|
27
33
|
if (!instances.vidya) instances.vidya = { settings: {} };
|
|
28
34
|
instances.vidya.settings[indicatorKey] = {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const volumeDelta = (main, index, { lag = 0 }) => {
|
|
2
|
+
const { verticalOhlcv, instances, len } = main;
|
|
3
|
+
const groupName = 'volume_delta';
|
|
4
|
+
|
|
5
|
+
if (index === 0) {
|
|
6
|
+
if (!instances[groupName]) instances[groupName] = { isBuyVolume: true, cross: 0 };
|
|
7
|
+
|
|
8
|
+
const keys = [
|
|
9
|
+
`${groupName}_open`,
|
|
10
|
+
`${groupName}_high`,
|
|
11
|
+
`${groupName}_low`,
|
|
12
|
+
`${groupName}_close`,
|
|
13
|
+
`${groupName}_cross`,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// allocate outputs
|
|
17
|
+
for (const k of keys) verticalOhlcv[k] = new Float64Array(len).fill(NaN);
|
|
18
|
+
|
|
19
|
+
// optional lag
|
|
20
|
+
if (lag > 0) main.lag(keys, lag);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const inst = instances[groupName];
|
|
24
|
+
|
|
25
|
+
// inputs
|
|
26
|
+
const vol = verticalOhlcv.volume[index];
|
|
27
|
+
const open = verticalOhlcv.open[index];
|
|
28
|
+
const close = verticalOhlcv.close[index];
|
|
29
|
+
const prevClose = index > 0 ? verticalOhlcv.close[index - 1] : NaN;
|
|
30
|
+
|
|
31
|
+
// TradingView tie-breaks:
|
|
32
|
+
// 1) close>open -> buy, 2) close<open -> sell,
|
|
33
|
+
// 3) equal: compare to prev close, 4) still equal: carry prior state
|
|
34
|
+
let isBuy = inst.isBuyVolume;
|
|
35
|
+
if (close > open) isBuy = true;
|
|
36
|
+
else if (close < open) isBuy = false;
|
|
37
|
+
else if (close > prevClose) isBuy = true;
|
|
38
|
+
else if (close < prevClose) isBuy = false;
|
|
39
|
+
|
|
40
|
+
inst.isBuyVolume = isBuy;
|
|
41
|
+
|
|
42
|
+
if(index < 1)
|
|
43
|
+
{
|
|
44
|
+
if(isBuy) inst.cross = 1
|
|
45
|
+
else inst.cross = -1
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if(isBuy)
|
|
49
|
+
{
|
|
50
|
+
if(inst.cross > 0) inst.cross = inst.cross + 1
|
|
51
|
+
else inst.cross = 1 //reset
|
|
52
|
+
} else {
|
|
53
|
+
if(inst.cross > 0) inst.cross = -1 //reset
|
|
54
|
+
else inst.cross = inst.cross - 1
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
const delta = Number.isFinite(vol) ? (isBuy ? vol : -vol) : NaN;
|
|
60
|
+
|
|
61
|
+
// delta "candle"
|
|
62
|
+
const openVal = Number.isNaN(delta) ? NaN : 0;
|
|
63
|
+
const closeVal = delta;
|
|
64
|
+
const highVal = Number.isNaN(delta) ? NaN : Math.max(delta, 0);
|
|
65
|
+
const lowVal = Number.isNaN(delta) ? NaN : Math.min(delta, 0);
|
|
66
|
+
|
|
67
|
+
main.pushToMain({ index, key: `${groupName}_open`, value: openVal });
|
|
68
|
+
main.pushToMain({ index, key: `${groupName}_high`, value: highVal });
|
|
69
|
+
main.pushToMain({ index, key: `${groupName}_low`, value: lowVal });
|
|
70
|
+
main.pushToMain({ index, key: `${groupName}_close`, value: closeVal });
|
|
71
|
+
main.pushToMain({ index, key: `${groupName}_cross`, value: inst.cross });
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { oneHotEncode } from "../machine-learning/ml-utilities.js"
|
|
2
2
|
import { buildArray } from "../utilities/assignTypes.js"
|
|
3
|
+
import { addPrecisionAsNumber } from "../utilities/precisionMultiplier.js"
|
|
3
4
|
const eq = (fast, slow) => fast === slow
|
|
4
5
|
const gt = (fast, slow) => fast > slow
|
|
5
6
|
const lt = (fast, slow) => fast < slow
|
|
@@ -132,9 +133,9 @@ class CrossInstance {
|
|
|
132
133
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
export const crossPairs = (main, index, crossPairsList) => {
|
|
136
|
+
export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
136
137
|
|
|
137
|
-
const {verticalOhlcv, verticalOhlcvTempCols, instances, len} = main
|
|
138
|
+
const {verticalOhlcv, verticalOhlcvTempCols, instances, len, precision, precisionMultiplier, priceBased} = main
|
|
138
139
|
|
|
139
140
|
for (const { fast, slow } of crossPairsList) {
|
|
140
141
|
|
|
@@ -147,7 +148,15 @@ export const crossPairs = (main, index, crossPairsList) => {
|
|
|
147
148
|
|
|
148
149
|
const col = slow.toString()
|
|
149
150
|
verticalOhlcvTempCols.add(col)
|
|
150
|
-
|
|
151
|
+
|
|
152
|
+
const requiresPrecision = precision && (fast === 'price' || priceBased.has(fast))
|
|
153
|
+
|
|
154
|
+
const slowVal = requiresPrecision
|
|
155
|
+
? addPrecisionAsNumber(col, precisionMultiplier)
|
|
156
|
+
: slow
|
|
157
|
+
|
|
158
|
+
const numArrType = (slow >= -125 && slow <= 125 && requiresPrecision === false) ? 'Int8Array' : 'Float64Array'
|
|
159
|
+
verticalOhlcv[col] = buildArray(numArrType, len, slowVal)
|
|
151
160
|
}
|
|
152
161
|
|
|
153
162
|
if(!instances.hasOwnProperty('crossPairs'))
|
|
@@ -162,10 +171,15 @@ export const crossPairs = (main, index, crossPairsList) => {
|
|
|
162
171
|
max: -Infinity
|
|
163
172
|
}
|
|
164
173
|
|
|
165
|
-
const crossArrType = '
|
|
174
|
+
const crossArrType = (limit !== null && limit <= 125) ? 'Int8Array' : 'Float64Array'
|
|
166
175
|
|
|
167
176
|
verticalOhlcv[crossName] = buildArray(crossArrType, len, 0)
|
|
168
177
|
|
|
178
|
+
if(oneHot)
|
|
179
|
+
{
|
|
180
|
+
verticalOhlcv[`one_hot_${crossName}`] = buildArray('Array', len, null)
|
|
181
|
+
}
|
|
182
|
+
|
|
169
183
|
} else if(index + 1 === len) {
|
|
170
184
|
// sanity checks
|
|
171
185
|
if (fast !== "price" && !verticalOhlcv.hasOwnProperty(fast)) {
|
|
@@ -197,9 +211,21 @@ export const crossPairs = (main, index, crossPairsList) => {
|
|
|
197
211
|
run.update(index, { fast: fastVal, slow: slowVal })
|
|
198
212
|
}
|
|
199
213
|
|
|
200
|
-
const
|
|
214
|
+
const rawValue = run.getResult()
|
|
215
|
+
const value = (limit !== null) ? Math.min(Math.max(rawValue, -limit), limit) : rawValue
|
|
201
216
|
main.pushToMain({index, key: crossName, value})
|
|
202
217
|
|
|
218
|
+
if(oneHot)
|
|
219
|
+
{
|
|
220
|
+
const oneHotIdx = value + limit;
|
|
221
|
+
const oneHotSize = 2 * limit + 1;
|
|
222
|
+
main.pushToMain({
|
|
223
|
+
index,
|
|
224
|
+
key: `one_hot_${crossName}`,
|
|
225
|
+
value: oneHotEncode(oneHotIdx, oneHotSize)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
203
229
|
}
|
|
204
230
|
|
|
205
231
|
return true
|
package/src/studies/lag.js
CHANGED
|
@@ -4,32 +4,37 @@ export const lag = (main, index, colKeys, lookback) => {
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
const { verticalOhlcv, len } = main
|
|
7
|
+
const { verticalOhlcv, len, priceBased } = main
|
|
8
8
|
|
|
9
9
|
if(index === 0)
|
|
10
10
|
{
|
|
11
|
-
for (const
|
|
12
|
-
if (!verticalOhlcv.hasOwnProperty(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
for (const targetKey of colKeys) {
|
|
12
|
+
if (!verticalOhlcv.hasOwnProperty(targetKey)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Lag processing invoked by col "${targetKey}" was not found in "verticalOhlcv".`
|
|
15
|
+
)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const addToPriceBased = priceBased.has(targetKey)
|
|
19
|
+
|
|
18
20
|
for (let step = 1; step <= lookback; step++) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const key = `${targetKey}_lag_${step}`
|
|
22
|
+
|
|
23
|
+
if(addToPriceBased) priceBased.add(key)
|
|
24
|
+
|
|
25
|
+
const thisArrType = getArrayType(targetKey, verticalOhlcv[targetKey])
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
verticalOhlcv[key] = buildArray(thisArrType, len)
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
for (const
|
|
28
|
-
const currentColumn = verticalOhlcv[
|
|
32
|
+
for (const targetKey of colKeys) {
|
|
33
|
+
const currentColumn = verticalOhlcv[targetKey]
|
|
29
34
|
|
|
30
35
|
// Populate the lagged values each tick:
|
|
31
36
|
for (let step = 1; step <= lookback; step++) {
|
|
32
|
-
const key = `${
|
|
37
|
+
const key = `${targetKey}_lag_${step}`
|
|
33
38
|
const laggedIndex = index - step
|
|
34
39
|
const value =
|
|
35
40
|
laggedIndex < 0 || currentColumn[laggedIndex] === undefined
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { classifyNum } from "./numberUtilities.js";
|
|
2
2
|
import { selectDateFormatter } from "./dateUtilities.js";
|
|
3
3
|
|
|
4
|
-
const numberKeys = new Set(['open', 'high', 'low', 'close'])
|
|
5
|
-
|
|
6
4
|
export const buildArray = (arrayType, len, fallbackValue) => {
|
|
7
5
|
const ctorMap = {
|
|
8
6
|
Array: Array,
|
|
@@ -35,7 +33,7 @@ export const buildArray = (arrayType, len, fallbackValue) => {
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
export const assignTypes = main => {
|
|
38
|
-
const {firstRow} = main
|
|
36
|
+
const {firstRow, precision, initialPriceBased} = main
|
|
39
37
|
const inputTypes = {}
|
|
40
38
|
const arrayTypes = {}
|
|
41
39
|
|
|
@@ -50,12 +48,12 @@ export const assignTypes = main => {
|
|
|
50
48
|
inputTypes[colName] = classifyNum(cellValue, true)
|
|
51
49
|
arrayTypes[colName] = 'Int32Array'
|
|
52
50
|
}
|
|
53
|
-
else if(
|
|
51
|
+
else if(initialPriceBased.has(colName))
|
|
54
52
|
{
|
|
55
|
-
inputTypes[colName] = classifyNum(cellValue, true)
|
|
53
|
+
inputTypes[colName] = classifyNum(cellValue, true, precision)
|
|
56
54
|
arrayTypes[colName] = 'Float64Array'
|
|
57
55
|
} else {
|
|
58
|
-
const thisType = classifyNum(cellValue, false)
|
|
56
|
+
const thisType = classifyNum(cellValue, false, precision)
|
|
59
57
|
inputTypes[colName] = thisType
|
|
60
58
|
arrayTypes[colName] = (thisType === 'number') ? 'Float64Array' : 'Array'
|
|
61
59
|
}
|
|
@@ -1,52 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
export const sanitizeDirtyNumberString = str => str.replace(/(?!^-)[^0-9.]/g, '');
|
|
1
|
+
import { addPrecisionAsNumber, revertPrecisionAsNumber } from "./precisionMultiplier.js";
|
|
3
2
|
|
|
4
|
-
export const
|
|
3
|
+
export const inputNumberFormatter = {
|
|
5
4
|
number: n => n,
|
|
6
5
|
numberCleanString: n => Number(n),
|
|
7
|
-
|
|
6
|
+
precisionNumberCleanString: (strNum, multiplier) => addPrecisionAsNumber(strNum, multiplier)
|
|
8
7
|
}
|
|
9
8
|
|
|
9
|
+
export const outputNumberFormatter = {
|
|
10
|
+
number: n => n,
|
|
11
|
+
numberCleanString: n => n,
|
|
12
|
+
precisionNumberCleanString: (strNum, multiplier) => revertPrecisionAsNumber(strNum, multiplier)
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
export const classifyNum = (num, throwError = false) => {
|
|
12
|
-
// 1) true numbers
|
|
13
|
-
if (typeof num === 'number' && !Number.isNaN(num)) {
|
|
14
|
-
return 'number';
|
|
15
|
-
}
|
|
15
|
+
export const classifyNum = (num, throwError = false, precision = false) => {
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const cleanRe = /^-?\d+(\.\d+)?$/;
|
|
18
|
+
const isNumber = typeof num === 'number' && !Number.isNaN(num)
|
|
19
|
+
const isString = typeof num === 'string'
|
|
20
|
+
const isCleanString = isString && cleanRe.test(num)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
if
|
|
24
|
-
return 'numberCleanString';
|
|
25
|
-
}
|
|
22
|
+
if(precision === true) {
|
|
23
|
+
if(isCleanString) return 'precisionNumberCleanString'
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const stripped = str.replace(/[^0-9.]/g, '');
|
|
30
|
-
if (stripped && cleanRe.test(stripped)) {
|
|
31
|
-
return 'numberDirtyString';
|
|
32
|
-
}
|
|
25
|
+
if(throwError) throw new Error('Invalid number forma')
|
|
26
|
+
return typeof num
|
|
33
27
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
throw new TypeError(
|
|
39
|
-
'Invalid input: expected a number or numeric string'
|
|
40
|
-
);
|
|
28
|
+
else {
|
|
29
|
+
if(isNumber) return 'number'
|
|
30
|
+
else if(isCleanString) return 'numberCleanString'
|
|
41
31
|
}
|
|
42
32
|
|
|
33
|
+
if(throwError) throw new TypeError('Invalid input: expected a number or numeric string')
|
|
43
34
|
return typeof num
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export const roundDecimalPlaces = (value, decimals) => {
|
|
48
|
-
|
|
49
|
-
const factor = 10 ** decimals
|
|
50
|
-
// add a tiny epsilon to mitigate floating-point quirks around .5
|
|
51
|
-
return Math.round((value + Number.EPSILON) * factor) / factor
|
|
52
|
-
};
|
|
35
|
+
}
|