ohlcv-indicators 2.1.9 → 2.2.0

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.
@@ -4,133 +4,137 @@ import { calcMagnitude } from '../utilities/numberUtilities.js';
4
4
  const defaultTarget = 'close'
5
5
 
6
6
  export const bollingerBands = (main, index, size, stdDev, { height, range = [], zScore = [], target, scale }) => {
7
- // Destructure options ensure range and zScore default to empty arrays if not provided.
8
-
9
7
  const { verticalOhlcv, instances, lastIndexReplace } = main;
10
- const suffix = target === defaultTarget ? '' : `_${target}`
11
- const indicatorKey = `${size}_${stdDev}${suffix}`
12
- let prefix
8
+ const suffix = target === defaultTarget ? '' : `_${target}`;
9
+ const indicatorKey = `${size}_${stdDev}${suffix}`;
10
+ let prefix;
13
11
 
14
- // Initialize indicator instances and output arrays on the first call.
12
+ // Initialization on the first call.
15
13
  if (index === 0) {
16
-
17
- const {priceBased, inputParams, nullArray, verticalOhlcv} = main
14
+ const { priceBased, inputParams, nullArray, verticalOhlcv } = main;
18
15
 
19
16
  if (!(target in verticalOhlcv)) {
20
- throw new Error(`bollingerBands could not find target "${target}"`)
17
+ throw new Error(`bollingerBands could not find target "${target}"`);
21
18
  }
22
19
 
23
- const numberOfIndicators = inputParams.filter(o => o.key === 'bollingerBands').length
24
- // Choose a prefix based on the number of indicators.
25
- const prefix = numberOfIndicators > 1
20
+ const numberOfIndicators = inputParams.filter(o => o.key === 'bollingerBands').length;
21
+ prefix = numberOfIndicators > 1
26
22
  ? `bollinger_bands_${indicatorKey}`
27
- : `bollinger_bands${suffix}`
28
-
29
- // Ensure the container exists.
30
- instances.bollinger_bands = {
31
- numberOfIndicators,
32
- settings: {
33
- [indicatorKey]: new FasterBollingerBands(size, stdDev)
34
- }
23
+ : `bollinger_bands${suffix}`;
24
+
25
+ // Only create the container if it doesn't already exist.
26
+ if (!instances.bollinger_bands) {
27
+ instances.bollinger_bands = {
28
+ numberOfIndicators,
29
+ settings: {}
30
+ };
35
31
  }
32
+ // Add (or override) the indicator instance keyed by indicatorKey.
33
+ instances.bollinger_bands.settings[indicatorKey] = new FasterBollingerBands(size, stdDev);
36
34
 
35
+ // Initialize output arrays.
37
36
  Object.assign(verticalOhlcv, {
38
37
  [`${prefix}_upper`]: [...nullArray],
39
38
  [`${prefix}_middle`]: [...nullArray],
40
39
  [`${prefix}_lower`]: [...nullArray]
41
- })
40
+ });
42
41
 
43
- if(height)
44
- {
45
- verticalOhlcv[`${prefix}_height`] = [...nullArray]
42
+ if (height) {
43
+ verticalOhlcv[`${prefix}_height`] = [...nullArray];
46
44
  }
47
45
 
48
46
  // Set up additional arrays for each range property.
49
47
  for (const rangeKey of range) {
50
48
  if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
51
- throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`)
49
+ throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
52
50
  }
53
-
54
- verticalOhlcv[`${prefix}_range_${rangeKey}`] = [...nullArray]
51
+ verticalOhlcv[`${prefix}_range_${rangeKey}`] = [...nullArray];
55
52
  }
56
53
 
57
54
  // Set up additional arrays for each zScore property.
58
55
  for (const zScoreKey of zScore) {
59
56
  if (!(zScoreKey in verticalOhlcv) || !priceBased.includes(zScoreKey)) {
60
- throw new Error(`Invalid zScore item value "${zScoreKey}" for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`)
57
+ throw new Error(`Invalid zScore item value "${zScoreKey}" for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
61
58
  }
62
- verticalOhlcv[`${prefix}_zscore_${zScoreKey}`] = [...nullArray]
59
+ verticalOhlcv[`${prefix}_zscore_${zScoreKey}`] = [...nullArray];
63
60
  }
64
61
 
65
- priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`)
62
+ priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`);
66
63
  }
67
64
 
68
- // For subsequent calls, if prefix wasn’t computed (i.e. index > 0), derive it.
65
+ // Derive prefix for subsequent calls if not set.
69
66
  if (!prefix) {
70
- const num = instances.bollinger_bands.numberOfIndicators
71
- prefix = num > 1 ? `bollinger_bands_${indicatorKey}` : `bollinger_bands${suffix}`
67
+ const num = instances.bollinger_bands.numberOfIndicators;
68
+ prefix = num > 1 ? `bollinger_bands_${indicatorKey}` : `bollinger_bands${suffix}`;
72
69
  }
73
- // Use the same prefix for output keys.
74
- const subPrefix = prefix
70
+ const subPrefix = prefix;
75
71
 
76
- // Retrieve the indicator instance(s) and update with the current value.
77
- const instance = instances.bollinger_bands.settings[indicatorKey]
78
- const value = verticalOhlcv[target][index]
79
- instance.update(value, lastIndexReplace)
72
+ // Update the indicator with the current value.
73
+ const instance = instances.bollinger_bands.settings[indicatorKey];
74
+ const value = verticalOhlcv[target][index];
75
+ instance.update(value, lastIndexReplace);
80
76
 
81
- let result
77
+ // Attempt to retrieve the result.
78
+ let result = null;
82
79
  try {
83
- result = instance.getResult()
80
+ result = instance.getResult();
84
81
  } catch (err) {
85
- // If the result is not available yet, simply exit.
82
+ // If not available, result stays null.
86
83
  }
87
- if (!result) return true
88
84
 
89
- const { upper, middle, lower } = result
90
-
91
- main.pushToMain({index, key: `${subPrefix}_upper`, value: upper})
92
- main.pushToMain({index, key: `${subPrefix}_middle`, value: middle})
93
- main.pushToMain({index, key: `${subPrefix}_lower`, value: lower})
85
+ // Use null fallbacks for the primary values.
86
+ const upper = result?.upper ?? null;
87
+ const middle = result?.middle ?? null;
88
+ const lower = result?.lower ?? null;
94
89
 
90
+ // Always push the indicator outputs.
91
+ main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
92
+ main.pushToMain({ index, key: `${subPrefix}_middle`, value: middle });
93
+ main.pushToMain({ index, key: `${subPrefix}_lower`, value: lower });
95
94
 
96
- // Process height if a height instance exists.
95
+ // Process height if requested.
97
96
  if (height) {
98
- let heightValue = ((upper - lower) / lower)
99
-
100
- if(scale)
101
- {
102
- heightValue = calcMagnitude(heightValue, scale)
97
+ let heightValue = null;
98
+ if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
99
+ heightValue = (upper - lower) / lower;
100
+ if (scale) {
101
+ heightValue = calcMagnitude(heightValue, scale);
102
+ }
103
103
  }
104
-
105
- main.pushToMain({index, key: `${subPrefix}_height`, value: heightValue})
104
+ main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
106
105
  }
107
106
 
108
107
  // Process each range property.
109
108
  for (const rangeKey of range) {
110
- let rangeValue = (verticalOhlcv[rangeKey][index] - lower) / (upper - lower)
111
-
112
- if(scale)
113
- {
114
- rangeValue = calcMagnitude(rangeValue, scale)
109
+ let rangeValue = null;
110
+ const priceValue = verticalOhlcv[rangeKey][index];
111
+ if (typeof priceValue === 'number' && typeof lower === 'number' && typeof upper === 'number' && (upper - lower) !== 0) {
112
+ rangeValue = (priceValue - lower) / (upper - lower);
113
+ if (scale) {
114
+ rangeValue = calcMagnitude(rangeValue, scale);
115
+ }
115
116
  }
116
-
117
- main.pushToMain({index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue})
117
+ main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
118
118
  }
119
119
 
120
120
  // Process each zScore property.
121
121
  for (const zScoreKey of zScore) {
122
- const denominator = upper - lower
123
- let zScoreValue = denominator !== 0
124
- ? (2 * stdDev * (verticalOhlcv[zScoreKey][index] - middle)) / denominator
125
- : 0
126
-
127
- if(denominator !== 0 && scale)
128
- {
129
- zScoreValue = calcMagnitude(zScoreValue, scale)
122
+ let zScoreValue = null;
123
+ const priceValue = verticalOhlcv[zScoreKey][index];
124
+ if (
125
+ typeof priceValue === 'number' &&
126
+ typeof middle === 'number' &&
127
+ typeof upper === 'number' &&
128
+ typeof lower === 'number' &&
129
+ (upper - lower) !== 0
130
+ ) {
131
+ zScoreValue = (2 * stdDev * (priceValue - middle)) / (upper - lower);
132
+ if (scale) {
133
+ zScoreValue = calcMagnitude(zScoreValue, scale);
134
+ }
130
135
  }
131
-
132
- main.pushToMain({index, key: `${subPrefix}_zscore_${zScoreKey}`, value: zScoreValue})
136
+ main.pushToMain({ index, key: `${subPrefix}_zscore_${zScoreKey}`, value: zScoreValue });
133
137
  }
134
138
 
135
- return true
139
+ return true;
136
140
  }
@@ -1,122 +1,130 @@
1
1
  import { calcMagnitude } from "../utilities/numberUtilities.js"
2
2
 
3
-
4
- export const donchianChannels = (main, index, size, offset, { height, range, scale}) => {
5
-
6
- const indicatorKey = `${size}_${offset}`
7
- const {verticalOhlcv, instances, len} = main
3
+ export const donchianChannels = (main, index, size, offset, { height, range, scale }) => {
4
+ const indicatorKey = `${size}_${offset}`;
5
+ const { verticalOhlcv, instances, len } = main;
8
6
 
9
7
  // Initialization: create output arrays and indicator instance on the first call.
10
8
  if (index === 0) {
11
- const {inputParams, priceBased, nullArray} = main
12
- const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length
13
- const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel'
9
+ const { inputParams, priceBased, nullArray } = main;
10
+ const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length;
11
+ const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel';
14
12
 
15
13
  Object.assign(verticalOhlcv, {
16
14
  [`${prefix}_upper`]: [...nullArray],
17
15
  [`${prefix}_basis`]: [...nullArray],
18
16
  [`${prefix}_lower`]: [...nullArray],
19
- })
17
+ });
20
18
 
21
- if(height)
22
- {
23
- verticalOhlcv[`${prefix}_height`] = [...nullArray]
19
+ if (height) {
20
+ verticalOhlcv[`${prefix}_height`] = [...nullArray];
24
21
  }
25
22
 
26
23
  priceBased.push(`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`);
27
24
 
28
25
  if (!instances.hasOwnProperty('donchian_channel')) {
29
- instances.donchian_channel = { numberOfIndicators, settings: {} }
26
+ instances.donchian_channel = { numberOfIndicators, settings: {} };
30
27
  }
31
28
 
32
29
  // Set up additional arrays for each range property.
33
30
  for (const rangeKey of range) {
34
31
  if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
35
- throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`)
32
+ throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
36
33
  }
37
- verticalOhlcv[`${prefix}_range_${rangeKey}`] = [...nullArray]
34
+ verticalOhlcv[`${prefix}_range_${rangeKey}`] = [...nullArray];
38
35
  }
39
36
 
40
37
  instances.donchian_channel.settings[indicatorKey] = {
41
38
  maxDeque: [], // will hold indices for highs in descending order
42
39
  minDeque: [] // will hold indices for lows in ascending order
43
- }
40
+ };
44
41
  }
45
42
 
46
- const numberOfIndicators = instances.donchian_channel.numberOfIndicators
47
- const subPrefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel'
48
- const state = instances.donchian_channel.settings[indicatorKey]
49
- const { maxDeque, minDeque } = state
43
+ const numberOfIndicators = instances.donchian_channel.numberOfIndicators;
44
+ const subPrefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel';
45
+ const state = instances.donchian_channel.settings[indicatorKey];
46
+ const { maxDeque, minDeque } = state;
50
47
 
51
48
  // Compute the “current bar index” for the window.
52
49
  // Note: We use index - offset so that the window is built on the proper part of the array.
53
- const currentBarIdx = index - offset
54
- const endIdx = currentBarIdx + 1 // currentBarIdx is inclusive
55
- const startIdx = endIdx - size // window: [startIdx, endIdx)
56
-
57
- // If the window is not fully available, exit early.
58
- if (startIdx < 0 || endIdx > len) return true
50
+ const currentBarIdx = index - offset;
51
+ const endIdx = currentBarIdx + 1; // currentBarIdx is inclusive
52
+ const startIdx = endIdx - size; // window: [startIdx, endIdx)
53
+
54
+ // If the window is not fully available, push null values for all computed outputs.
55
+ if (startIdx < 0 || endIdx > len) {
56
+ main.pushToMain({ index, key: `${subPrefix}_upper`, value: null });
57
+ main.pushToMain({ index, key: `${subPrefix}_basis`, value: null });
58
+ main.pushToMain({ index, key: `${subPrefix}_lower`, value: null });
59
+ if (height) {
60
+ main.pushToMain({ index, key: `${subPrefix}_height`, value: null });
61
+ }
62
+ for (const rangeKey of range) {
63
+ main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: null });
64
+ }
65
+ return true;
66
+ }
59
67
 
60
- const highs = verticalOhlcv.high
61
- const lows = verticalOhlcv.low
68
+ const highs = verticalOhlcv.high;
69
+ const lows = verticalOhlcv.low;
62
70
 
63
71
  // **Update the maximum deque:**
64
- // Remove indices that are out of the current window.
65
72
  while (maxDeque.length && maxDeque[0] < startIdx) {
66
- maxDeque.shift()
73
+ maxDeque.shift();
67
74
  }
68
- // Remove indices whose corresponding high value is less than or equal to the current high.
69
75
  while (maxDeque.length && highs[maxDeque[maxDeque.length - 1]] <= highs[currentBarIdx]) {
70
- maxDeque.pop()
76
+ maxDeque.pop();
71
77
  }
72
- // Add the current index.
73
- maxDeque.push(currentBarIdx)
78
+ maxDeque.push(currentBarIdx);
74
79
 
75
80
  // **Update the minimum deque:**
76
- // Remove indices that are out of the current window.
77
81
  while (minDeque.length && minDeque[0] < startIdx) {
78
- minDeque.shift()
82
+ minDeque.shift();
79
83
  }
80
- // Remove indices whose corresponding low value is greater than or equal to the current low.
81
84
  while (minDeque.length && lows[minDeque[minDeque.length - 1]] >= lows[currentBarIdx]) {
82
- minDeque.pop()
85
+ minDeque.pop();
83
86
  }
84
- // Add the current index.
85
- minDeque.push(currentBarIdx)
87
+ minDeque.push(currentBarIdx);
86
88
 
87
- // Now the maximum high is at the front of maxDeque and the minimum low is at the front of minDeque.
88
- const upper = highs[maxDeque[0]]
89
- const lower = lows[minDeque[0]]
90
- const basis = (upper + lower) / 2
91
-
92
- // Update the computed values in the output arrays.
89
+ // Retrieve computed values with safety checks.
90
+ const upper = maxDeque.length ? highs[maxDeque[0]] : null;
91
+ const lower = minDeque.length ? lows[minDeque[0]] : null;
92
+ const basis = (typeof upper === 'number' && typeof lower === 'number') ? (upper + lower) / 2 : null;
93
93
 
94
- main.pushToMain({index, key: `${subPrefix}_upper`, value: upper})
95
- main.pushToMain({index, key: `${subPrefix}_basis`, value: basis})
96
- main.pushToMain({index, key: `${subPrefix}_lower`, value: lower})
94
+ // Always push the main indicator values.
95
+ main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
96
+ main.pushToMain({ index, key: `${subPrefix}_basis`, value: basis });
97
+ main.pushToMain({ index, key: `${subPrefix}_lower`, value: lower });
97
98
 
99
+ // Process height if enabled.
98
100
  if (height) {
99
- let heightValue = ((upper - lower) / lower)
100
-
101
- if(scale)
102
- {
103
- heightValue = calcMagnitude(heightValue, scale)
101
+ let heightValue = null;
102
+ if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
103
+ heightValue = (upper - lower) / lower;
104
+ if (scale) {
105
+ heightValue = calcMagnitude(heightValue, scale);
106
+ }
104
107
  }
105
-
106
- main.pushToMain({index, key: `${subPrefix}_height`, value: heightValue})
108
+ main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
107
109
  }
108
110
 
109
111
  // Process each range property.
110
112
  for (const rangeKey of range) {
111
- let rangeValue = (verticalOhlcv[rangeKey][index] - lower) / (upper - lower)
112
-
113
- if(scale)
114
- {
115
- rangeValue = calcMagnitude(rangeValue, scale)
113
+ let rangeValue = null;
114
+ const priceValue = verticalOhlcv[rangeKey][index];
115
+ if (
116
+ typeof priceValue === 'number' &&
117
+ typeof upper === 'number' &&
118
+ typeof lower === 'number' &&
119
+ (upper - lower) !== 0
120
+ ) {
121
+ rangeValue = (priceValue - lower) / (upper - lower);
122
+ if (scale) {
123
+ rangeValue = calcMagnitude(rangeValue, scale);
124
+ }
116
125
  }
117
-
118
- main.pushToMain({index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue})
126
+ main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
119
127
  }
120
128
 
121
- return true
129
+ return true;
122
130
  }
@@ -6,22 +6,22 @@ export const macd = (main, index, fast, slow, signal, options) => {
6
6
  const { target } = options;
7
7
  const { verticalOhlcv, instances, lastIndexReplace } = main;
8
8
 
9
- // Instance key (for internal storage) includes target if not default.
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
- // On the first index, do the initialization.
12
+ // Initialization on the first index.
13
13
  if (index === 0) {
14
- const { inputParams, crossPairsList, nullArray } = main;
14
+ const { inputParams, crossPairsList, nullArray, priceBased } = main;
15
15
 
16
16
  if (!verticalOhlcv.hasOwnProperty(target)) {
17
17
  throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`);
18
18
  }
19
19
 
20
20
  const numberOfIndicators = inputParams.filter(o => o.key === 'macd').length;
21
- // For display keys: if there's more than one indicator, include the parameters in the prefix.
21
+ // Choose a display prefix based on the number of indicators.
22
22
  const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd';
23
23
 
24
- // Build the keys: if target is not the default, append it at the end.
24
+ // Build the keys.
25
25
  const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}`;
26
26
  const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}`;
27
27
  const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}`;
@@ -39,18 +39,18 @@ export const macd = (main, index, fast, slow, signal, options) => {
39
39
  new FasterEMA(fast),
40
40
  new FasterEMA(slow),
41
41
  new FasterEMA(signal)
42
- )
42
+ );
43
43
 
44
44
  Object.assign(verticalOhlcv, {
45
45
  [diffKey]: [...nullArray],
46
46
  [deaKey]: [...nullArray],
47
47
  [histogramKey]: [...nullArray],
48
- })
49
- }
48
+ });
50
49
 
51
- const {numberOfIndicators, settings} = instances.macd
50
+ priceBased.push(diffKey, deaKey, histogramKey)
51
+ }
52
52
 
53
- // Determine display prefix for updating values.
53
+ const { numberOfIndicators, settings } = instances.macd;
54
54
  const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd';
55
55
  const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}`;
56
56
  const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}`;
@@ -60,18 +60,18 @@ export const macd = (main, index, fast, slow, signal, options) => {
60
60
  const value = verticalOhlcv[target][index];
61
61
  macdInstance.update(value, lastIndexReplace);
62
62
 
63
- let macdResult;
63
+ let macdResult = null;
64
64
  try {
65
65
  macdResult = macdInstance.getResult();
66
66
  } catch (err) {
67
- // Do nothing if no result is available.
67
+ // If the result is unavailable, macdResult remains null.
68
+ macdResult = null;
68
69
  }
69
70
 
70
- if (macdResult) {
71
- main.pushToMain({ index, key: diffKey, value: macdResult.macd });
72
- main.pushToMain({ index, key: deaKey, value: macdResult.signal });
73
- main.pushToMain({ index, key: histogramKey, value: macdResult.histogram });
74
- }
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 });
75
75
 
76
76
  return true;
77
77
  };