ohlcv-indicators 3.5.78 → 3.5.80

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.
Files changed (43) hide show
  1. package/AGENTS.md +113 -0
  2. package/README.md +456 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
  5. package/index.js +112 -84
  6. package/package.json +2 -5
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +35 -30
  9. package/src/core-functions/pushToMain.js +8 -27
  10. package/src/core-indicators/README.md +60 -0
  11. package/src/core-indicators/composites.js +136 -0
  12. package/src/core-indicators/index.js +5 -0
  13. package/src/core-indicators/indicator.js +81 -0
  14. package/src/core-indicators/movingAverages.js +88 -0
  15. package/src/core-indicators/rsi.js +41 -0
  16. package/src/core-indicators/volatility.js +107 -0
  17. package/src/moving-averages/bollingerBands.js +39 -31
  18. package/src/moving-averages/donchianChannel.js +62 -36
  19. package/src/moving-averages/heikenAshi.js +33 -52
  20. package/src/moving-averages/macd.js +10 -26
  21. package/src/moving-averages/movingAverages.js +19 -25
  22. package/src/moving-averages/relativeVolume.js +30 -24
  23. package/src/oscillators/mfi.js +56 -0
  24. package/src/oscillators/rsi.js +7 -26
  25. package/src/oscillators/stochastic.js +6 -19
  26. package/src/oscillators/volumeDelta.js +36 -28
  27. package/src/oscillators/volumeOscillator.js +24 -32
  28. package/src/studies/candleFeatures.js +40 -28
  29. package/src/studies/dateTime.js +54 -56
  30. package/src/studies/findCrosses.js +18 -16
  31. package/src/studies/lag.js +32 -20
  32. package/src/utilities/assignTypes.js +1 -1
  33. package/src/utilities/config.js +31 -0
  34. package/src/utilities/numberUtilities.js +5 -3
  35. package/src/utilities/precisionMultiplier.js +11 -6
  36. package/src/utilities/validators.js +40 -1
  37. package/src/utilities/verticalToHorizontal.js +26 -16
  38. package/src/volatility/adx.js +7 -20
  39. package/src/volatility/atr.js +10 -23
  40. package/test/test.js +1230 -0
  41. package/webpack.config.js +0 -11
  42. package/src/moving-averages/vidya.js +0 -181
  43. package/src/studies/mapCols.js +0 -46
@@ -1,5 +1,7 @@
1
- import { FasterStochasticOscillator } from 'trading-signals';
1
+ import { FasterStochasticOscillator } from '../core-indicators/index.js';
2
2
  import { mathLog } from '../utilities/math.js';
3
+ import { validateInputValues } from '../utilities/validators.js';
4
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
3
5
 
4
6
  export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}) => {
5
7
 
@@ -13,39 +15,24 @@ export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag,
13
15
 
14
16
  // Initialization on the first index.
15
17
  if (index === 0) {
16
-
17
- const {len } = main;
18
+ validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'stochastic');
18
19
 
19
20
  Object.assign(instances, {
20
21
  [instanceKey]: new FasterStochasticOscillator(kPeriod, kSlowingPeriod, dPeriod)
21
22
  })
22
23
 
23
- Object.assign(verticalOhlcv, {
24
- [stochD]: new Float64Array(len).fill(NaN),
25
- [stochK]: new Float64Array(len).fill(NaN),
26
- })
27
-
28
- const baseKeys = [stochD, stochK]
29
-
30
- if (lag > 0) {
31
- main.lag(baseKeys, lag);
32
- }
24
+ initializeColumns(main, [{ key: stochD }, { key: stochK }], { lag });
33
25
 
34
26
  }
35
27
 
36
28
  const close = verticalOhlcv['close'][index]
37
29
  const low = verticalOhlcv['low'][index]
38
30
  const high = verticalOhlcv['high'][index]
39
- let stockObj = null
40
31
 
41
32
  // Update the stochastic indicator.
42
33
  instances[instanceKey].update({close, low, high})
43
34
 
44
- try {
45
- stockObj = instances[instanceKey].getResult()
46
- } catch (err) {
47
- stockObj = null
48
- }
35
+ const stockObj = instances[instanceKey].isStable ? instances[instanceKey].getResult() : null
49
36
 
50
37
  const kVal = stockObj ? (retLogs ? mathLog(stockObj.stochK, 50) : stockObj.stochK) : NaN
51
38
  const dVal = stockObj ? (retLogs ? mathLog(stockObj.stochD, 50) : stockObj.stochD) : NaN
@@ -1,29 +1,41 @@
1
+ import { isPositiveInteger } from '../utilities/numberUtilities.js';
2
+ import { validateInputValues } from '../utilities/validators.js';
3
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
4
+
5
+
1
6
  export const volumeDelta = (main, index, { lag = 0 }) => {
2
- const { verticalOhlcv, instances, len } = main;
3
- const groupName = 'volume_delta';
7
+ const { verticalOhlcv, instances } = main;
8
+ const key = 'volume_delta';
4
9
 
5
10
  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
- ];
11
+ validateInputValues({ open: true, close: true, volume: true }, verticalOhlcv, index, 'volumeDelta');
15
12
 
16
- // allocate outputs
17
- for (const k of keys) verticalOhlcv[k] = new Float64Array(len).fill(NaN);
13
+ if (!instances[key]) {
14
+ instances[key] = { isBuyVolume: true, cross: 0};
15
+ }
16
+
17
+ const keyNames = [
18
+ `${key}_high`,
19
+ `${key}_low`,
20
+ `${key}_close`,
21
+ `${key}_cross`,
22
+ ];
18
23
 
19
- // optional lag
20
- if (lag > 0) main.lag(keys, lag);
24
+ initializeColumns(main, keyNames.map(key => ({ key })), { lag });
21
25
  }
22
26
 
23
- const inst = instances[groupName];
27
+ const inst = instances[key];
28
+
29
+ const {hasInvalidInputValue} = inst
24
30
 
25
31
  // inputs
26
- const vol = verticalOhlcv.volume[index];
32
+ const volume = verticalOhlcv.volume[index];
33
+
34
+ if(volume === 0) {
35
+ return;
36
+ }
37
+
38
+
27
39
  const open = verticalOhlcv.open[index];
28
40
  const close = verticalOhlcv.close[index];
29
41
  const prevClose = index > 0 ? verticalOhlcv.close[index - 1] : NaN;
@@ -55,20 +67,16 @@ export const volumeDelta = (main, index, { lag = 0 }) => {
55
67
  }
56
68
  }
57
69
 
58
-
59
- const delta = Number.isFinite(vol) ? (isBuy ? vol : -vol) : NaN;
70
+ const delta = isBuy ? volume : -volume;
60
71
 
61
72
  // delta "candle"
62
- const openVal = Number.isNaN(delta) ? NaN : 0;
63
73
  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);
74
+ const highVal = Math.max(delta, 0);
75
+ const lowVal = Math.min(delta, 0);
66
76
 
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 });
77
+ main.pushToMain({ index, key: `${key}_high`, value: highVal });
78
+ main.pushToMain({ index, key: `${key}_low`, value: lowVal });
79
+ main.pushToMain({ index, key: `${key}_close`, value: closeVal });
80
+ main.pushToMain({ index, key: `${key}_cross`, value: inst.cross });
72
81
 
73
- return true;
74
82
  };
@@ -1,59 +1,51 @@
1
- import { FasterEMA } from 'trading-signals';
1
+ import { FasterEMA } from '../core-indicators/index.js';
2
+ import { mathLog } from '../utilities/math.js';
3
+ import { isPositiveInteger } from '../utilities/numberUtilities.js';
4
+ import { validateInputValues } from '../utilities/validators.js';
5
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
2
6
 
3
- export const volumeOscillator = (main, index, fast, slow, {lag}) => {
4
-
5
-
7
+ export const volumeOscillator = (main, index, fast, slow, {lag, retLogs}) => {
6
8
 
7
9
  const {verticalOhlcv, instances} = main
8
- const value = verticalOhlcv.volume[index]
9
- const key = `volume_oscillator_${fast}_${slow}`
10
+ const key = retLogs ? `ret_log_volume_oscillator_${fast}_${slow}` : `volume_oscillator_${fast}_${slow}`
10
11
 
11
12
  if (index === 0) {
13
+ validateInputValues({ volume: true }, verticalOhlcv, index, 'volumeOscillator');
12
14
 
13
- const {len} = main
14
15
  Object.assign(instances, {
15
16
  [key]: {
16
17
  fastEMA: new FasterEMA(fast),
17
- slowEMA: new FasterEMA(slow)
18
+ slowEMA: new FasterEMA(slow),
18
19
  }
19
20
  })
20
21
 
21
- verticalOhlcv[key] = new Float64Array(len).fill(NaN)
22
-
23
- if(lag > 0)
24
- {
25
- main.lag([key], lag)
26
- }
22
+ initializeColumns(main, [{ key }], { lag });
27
23
  }
28
24
 
29
25
  const { fastEMA, slowEMA } = instances[key];
30
26
 
31
- fastEMA.update(value);
32
- slowEMA.update(value);
33
-
34
- let fastValue = NaN;
35
- let slowValue = NaN;
27
+ const volume = verticalOhlcv.volume[index];
36
28
 
37
- try {
38
- fastValue = fastEMA.getResult();
39
- } catch (err) {
40
-
29
+ if(volume === 0) {
30
+ return;
41
31
  }
42
32
 
43
- try {
44
- slowValue = slowEMA.getResult();
45
- } catch (err) {
33
+ fastEMA.update(volume);
34
+ slowEMA.update(volume);
46
35
 
47
- }
36
+ const fastValue = fastEMA.isStable ? fastEMA.getResult() : NaN;
37
+ const slowValue = slowEMA.isStable ? slowEMA.getResult() : NaN;
48
38
 
49
39
  let volumeOscValue = NaN
40
+ let volumeOscRetLog = NaN
50
41
 
51
- if(!Number.isNaN(fastValue) && !Number.isNaN(slowValue))
52
- {
53
- volumeOscValue = 100 * (fastValue - slowValue) / slowValue
42
+ if(Number.isNaN(fastValue) || Number.isNaN(slowValue)) {
43
+ return;
54
44
  }
55
45
 
56
- main.pushToMain({index, key, value: volumeOscValue})
46
+ volumeOscValue = retLogs
47
+ ? mathLog(fastValue, slowValue)
48
+ : 100 * (fastValue - slowValue) / slowValue
57
49
 
58
- return true;
50
+ main.pushToMain({index, key, value: volumeOscValue})
59
51
  };
@@ -1,4 +1,5 @@
1
-
1
+ import { validateInputValues } from '../utilities/validators.js'
2
+ import { initializeColumns } from '../core-functions/initializeColumns.js'
2
3
 
3
4
  const isBadNumber = (v) => v == null || !Number.isFinite(v)
4
5
 
@@ -9,19 +10,16 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
9
10
  const prefix = (retLogs) ? 'ret_log_' : 'ret_'
10
11
 
11
12
  if(index === 0) {
12
- const {len} = main
13
-
14
- const newCols = {
15
- [`${prefix}change`]: new Float64Array(len).fill(NaN),
16
- [`${prefix}mid_price_change`]: new Float64Array(len).fill(NaN),
17
- [`${prefix}upper_wick`]: new Float64Array(len).fill(NaN),
18
- [`${prefix}lower_wick`]: new Float64Array(len).fill(NaN),
19
- [`${prefix}gap`]: new Float64Array(len).fill(NaN),
20
- [`${prefix}body`]: new Float64Array(len).fill(NaN),
21
- [`${prefix}range`]: new Float64Array(len).fill(NaN)
22
- }
13
+ validateInputValues({ open: true, high: true, low: true, close: true }, verticalOhlcv, index, 'candleFeatures')
14
+
15
+ const columns = [
16
+ 'change', 'mid_price_change', 'upper_wick', 'lower_wick',
17
+ 'gap', 'body', 'range'
18
+ ].map(name => ({ key: `${prefix}${name}` }))
23
19
 
24
20
  for(const target of colKeys) {
21
+ validateInputValues({ [target]: true }, verticalOhlcv, index, 'candleFeatures')
22
+
25
23
  if(!verticalOhlcv.hasOwnProperty(target)) {
26
24
  throw new Error(
27
25
  `Target property in "options.colKey" array "${target}" not found in verticalOhlcv for "candleFeatures".`
@@ -34,16 +32,10 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
34
32
  );
35
33
  }
36
34
 
37
- newCols[`${prefix}${target}`] = new Float64Array(len).fill(NaN)
35
+ columns.push({ key: `${prefix}${target}` })
38
36
  }
39
37
 
40
- Object.assign(verticalOhlcv, newCols)
41
-
42
- scaledGroups.candleFeatures = Object.keys(newCols)
43
-
44
- if(lag) {
45
- main.lag(Object.keys(newCols), lag)
46
- }
38
+ scaledGroups.candleFeatures = initializeColumns(main, columns, { lag })
47
39
  }
48
40
 
49
41
  const prevClose = verticalOhlcv.close[index - 1]
@@ -64,14 +56,34 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
64
56
  const upperWickTop = Math.max(currOpen, currClose)
65
57
  const lowerWickTop = Math.min(currOpen, currClose)
66
58
 
59
+ const change = getRet(currClose, prevClose)
60
+ const midPriceChange = getRet(currMidPrice, prevMidPrice)
61
+ const upperWick = getRet(currHigh, upperWickTop)
62
+ const lowerWick = getRet(lowerWickTop, currLow)
63
+ const gap = getRet(currOpen, prevClose)
64
+ const body = getRet(currClose, currOpen)
65
+ const range = getRet(currHigh, currLow)
66
+
67
+ if (colKeys.length === 0) {
68
+ main.pushToMain({ index, key: `${prefix}change`, value: change })
69
+ main.pushToMain({ index, key: `${prefix}mid_price_change`, value: midPriceChange })
70
+ main.pushToMain({ index, key: `${prefix}upper_wick`, value: upperWick })
71
+ main.pushToMain({ index, key: `${prefix}lower_wick`, value: lowerWick })
72
+ main.pushToMain({ index, key: `${prefix}gap`, value: gap })
73
+ main.pushToMain({ index, key: `${prefix}body`, value: body })
74
+ main.pushToMain({ index, key: `${prefix}range`, value: range })
75
+ return
76
+ }
77
+
78
+ // Keep optional-target overwrite/order semantics and all-or-nothing validation.
67
79
  const row = {
68
- [`${prefix}change`]: getRet(currClose, prevClose),
69
- [`${prefix}mid_price_change`]: getRet(currMidPrice, prevMidPrice),
70
- [`${prefix}upper_wick`]: getRet(currHigh, upperWickTop),
71
- [`${prefix}lower_wick`]: getRet(lowerWickTop, currLow),
72
- [`${prefix}gap`]: getRet(currOpen, prevClose),
73
- [`${prefix}body`]: getRet(currClose, currOpen),
74
- [`${prefix}range`]: getRet(currHigh, currLow)
80
+ [`${prefix}change`]: change,
81
+ [`${prefix}mid_price_change`]: midPriceChange,
82
+ [`${prefix}upper_wick`]: upperWick,
83
+ [`${prefix}lower_wick`]: lowerWick,
84
+ [`${prefix}gap`]: gap,
85
+ [`${prefix}body`]: body,
86
+ [`${prefix}range`]: range
75
87
  }
76
88
 
77
89
  let hasInvalidTarget = false
@@ -94,4 +106,4 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
94
106
  main.pushToMain({index, key, value})
95
107
  }
96
108
 
97
- }
109
+ }
@@ -1,4 +1,7 @@
1
1
  import {oneHotEncode} from '../machine-learning/ml-utilities.js'
2
+ import { initializeColumns } from '../core-functions/initializeColumns.js'
3
+
4
+ const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
2
5
 
3
6
  export const dateTime = (main, index, {lag, oneHot, precompute}) => {
4
7
 
@@ -9,11 +12,16 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
9
12
 
10
13
  if(index === 0)
11
14
  {
12
- const {len, dateType} = main
15
+ const {dateType} = main
13
16
  if(!dateType) throw Error('dateTime method found and invalid "date" in input ohlcv')
14
17
 
15
18
  Object.assign(instances, {
16
19
  dateTime: {
20
+ formatter: new Intl.DateTimeFormat('en-US-u-ca-gregory-nu-latn', {
21
+ timeZone: main.timeZone,
22
+ year: 'numeric', month: 'numeric', day: 'numeric', weekday: 'short',
23
+ hour: 'numeric', minute: 'numeric', hourCycle: 'h23'
24
+ }),
17
25
  colKeys: [...precompute.colKeys, `${prefix}year`].filter(v => v !== 'one_hot_year'),
18
26
  colKeySizes: {
19
27
  ...precompute.colKeySizes
@@ -23,65 +31,55 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
23
31
 
24
32
  const { colKeys } = instances.dateTime
25
33
 
26
- // choose your ctor, fill-value and type-name once
27
- const ctor = oneHot ? Array : Int16Array
28
- const fillVal = oneHot ? null : NaN
29
-
30
- // single loop instead of three
31
- for (const key of colKeys) {
32
-
33
- verticalOhlcv[key] = new ctor(len).fill(fillVal)
34
-
35
- }
36
-
37
- // finally, apply lag once
38
- if (lag > 0) {
39
- main.lag(colKeys, lag)
40
- }
34
+ initializeColumns(main, colKeys.map(key => ({
35
+ key,
36
+ type: oneHot ? 'Array' : 'Int16Array',
37
+ fill: oneHot ? null : 0
38
+ })), { lag })
41
39
  }
42
40
 
43
- const {colKeySizes} = instances.dateTime
41
+ const {colKeySizes, formatter} = instances.dateTime
44
42
 
45
43
  const currDate = verticalOhlcv.date[index]
46
44
 
47
- const dateInfo = getDateInfo(currDate, oneHot, colKeySizes)
48
-
49
- for(const [key, value] of Object.entries(dateInfo))
50
- {
51
- main.pushToMain({index, key, value})
45
+ let yearPart, monthPart, hourPart, minutePart, weekdayPart, dayPart
46
+ for (const part of formatter.formatToParts(currDate)) {
47
+ switch (part.type) {
48
+ case 'year': yearPart = part.value; break
49
+ case 'month': monthPart = part.value; break
50
+ case 'hour': hourPart = part.value; break
51
+ case 'minute': minutePart = part.value; break
52
+ case 'weekday': weekdayPart = part.value; break
53
+ case 'day': dayPart = part.value; break
54
+ }
55
+ }
56
+ const year = Number(yearPart)
57
+ const month = Number(monthPart) - 1
58
+ const hour = Number(hourPart)
59
+ const minute = Number(minutePart)
60
+ const dayOfWeek = weekdays.indexOf(weekdayPart)
61
+ const dayOfMonth = Number(dayPart) - 1
62
+
63
+ if (!oneHot) {
64
+ main.pushToMain({ index, key: 'year', value: year })
65
+ main.pushToMain({ index, key: 'month', value: month + 1 })
66
+ main.pushToMain({ index, key: 'hour', value: hour })
67
+ main.pushToMain({ index, key: 'minute', value: minute })
68
+ main.pushToMain({ index, key: 'day_of_the_week', value: ((dayOfWeek + 6) % 7) + 1 })
69
+ main.pushToMain({ index, key: 'day_of_the_month', value: dayOfMonth + 1 })
70
+ return
52
71
  }
53
- }
54
-
55
-
56
-
57
- const getDateInfo = (date, oneHot, colKeySizes) => {
58
-
59
-
60
- const year = date.getFullYear();
61
-
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
68
-
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
- }
79
72
 
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
- };
73
+ // Build a fresh vector for every field and row before writing.
74
+ const oneHotMonth = oneHotEncode(month, colKeySizes.one_hot_month)
75
+ const oneHotHour = oneHotEncode(hour, colKeySizes.one_hot_hour)
76
+ const oneHotMinute = oneHotEncode(minute, colKeySizes.one_hot_minute)
77
+ const oneHotWeekday = oneHotEncode(dayOfWeek, colKeySizes.one_hot_day_of_the_week)
78
+ const oneHotDay = oneHotEncode(dayOfMonth, colKeySizes.one_hot_day_of_the_month)
79
+
80
+ main.pushToMain({ index, key: 'one_hot_month', value: oneHotMonth })
81
+ main.pushToMain({ index, key: 'one_hot_hour', value: oneHotHour })
82
+ main.pushToMain({ index, key: 'one_hot_minute', value: oneHotMinute })
83
+ main.pushToMain({ index, key: 'one_hot_day_of_the_week', value: oneHotWeekday })
84
+ main.pushToMain({ index, key: 'one_hot_day_of_the_month', value: oneHotDay })
85
+ }
@@ -1,6 +1,8 @@
1
1
  import { oneHotEncode } from "../machine-learning/ml-utilities.js"
2
2
  import { buildArray } from "../utilities/assignTypes.js"
3
3
  import { addPrecisionAsNumber } from "../utilities/precisionMultiplier.js"
4
+ import { validateInputValues } from "../utilities/validators.js"
5
+ import { initializeColumns } from "../core-functions/initializeColumns.js"
4
6
  const eq = (fast, slow) => fast === slow
5
7
  const gt = (fast, slow) => fast > slow
6
8
  const lt = (fast, slow) => fast < slow
@@ -22,11 +24,7 @@ class CrossInstance {
22
24
  prevSlow: NaN,
23
25
  prevHigh: NaN,
24
26
  prevLow: NaN,
25
- areHighAndLowUndefined: false,
26
- crossIndexes: {
27
- up: [],
28
- down: []
29
- }
27
+ areHighAndLowUndefined: false
30
28
  })
31
29
 
32
30
  }
@@ -113,9 +111,6 @@ class CrossInstance {
113
111
  }
114
112
 
115
113
 
116
- if(this.interval === 1) this.crossIndexes.up.push(index)
117
- if(this.interval === -1) this.crossIndexes.down.push(index)
118
-
119
114
  //save prev state
120
115
  Object.assign(this, {
121
116
  prevState: currState,
@@ -143,6 +138,16 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
143
138
 
144
139
  if(index === 0)
145
140
  {
141
+ validateInputValues(
142
+ {
143
+ ...(fast === 'price' ? { high: true, low: true, close: true } : { [fast]: true }),
144
+ ...(typeof slow === 'number' ? {} : { [slow]: true })
145
+ },
146
+ verticalOhlcv,
147
+ index,
148
+ 'crossPairs'
149
+ )
150
+
146
151
  // allow numeric 'slow' as a constant column
147
152
  if (typeof slow === 'number') {
148
153
 
@@ -173,12 +178,10 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
173
178
 
174
179
  const crossArrType = (limit !== null && limit <= 125) ? 'Int8Array' : 'Float64Array'
175
180
 
176
- verticalOhlcv[crossName] = buildArray(crossArrType, len, 0)
177
-
178
- if(oneHot)
179
- {
180
- verticalOhlcv[`one_hot_${crossName}`] = buildArray('Array', len, null)
181
- }
181
+ initializeColumns(main, [
182
+ { key: crossName, type: crossArrType, fill: 0 },
183
+ { key: `one_hot_${crossName}`, type: 'Array', enabled: oneHot }
184
+ ])
182
185
 
183
186
  } else if(index + 1 === len) {
184
187
  // sanity checks
@@ -228,5 +231,4 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
228
231
 
229
232
  }
230
233
 
231
- return true
232
- }
234
+ }
@@ -1,44 +1,57 @@
1
- import { buildArray, getArrayType } from "../utilities/assignTypes.js"
1
+ import { initializeColumns } from "../core-functions/initializeColumns.js"
2
+
3
+ const lagColumnsKey = Symbol('lagColumns')
2
4
 
3
5
  export const lag = (main, index, colKeys, lookback) => {
4
6
 
5
-
6
-
7
- const { verticalOhlcv, len, priceBased } = main
7
+ const { verticalOhlcv, priceBased, instances } = main
8
8
 
9
9
  if(index === 0)
10
10
  {
11
- for (const targetKey of colKeys) {
12
- if (!verticalOhlcv.hasOwnProperty(targetKey)) {
11
+ const lagColumns = new Map()
12
+
13
+ for (const target of colKeys) {
14
+ if (!verticalOhlcv.hasOwnProperty(target)) {
13
15
  throw new Error(
14
- `Lag processing invoked by col "${targetKey}" was not found in "verticalOhlcv".`
16
+ `Lag processing invoked by col "${target}" was not found in "verticalOhlcv".`
15
17
  )
16
18
  }
17
19
 
18
- const addToPriceBased = priceBased.has(targetKey)
20
+ const addToPriceBased = priceBased.has(target)
21
+ const type = Array.isArray(verticalOhlcv[target]) ? 'Array' : 'Float64Array'
22
+ const columns = []
23
+ const keyNames = []
19
24
 
20
25
  for (let step = 1; step <= lookback; step++) {
21
- const key = `${targetKey}_lag_${step}`
22
-
23
- if(addToPriceBased) priceBased.add(key)
24
-
25
- const thisArrType = getArrayType(targetKey, verticalOhlcv[targetKey])
26
+ const key = `${target}_lag_${step}`
26
27
 
27
- verticalOhlcv[key] = buildArray(thisArrType, len)
28
+ columns.push({ key, type, priceBased: addToPriceBased, includeInLag: false })
29
+ keyNames.push(key)
28
30
  }
31
+
32
+ initializeColumns(main, columns)
33
+ lagColumns.set(target, keyNames)
29
34
  }
35
+
36
+ // colKeys belongs to the runtime queue; keep cached names out of exported params.
37
+ if (!instances[lagColumnsKey]) instances[lagColumnsKey] = new WeakMap()
38
+ instances[lagColumnsKey].set(colKeys, lagColumns)
30
39
  }
31
40
 
32
- for (const targetKey of colKeys) {
33
- const currentColumn = verticalOhlcv[targetKey]
41
+ const lagColumns = instances[lagColumnsKey]?.get(colKeys)
42
+
43
+ for (const target of colKeys) {
44
+ const keyNames = lagColumns?.get(target)
45
+ const currentColumn = verticalOhlcv[target]
46
+ const missingValue = Array.isArray(currentColumn) ? null : NaN
34
47
 
35
48
  // Populate the lagged values each tick:
36
49
  for (let step = 1; step <= lookback; step++) {
37
- const key = `${targetKey}_lag_${step}`
50
+ const key = keyNames?.[step - 1] ?? `${target}_lag_${step}`
38
51
  const laggedIndex = index - step
39
52
  const value =
40
- laggedIndex < 0 || currentColumn[laggedIndex] === undefined
41
- ? null
53
+ laggedIndex < 0 || currentColumn[laggedIndex] == null
54
+ ? missingValue
42
55
  : currentColumn[laggedIndex]
43
56
 
44
57
  main.pushToMain({ index, key, value })
@@ -46,5 +59,4 @@ export const lag = (main, index, colKeys, lookback) => {
46
59
  }
47
60
  }
48
61
 
49
- return true
50
62
  }
@@ -29,7 +29,7 @@ export const buildArray = (arrayType, len, fallbackValue) => {
29
29
 
30
30
 
31
31
  // Ctor.from({ length }, mapFn) works for Array *and* all TypedArrays
32
- return Ctor.from({ length: len }, () => fillValue)
32
+ return new Ctor(len).fill(fillValue)
33
33
  }
34
34
 
35
35
  export const assignTypes = main => {
@@ -0,0 +1,31 @@
1
+ export const CONFIG_VERSION = 1;
2
+
3
+ // Copy configuration without JSON's silent conversion of functions/NaN to null.
4
+ export const copyConfig = (value, ancestors = new Set()) => {
5
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
6
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
7
+ if (typeof value !== 'object' || value === null) {
8
+ throw new TypeError('Configuration values must be JSON-safe: no undefined, non-finite numbers, or functions.');
9
+ }
10
+ if (ancestors.has(value)) throw new TypeError('Configuration must not contain circular references.');
11
+ const prototype = Object.getPrototypeOf(value);
12
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
13
+ throw new TypeError('Configuration must contain only plain objects and arrays.');
14
+ }
15
+ if (Object.getOwnPropertySymbols(value).length) throw new TypeError('Configuration must not contain symbol keys.');
16
+
17
+ ancestors.add(value);
18
+ const result = Array.isArray(value)
19
+ ? Array.from(value, item => copyConfig(item, ancestors))
20
+ : Object.fromEntries(Object.entries(value).map(([key, item]) => [key, copyConfig(item, ancestors)]));
21
+ ancestors.delete(value);
22
+ return result;
23
+ };
24
+
25
+ export const freezeConfig = value => {
26
+ if (value !== null && typeof value === 'object') {
27
+ for (const item of Object.values(value)) freezeConfig(item);
28
+ Object.freeze(value);
29
+ }
30
+ return value;
31
+ };