ohlcv-indicators 3.5.4 → 3.5.42
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 +533 -67
- package/package.json +11 -2
- package/src/core-functions/mainLoop.js +70 -16
- package/src/core-functions/pushToMain.js +28 -7
- package/src/machine-learning/classifier.js +267 -0
- package/src/machine-learning/ml-config.js +219 -0
- package/src/machine-learning/ml-utilities.js +148 -0
- package/src/machine-learning/pca.js +72 -79
- package/src/machine-learning/regressor.js +227 -0
- package/src/machine-learning/scaler.js +97 -64
- package/src/machine-learning/trainX.js +73 -0
- package/src/moving-averages/bollingerBands.js +30 -31
- package/src/moving-averages/donchianChannel.js +6 -4
- package/src/moving-averages/macd.js +3 -7
- package/src/moving-averages/movingAverages.js +30 -33
- package/src/moving-averages/relativeVolume.js +2 -0
- package/src/moving-averages/vidya.js +181 -0
- package/src/oscillators/rsi.js +26 -15
- package/src/oscillators/stochastic.js +74 -0
- package/src/oscillators/volumeOscillator.js +3 -2
- package/src/studies/dateTime.js +58 -65
- package/src/studies/findCrosses.js +88 -50
- package/src/studies/lag.js +37 -70
- package/src/studies/mapCols.js +47 -0
- package/src/utilities/assignTypes.js +28 -4
- package/src/utilities/numberUtilities.js +0 -26
- package/src/utilities/order.js +51 -0
- package/src/utilities/precisionMultiplier.js +3 -4
- package/src/utilities/validators.js +7 -0
- package/src/utilities/verticalToHorizontal.js +2 -2
- package/src/volatility/atr.js +2 -7
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { FasterSMA } from 'trading-signals';
|
|
2
|
+
// Variable Index Dynamic Average (VIDYA) indicator with volume stats
|
|
3
|
+
|
|
4
|
+
export const vidya = (main, index, size, momentum, options = {}) => {
|
|
5
|
+
const { verticalOhlcv, instances, len, arrayTypes, lag } = main;
|
|
6
|
+
const { target = 'close', atrLength = 200, bandDistance = 2, liquidityLookback = 20 } = options
|
|
7
|
+
const indicatorKey = `${size}_${momentum}`;
|
|
8
|
+
|
|
9
|
+
// Initialization on the first bar
|
|
10
|
+
if (index === 0) {
|
|
11
|
+
if (!(target in verticalOhlcv)) {
|
|
12
|
+
throw new Error(`VIDYA could not find target "${target}"`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const prefix = `vidya_${indicatorKey}`;
|
|
16
|
+
const keyBase = target === 'close' ? prefix : `${prefix}_${target}`;
|
|
17
|
+
const keys = {
|
|
18
|
+
vidya: keyBase,
|
|
19
|
+
upper: `${prefix}_upper`,
|
|
20
|
+
lower: `${prefix}_lower`,
|
|
21
|
+
delta_vol: `${prefix}_delta_vol`,
|
|
22
|
+
cross: `${prefix}_cross`,
|
|
23
|
+
liquidity: `${prefix}_liquidity`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Prepare instances container
|
|
27
|
+
if (!instances.vidya) instances.vidya = { settings: {} };
|
|
28
|
+
instances.vidya.settings[indicatorKey] = {
|
|
29
|
+
momentumBuffer: [], prevVidya: NaN,
|
|
30
|
+
sma: new FasterSMA(15), atrBuffer: [],
|
|
31
|
+
isTrendUp: false, upVol: 0, downVol: 0,
|
|
32
|
+
liquidity: {
|
|
33
|
+
high: [],
|
|
34
|
+
low: []
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Allocate output arrays
|
|
39
|
+
Object.values(keys).forEach(key => {
|
|
40
|
+
verticalOhlcv[key] = new Float64Array(len).fill(NaN);
|
|
41
|
+
arrayTypes[key] = 'Float64Array';
|
|
42
|
+
});
|
|
43
|
+
if (lag > 0) main.lag(Object.values(keys), lag);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Per-bar computation
|
|
47
|
+
const inst = instances.vidya.settings[indicatorKey];
|
|
48
|
+
const prefix = `vidya_${indicatorKey}`;
|
|
49
|
+
const keyBase = target === 'close' ? prefix : `${prefix}_${target}`;
|
|
50
|
+
const keys = {
|
|
51
|
+
vidya: keyBase,
|
|
52
|
+
upper: `${prefix}_upper`,
|
|
53
|
+
lower: `${prefix}_lower`,
|
|
54
|
+
delta_vol: `${prefix}_delta_vol`,
|
|
55
|
+
cross: `${prefix}_cross`,
|
|
56
|
+
liquidity: `${prefix}_liquidity`,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Series inputs
|
|
60
|
+
const price = verticalOhlcv[target][index];
|
|
61
|
+
const high = verticalOhlcv.high[index];
|
|
62
|
+
const low = verticalOhlcv.low[index];
|
|
63
|
+
const close = verticalOhlcv.close[index];
|
|
64
|
+
const open = verticalOhlcv.open[index];
|
|
65
|
+
const vol = verticalOhlcv.volume[index];
|
|
66
|
+
const prevCross = index > 0 ? verticalOhlcv[keys.cross][index - 1] : 0;
|
|
67
|
+
|
|
68
|
+
const highest = (inst.liquidity.high.length === liquidityLookback) ? Math.max(...inst.liquidity.high) : NaN
|
|
69
|
+
const lowest = (inst.liquidity.low.length === liquidityLookback) ? Math.min(...inst.liquidity.low) : NaN
|
|
70
|
+
|
|
71
|
+
// Compute ATR (Wilder's RMA)
|
|
72
|
+
if (index > 0) {
|
|
73
|
+
const prevClose = verticalOhlcv.close[index - 1];
|
|
74
|
+
const tr = Math.max(
|
|
75
|
+
high - low,
|
|
76
|
+
Math.abs(high - prevClose),
|
|
77
|
+
Math.abs(low - prevClose)
|
|
78
|
+
);
|
|
79
|
+
inst.atrBuffer.push(tr);
|
|
80
|
+
if (inst.atrBuffer.length > atrLength) inst.atrBuffer.shift();
|
|
81
|
+
}
|
|
82
|
+
const atr = inst.atrBuffer.length === atrLength
|
|
83
|
+
? inst.atrBuffer.reduce((a, b) => a + b, 0) / atrLength
|
|
84
|
+
: NaN;
|
|
85
|
+
|
|
86
|
+
// Compute raw VIDYA
|
|
87
|
+
let rawVidya = NaN;
|
|
88
|
+
let mom = NaN;
|
|
89
|
+
if (index > 0) mom = price - verticalOhlcv[target][index - 1];
|
|
90
|
+
inst.momentumBuffer.push(mom);
|
|
91
|
+
if (inst.momentumBuffer.length > momentum) inst.momentumBuffer.shift();
|
|
92
|
+
if (inst.momentumBuffer.length === momentum) {
|
|
93
|
+
let sumPos = 0, sumNeg = 0;
|
|
94
|
+
inst.momentumBuffer.forEach(m => m >= 0 ? sumPos += m : sumNeg += -m);
|
|
95
|
+
const denom = sumPos + sumNeg;
|
|
96
|
+
const cmo = denom === 0 ? 0 : Math.abs((sumPos - sumNeg) / denom) * 100;
|
|
97
|
+
const alpha = 2 / (size + 1);
|
|
98
|
+
const a = alpha * (cmo / 100);
|
|
99
|
+
rawVidya = isNaN(inst.prevVidya) ? price : (a * price + (1 - a) * inst.prevVidya);
|
|
100
|
+
inst.prevVidya = rawVidya;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Smooth VIDYA using fixed 15-bar SMA
|
|
104
|
+
inst.sma.update(rawVidya);
|
|
105
|
+
let smoothedVidya = NaN;
|
|
106
|
+
try { smoothedVidya = inst.sma.getResult(); } catch {}
|
|
107
|
+
|
|
108
|
+
// Bands
|
|
109
|
+
const upper = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
110
|
+
? smoothedVidya + atr * bandDistance
|
|
111
|
+
: NaN;
|
|
112
|
+
const lower = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
113
|
+
? smoothedVidya - atr * bandDistance
|
|
114
|
+
: NaN;
|
|
115
|
+
|
|
116
|
+
// Detect crosses
|
|
117
|
+
const prevPrice = index > 0 ? verticalOhlcv[target][index - 1] : NaN;
|
|
118
|
+
const prevUpper = index > 0 ? verticalOhlcv[keys.upper][index - 1] : NaN;
|
|
119
|
+
const prevLower = index > 0 ? verticalOhlcv[keys.lower][index - 1] : NaN;
|
|
120
|
+
const crossUp = index > 0 && prevPrice <= prevUpper && price > upper;
|
|
121
|
+
const crossDown = index > 0 && prevPrice >= prevLower && price < lower;
|
|
122
|
+
|
|
123
|
+
// Update trend state
|
|
124
|
+
if (crossUp) inst.isTrendUp = true;
|
|
125
|
+
if (crossDown) inst.isTrendUp = false;
|
|
126
|
+
|
|
127
|
+
// Cumulative cross counter: positive when uptrend, negative when downtrend
|
|
128
|
+
let cross = 0;
|
|
129
|
+
if (index > 0) {
|
|
130
|
+
if (inst.isTrendUp) {
|
|
131
|
+
cross = prevCross > 0 ? prevCross + 1 : 1;
|
|
132
|
+
} else {
|
|
133
|
+
cross = prevCross < 0 ? prevCross - 1 : -1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Volume accumulation
|
|
138
|
+
if (crossUp || crossDown) {
|
|
139
|
+
inst.upVol = inst.downVol = 0;
|
|
140
|
+
} else {
|
|
141
|
+
inst.upVol += (close > open ? vol : 0);
|
|
142
|
+
inst.downVol += (close < open ? vol : 0);
|
|
143
|
+
}
|
|
144
|
+
const avgVol = (inst.upVol + inst.downVol) / 2 || 1;
|
|
145
|
+
const deltaPct = (inst.upVol - inst.downVol) / avgVol;
|
|
146
|
+
|
|
147
|
+
// Trend-based boundary (reset on crossover)
|
|
148
|
+
const trendValue = (!crossUp && !crossDown)
|
|
149
|
+
? (inst.isTrendUp ? lower : upper)
|
|
150
|
+
: NaN;
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
let liquidity = NaN
|
|
154
|
+
|
|
155
|
+
if(cross > 0)
|
|
156
|
+
{
|
|
157
|
+
liquidity = lowest
|
|
158
|
+
}
|
|
159
|
+
else if(cross < 0)
|
|
160
|
+
{
|
|
161
|
+
liquidity = highest
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(keys.liquidity, liquidity)
|
|
165
|
+
|
|
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 });
|
|
173
|
+
|
|
174
|
+
inst.liquidity.high.push(high)
|
|
175
|
+
inst.liquidity.low.push(low)
|
|
176
|
+
|
|
177
|
+
if(inst.liquidity.high.length > liquidityLookback) inst.liquidity.high.shift()
|
|
178
|
+
if(inst.liquidity.low.length > liquidityLookback) inst.liquidity.low.shift()
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
};
|
package/src/oscillators/rsi.js
CHANGED
|
@@ -2,41 +2,52 @@ import { FasterRSI } from 'trading-signals';
|
|
|
2
2
|
import { FasterSMA } from 'trading-signals';
|
|
3
3
|
|
|
4
4
|
const defaultTarget = 'close'
|
|
5
|
-
export const rsi = (main, index, size, { target, lag }) => {
|
|
5
|
+
export const rsi = (main, index, size, { target, lag, parser, prefix, minmax }) => {
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
6
9
|
const { verticalOhlcv, instances } = main;
|
|
7
10
|
|
|
8
11
|
const suffix = target === defaultTarget ? '' : `_${target}`;
|
|
9
|
-
const rsiKey =
|
|
10
|
-
const rsiSmaKey =
|
|
12
|
+
const rsiKey = `${prefix}rsi_${size}${suffix}`;
|
|
13
|
+
const rsiSmaKey = `${prefix}rsi_sma_${size}${suffix}`;
|
|
11
14
|
|
|
12
15
|
// Initialization on the first index.
|
|
13
16
|
if (index === 0) {
|
|
14
17
|
|
|
15
|
-
const {
|
|
18
|
+
const {len, arrayTypes } = main;
|
|
16
19
|
|
|
17
20
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
18
21
|
throw new Error(`Target property ${target} not found in verticalOhlcv for rsi.`);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
crossPairsList.push({ fast: rsiKey, slow: rsiSmaKey, isDefault: true });
|
|
22
|
-
|
|
23
24
|
Object.assign(instances, {
|
|
24
25
|
[rsiKey]: new FasterRSI(size),
|
|
25
26
|
[rsiSmaKey]: new FasterSMA(size)
|
|
26
|
-
})
|
|
27
|
+
})
|
|
27
28
|
|
|
28
29
|
Object.assign(verticalOhlcv, {
|
|
29
30
|
[rsiKey]: new Float64Array(len).fill(NaN),
|
|
30
31
|
[rsiSmaKey]: new Float64Array(len).fill(NaN),
|
|
31
|
-
})
|
|
32
|
-
|
|
32
|
+
})
|
|
33
33
|
|
|
34
|
+
const baseKeys = [rsiKey, rsiSmaKey]
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
if (lag > 0) {
|
|
37
|
+
main.lag(baseKeys, lag);
|
|
38
|
+
}
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
//[key, key_lag_1, …, key_lag_n] for each key
|
|
41
|
+
const keyNames = lag > 0
|
|
42
|
+
? baseKeys.flatMap(key => [
|
|
43
|
+
key,
|
|
44
|
+
...Array.from({ length: lag }, (_, i) => `${key}_lag_${i + 1}`)
|
|
45
|
+
])
|
|
46
|
+
: baseKeys;
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(minmax)) {
|
|
49
|
+
const group = main.scaledGroups.minmax_rsi ??= [];
|
|
50
|
+
group.push(...keyNames);
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
for(const key of keyNames)
|
|
@@ -61,7 +72,7 @@ export const rsi = (main, index, size, { target, lag }) => {
|
|
|
61
72
|
|
|
62
73
|
|
|
63
74
|
// Always push the RSI value, using NaN as a fallback.
|
|
64
|
-
main.pushToMain({ index, key: rsiKey, value: currentRsi });
|
|
75
|
+
main.pushToMain({ index, key: rsiKey, value: (Number.isNaN(currentRsi)) ? NaN : parser(currentRsi) });
|
|
65
76
|
|
|
66
77
|
// Update the SMA indicator only if a valid RSI value is available.
|
|
67
78
|
if (!Number.isNaN(currentRsi)) {
|
|
@@ -75,5 +86,5 @@ export const rsi = (main, index, size, { target, lag }) => {
|
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
// Always push the smoothed RSI value.
|
|
78
|
-
main.pushToMain({ index, key: rsiSmaKey, value: smoothedRsi });
|
|
89
|
+
main.pushToMain({ index, key: rsiSmaKey, value: (Number.isNaN(smoothedRsi)) ? NaN : parser(smoothedRsi) });
|
|
79
90
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { FasterStochasticOscillator } from 'trading-signals';
|
|
2
|
+
|
|
3
|
+
const defaultTarget = 'close'
|
|
4
|
+
export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {minmax, prefix, lag, parser}) => {
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const { verticalOhlcv, instances, scaledGroups } = main;
|
|
9
|
+
|
|
10
|
+
const paramsKey = (kPeriod === 14 && kSlowingPeriod === 3 && dPeriod === 3) ? '' : `${kPeriod}_${kSlowingPeriod}_${dPeriod}`
|
|
11
|
+
const stochD = `${prefix}stoch_d${paramsKey}`;
|
|
12
|
+
const stochK = `${prefix}stoch_k${paramsKey}`;
|
|
13
|
+
const instanceKey = `${prefix}${paramsKey}`
|
|
14
|
+
|
|
15
|
+
// Initialization on the first index.
|
|
16
|
+
if (index === 0) {
|
|
17
|
+
|
|
18
|
+
const {len, arrayTypes } = main;
|
|
19
|
+
|
|
20
|
+
Object.assign(instances, {
|
|
21
|
+
[instanceKey]: new FasterStochasticOscillator(kPeriod, kSlowingPeriod, dPeriod)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Object.assign(verticalOhlcv, {
|
|
25
|
+
[stochD]: new Float64Array(len).fill(NaN),
|
|
26
|
+
[stochK]: new Float64Array(len).fill(NaN),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const baseKeys = [stochD, stochK]
|
|
30
|
+
|
|
31
|
+
if (lag > 0) {
|
|
32
|
+
main.lag(baseKeys, lag);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//[key, key_lag_1, …, key_lag_n] for each key
|
|
36
|
+
const keyNames = lag > 0
|
|
37
|
+
? baseKeys.flatMap(key => [
|
|
38
|
+
key,
|
|
39
|
+
...Array.from({ length: lag }, (_, i) => `${key}_lag_${i + 1}`)
|
|
40
|
+
])
|
|
41
|
+
: baseKeys;
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(minmax)) {
|
|
44
|
+
const group = main.scaledGroups.minmax_rsi ??= [];
|
|
45
|
+
group.push(...keyNames)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for(const key of keyNames)
|
|
49
|
+
{
|
|
50
|
+
arrayTypes[key] = 'Float64Array'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const close = verticalOhlcv['close'][index]
|
|
55
|
+
const low = verticalOhlcv['low'][index]
|
|
56
|
+
const high = verticalOhlcv['high'][index]
|
|
57
|
+
let stockObj = null
|
|
58
|
+
|
|
59
|
+
// Update the stochastic indicator.
|
|
60
|
+
instances[instanceKey].update({close, low, high})
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
stockObj = instances[instanceKey].getResult()
|
|
64
|
+
} catch (err) {
|
|
65
|
+
stockObj = null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const kVal = stockObj ? parser(stockObj.stochK) : NaN
|
|
69
|
+
const dVal = stockObj ? parser(stockObj.stochD) : NaN
|
|
70
|
+
|
|
71
|
+
main.pushToMain({ index, key: stochK, value: kVal })
|
|
72
|
+
main.pushToMain({ index, key: stochD, value: dVal })
|
|
73
|
+
|
|
74
|
+
}
|
|
@@ -2,13 +2,15 @@ import { FasterEMA } from 'trading-signals';
|
|
|
2
2
|
|
|
3
3
|
export const volumeOscillator = (main, index, fast, slow, {lag}) => {
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
|
|
5
7
|
const {verticalOhlcv, instances} = main
|
|
6
8
|
const value = verticalOhlcv.volume[index]
|
|
7
9
|
const key = `volume_oscillator_${fast}_${slow}`
|
|
8
10
|
|
|
9
11
|
if (index === 0) {
|
|
10
12
|
|
|
11
|
-
const {
|
|
13
|
+
const {len, arrayTypes} = main
|
|
12
14
|
Object.assign(instances, {
|
|
13
15
|
[key]: {
|
|
14
16
|
fastEMA: new FasterEMA(fast),
|
|
@@ -17,7 +19,6 @@ export const volumeOscillator = (main, index, fast, slow, {lag}) => {
|
|
|
17
19
|
})
|
|
18
20
|
|
|
19
21
|
verticalOhlcv[key] = new Float64Array(len).fill(NaN)
|
|
20
|
-
crossPairsList.push({ fast: key, slow: 0, isDefault: true })
|
|
21
22
|
|
|
22
23
|
if(lag > 0)
|
|
23
24
|
{
|
package/src/studies/dateTime.js
CHANGED
|
@@ -1,96 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
import {oneHotEncode} from '../machine-learning/ml-utilities.js'
|
|
2
|
+
|
|
3
|
+
export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const {instances, verticalOhlcv, notNumberKeys} = main
|
|
8
|
+
const {prefix} = precompute
|
|
2
9
|
|
|
3
|
-
const {instances, verticalOhlcv} = main
|
|
4
|
-
|
|
5
10
|
if(index === 0)
|
|
6
11
|
{
|
|
7
12
|
const {len, dateType, arrayTypes} = main
|
|
8
13
|
if(!dateType) throw Error('dateTime method found and invalid "date" in input ohlcv')
|
|
9
14
|
|
|
15
|
+
const startYear = verticalOhlcv.date[0].getUTCFullYear()
|
|
16
|
+
const currentYear = new Date().getUTCFullYear()
|
|
17
|
+
|
|
10
18
|
Object.assign(instances, {
|
|
11
19
|
dateTime: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
colKeys: [...precompute.colKeys, `${prefix}year`],
|
|
21
|
+
colKeySizes: {
|
|
22
|
+
...precompute.colKeySizes,
|
|
23
|
+
[`${prefix}year`]: (currentYear - startYear) + 1
|
|
24
|
+
}
|
|
25
|
+
,startYear
|
|
16
26
|
}
|
|
17
27
|
})
|
|
18
28
|
|
|
19
|
-
const colKeys =
|
|
29
|
+
const { colKeys } = instances.dateTime
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
// choose your ctor, fill-value and type-name once
|
|
32
|
+
const ctor = oneHot ? Array : Int16Array
|
|
33
|
+
const fillVal = oneHot ? null : NaN
|
|
34
|
+
const typeName = oneHot ? 'Array' : 'Int16Array'
|
|
35
|
+
|
|
36
|
+
// single loop instead of three
|
|
37
|
+
for (const key of colKeys) {
|
|
38
|
+
// 1) set the arrayType
|
|
39
|
+
arrayTypes[key] = typeName
|
|
25
40
|
|
|
26
|
-
|
|
41
|
+
// 2) allocate and fill the backing array
|
|
42
|
+
verticalOhlcv[key] = new ctor(len).fill(fillVal)
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
// 3) mark as non-numeric
|
|
45
|
+
notNumberKeys.add(key)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// finally, apply lag once
|
|
49
|
+
if (lag > 0) {
|
|
50
|
+
main.lag(colKeys, lag)
|
|
31
51
|
}
|
|
32
52
|
}
|
|
33
53
|
|
|
54
|
+
const {colKeySizes, startYear} = instances.dateTime
|
|
34
55
|
|
|
35
56
|
const currDate = verticalOhlcv.date[index]
|
|
36
57
|
|
|
37
|
-
const
|
|
38
|
-
day_of_the_week,
|
|
39
|
-
day_of_the_month,
|
|
40
|
-
week_of_the_month,
|
|
41
|
-
month,
|
|
42
|
-
year,
|
|
43
|
-
hour,
|
|
44
|
-
minute
|
|
45
|
-
} = getDateInfo(currDate)
|
|
46
|
-
|
|
58
|
+
const dateInfo = getDateInfo(currDate, oneHot, colKeySizes, prefix, startYear)
|
|
47
59
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
if(currDateStr !== instances.dateTime.prevDateStr)
|
|
60
|
+
for(const [key, value] of Object.entries(dateInfo))
|
|
51
61
|
{
|
|
52
|
-
|
|
53
|
-
instances.dateTime.sessionDailyIndexCount++
|
|
54
|
-
instances.dateTime.sessionIntradayIndexCount = 0
|
|
62
|
+
main.pushToMain({index, key, value})
|
|
55
63
|
}
|
|
64
|
+
}
|
|
56
65
|
|
|
57
|
-
|
|
58
66
|
|
|
59
|
-
main.pushToMain({index, key: 'session_daily_index', value: instances.dateTime.sessionDailyIndexCount})
|
|
60
|
-
main.pushToMain({index, key: 'session_intraday_index', value: instances.dateTime.sessionIntradayIndexCount})
|
|
61
|
-
main.pushToMain({index, key: 'day_of_the_week', value: day_of_the_week})
|
|
62
|
-
main.pushToMain({index, key: 'day_of_the_month', value: day_of_the_month})
|
|
63
|
-
main.pushToMain({index, key: 'week_of_the_month', value: week_of_the_month})
|
|
64
|
-
main.pushToMain({index, key: 'month', value: month})
|
|
65
|
-
main.pushToMain({index, key: 'year', value: year})
|
|
66
|
-
main.pushToMain({index, key: 'hour', value: hour})
|
|
67
|
-
main.pushToMain({index, key: 'minute', value: minute})
|
|
68
67
|
|
|
68
|
+
const getDateInfo = (date, oneHot, colKeySizes, prefix, startYear) => {
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const encode = (value, size) => (oneHot) ? oneHotEncode(value, size) : value
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
const output = {
|
|
73
|
+
[`${prefix}year`]: date.getUTCFullYear(),
|
|
74
|
+
[`${prefix}month`]: date.getUTCMonth(),
|
|
75
|
+
[`${prefix}hour`]: date.getUTCHours(),
|
|
76
|
+
[`${prefix}minute`]: date.getUTCMinutes(),
|
|
77
|
+
[`${prefix}day_of_the_week`]: date.getUTCDay(),
|
|
78
|
+
[`${prefix}day_of_the_month`]: date.getUTCDate() - 1,
|
|
79
|
+
}
|
|
73
80
|
|
|
81
|
+
for(const [key, size] of Object.entries(colKeySizes))
|
|
82
|
+
{
|
|
83
|
+
const value = (key === `${prefix}year`) ? output[key] - startYear: output[key]
|
|
84
|
+
output[key] = encode(value, size)
|
|
85
|
+
}
|
|
74
86
|
|
|
87
|
+
return output
|
|
88
|
+
}
|
|
75
89
|
|
|
76
|
-
const getDateInfo = date => {
|
|
77
|
-
const month = date.getMonth()
|
|
78
|
-
const year = date.getFullYear()
|
|
79
|
-
const hour = date.getHours()
|
|
80
|
-
const minute = date.getMinutes()
|
|
81
|
-
const day_of_the_week = date.getDay();
|
|
82
|
-
const day_of_the_month = date.getDate();
|
|
83
|
-
const firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1)
|
|
84
|
-
const dayOffset = firstDayOfMonth.getDay();
|
|
85
|
-
const week_of_the_month = Math.ceil((day_of_the_month + dayOffset) / 7)
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
month,
|
|
89
|
-
year,
|
|
90
|
-
hour,
|
|
91
|
-
minute,
|
|
92
|
-
day_of_the_week,
|
|
93
|
-
day_of_the_month,
|
|
94
|
-
week_of_the_month,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { oneHotEncode } from "../machine-learning/ml-utilities.js"
|
|
2
|
+
import { buildArray } from "../utilities/assignTypes.js"
|
|
1
3
|
const eq = (fast, slow) => fast === slow
|
|
2
4
|
const gt = (fast, slow) => fast > slow
|
|
3
5
|
const lt = (fast, slow) => fast < slow
|
|
@@ -130,77 +132,113 @@ class CrossInstance {
|
|
|
130
132
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
export const crossPairs = (main, index) => {
|
|
135
|
+
export const crossPairs = (main, index, crossPairsList, {limit, oneHot}) => {
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
const {verticalOhlcv, verticalOhlcvTempCols, instances, len, arrayTypes, notNumberKeys} = main
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
const {inputParams} = main
|
|
139
|
+
for (const { fast, slow } of crossPairsList) {
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
if (key === 'crossPairs') {
|
|
143
|
-
for (const group of params) {
|
|
144
|
-
for (const pair of group) {
|
|
145
|
-
acc.push(pair)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return acc;
|
|
150
|
-
}, [])
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const {crossPairsList} = instances
|
|
141
|
+
const crossName = `${fast}_x_${slow}`
|
|
154
142
|
|
|
155
|
-
|
|
143
|
+
if(index === 0)
|
|
156
144
|
{
|
|
157
|
-
|
|
145
|
+
// allow numeric 'slow' as a constant column
|
|
146
|
+
if (typeof slow === 'number') {
|
|
158
147
|
|
|
159
|
-
|
|
148
|
+
const col = slow.toString()
|
|
149
|
+
verticalOhlcvTempCols.add(col)
|
|
150
|
+
notNumberKeys.add(col)
|
|
160
151
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if(
|
|
152
|
+
let slowNumArrType = 'Int16Array'
|
|
153
|
+
|
|
154
|
+
if(slow === 0 || slow === 1)
|
|
155
|
+
{
|
|
156
|
+
slowNumArrType = 'Uint8Array'
|
|
157
|
+
} else if(slow >= -125 && slow <= 125)
|
|
164
158
|
{
|
|
165
|
-
|
|
166
|
-
verticalOhlcv[slow.toString()] = new Int32Array(len).fill(slow)
|
|
159
|
+
slowNumArrType = 'Int8Array'
|
|
167
160
|
}
|
|
168
|
-
|
|
169
|
-
if(fast !== 'price' && !verticalOhlcv.hasOwnProperty(fast)) throw Error(`fast "${fast} not found in crossPairs"`)
|
|
170
|
-
if(!verticalOhlcv.hasOwnProperty(slow)) throw Error(`slow "${slow} not found in crossPairs"`)
|
|
171
161
|
|
|
162
|
+
verticalOhlcv[col] = buildArray(slowNumArrType, len)
|
|
163
|
+
arrayTypes[col] = 'slowNumArrType'
|
|
164
|
+
}
|
|
172
165
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
166
|
+
if(!instances.hasOwnProperty('crossPairs'))
|
|
167
|
+
{
|
|
168
|
+
instances.crossPairs = {}
|
|
169
|
+
}
|
|
176
170
|
|
|
177
|
-
|
|
171
|
+
// create instance + output buffer
|
|
172
|
+
instances.crossPairs[crossName] = {
|
|
173
|
+
run: new CrossInstance(),
|
|
174
|
+
min: Infinity,
|
|
175
|
+
max: -Infinity
|
|
178
176
|
}
|
|
179
177
|
|
|
180
|
-
let
|
|
181
|
-
let closeValue
|
|
182
|
-
let highValue
|
|
183
|
-
let lowValue
|
|
184
|
-
let slowValue
|
|
178
|
+
let crossArrType = 'Int16Array'
|
|
185
179
|
|
|
186
|
-
if(
|
|
180
|
+
if(limit <= 125)
|
|
187
181
|
{
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
lowValue = verticalOhlcv.low[index]
|
|
191
|
-
slowValue = verticalOhlcv[slow][index]
|
|
192
|
-
|
|
193
|
-
instances[crossName].update(index, {fast: closeValue, slow: slowValue, high: highValue, low: lowValue})
|
|
182
|
+
crossArrType = 'Int8Array'
|
|
183
|
+
}
|
|
194
184
|
|
|
195
|
-
|
|
185
|
+
verticalOhlcv[crossName] = buildArray(crossArrType, len)
|
|
186
|
+
|
|
187
|
+
if(oneHot)
|
|
196
188
|
{
|
|
197
|
-
|
|
198
|
-
|
|
189
|
+
verticalOhlcv[`one_hot_${crossName}`] = buildArray('Array', len)
|
|
190
|
+
notNumberKeys.add(`one_hot_${crossName}`)
|
|
191
|
+
}
|
|
199
192
|
|
|
200
|
-
|
|
193
|
+
notNumberKeys.add(crossName)
|
|
194
|
+
|
|
195
|
+
arrayTypes[crossName] = 'crossArrType'
|
|
196
|
+
|
|
197
|
+
} else if(index + 1 === len) {
|
|
198
|
+
// sanity checks
|
|
199
|
+
if (fast !== "price" && !verticalOhlcv.hasOwnProperty(fast)) {
|
|
200
|
+
throw new Error(`crossPairs invalid param: fast "${fast}" not found in "verticalOhlcv": ${JSON.stringify(Object.keys(verticalOhlcv))}`)
|
|
201
|
+
}
|
|
202
|
+
if (!verticalOhlcv.hasOwnProperty(slow)) {
|
|
203
|
+
throw new Error(`crossPairs invalid param: slow "${slow}" not found in "verticalOhlcv": ${JSON.stringify(Object.keys(verticalOhlcv))}`)
|
|
201
204
|
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (fast !== "price" && !verticalOhlcv.hasOwnProperty(fast)) return
|
|
208
|
+
if (!verticalOhlcv.hasOwnProperty(slow)) return
|
|
209
|
+
|
|
210
|
+
const {run} = instances.crossPairs[crossName]
|
|
211
|
+
|
|
212
|
+
// ——— Per-bar update ———
|
|
213
|
+
if (fast === 'price') {
|
|
214
|
+
const fastVal = verticalOhlcv.close[index]
|
|
215
|
+
const slowVal = verticalOhlcv[slow][index]
|
|
216
|
+
run.update(index, {
|
|
217
|
+
fast: fastVal,
|
|
218
|
+
slow: slowVal,
|
|
219
|
+
high: verticalOhlcv.high[index],
|
|
220
|
+
low: verticalOhlcv.low[index],
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
const fastVal = verticalOhlcv[fast][index]
|
|
224
|
+
const slowVal = verticalOhlcv[slow][index]
|
|
225
|
+
run.update(index, { fast: fastVal, slow: slowVal })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const rawValue = run.getResult()
|
|
229
|
+
|
|
230
|
+
const value = Math.max(-limit, Math.min(limit, rawValue))
|
|
202
231
|
|
|
203
|
-
|
|
232
|
+
main.pushToMain({index, key: crossName, value})
|
|
233
|
+
|
|
234
|
+
if(oneHot)
|
|
235
|
+
{
|
|
236
|
+
const oneHotIdx = value + limit
|
|
237
|
+
const oneHotSize = 2 * limit + 1
|
|
238
|
+
main.pushToMain({index, key: `one_hot_${crossName}`, value: oneHotEncode(oneHotIdx, oneHotSize)})
|
|
204
239
|
}
|
|
205
240
|
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return true
|
|
206
244
|
}
|