ohlcv-indicators 3.3.1 → 3.3.4

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,100 @@
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
+ // Processes the inputParams into a list of indicator calls
33
+ const processIndicatorCalls = inputParams =>
34
+ Array.isArray(inputParams)
35
+ ? inputParams.map(({ key, params }) => ({
36
+ key,
37
+ fn: indicatorFunctions[key],
38
+ args: params
39
+ }))
40
+ : [];
41
+
42
+ export const mainLoop = (input, main) => {
43
+ const { len, inputParams, priceBased, precisionMultiplier, arrayTypes } = main;
44
+
45
+ validateInputParams(main)
46
+
47
+ for(const key of Object.keys(main.inputTypes))
48
+ {
49
+ main.verticalOhlcv[key] = buildArray(arrayTypes[key], len)
50
+ }
51
+
52
+ // Prepare the list of indicator function calls
53
+ const indicatorCalls = processIndicatorCalls(inputParams);
54
+
55
+ // Process each row in the input
56
+ for (let x = 0; x < len; x++) {
57
+ const curr = input[x]
58
+
59
+ processThisRow({x, main, curr, indicatorCalls, priceBased, precisionMultiplier})
60
+ }
61
+ }
62
+
63
+ const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMultiplier}) => {
64
+
65
+
66
+ for(const [key, formaterKey] of Object.entries(main.inputTypes))
67
+ {
68
+ const value = curr[key]
69
+
70
+ if(numberFormater.hasOwnProperty(formaterKey))
71
+ {
72
+ let formatedValue = numberFormater[formaterKey](value)
73
+
74
+ if(precisionMultiplier > 1 && priceBased.includes(key))
75
+ {
76
+ formatedValue = formatedValue * precisionMultiplier
77
+ }
78
+
79
+ main.pushToMain({index: x, key, value: formatedValue})
80
+ }
81
+ else if(dateFormaters.hasOwnProperty(formaterKey))
82
+ {
83
+ main.pushToMain({index: x, key, value: dateFormaters[formaterKey](value)})
84
+ }
85
+ else
86
+ {
87
+ main.pushToMain({index: x, key, value})
88
+ }
89
+ }
90
+
91
+ // Run all indicator functions except for the ones processed later
92
+ for (const { key, fn, args } of indicatorCalls) {
93
+ if (['crossPairs', 'lag'].includes(key)) continue;
94
+ fn(main, x, ...args)
95
+ }
96
+
97
+ // Process these indicators separately (ensuring their execution order)
98
+ lag(main, x)
99
+ crossPairs(main, x)
100
+ }
@@ -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}"`);
@@ -51,14 +50,9 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
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)
@@ -67,6 +61,11 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
67
61
  }
68
62
 
69
63
  priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`);
64
+
65
+ for(const key of keyNames)
66
+ {
67
+ arrayTypes[key] = 'Float64Array'
68
+ }
70
69
  }
71
70
 
72
71
  // Derive prefix for subsequent calls if not set.
@@ -79,20 +78,20 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
79
78
  // Update the indicator with the current value.
80
79
  const instance = instances.bollinger_bands.settings[indicatorKey];
81
80
  const value = verticalOhlcv[target][index];
82
- instance.update(value, lastIndexReplace);
81
+ instance.update(value);
83
82
 
84
83
  // Attempt to retrieve the result.
85
- let result = null;
84
+ let result = {};
86
85
  try {
87
86
  result = instance.getResult();
88
87
  } catch (err) {
89
- // If not available, result stays null.
88
+ // If not available, result stays {}.
90
89
  }
91
90
 
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;
91
+ // Use NaN fallbacks for the primary values.
92
+ const upper = result?.upper ?? NaN;
93
+ const middle = result?.middle ?? NaN;
94
+ const lower = result?.lower ?? NaN;
96
95
 
97
96
  // Always push the indicator outputs.
98
97
  main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
@@ -101,26 +100,19 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
101
100
 
102
101
  // Process height if requested.
103
102
  if (height) {
104
- let heightValue = null;
105
- if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
106
- heightValue = calcMagnitude( ((upper - lower) / lower), 0.001)
103
+ let heightValue = NaN;
104
+ if (!Number.isNaN(lower) && !Number.isNaN(upper)) {
105
+ heightValue = ((upper - lower) / lower)
107
106
  }
108
107
  main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
109
108
  }
110
109
 
111
110
  // Process each range property.
112
111
  for (const rangeKey of range) {
113
- let rangeValue = null;
112
+ let rangeValue = NaN;
114
113
  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
- }
114
+ if (!Number.isNaN(priceValue) && !Number.isNaN(lower) && !Number.isNaN(upper)) {
115
+ rangeValue = (priceValue - lower) / (upper - lower)
124
116
  }
125
117
  main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
126
118
  }
@@ -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
 
@@ -33,14 +32,9 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
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)
@@ -54,6 +48,11 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
54
48
  maxDeque: [], // will hold indices for highs in descending order
55
49
  minDeque: [] // will hold indices for lows in ascending order
56
50
  };
51
+
52
+ for(const key of keyNames)
53
+ {
54
+ arrayTypes[key] = 'Float64Array'
55
+ }
57
56
  }
58
57
 
59
58
  const numberOfIndicators = instances.donchian_channel.numberOfIndicators;
@@ -67,16 +66,16 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
67
66
  const endIdx = currentBarIdx + 1; // currentBarIdx is inclusive
68
67
  const startIdx = endIdx - size; // window: [startIdx, endIdx)
69
68
 
70
- // If the window is not fully available, push null values for all computed outputs.
69
+ // If the window is not fully available, push NaN values for all computed outputs.
71
70
  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 });
71
+ main.pushToMain({ index, key: `${subPrefix}_upper`, value: NaN });
72
+ main.pushToMain({ index, key: `${subPrefix}_basis`, value: NaN });
73
+ main.pushToMain({ index, key: `${subPrefix}_lower`, value: NaN });
75
74
  if (height) {
76
- main.pushToMain({ index, key: `${subPrefix}_height`, value: null });
75
+ main.pushToMain({ index, key: `${subPrefix}_height`, value: NaN });
77
76
  }
78
77
  for (const rangeKey of range) {
79
- main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: null });
78
+ main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: NaN });
80
79
  }
81
80
  return true;
82
81
  }
@@ -103,9 +102,9 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
103
102
  minDeque.push(currentBarIdx);
104
103
 
105
104
  // 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;
105
+ const upper = maxDeque.length ? highs[maxDeque[0]] : NaN;
106
+ const lower = minDeque.length ? lows[minDeque[0]] : NaN;
107
+ const basis = (!Number.isNaN(upper) && !Number.isNaN(lower)) ? (upper + lower) / 2 : NaN;
109
108
 
110
109
  // Always push the main indicator values.
111
110
  main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
@@ -114,30 +113,23 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
114
113
 
115
114
  // Process height if enabled.
116
115
  if (height) {
117
- let heightValue = null;
118
- if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
119
- heightValue = calcMagnitude( ((upper - lower) / lower), 0.001)
116
+ let heightValue = NaN;
117
+ if (!Number.isNaN(upper) && !Number.isNaN(lower)) {
118
+ heightValue = ((upper - lower) / lower)
120
119
  }
121
120
  main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
122
121
  }
123
122
 
124
123
  // Process each range property.
125
124
  for (const rangeKey of range) {
126
- let rangeValue = null;
127
- const priceValue = verticalOhlcv[rangeKey][index];
125
+ let rangeValue = NaN;
126
+ const priceValue = verticalOhlcv[rangeKey][index]
128
127
  if (
129
- typeof priceValue === 'number' &&
130
- typeof upper === 'number' &&
131
- typeof lower === 'number' &&
132
- (upper - lower) !== 0
128
+ !Number.isNaN(priceValue) &&
129
+ !Number.isNaN(upper) &&
130
+ !Number.isNaN(lower)
133
131
  ) {
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
- }
132
+ rangeValue = (priceValue - lower) / (upper - lower)
141
133
  }
142
134
  main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
143
135
  }
@@ -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,25 @@ 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
50
  priceBased.push(diffKey, deaKey, histogramKey)
51
+
52
+ const keyNames = [diffKey, deaKey, histogramKey]
53
+
54
+ if(lag > 0)
55
+ {
56
+ main.lag(keyNames, lag)
57
+ }
58
+
59
+ for(const key of keyNames)
60
+ {
61
+ arrayTypes[key] = 'Float64Array'
62
+ }
63
+
51
64
  }
52
65
 
53
66
  const { numberOfIndicators, settings } = instances.macd;
@@ -58,20 +71,19 @@ export const macd = (main, index, fast, slow, signal, options) => {
58
71
 
59
72
  const macdInstance = settings[instanceKey];
60
73
  const value = verticalOhlcv[target][index];
61
- macdInstance.update(value, lastIndexReplace);
74
+ macdInstance.update(value);
62
75
 
63
- let macdResult = null;
76
+ let macdResult = {}
64
77
  try {
65
78
  macdResult = macdInstance.getResult();
66
79
  } catch (err) {
67
- // If the result is unavailable, macdResult remains null.
68
- macdResult = null;
80
+ // If the result is unavailable, macdResult remains NaN.
69
81
  }
70
82
 
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 });
83
+ // Always push values; use NaN as fallback when macdResult is missing.
84
+ main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN });
85
+ main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN });
86
+ main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN });
75
87
 
76
88
  return true;
77
89
  };
@@ -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(
@@ -26,152 +24,36 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
26
24
 
27
25
  // Create the main moving average instance.
28
26
  instances[keyName] = {
29
- maInstance: new indicatorClasses[indicatorName](size),
30
- arrayChunk: {[diffKeyName]: []}
27
+ maInstance: new indicatorClasses[indicatorName](size)
31
28
  };
32
29
 
33
- verticalOhlcv[keyName] = [...nullArray];
34
-
35
- // If diff configuration is provided, create the diff indicator.
36
- if (diff !== null) {
37
- instances[keyName].diffInstance = new FasterBollingerBands(
38
- diff.size,
39
- diff.stdDev
40
- );
41
-
42
- const lagDiffArr = [];
43
-
44
- for (const targetKey of diff.targets) {
45
- verticalOhlcv[`${diffKeyName}_${targetKey}`] = [...nullArray];
46
-
47
- if(diff.autoMinMax)
48
- {
49
- main.autoMinMaxKeys.push(`${diffKeyName}_${targetKey}`)
50
- }
51
-
52
- if (diff.lag > 0) {
53
- lagDiffArr.push(`${diffKeyName}_${targetKey}`);
54
- }
55
- }
30
+ verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
31
+ priceBased.push(keyName);
56
32
 
57
- if (diff.lag > 0) {
58
- main.lag(lagDiffArr, diff.lag);
59
- }
33
+ if(lag > 0)
34
+ {
35
+ main.lag([keyName], lag)
60
36
  }
61
37
 
62
- priceBased.push(keyName);
38
+ arrayTypes[keyName] = 'Float64Array'
39
+
63
40
  }
64
41
 
65
42
  // Retrieve the current price value.
66
43
  const value = verticalOhlcv[target][index];
67
- const { maInstance, diffInstance, arrayChunk } = instances[keyName];
44
+ const { maInstance } = instances[keyName];
68
45
 
69
46
  // Update the moving average instance.
70
- maInstance.update(value, lastIndexReplace);
71
- let currMa = null;
47
+ maInstance.update(value);
48
+ let currMa = NaN;
72
49
  try {
73
50
  currMa = maInstance.getResult();
74
51
  } catch (err) {
75
- currMa = null;
52
+ currMa = NaN;
76
53
  }
77
54
 
78
- // Always push the MA value (even if null).
55
+ // Always push the MA value (even if NaN).
79
56
  main.pushToMain({ index, key: keyName, value: currMa });
80
57
 
81
- // Process the diff values if a diff is requested.
82
- if (diff !== null) {
83
- for (const targetKey of diff.targets) {
84
- // Compute diffValue only if we have a valid MA value.
85
- let diffValue = null;
86
- let zScore = null
87
- if (currMa !== null && typeof currMa === 'number') {
88
- diffValue = verticalOhlcv[targetKey][index] - currMa;
89
- }
90
-
91
- // Update the diff indicator only if diffValue is a valid number.
92
- if (diffValue !== null && typeof diffValue === 'number') {
93
- diffInstance.update(Math.abs(diffValue), lastIndexReplace);
94
- zScore = calcZScore(arrayChunk, diffKeyName, diff.size, diffValue, lastIndexReplace)
95
- }
96
-
97
- let diffBoll = null;
98
- try {
99
- diffBoll = diffInstance.getResult();
100
- } catch (err) {
101
- diffBoll = null;
102
- }
103
-
104
- const classified = classifyBoll(diffValue, diffBoll, diff.scale, diff.autoMinMax);
105
- main.pushToMain({
106
- index,
107
- key: `${diffKeyName}_${targetKey}`,
108
- value: zScore
109
- });
110
- }
111
- }
112
-
113
58
  return true;
114
- };
115
-
116
-
117
- export const getMovingAveragesParams = (indicatorName, size, options, validMagnitudeValues) => {
118
- if (!options || typeof options !== 'object') {
119
- throw new Error(`"options" must be an object in ${indicatorName}. eg: {target, height, range}`);
120
- }
121
-
122
- let { target = 'close', diff = null } = options;
123
-
124
- if(typeof target !== 'string')
125
- {
126
- throw new Error(`"target" must be a valid keyName ${indicatorName}.`);
127
- }
128
-
129
- if (typeof size !== 'number' || size <= 0) {
130
- throw new Error(`"size" must be a positive number in ${indicatorName}.`);
131
- }
132
-
133
- //diff can be null, true, false or an object with optional {stdDev, scale, targets, and size}
134
- const optionArgs = { target, diff: null };
135
-
136
- if (diff !== null && (typeof diff === 'object' || (typeof diff === 'boolean' && diff === true))) {
137
-
138
- diff = (diff === true) ? ({}) : diff
139
-
140
- const {
141
- stdDev = 2,
142
- scale = 0.05,
143
- targets = ['close'],
144
- size: diffSize = size,
145
- lag = 0,
146
- autoMinMax = false
147
- } = diff;
148
-
149
- if (typeof stdDev !== 'number' || stdDev <= 0) {
150
- throw new Error(`"diff.stdDev" must be a number greater than 0 in ${indicatorName}.`);
151
- }
152
-
153
- if (typeof scale !== 'number' || !validMagnitudeValues.includes(scale)) {
154
- throw new Error(`"diff.scale" must be a number between 0.01 and 1 in ${indicatorName}.`);
155
- }
156
-
157
- if (typeof diffSize !== 'number' || !Number.isInteger(diffSize) || diffSize <= 0) {
158
- throw new Error(`"diff.size" must be an integer greater than or equal to size in ${indicatorName}.`);
159
- }
160
-
161
- if (!Array.isArray(targets)) {
162
- throw new Error(`"diff.targest" must be an array of keyNames with at least 1 item in ${indicatorName}.`);
163
- }
164
-
165
- if (typeof lag !== 'number' || !Number.isInteger(lag) || lag < 0) {
166
- throw new Error(`"diff.lag" must be an integer greater than 0 in ${indicatorName}.`);
167
- }
168
-
169
- if (typeof autoMinMax !== 'boolean') {
170
- throw new Error(`"diff.autoMinMax" must be an boolean in ${indicatorName}.`);
171
- }
172
-
173
- optionArgs.diff = { stdDev, scale, size: diffSize, targets, lag, autoMinMax};
174
- }
175
-
176
- return optionArgs;
177
59
  }
@@ -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 });