ohlcv-indicators 3.3.2 → 3.3.5

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,85 @@
1
+ import { validateInputParams } from "../utilities/validators.js";
2
+ import { rsi } from "../oscillators/rsi.js";
3
+ import { movingAverages } from "../moving-averages/movingAverages.js";
4
+ import { macd } from "../moving-averages/macd.js";
5
+ import { relativeVolume } from "../moving-averages/relativeVolume.js";
6
+ import { donchianChannels } from "../moving-averages/donchianChannel.js";
7
+ import { bollingerBands } from "../moving-averages/bollingerBands.js";
8
+ import { volumeOscillator } from "../oscillators/volumeOscillator.js";
9
+ import { lag } from "../studies/lag.js";
10
+ import { crossPairs } from "../studies/findCrosses.js";
11
+ import { dateTime } from "../studies/dateTime.js";
12
+ import { Scaler } from "../studies/scaler.js";
13
+
14
+ import { buildArray } from "../utilities/assignTypes.js";
15
+ import { numberFormater } from "../utilities/numberUtilities.js";
16
+ import { dateFormaters } from "../utilities/dateUtilities.js";
17
+
18
+ // Map indicator keys to their respective functions
19
+ const indicatorFunctions = {
20
+ dateTime,
21
+ rsi,
22
+ ema: movingAverages,
23
+ sma: movingAverages,
24
+ macd,
25
+ relativeVolume,
26
+ donchianChannels,
27
+ bollingerBands,
28
+ volumeOscillator,
29
+ Scaler
30
+ };
31
+
32
+
33
+ export const mainLoop = (input, main) => {
34
+ const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes } = main;
35
+
36
+ validateInputParams({inputParams, len})
37
+
38
+ for(const key of Object.keys(inputTypes))
39
+ {
40
+ verticalOhlcv[key] = buildArray(arrayTypes[key], len)
41
+ }
42
+
43
+ // Process each row in the input
44
+ for (let index = 0; index < len; index++) {
45
+ const curr = input[index]
46
+
47
+ for(const [key, formaterKey] of Object.entries(inputTypes))
48
+ {
49
+ const value = curr[key]
50
+
51
+ if(numberFormater.hasOwnProperty(formaterKey))
52
+ {
53
+ let formatedValue = numberFormater[formaterKey](value)
54
+
55
+ if(precisionMultiplier > 1 && priceBased.has(key))
56
+ {
57
+ formatedValue = formatedValue * precisionMultiplier
58
+ }
59
+
60
+ main.pushToMain({index, key, value: formatedValue})
61
+ }
62
+ else if(dateFormaters.hasOwnProperty(formaterKey))
63
+ {
64
+ main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
65
+ }
66
+ else
67
+ {
68
+ main.pushToMain({index, key, value})
69
+ }
70
+ }
71
+
72
+ // Run all indicator functions except for the ones processed later
73
+ for (const { key, params } of inputParams) {
74
+ if (key === 'lag' || key === 'crossPairs') continue;
75
+ // resolve fn on-demand, no per-item object allocation here
76
+ indicatorFunctions[key](main, index, ...params)
77
+ }
78
+
79
+ // Process these indicators separately (ensuring their execution order)
80
+ lag(main, index)
81
+ crossPairs(main, index)
82
+ }
83
+
84
+ verticalOhlcvKeyNames.push(...Object.keys(verticalOhlcv))
85
+ }
@@ -0,0 +1,15 @@
1
+ export const pushToMain = ({main, index, key, value}) => {
2
+
3
+ const {verticalOhlcv} = main
4
+
5
+ if(value === null || typeof value === 'undefined' || Number.isNaN(value))
6
+ {
7
+ main.invalidValueIndex = index
8
+ verticalOhlcv[key][index] = value
9
+ return false
10
+ }
11
+
12
+ verticalOhlcv[key][index] = value
13
+
14
+ return true
15
+ }
@@ -1,10 +1,9 @@
1
1
  import { FasterBollingerBands } from 'trading-signals'
2
- import { calcMagnitude } from '../utilities/numberUtilities.js';
3
2
 
4
3
  const defaultTarget = 'close'
5
4
 
6
- export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, scale, lag, autoMinMax }) => {
7
- const { verticalOhlcv, instances, lastIndexReplace } = main;
5
+ export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, lag }) => {
6
+ const { verticalOhlcv, instances } = main;
8
7
  const suffix = target === defaultTarget ? '' : `_${target}`;
9
8
  const indicatorKey = `${size}_${stdDev}${suffix}`;
10
9
  let prefix;
@@ -12,7 +11,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
12
11
  // Initialization on the first call.
13
12
  if (index === 0) {
14
13
 
15
- const { priceBased, inputParams, nullArray, verticalOhlcv } = main;
14
+ const { priceBased, inputParams, verticalOhlcv, len, arrayTypes } = main;
16
15
 
17
16
  if (!(target in verticalOhlcv)) {
18
17
  throw new Error(`bollingerBands could not find target "${target}"`);
@@ -46,19 +45,14 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
46
45
 
47
46
  // Set up additional arrays for each range property.
48
47
  for (const rangeKey of range) {
49
- if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
48
+ if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
50
49
  throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
51
50
  }
52
51
  keyNames.push(`${prefix}_range_${rangeKey}`)
53
52
 
54
- if(autoMinMax)
55
- {
56
- main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
57
- }
58
-
59
53
  }
60
54
 
61
- const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, [...nullArray]]))
55
+ const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, new Float64Array(len).fill(NaN)]))
62
56
  Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
63
57
 
64
58
  if(lag > 0)
@@ -66,7 +60,14 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
66
60
  main.lag(keyNames, lag)
67
61
  }
68
62
 
69
- priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`);
63
+ [`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`].forEach(v => {
64
+ priceBased.add(v)
65
+ })
66
+
67
+ for(const key of keyNames)
68
+ {
69
+ arrayTypes[key] = 'Float64Array'
70
+ }
70
71
  }
71
72
 
72
73
  // Derive prefix for subsequent calls if not set.
@@ -79,20 +80,20 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
79
80
  // Update the indicator with the current value.
80
81
  const instance = instances.bollinger_bands.settings[indicatorKey];
81
82
  const value = verticalOhlcv[target][index];
82
- instance.update(value, lastIndexReplace);
83
+ instance.update(value);
83
84
 
84
85
  // Attempt to retrieve the result.
85
- let result = null;
86
+ let result = {};
86
87
  try {
87
88
  result = instance.getResult();
88
89
  } catch (err) {
89
- // If not available, result stays null.
90
+ // If not available, result stays {}.
90
91
  }
91
92
 
92
- // Use null fallbacks for the primary values.
93
- const upper = result?.upper ?? null;
94
- const middle = result?.middle ?? null;
95
- const lower = result?.lower ?? null;
93
+ // Use NaN fallbacks for the primary values.
94
+ const upper = result?.upper ?? NaN;
95
+ const middle = result?.middle ?? NaN;
96
+ const lower = result?.lower ?? NaN;
96
97
 
97
98
  // Always push the indicator outputs.
98
99
  main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
@@ -101,26 +102,19 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
101
102
 
102
103
  // Process height if requested.
103
104
  if (height) {
104
- let heightValue = null;
105
- if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
106
- heightValue = calcMagnitude( ((upper - lower) / lower), 0.001)
105
+ let heightValue = NaN;
106
+ if (!Number.isNaN(lower) && !Number.isNaN(upper)) {
107
+ heightValue = ((upper - lower) / lower)
107
108
  }
108
109
  main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
109
110
  }
110
111
 
111
112
  // Process each range property.
112
113
  for (const rangeKey of range) {
113
- let rangeValue = null;
114
+ let rangeValue = NaN;
114
115
  const priceValue = verticalOhlcv[rangeKey][index];
115
- if (typeof priceValue === 'number' && typeof lower === 'number' && typeof upper === 'number' && (upper - lower) !== 0) {
116
- rangeValue = (autoMinMax)
117
- ? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
118
- : (priceValue - lower) / (upper - lower)
119
-
120
-
121
- if (scale) {
122
- rangeValue = calcMagnitude(rangeValue, scale);
123
- }
116
+ if (!Number.isNaN(priceValue) && !Number.isNaN(lower) && !Number.isNaN(upper)) {
117
+ rangeValue = (priceValue - lower) / (upper - lower)
124
118
  }
125
119
  main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
126
120
  }
@@ -1,12 +1,11 @@
1
- import { calcMagnitude } from "../utilities/numberUtilities.js"
2
1
 
3
- export const donchianChannels = (main, index, size, offset, { height, range, scale, lag, autoMinMax }) => {
2
+ export const donchianChannels = (main, index, size, offset, { height, range, lag }) => {
4
3
  const indicatorKey = `${size}_${offset}`;
5
4
  const { verticalOhlcv, instances, len } = main;
6
5
 
7
6
  // Initialization: create output arrays and indicator instance on the first call.
8
7
  if (index === 0) {
9
- const { inputParams, priceBased, nullArray } = main;
8
+ const { inputParams, priceBased, arrayTypes} = main;
10
9
  const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length;
11
10
  const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel';
12
11
 
@@ -28,19 +27,14 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
28
27
 
29
28
  // Set up additional arrays for each range property.
30
29
  for (const rangeKey of range) {
31
- if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
30
+ if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
32
31
  throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
33
32
  }
34
33
  keyNames.push(`${prefix}_range_${rangeKey}`)
35
34
 
36
- if(autoMinMax)
37
- {
38
- main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
39
- }
40
-
41
35
  }
42
36
 
43
- const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, [...nullArray]]))
37
+ const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, new Float64Array(len).fill(NaN)]))
44
38
  Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
45
39
 
46
40
  if(lag > 0)
@@ -48,12 +42,20 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
48
42
  main.lag(keyNames, lag)
49
43
  }
50
44
 
51
- priceBased.push(`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`);
45
+
46
+ [`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`].forEach(v => {
47
+ priceBased.add(v)
48
+ })
52
49
 
53
50
  instances.donchian_channel.settings[indicatorKey] = {
54
51
  maxDeque: [], // will hold indices for highs in descending order
55
52
  minDeque: [] // will hold indices for lows in ascending order
56
53
  };
54
+
55
+ for(const key of keyNames)
56
+ {
57
+ arrayTypes[key] = 'Float64Array'
58
+ }
57
59
  }
58
60
 
59
61
  const numberOfIndicators = instances.donchian_channel.numberOfIndicators;
@@ -67,16 +69,16 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
67
69
  const endIdx = currentBarIdx + 1; // currentBarIdx is inclusive
68
70
  const startIdx = endIdx - size; // window: [startIdx, endIdx)
69
71
 
70
- // If the window is not fully available, push null values for all computed outputs.
72
+ // If the window is not fully available, push NaN values for all computed outputs.
71
73
  if (startIdx < 0 || endIdx > len) {
72
- main.pushToMain({ index, key: `${subPrefix}_upper`, value: null });
73
- main.pushToMain({ index, key: `${subPrefix}_basis`, value: null });
74
- main.pushToMain({ index, key: `${subPrefix}_lower`, value: null });
74
+ main.pushToMain({ index, key: `${subPrefix}_upper`, value: NaN });
75
+ main.pushToMain({ index, key: `${subPrefix}_basis`, value: NaN });
76
+ main.pushToMain({ index, key: `${subPrefix}_lower`, value: NaN });
75
77
  if (height) {
76
- main.pushToMain({ index, key: `${subPrefix}_height`, value: null });
78
+ main.pushToMain({ index, key: `${subPrefix}_height`, value: NaN });
77
79
  }
78
80
  for (const rangeKey of range) {
79
- main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: null });
81
+ main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: NaN });
80
82
  }
81
83
  return true;
82
84
  }
@@ -103,9 +105,9 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
103
105
  minDeque.push(currentBarIdx);
104
106
 
105
107
  // Retrieve computed values with safety checks.
106
- const upper = maxDeque.length ? highs[maxDeque[0]] : null;
107
- const lower = minDeque.length ? lows[minDeque[0]] : null;
108
- const basis = (typeof upper === 'number' && typeof lower === 'number') ? (upper + lower) / 2 : null;
108
+ const upper = maxDeque.length ? highs[maxDeque[0]] : NaN;
109
+ const lower = minDeque.length ? lows[minDeque[0]] : NaN;
110
+ const basis = (!Number.isNaN(upper) && !Number.isNaN(lower)) ? (upper + lower) / 2 : NaN;
109
111
 
110
112
  // Always push the main indicator values.
111
113
  main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
@@ -114,30 +116,23 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
114
116
 
115
117
  // Process height if enabled.
116
118
  if (height) {
117
- let heightValue = null;
118
- if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
119
- heightValue = calcMagnitude( ((upper - lower) / lower), 0.001)
119
+ let heightValue = NaN;
120
+ if (!Number.isNaN(upper) && !Number.isNaN(lower)) {
121
+ heightValue = ((upper - lower) / lower)
120
122
  }
121
123
  main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
122
124
  }
123
125
 
124
126
  // Process each range property.
125
127
  for (const rangeKey of range) {
126
- let rangeValue = null;
127
- const priceValue = verticalOhlcv[rangeKey][index];
128
+ let rangeValue = NaN;
129
+ const priceValue = verticalOhlcv[rangeKey][index]
128
130
  if (
129
- typeof priceValue === 'number' &&
130
- typeof upper === 'number' &&
131
- typeof lower === 'number' &&
132
- (upper - lower) !== 0
131
+ !Number.isNaN(priceValue) &&
132
+ !Number.isNaN(upper) &&
133
+ !Number.isNaN(lower)
133
134
  ) {
134
- rangeValue = (autoMinMax)
135
- ? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
136
- : (priceValue - lower) / (upper - lower)
137
-
138
- if (scale) {
139
- rangeValue = calcMagnitude(rangeValue, scale);
140
- }
135
+ rangeValue = (priceValue - lower) / (upper - lower)
141
136
  }
142
137
  main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
143
138
  }
@@ -2,16 +2,16 @@ import { FasterEMA, FasterMACD } from 'trading-signals';
2
2
 
3
3
  const defaultTarget = 'close';
4
4
 
5
- export const macd = (main, index, fast, slow, signal, options) => {
6
- const { target } = options;
7
- const { verticalOhlcv, instances, lastIndexReplace } = main;
5
+ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
6
+
7
+ const { verticalOhlcv, instances } = main;
8
8
 
9
9
  // Create an instance key that includes the target if it isn't the default.
10
10
  const instanceKey = `${fast}_${slow}_${signal}${target === defaultTarget ? '' : `_${target}`}`;
11
11
 
12
12
  // Initialization on the first index.
13
13
  if (index === 0) {
14
- const { inputParams, crossPairsList, nullArray, priceBased } = main;
14
+ const { inputParams, crossPairsList, priceBased, len , arrayTypes} = main;
15
15
 
16
16
  if (!verticalOhlcv.hasOwnProperty(target)) {
17
17
  throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`);
@@ -42,12 +42,27 @@ export const macd = (main, index, fast, slow, signal, options) => {
42
42
  );
43
43
 
44
44
  Object.assign(verticalOhlcv, {
45
- [diffKey]: [...nullArray],
46
- [deaKey]: [...nullArray],
47
- [histogramKey]: [...nullArray],
45
+ [diffKey]: new Float64Array(len).fill(NaN),
46
+ [deaKey]: new Float64Array(len).fill(NaN),
47
+ [histogramKey]: new Float64Array(len).fill(NaN),
48
48
  });
49
49
 
50
- priceBased.push(diffKey, deaKey, histogramKey)
50
+ [diffKey, deaKey, histogramKey].forEach(v => {
51
+ priceBased.add(v)
52
+ })
53
+
54
+ const keyNames = [diffKey, deaKey, histogramKey]
55
+
56
+ if(lag > 0)
57
+ {
58
+ main.lag(keyNames, lag)
59
+ }
60
+
61
+ for(const key of keyNames)
62
+ {
63
+ arrayTypes[key] = 'Float64Array'
64
+ }
65
+
51
66
  }
52
67
 
53
68
  const { numberOfIndicators, settings } = instances.macd;
@@ -58,20 +73,19 @@ export const macd = (main, index, fast, slow, signal, options) => {
58
73
 
59
74
  const macdInstance = settings[instanceKey];
60
75
  const value = verticalOhlcv[target][index];
61
- macdInstance.update(value, lastIndexReplace);
76
+ macdInstance.update(value);
62
77
 
63
- let macdResult = null;
78
+ let macdResult = {}
64
79
  try {
65
80
  macdResult = macdInstance.getResult();
66
81
  } catch (err) {
67
- // If the result is unavailable, macdResult remains null.
68
- macdResult = null;
82
+ // If the result is unavailable, macdResult remains NaN.
69
83
  }
70
84
 
71
- // Always push values; use null as fallback when macdResult is missing.
72
- main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : null });
73
- main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : null });
74
- main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : null });
85
+ // Always push values; use NaN as fallback when macdResult is missing.
86
+ main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN });
87
+ main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN });
88
+ main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN });
75
89
 
76
90
  return true;
77
91
  };
@@ -1,11 +1,10 @@
1
1
 
2
2
  import {FasterEMA, FasterSMA, FasterBollingerBands} from 'trading-signals';
3
- import { classifyBoll, calcZScore } from '../utilities/classification.js'
4
3
 
5
4
  const indicatorClasses = {ema: FasterEMA, sma: FasterSMA}
6
5
 
7
- export const movingAverages = (main, index, indicatorName, size, { target, diff }) => {
8
- const { verticalOhlcv, instances, priceBased, lastIndexReplace } = main;
6
+ export const movingAverages = (main, index, indicatorName, size, { target, lag }) => {
7
+ const { verticalOhlcv, instances, priceBased } = main;
9
8
  let suffix =
10
9
  typeof target === 'string' &&
11
10
  verticalOhlcv.hasOwnProperty(target) &&
@@ -13,10 +12,9 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
13
12
  ? `_${target}`
14
13
  : '';
15
14
  const keyName = `${indicatorName}_${size}${suffix}`;
16
- const diffKeyName = `${indicatorName}_${size}_diff${suffix}`;
17
15
 
18
16
  if (index === 0) {
19
- const { nullArray } = main;
17
+ const { len, arrayTypes } = main;
20
18
 
21
19
  if (!verticalOhlcv.hasOwnProperty(target)) {
22
20
  throw new Error(
@@ -29,146 +27,34 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
29
27
  maInstance: new indicatorClasses[indicatorName](size)
30
28
  };
31
29
 
32
- verticalOhlcv[keyName] = [...nullArray];
30
+ verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
33
31
 
34
- // If diff configuration is provided, create the diff indicator.
35
- if (diff !== null) {
36
- instances[keyName].diffInstance = new FasterBollingerBands(
37
- diff.size,
38
- diff.stdDev
39
- );
40
-
41
- const lagDiffArr = [];
42
-
43
- for (const targetKey of diff.targets) {
44
- verticalOhlcv[`${diffKeyName}_${targetKey}`] = [...nullArray];
45
-
46
- if(diff.autoMinMax)
47
- {
48
- main.autoMinMaxKeys.push(`${diffKeyName}_${targetKey}`)
49
- }
50
-
51
- if (diff.lag > 0) {
52
- lagDiffArr.push(`${diffKeyName}_${targetKey}`);
53
- }
54
- }
32
+ priceBased.add(keyName);
55
33
 
56
- if (diff.lag > 0) {
57
- main.lag(lagDiffArr, diff.lag);
58
- }
34
+ if(lag > 0)
35
+ {
36
+ main.lag([keyName], lag)
59
37
  }
60
38
 
61
- priceBased.push(keyName);
39
+ arrayTypes[keyName] = 'Float64Array'
40
+
62
41
  }
63
42
 
64
43
  // Retrieve the current price value.
65
44
  const value = verticalOhlcv[target][index];
66
- const { maInstance, diffInstance } = instances[keyName];
45
+ const { maInstance } = instances[keyName];
67
46
 
68
47
  // Update the moving average instance.
69
- maInstance.update(value, lastIndexReplace);
70
- let currMa = null;
48
+ maInstance.update(value);
49
+ let currMa = NaN;
71
50
  try {
72
51
  currMa = maInstance.getResult();
73
52
  } catch (err) {
74
- currMa = null;
53
+ currMa = NaN;
75
54
  }
76
55
 
77
- // Always push the MA value (even if null).
56
+ // Always push the MA value (even if NaN).
78
57
  main.pushToMain({ index, key: keyName, value: currMa });
79
58
 
80
- // Process the diff values if a diff is requested.
81
- if (diff !== null) {
82
- for (const targetKey of diff.targets) {
83
- // Compute diffValue only if we have a valid MA value.
84
- let diffValue = null;
85
- if (currMa !== null && typeof currMa === 'number') {
86
- diffValue = verticalOhlcv[targetKey][index] - currMa;
87
- }
88
-
89
- // Update the diff indicator only if diffValue is a valid number.
90
- if (diffValue !== null && typeof diffValue === 'number') {
91
- diffInstance.update(Math.abs(diffValue), lastIndexReplace);
92
- }
93
-
94
- let diffBoll = null;
95
- try {
96
- diffBoll = diffInstance.getResult();
97
- } catch (err) {
98
- diffBoll = null;
99
- }
100
-
101
- const classified = classifyBoll(diffValue, diffBoll, diff.scale, diff.autoMinMax);
102
- main.pushToMain({
103
- index,
104
- key: `${diffKeyName}_${targetKey}`,
105
- value: classified
106
- });
107
- }
108
- }
109
-
110
59
  return true;
111
- };
112
-
113
-
114
- export const getMovingAveragesParams = (indicatorName, size, options, validMagnitudeValues) => {
115
- if (!options || typeof options !== 'object') {
116
- throw new Error(`"options" must be an object in ${indicatorName}. eg: {target, height, range}`);
117
- }
118
-
119
- let { target = 'close', diff = null } = options;
120
-
121
- if(typeof target !== 'string')
122
- {
123
- throw new Error(`"target" must be a valid keyName ${indicatorName}.`);
124
- }
125
-
126
- if (typeof size !== 'number' || size <= 0) {
127
- throw new Error(`"size" must be a positive number in ${indicatorName}.`);
128
- }
129
-
130
- //diff can be null, true, false or an object with optional {stdDev, scale, targets, and size}
131
- const optionArgs = { target, diff: null };
132
-
133
- if (diff !== null && (typeof diff === 'object' || (typeof diff === 'boolean' && diff === true))) {
134
-
135
- diff = (diff === true) ? ({}) : diff
136
-
137
- const {
138
- stdDev = 2,
139
- scale = 0.05,
140
- targets = ['close'],
141
- size: diffSize = size,
142
- lag = 0,
143
- autoMinMax = false
144
- } = diff;
145
-
146
- if (typeof stdDev !== 'number' || stdDev <= 0) {
147
- throw new Error(`"diff.stdDev" must be a number greater than 0 in ${indicatorName}.`);
148
- }
149
-
150
- if (typeof scale !== 'number' || !validMagnitudeValues.includes(scale)) {
151
- throw new Error(`"diff.scale" must be a number between 0.01 and 1 in ${indicatorName}.`);
152
- }
153
-
154
- if (typeof diffSize !== 'number' || !Number.isInteger(diffSize) || diffSize <= 0) {
155
- throw new Error(`"diff.size" must be an integer greater than or equal to size in ${indicatorName}.`);
156
- }
157
-
158
- if (!Array.isArray(targets)) {
159
- throw new Error(`"diff.targest" must be an array of keyNames with at least 1 item in ${indicatorName}.`);
160
- }
161
-
162
- if (typeof lag !== 'number' || !Number.isInteger(lag) || lag < 0) {
163
- throw new Error(`"diff.lag" must be an integer greater than 0 in ${indicatorName}.`);
164
- }
165
-
166
- if (typeof autoMinMax !== 'boolean') {
167
- throw new Error(`"diff.autoMinMax" must be an boolean in ${indicatorName}.`);
168
- }
169
-
170
- optionArgs.diff = { stdDev, scale, size: diffSize, targets, lag, autoMinMax};
171
- }
172
-
173
- return optionArgs;
174
60
  }
@@ -1,39 +1,45 @@
1
1
  import { FasterSMA } from 'trading-signals';
2
- import { calcMagnitude } from '../utilities/numberUtilities.js';
3
2
 
4
- export const relativeVolume = (main, index, size = 10, { scale }) => {
3
+ export const relativeVolume = (main, index, size, {lag}) => {
4
+
5
5
  const key = `relative_volume_${size}`;
6
- const { instances, verticalOhlcv, lastIndexReplace } = main;
6
+ const { instances, verticalOhlcv } = main;
7
7
 
8
8
  if (index === 0) {
9
- const { nullArray } = main;
9
+ const { len, arrayTypes } = main;
10
+
10
11
  instances[key] = {
11
12
  instance: new FasterSMA(size),
12
- prevRelativeVolumeSma: null
13
+ prevRelativeVolumeSma: NaN
13
14
  };
14
- verticalOhlcv[key] = [...nullArray];
15
+
16
+ verticalOhlcv[key] = new Float64Array(len).fill(NaN);
17
+
18
+ if(lag > 0)
19
+ {
20
+ main.lag([key], lag)
21
+ }
22
+
23
+ arrayTypes[key] = 'Float64Array'
15
24
  }
16
25
 
17
26
  const value = verticalOhlcv.volume[index];
18
27
  const smaInstance = instances[key].instance;
19
- smaInstance.update(value, lastIndexReplace);
28
+ smaInstance.update(value);
20
29
 
21
- let smaValue = null;
30
+ let smaValue = NaN;
22
31
  try {
23
32
  smaValue = smaInstance.getResult();
24
33
  } catch (err) {
25
- smaValue = null;
34
+
26
35
  }
27
36
 
28
37
  const prevSma = instances[key].prevRelativeVolumeSma;
29
- let currRelativeVolume = null;
38
+ let currRelativeVolume = NaN;
30
39
 
31
40
  // Only calculate relative volume if both current SMA and previous SMA are valid numbers and prevSma is not zero.
32
- if (typeof smaValue === 'number' && typeof prevSma === 'number' && prevSma !== 0) {
41
+ if (!Number.isNaN(smaValue) && !Number.isNaN(prevSma)) {
33
42
  currRelativeVolume = value / prevSma;
34
- if (scale) {
35
- currRelativeVolume = calcMagnitude(currRelativeVolume, scale);
36
- }
37
43
  }
38
44
 
39
45
  main.pushToMain({ index, key, value: currRelativeVolume });