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.
@@ -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
+ };
@@ -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 = `rsi_${size}${suffix}`;
10
- const rsiSmaKey = `rsi_sma_${size}${suffix}`;
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 { crossPairsList, len, arrayTypes } = main;
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
- const keyNames = [rsiKey, rsiSmaKey]
36
+ if (lag > 0) {
37
+ main.lag(baseKeys, lag);
38
+ }
36
39
 
37
- if(lag > 0)
38
- {
39
- main.lag(keyNames, lag)
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 {crossPairsList, len, arrayTypes} = main
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
  {
@@ -1,96 +1,89 @@
1
- export const dateTime = (main, index, {lag}) => {
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
- prevDateStr: verticalOhlcv.date[0],
13
- sessionDailyIndexCount: 0,
14
- sessionIntradayIndexCount: 0,
15
- cachedDayInfo: verticalOhlcv.date[0]
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 = ['day_of_the_week', 'day_of_the_month', 'week_of_the_month', 'minute', 'hour', 'month', 'year', 'session_daily_index', 'session_intraday_index']
29
+ const { colKeys } = instances.dateTime
20
30
 
21
- for(const key of colKeys)
22
- {
23
- arrayTypes[key] = 'Int32Array'
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
- Object.assign(verticalOhlcv, {...Object.fromEntries(colKeys.map(k => [k, new Int32Array(len).fill(NaN)]))})
41
+ // 2) allocate and fill the backing array
42
+ verticalOhlcv[key] = new ctor(len).fill(fillVal)
27
43
 
28
- if(lag > 0)
29
- {
30
- main.lag(colKeys, lag)
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 currDateStr = currDate
49
-
50
- if(currDateStr !== instances.dateTime.prevDateStr)
60
+ for(const [key, value] of Object.entries(dateInfo))
51
61
  {
52
- instances.dateTime.prevDateStr = currDateStr
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
- instances.dateTime.sessionIntradayIndexCount++
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
- const {verticalOhlcv, instances, len, arrayTypes, verticalOhlcvTempCols} = main
137
+ const {verticalOhlcv, verticalOhlcvTempCols, instances, len, arrayTypes, notNumberKeys} = main
136
138
 
137
- if(index === 0)
138
- {
139
- const {inputParams} = main
139
+ for (const { fast, slow } of crossPairsList) {
140
140
 
141
- instances.crossPairsList = inputParams.reduce((acc, { key, params }) => {
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
- for (const { fast, slow } of crossPairsList)
143
+ if(index === 0)
156
144
  {
157
- if (fast == null || slow == null) continue;
145
+ // allow numeric 'slow' as a constant column
146
+ if (typeof slow === 'number') {
158
147
 
159
- let crossName = `${fast}_x_${slow}`
148
+ const col = slow.toString()
149
+ verticalOhlcvTempCols.add(col)
150
+ notNumberKeys.add(col)
160
151
 
161
- if(index === 0)
162
- {
163
- if(typeof slow === 'number')
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
- verticalOhlcvTempCols.add(slow.toString())
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
- instances[crossName] = new CrossInstance()
174
-
175
- verticalOhlcv[crossName] = new Int32Array(len).fill(NaN)
166
+ if(!instances.hasOwnProperty('crossPairs'))
167
+ {
168
+ instances.crossPairs = {}
169
+ }
176
170
 
177
- arrayTypes[crossName] = 'Int32Array'
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 fastValue
181
- let closeValue
182
- let highValue
183
- let lowValue
184
- let slowValue
178
+ let crossArrType = 'Int16Array'
185
179
 
186
- if(fast === 'price')
180
+ if(limit <= 125)
187
181
  {
188
- closeValue = verticalOhlcv.close[index]
189
- highValue = verticalOhlcv.high[index]
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
- } else
185
+ verticalOhlcv[crossName] = buildArray(crossArrType, len)
186
+
187
+ if(oneHot)
196
188
  {
197
- fastValue = verticalOhlcv[fast][index]
198
- slowValue = verticalOhlcv[slow][index]
189
+ verticalOhlcv[`one_hot_${crossName}`] = buildArray('Array', len)
190
+ notNumberKeys.add(`one_hot_${crossName}`)
191
+ }
199
192
 
200
- instances[crossName].update(index, {fast: fastValue, slow: slowValue})
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
- main.pushToMain({index, key: crossName, value: instances[crossName].getResult()})
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
  }