ohlcv-indicators 3.5.78 → 3.5.79

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 (40) hide show
  1. package/AGENTS.md +105 -0
  2. package/README.md +415 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
  5. package/index.js +93 -55
  6. package/package.json +1 -1
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +47 -22
  9. package/src/core-functions/pushToMain.js +1 -3
  10. package/src/core-indicators/README.md +61 -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 +82 -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 +11 -26
  18. package/src/moving-averages/donchianChannel.js +30 -29
  19. package/src/moving-averages/heikenAshi.js +23 -33
  20. package/src/moving-averages/macd.js +10 -26
  21. package/src/moving-averages/movingAverages.js +7 -23
  22. package/src/moving-averages/relativeVolume.js +23 -21
  23. package/src/oscillators/rsi.js +7 -26
  24. package/src/oscillators/stochastic.js +6 -19
  25. package/src/oscillators/volumeDelta.js +36 -28
  26. package/src/oscillators/volumeOscillator.js +29 -31
  27. package/src/studies/candleFeatures.js +12 -20
  28. package/src/studies/dateTime.js +22 -27
  29. package/src/studies/findCrosses.js +16 -7
  30. package/src/studies/lag.js +26 -13
  31. package/src/studies/mapCols.js +14 -15
  32. package/src/utilities/assignTypes.js +1 -1
  33. package/src/utilities/config.js +64 -0
  34. package/src/utilities/numberUtilities.js +2 -0
  35. package/src/utilities/validators.js +40 -1
  36. package/src/utilities/verticalToHorizontal.js +21 -16
  37. package/src/volatility/adx.js +7 -20
  38. package/src/volatility/atr.js +10 -23
  39. package/test/test.js +533 -0
  40. package/src/moving-averages/vidya.js +0 -181
@@ -1,4 +1,6 @@
1
- import { FasterEMA } from 'trading-signals';
1
+ import { FasterEMA } from '../core-indicators/index.js';
2
+ import { validateInputValues } from '../utilities/validators.js';
3
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
2
4
 
3
5
  const isBadNumber = v => v == null || !Number.isFinite(v);
4
6
 
@@ -9,7 +11,7 @@ export const heikenAshi = (
9
11
  afterSmoothLength,
10
12
  { lag = 0, bothNull = false, retLogs = true } = {}
11
13
  ) => {
12
- const { verticalOhlcv, instances, len, scaledGroups } = main;
14
+ const { verticalOhlcv, instances, scaledGroups } = main;
13
15
  const indicatorKey = `${smoothLength}_${afterSmoothLength}`;
14
16
  const ohlcKeys = ['open', 'high', 'low', 'close'];
15
17
 
@@ -40,6 +42,8 @@ export const heikenAshi = (
40
42
 
41
43
  // ---- INIT ----
42
44
  if (index === 0) {
45
+ validateInputValues({ open: true, high: true, low: true, close: true }, verticalOhlcv, index, 'heikenAshi');
46
+
43
47
  instances[instanceKey] = {
44
48
  emaPre: !bothNull
45
49
  ? Object.fromEntries(ohlcKeys.map(k => [k, new FasterEMA(smoothLength)]))
@@ -56,19 +60,11 @@ export const heikenAshi = (
56
60
 
57
61
  const keyNames = featureKeys.map(getKey);
58
62
 
59
- const verticalOhlcvSetup = Object.fromEntries(
60
- [...keyNames, crossKey].map(k => [k, new Float64Array(len).fill(NaN)])
61
- );
62
-
63
- Object.assign(verticalOhlcv, verticalOhlcvSetup);
63
+ initializeColumns(main, [...keyNames, crossKey].map(key => ({ key })), { lag });
64
64
 
65
65
  if (scaledGroups) {
66
66
  scaledGroups.heikenAshi = keyNames;
67
67
  }
68
-
69
- if (lag > 0) {
70
- main.lag(keyNames, lag);
71
- }
72
68
  }
73
69
 
74
70
  // ---- FETCH RAW INPUTS ----
@@ -84,7 +80,7 @@ export const heikenAshi = (
84
80
  isBadNumber(low) ||
85
81
  isBadNumber(close)
86
82
  ) {
87
- return true;
83
+ return;
88
84
  }
89
85
 
90
86
  let sOpen, sHigh, sLow, sClose;
@@ -96,14 +92,12 @@ export const heikenAshi = (
96
92
  inst.emaPre.low.update(low);
97
93
  inst.emaPre.close.update(close);
98
94
 
99
- try {
100
- sOpen = inst.emaPre.open.getResult();
101
- sHigh = inst.emaPre.high.getResult();
102
- sLow = inst.emaPre.low.getResult();
103
- sClose = inst.emaPre.close.getResult();
104
- } catch {
105
- return true;
106
- }
95
+ if (!inst.emaPre.open.isStable || !inst.emaPre.high.isStable ||
96
+ !inst.emaPre.low.isStable || !inst.emaPre.close.isStable) return;
97
+ sOpen = inst.emaPre.open.getResult();
98
+ sHigh = inst.emaPre.high.getResult();
99
+ sLow = inst.emaPre.low.getResult();
100
+ sClose = inst.emaPre.close.getResult();
107
101
  } else {
108
102
  sOpen = open;
109
103
  sHigh = high;
@@ -117,7 +111,7 @@ export const heikenAshi = (
117
111
  isBadNumber(sLow) ||
118
112
  isBadNumber(sClose)
119
113
  ) {
120
- return true;
114
+ return;
121
115
  }
122
116
 
123
117
  // ---- HEIKEN ASHI CORE ----
@@ -145,14 +139,12 @@ export const heikenAshi = (
145
139
  inst.emaPost.low.update(haLow);
146
140
  inst.emaPost.close.update(haClose);
147
141
 
148
- try {
149
- smOpen = inst.emaPost.open.getResult();
150
- smHigh = inst.emaPost.high.getResult();
151
- smLow = inst.emaPost.low.getResult();
152
- smClose = inst.emaPost.close.getResult();
153
- } catch {
154
- return true;
155
- }
142
+ if (!inst.emaPost.open.isStable || !inst.emaPost.high.isStable ||
143
+ !inst.emaPost.low.isStable || !inst.emaPost.close.isStable) return;
144
+ smOpen = inst.emaPost.open.getResult();
145
+ smHigh = inst.emaPost.high.getResult();
146
+ smLow = inst.emaPost.low.getResult();
147
+ smClose = inst.emaPost.close.getResult();
156
148
  } else {
157
149
  smOpen = haOpen;
158
150
  smHigh = haHigh;
@@ -170,7 +162,7 @@ export const heikenAshi = (
170
162
  smLow <= 0 ||
171
163
  smClose <= 0
172
164
  ) {
173
- return true;
165
+ return;
174
166
  }
175
167
 
176
168
  // ---- TREND/CROSS LOGIC ----
@@ -229,6 +221,4 @@ export const heikenAshi = (
229
221
 
230
222
  // Do not add return logs to cross.
231
223
  main.pushToMain({ index, key: crossKey, value: cross });
232
-
233
- return true;
234
- };
224
+ };
@@ -1,14 +1,18 @@
1
- import { FasterEMA, FasterMACD } from 'trading-signals'
1
+ import { FasterEMA, FasterMACD } from '../core-indicators/index.js'
2
+ import { validateInputValues } from '../utilities/validators.js'
3
+ import { initializeColumns } from '../core-functions/initializeColumns.js'
2
4
 
3
5
  const defaultTarget = 'close'
4
6
 
5
7
  export const macd = (main, index, fast, slow, signal, { target, lag, precomputed }) => {
6
- const { verticalOhlcv, instances, priceBased, useFullNames } = main
8
+ const { verticalOhlcv, instances, useFullNames } = main
7
9
  const { instanceKey } = precomputed
8
10
 
9
11
  // Initialization on the first index.
10
12
  if (index === 0) {
11
- const { inputParams, len } = main
13
+ validateInputValues({ [target]: true }, verticalOhlcv, index, 'macd')
14
+
15
+ const { inputParams } = main
12
16
 
13
17
  if (!verticalOhlcv.hasOwnProperty(target)) {
14
18
  throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`)
@@ -53,21 +57,8 @@ export const macd = (main, index, fast, slow, signal, { target, lag, precomputed
53
57
  new FasterEMA(signal)
54
58
  )
55
59
 
56
- Object.assign(verticalOhlcv, {
57
- [diffKey]: new Float64Array(len).fill(NaN),
58
- [deaKey]: new Float64Array(len).fill(NaN),
59
- [histogramKey]: new Float64Array(len).fill(NaN),
60
- })
61
-
62
60
  const keyNames = [diffKey, deaKey, histogramKey]
63
-
64
- for (const k of keyNames) {
65
- priceBased.add(k)
66
- }
67
-
68
- if (lag > 0) {
69
- main.lag(keyNames, lag)
70
- }
61
+ initializeColumns(main, keyNames.map(key => ({ key, priceBased: true })), { lag })
71
62
  }
72
63
 
73
64
  const { numberOfIndicators, settings } = instances.macd
@@ -94,17 +85,10 @@ export const macd = (main, index, fast, slow, signal, { target, lag, precomputed
94
85
  const value = verticalOhlcv[target][index]
95
86
  macdInstance.update(value)
96
87
 
97
- let macdResult = {}
98
- try {
99
- macdResult = macdInstance.getResult()
100
- } catch (err) {
101
- // If the result is unavailable, macdResult remains NaN.
102
- }
88
+ const macdResult = macdInstance.isStable ? macdInstance.getResult() : null
103
89
 
104
90
  // Always push values; use NaN as fallback when macdResult is missing.
105
91
  main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN })
106
92
  main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN })
107
93
  main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN })
108
-
109
- return true
110
- }
94
+ }
@@ -1,5 +1,7 @@
1
1
 
2
- import {FasterEMA, FasterSMA} from 'trading-signals';
2
+ import {FasterEMA, FasterSMA} from '../core-indicators/index.js';
3
+ import { validateInputValues } from '../utilities/validators.js';
4
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
3
5
 
4
6
  const indicatorClasses = {
5
7
  ema: FasterEMA,
@@ -13,8 +15,7 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
13
15
  const keyName = `${methodName}_${size}${suffix}`
14
16
 
15
17
  if (index === 0) {
16
-
17
- const {len} = main
18
+ validateInputValues({ [target]: true }, verticalOhlcv, index, methodName)
18
19
 
19
20
  if (!verticalOhlcv.hasOwnProperty(target)) {
20
21
  throw new Error(
@@ -25,16 +26,7 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
25
26
  // Create the main moving average instance.
26
27
  instances[keyName] = new indicatorClasses[methodName](size)
27
28
 
28
- verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
29
-
30
- if(priceBased.has(target)){
31
- priceBased.add(keyName)
32
- }
33
-
34
- if(lag > 0)
35
- {
36
- main.lag([keyName], lag)
37
- }
29
+ initializeColumns(main, [{ key: keyName, priceBased: priceBased.has(target) }], { lag })
38
30
  }
39
31
 
40
32
  // Retrieve the current price value
@@ -44,16 +36,8 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
44
36
  // Update the moving average instance.
45
37
  instance.update(value)
46
38
 
47
- let currMa = NaN
48
-
49
- try {
50
- currMa = instance.getResult()
51
- } catch (err) {
52
-
53
- }
39
+ const currMa = instance.isStable ? instance.getResult() : NaN
54
40
 
55
41
  // Always push the MA value (even if NaN).
56
42
  main.pushToMain({ index, key: keyName, value: currMa })
57
-
58
- return true
59
- }
43
+ }
@@ -1,48 +1,50 @@
1
- import { FasterSMA } from 'trading-signals';
1
+ import { FasterSMA } from '../core-indicators/index.js';
2
+ import { isPositiveInteger } from '../utilities/numberUtilities.js';
3
+ import { validateInputValues } from '../utilities/validators.js';
4
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
5
+
2
6
 
3
7
  export const relativeVolume = (main, index, size, {lag}) => {
4
8
 
5
9
  const key = `relative_volume_${size}`;
6
10
  const { instances, verticalOhlcv } = main;
11
+
7
12
 
8
13
  if (index === 0) {
9
- const { len } = main;
14
+ validateInputValues({ volume: true }, verticalOhlcv, index, 'relativeVolume');
10
15
 
11
16
  instances[key] = {
12
17
  instance: new FasterSMA(size),
13
18
  prevRelativeVolumeSma: NaN
14
19
  };
15
20
 
16
- verticalOhlcv[key] = new Float64Array(len).fill(NaN);
17
-
18
- if(lag > 0)
19
- {
20
- main.lag([key], lag)
21
- }
21
+ initializeColumns(main, [{ key }], { lag });
22
22
 
23
23
  }
24
24
 
25
- const value = verticalOhlcv.volume[index];
26
- const smaInstance = instances[key].instance;
27
- smaInstance.update(value);
28
-
29
- let smaValue = NaN;
30
- try {
31
- smaValue = smaInstance.getResult();
32
- } catch (err) {
25
+ const volume = verticalOhlcv.volume[index];
33
26
 
27
+ if(volume === 0) {
28
+ return;
34
29
  }
30
+
31
+ const smaInstance = instances[key].instance;
32
+ smaInstance.update(volume);
33
+
34
+ const smaValue = smaInstance.isStable ? smaInstance.getResult() : NaN;
35
35
 
36
36
  const prevSma = instances[key].prevRelativeVolumeSma;
37
- let currRelativeVolume = NaN;
38
37
 
39
- // Only calculate relative volume if both current SMA and previous SMA are valid numbers and prevSma is not zero.
40
- if (!Number.isNaN(smaValue) && !Number.isNaN(prevSma)) {
41
- currRelativeVolume = value / prevSma;
38
+ // Save the current SMA for the next nonzero-volume row.
39
+ instances[key].prevRelativeVolumeSma = smaValue;
40
+
41
+ if(Number.isNaN(smaValue) || Number.isNaN(prevSma)) {
42
+ return
42
43
  }
43
44
 
45
+ let currRelativeVolume = volume / prevSma;
46
+
44
47
  main.pushToMain({ index, key, value: currRelativeVolume });
45
48
  instances[key].prevRelativeVolumeSma = smaValue;
46
49
 
47
- return true;
48
50
  };
@@ -1,6 +1,7 @@
1
- import { FasterRSI } from 'trading-signals';
2
- import { FasterSMA } from 'trading-signals';
1
+ import { FasterRSI, FasterSMA } from '../core-indicators/index.js';
3
2
  import { mathLog } from '../utilities/math.js';
3
+ import { validateInputValues } from '../utilities/validators.js';
4
+ import { initializeColumns } from '../core-functions/initializeColumns.js';
4
5
 
5
6
  const defaultTarget = 'close'
6
7
  export const rsi = (main, index, size, { target, lag, retLogs }) => {
@@ -15,8 +16,7 @@ export const rsi = (main, index, size, { target, lag, retLogs }) => {
15
16
 
16
17
  // Initialization on the first index.
17
18
  if (index === 0) {
18
-
19
- const {len } = main;
19
+ validateInputValues({ [target]: true }, verticalOhlcv, index, 'rsi');
20
20
 
21
21
  if (!verticalOhlcv.hasOwnProperty(target)) {
22
22
  throw new Error(`Target property ${target} not found in verticalOhlcv for rsi.`);
@@ -27,32 +27,17 @@ export const rsi = (main, index, size, { target, lag, retLogs }) => {
27
27
  [rsiSmaKey]: new FasterSMA(size)
28
28
  })
29
29
 
30
- Object.assign(verticalOhlcv, {
31
- [rsiKey]: new Float64Array(len).fill(NaN),
32
- [rsiSmaKey]: new Float64Array(len).fill(NaN),
33
- })
34
-
35
- const baseKeys = [rsiKey, rsiSmaKey]
36
-
37
- if (lag > 0) {
38
- main.lag(baseKeys, lag);
39
- }
30
+ initializeColumns(main, [{ key: rsiKey }, { key: rsiSmaKey }], { lag });
40
31
 
41
32
 
42
33
  }
43
34
 
44
35
  const value = verticalOhlcv[target][index];
45
- let currentRsi = NaN;
46
- let smoothedRsi = NaN;
47
36
 
48
37
  // Update the RSI indicator.
49
38
  instances[rsiKey].update(value);
50
39
 
51
- try {
52
- currentRsi = instances[rsiKey].getResult();
53
- } catch (err) {
54
- currentRsi = NaN;
55
- }
40
+ const currentRsi = instances[rsiKey].isStable ? instances[rsiKey].getResult() : NaN;
56
41
 
57
42
 
58
43
  const rsiVal = Number.isNaN(currentRsi) ? NaN : (retLogs ? mathLog(currentRsi, 50) : currentRsi)
@@ -63,11 +48,7 @@ export const rsi = (main, index, size, { target, lag, retLogs }) => {
63
48
  instances[rsiSmaKey].update(currentRsi);
64
49
  }
65
50
 
66
- try {
67
- smoothedRsi = instances[rsiSmaKey].getResult();
68
- } catch (err) {
69
- smoothedRsi = NaN;
70
- }
51
+ const smoothedRsi = instances[rsiSmaKey].isStable ? instances[rsiSmaKey].getResult() : NaN;
71
52
 
72
53
  const smoothedRsiVal = Number.isNaN(smoothedRsi) ? NaN : (retLogs ? mathLog(smoothedRsi, 50) : smoothedRsi)
73
54
 
@@ -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,57 @@
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
10
  const key = `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, [
23
+ { key },
24
+ { key: `ret_log_${key}`, enabled: retLogs }
25
+ ], { lag });
27
26
  }
28
27
 
29
28
  const { fastEMA, slowEMA } = instances[key];
30
29
 
31
- fastEMA.update(value);
32
- slowEMA.update(value);
30
+ const volume = verticalOhlcv.volume[index];
33
31
 
34
- let fastValue = NaN;
35
- let slowValue = NaN;
36
-
37
- try {
38
- fastValue = fastEMA.getResult();
39
- } catch (err) {
40
-
32
+ if(volume === 0) {
33
+ return;
41
34
  }
42
35
 
43
- try {
44
- slowValue = slowEMA.getResult();
45
- } catch (err) {
36
+ fastEMA.update(volume);
37
+ slowEMA.update(volume);
46
38
 
47
- }
39
+ const fastValue = fastEMA.isStable ? fastEMA.getResult() : NaN;
40
+ const slowValue = slowEMA.isStable ? slowEMA.getResult() : NaN;
48
41
 
49
42
  let volumeOscValue = NaN
43
+ let volumeOscRetLog = NaN
50
44
 
51
- if(!Number.isNaN(fastValue) && !Number.isNaN(slowValue))
52
- {
53
- volumeOscValue = 100 * (fastValue - slowValue) / slowValue
45
+ if(Number.isNaN(fastValue) || Number.isNaN(slowValue)) {
46
+ return;
54
47
  }
55
48
 
56
- main.pushToMain({index, key, value: volumeOscValue})
49
+ volumeOscValue = 100 * (fastValue - slowValue) / slowValue
50
+
51
+ if(retLogs) {
52
+ volumeOscRetLog = mathLog(fastValue, slowValue)
53
+ main.pushToMain({index, key: `ret_log_${key}`, value: volumeOscRetLog})
54
+ }
57
55
 
58
- return true;
56
+ main.pushToMain({index, key, value: volumeOscValue})
59
57
  };
@@ -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]