ohlcv-indicators 2.1.4 → 2.1.6

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.
package/index.js CHANGED
@@ -3,7 +3,10 @@ import { correlation } from './src/studies/correlation.js'
3
3
  import { setIndicatorsFromInputParams } from './src/utilities/setIndicatorsFromInputParams.js'
4
4
  import { validateDate } from './src/utilities/validators.js'
5
5
  import { isAlreadyComputed } from './src/utilities/validators.js'
6
- import { divideByMultiplier } from './src/utilities/precision.js'
6
+ import { divideByMultiplier } from './src/utilities/numberUtilities.js'
7
+ import { getMovingAveragesParams } from './src/moving-averages/movingAverages.js'
8
+
9
+ const validMagnitudeValues = [0.01, 0.02, 0.025, 0.05, 0.1, 0.20, 0.25, 0.5, 1]
7
10
 
8
11
  export default class OHLCV_INDICATORS {
9
12
  constructor({input, ticker = null, precision = true}) {
@@ -206,7 +209,7 @@ export default class OHLCV_INDICATORS {
206
209
  {
207
210
  const key = colKeys[x]
208
211
 
209
- if(this.priceBased.find(v => key.startsWith(v)))
212
+ if(this.priceBased.find(v => key === v))
210
213
  {
211
214
  this.priceBased.push(key)
212
215
  }
@@ -215,11 +218,18 @@ export default class OHLCV_INDICATORS {
215
218
  return this;
216
219
  }
217
220
 
218
- relativeVolume(size) {
221
+ relativeVolume(size, options = {}) {
219
222
 
220
223
  isAlreadyComputed(this)
221
224
 
222
- this.inputParams.push({key: 'relativeVolume', params: [size]})
225
+ const {scale = null} = options
226
+
227
+ if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
228
+
229
+ throw new Error(`"scale" value in relativeVolume must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
230
+ }
231
+
232
+ this.inputParams.push({key: 'relativeVolume', params: [size, {scale}]})
223
233
 
224
234
  return this
225
235
  }
@@ -228,19 +238,9 @@ export default class OHLCV_INDICATORS {
228
238
 
229
239
  isAlreadyComputed(this)
230
240
 
231
- if(!options || typeof options !== 'object')
232
- {
233
- throw new Error('"options" must be an object in ema. eg: {target, height, range}');
234
- }
235
-
236
- const {target = 'close'} = options
237
-
238
- if (typeof size !== 'number' || size <= 0) {
239
- throw new Error('"size" must be a positive number in ema.');
240
- }
241
+ const optionArgs = getMovingAveragesParams('ema', size, options, validMagnitudeValues)
241
242
 
242
- this.inputParams.push({key: 'ema', params: [size, target]})
243
- this.priceBased.push(`ema_${size}`)
243
+ this.inputParams.push({key: 'movingAverages', params: ['ema', size, optionArgs]})
244
244
 
245
245
  return this
246
246
  }
@@ -248,23 +248,14 @@ export default class OHLCV_INDICATORS {
248
248
 
249
249
  isAlreadyComputed(this)
250
250
 
251
- if(!options || typeof options !== 'object')
252
- {
253
- throw new Error('"options" must be an object in sma. eg: {target, height, range}');
254
- }
255
-
256
- const {target = 'close'} = options
257
-
258
- if (typeof size !== 'number' || size <= 0) {
259
- throw new Error('"size" must be a positive number in sma.');
260
- }
261
-
251
+ const optionArgs = getMovingAveragesParams('sma', size, options, validMagnitudeValues)
262
252
 
263
- this.inputParams.push({key: 'sma', params: [size, target]})
264
- this.priceBased.push(`sma_${size}`)
253
+ this.inputParams.push({key: 'movingAverages', params: ['sma', size, optionArgs]})
265
254
 
266
- return this
255
+ return this
267
256
  }
257
+
258
+
268
259
  macd(fast = 12, slow = 26, signal = 9, options = {}) {
269
260
 
270
261
  isAlreadyComputed(this)
@@ -300,7 +291,7 @@ export default class OHLCV_INDICATORS {
300
291
  throw new Error('"options" must be an object in bollingerBands. eg: {target, height, range}');
301
292
  }
302
293
 
303
- const {target = 'close', height = false, range = [], zScore = []} = options
294
+ const {target = 'close', height = false, range = [], zScore = [], scale = null} = options
304
295
 
305
296
  // Validate size and times
306
297
  if (typeof size !== 'number' || size <= 0) {
@@ -317,15 +308,23 @@ export default class OHLCV_INDICATORS {
317
308
  }
318
309
  if (typeof height !== 'boolean') {
319
310
  throw new Error('"height" must be a boolean in bollingerBands.');
320
- }
311
+ }
312
+ else
313
+ {
314
+ if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
315
+
316
+ throw new Error(`"scale" value in bollingerBands must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
317
+ }
318
+ }
319
+
320
+
321
321
 
322
- this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, range, zScore}]});
323
- this.priceBased.push('bollinger_bands_middle', 'bollinger_bands_upper', 'bollinger_bands_lower');
322
+ this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range, zScore}]});
324
323
 
325
324
  return this;
326
325
  }
327
326
 
328
- rsi(size)
327
+ rsi(size, options = {})
329
328
  {
330
329
  isAlreadyComputed(this)
331
330
 
@@ -334,63 +333,97 @@ export default class OHLCV_INDICATORS {
334
333
  throw new Error('"size" must be a positive number in rsi.');
335
334
  }
336
335
 
337
- this.inputParams.push({key: 'rsi', params: [size]})
336
+ const {scale = null, target = 'close'} = options
337
+
338
+ if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
339
+
340
+ throw new Error(`"scale" value in rsi must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
341
+ }
342
+
343
+ this.inputParams.push({key: 'rsi', params: [size, {scale, target}]})
338
344
 
339
345
  return this
340
346
  }
341
- donchianChannels(size = 20, offset = 0, options = {})
342
- {
343
- isAlreadyComputed(this)
344
-
347
+ donchianChannels(size = 20, offset = 0, options = {}) {
348
+ isAlreadyComputed(this);
349
+
345
350
  if (typeof size !== 'number' || size <= 0) {
346
- throw new Error('"size" must be a positive number or 0 in donchianChannels.');
351
+ throw new Error('"size" must be a positive number greater than 0 in donchianChannels.');
347
352
  }
348
-
349
- if (typeof offset !== 'number' || offset <= 0) {
350
- throw new Error('"offset" must be a positive number or 0 in donchianChannels.');
353
+
354
+ if (typeof offset !== 'number' || offset < 0) {
355
+ throw new Error('"offset" must be a number greater than or equal to 0 in donchianChannels.');
351
356
  }
357
+
358
+ const { height = false, range = [], scale = null } = options;
359
+
360
+ if (!Array.isArray(range)) {
361
+ throw new Error('If set, "range" must be an array of column names in donchianChannels.');
362
+ }
363
+
364
+ if (typeof height !== 'boolean') {
365
+ throw new Error('"height" must be a boolean in donchianChannels.');
366
+ }
367
+ else
368
+ {
352
369
 
353
- const {height = false, range = false} = options
354
-
355
- this.inputParams.push({key: 'donchianChannels', params: [size, offset, {height, range}]})
356
- this.priceBased.push('donchian_channel_upper', 'donchian_channel_lower', 'donchian_channel_basis')
370
+ if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
357
371
 
358
- return this
372
+ throw new Error(`"scale" value in donchianChannels must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
373
+ }
374
+ }
375
+
376
+ this.inputParams.push({ key: 'donchianChannels', params: [size, offset, { height, range, scale }] });
377
+
378
+ return this;
359
379
  }
360
- candleStudies(size = 20, options = {})
361
- {
362
-
363
- isAlreadyComputed(this)
380
+
364
381
 
382
+ candleStudies(size = 20, stdDev = 2, options = {}) {
383
+ isAlreadyComputed(this);
384
+
365
385
  if (typeof size !== 'number' || size <= 0) {
366
- throw new Error('"size" must be a positive number or 0 in candleStudies.');
386
+ throw new Error('"size" must be a positive number greater than 0 in candleStudies.');
387
+ }
388
+
389
+ if (typeof stdDev !== 'number' || stdDev <= 0) {
390
+ throw new Error('"stdDev" must be a positive number greater than 0 in candleStudies.');
367
391
  }
392
+
393
+ const { lag = 0, scale = 0.05 } = options;
368
394
 
369
- const {stdDev = 2, lag = 0} = options
370
395
 
371
- if(typeof stdDev !== 'number' || (typeof stdDev === 'number' && stdDev <= 0))
372
- {
373
- throw new Error('"stdDev" must be a positive number greater than 0 in candleStudies.');
374
- }
396
+ if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
375
397
 
376
- this.inputParams.push({key: 'candleStudies', params: [size, stdDev, lag]})
377
- return this
378
- }
398
+ throw new Error(`"scale" value in candleStudies must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
399
+ }
400
+
401
+ this.inputParams.push({ key: 'candleStudies', params: [size, stdDev, {lag, scale}] });
402
+ return this;
403
+ }
404
+
379
405
 
380
- volumeOscillator(fastSize = 5, slowSize = 10)
406
+ volumeOscillator(fastSize = 5, slowSize = 10, options = {})
381
407
  {
382
408
 
383
409
  isAlreadyComputed(this)
384
410
 
385
- if (typeof fastSize !== 'number' || fastSize <= 0) {
386
- throw new Error('fastSize" must be a positive number in volumeOscillator.');
411
+ if (typeof fastSize !== 'number' || fastSize <= 1) {
412
+ throw new Error('fastSize" must be a positive number greater than 1 in volumeOscillator.');
387
413
  }
388
414
 
389
415
  if (typeof slowSize !== 'number' || slowSize <= fastSize) {
390
416
  throw new Error('"slowSize" must be a positive number greater than the "fastSize" in volumeOscillator.');
391
417
  }
392
418
 
393
- this.inputParams.push({key: 'volumeOscillator', params: [fastSize, slowSize]})
419
+ const {scale = null} = options
420
+
421
+ if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
422
+
423
+ throw new Error(`"scale" value in volumeOscillator must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
424
+ }
425
+
426
+ this.inputParams.push({key: 'volumeOscillator', params: [fastSize, slowSize, {scale}]})
394
427
  return this
395
428
  }
396
429
  dateTime()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "A JavaScript library featuring optimized trading indicators for OHLCV arrays [{open, high, low, close, value}]. This library is optimized for backtesting and trading bot development.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,12 +1,12 @@
1
1
  import { FasterBollingerBands } from 'trading-signals'
2
+ import { calcMagnitude } from '../utilities/numberUtilities.js';
2
3
 
3
4
  const defaultTarget = 'close'
4
5
 
5
- export const bollingerBands = (main, index, size, stdDev, options) => {
6
+ export const bollingerBands = (main, index, size, stdDev, { height, range = [], zScore = [], target, scale }) => {
6
7
  // Destructure options ensure range and zScore default to empty arrays if not provided.
7
8
 
8
9
  const { verticalOhlcv, instances, lastIndexReplace } = main;
9
- const { height, range = [], zScore = [], target } = options
10
10
  const suffix = target === defaultTarget ? '' : `_${target}`
11
11
  const indicatorKey = `${size}_${stdDev}${suffix}`
12
12
  let prefix // Will be computed during initialization
@@ -47,10 +47,12 @@ export const bollingerBands = (main, index, size, stdDev, options) => {
47
47
  ...(height && { [`${prefix}_height`]: [...nullArray] })
48
48
  }
49
49
 
50
+ main.priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`)
51
+
50
52
  // Set up additional arrays for each range property.
51
53
  for (const rangeKey of range) {
52
54
  if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
53
- throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(main.priceBased)}`)
55
+ throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`)
54
56
  }
55
57
  ohlcvSetup[`${prefix}_range_${rangeKey}`] = [...nullArray]
56
58
  }
@@ -58,7 +60,7 @@ export const bollingerBands = (main, index, size, stdDev, options) => {
58
60
  // Set up additional arrays for each zScore property.
59
61
  for (const zScoreKey of zScore) {
60
62
  if (!(zScoreKey in verticalOhlcv) || !priceBased.includes(zScoreKey)) {
61
- throw new Error(`Invalid zScore item value "${zScoreKey}" for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(main.priceBased)}`)
63
+ throw new Error(`Invalid zScore item value "${zScoreKey}" for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`)
62
64
  }
63
65
  ohlcvSetup[`${prefix}_zscore_${zScoreKey}`] = [...nullArray]
64
66
  }
@@ -97,23 +99,40 @@ export const bollingerBands = (main, index, size, stdDev, options) => {
97
99
 
98
100
  // Process height if a height instance exists.
99
101
  if (height) {
100
- const heightValue = ((upper - lower) / lower) * 100
102
+ let heightValue = ((upper - lower) / lower)
103
+
104
+ if(scale)
105
+ {
106
+ heightValue = calcMagnitude(heightValue, scale)
107
+ }
108
+
101
109
  main.pushToMain({index, key: `${subPrefix}_height`, value: heightValue})
102
110
  }
103
111
 
104
112
  // Process each range property.
105
113
  for (const rangeKey of range) {
106
- const rangeValue = (verticalOhlcv[rangeKey][index] - lower) / (upper - lower)
114
+ let rangeValue = (verticalOhlcv[rangeKey][index] - lower) / (upper - lower)
115
+
116
+ if(scale)
117
+ {
118
+ rangeValue = calcMagnitude(rangeValue, scale)
119
+ }
120
+
107
121
  main.pushToMain({index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue})
108
122
  }
109
123
 
110
124
  // Process each zScore property.
111
125
  for (const zScoreKey of zScore) {
112
126
  const denominator = upper - lower
113
- const zScoreValue = denominator !== 0
127
+ let zScoreValue = denominator !== 0
114
128
  ? (2 * stdDev * (verticalOhlcv[zScoreKey][index] - middle)) / denominator
115
129
  : 0
116
130
 
131
+ if(denominator !== 0 && scale)
132
+ {
133
+ zScoreValue = calcMagnitude(zScoreValue, scale)
134
+ }
135
+
117
136
  main.pushToMain({index, key: `${subPrefix}_zscore_${zScoreKey}`, value: zScoreValue})
118
137
  }
119
138
 
@@ -1,41 +1,47 @@
1
+ import { calcMagnitude } from "../utilities/numberUtilities.js"
2
+
3
+
4
+ export const donchianChannels = (main, index, size, offset, { height, range, scale}) => {
1
5
 
2
- export const donchianChannels = (main, index, size, offset, options) => {
3
- const { height, range } = options
4
6
  const indicatorKey = `${size}_${offset}`
7
+ const {verticalOhlcv, instances, len} = main
5
8
 
6
9
  // Initialization: create output arrays and indicator instance on the first call.
7
10
  if (index === 0) {
8
- const numberOfIndicators = main.inputParams.filter(o => o.key === 'donchianChannels').length
11
+ const {inputParams, nullArray, priceBased} = main
12
+ const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length
9
13
  const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel'
10
14
 
11
- Object.assign(main.verticalOhlcv, {
12
- [`${prefix}_upper`]: [...main.nullArray],
13
- [`${prefix}_basis`]: [...main.nullArray],
14
- [`${prefix}_lower`]: [...main.nullArray],
15
- ...(height && { [`${prefix}_height`]: [...main.nullArray] })
15
+ Object.assign(verticalOhlcv, {
16
+ [`${prefix}_upper`]: [...nullArray],
17
+ [`${prefix}_basis`]: [...nullArray],
18
+ [`${prefix}_lower`]: [...nullArray],
19
+ ...(height && { [`${prefix}_height`]: [...nullArray] })
16
20
  })
17
21
 
18
- if (!main.instances.hasOwnProperty('donchian_channel')) {
19
- main.instances.donchian_channel = { numberOfIndicators, settings: {} }
22
+ priceBased.push(`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`);
23
+
24
+ if (!instances.hasOwnProperty('donchian_channel')) {
25
+ instances.donchian_channel = { numberOfIndicators, settings: {} }
20
26
  }
21
27
 
22
28
  // Set up additional arrays for each range property.
23
29
  for (const rangeKey of range) {
24
- if (!(rangeKey in main.verticalOhlcv) || !main.priceBased.includes(rangeKey)) {
25
- throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(main.priceBased)}`)
30
+ if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
31
+ throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`)
26
32
  }
27
- main.verticalOhlcv[`${prefix}_range_${rangeKey}`] = [...main.nullArray]
33
+ verticalOhlcv[`${prefix}_range_${rangeKey}`] = [...nullArray]
28
34
  }
29
35
 
30
- main.instances.donchian_channel.settings[indicatorKey] = {
36
+ instances.donchian_channel.settings[indicatorKey] = {
31
37
  maxDeque: [], // will hold indices for highs in descending order
32
38
  minDeque: [] // will hold indices for lows in ascending order
33
39
  }
34
40
  }
35
41
 
36
- const numberOfIndicators = main.instances.donchian_channel.numberOfIndicators
42
+ const numberOfIndicators = instances.donchian_channel.numberOfIndicators
37
43
  const subPrefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel'
38
- const state = main.instances.donchian_channel.settings[indicatorKey]
44
+ const state = instances.donchian_channel.settings[indicatorKey]
39
45
  const { maxDeque, minDeque } = state
40
46
 
41
47
  // Compute the “current bar index” for the window.
@@ -45,10 +51,10 @@ export const donchianChannels = (main, index, size, offset, options) => {
45
51
  const startIdx = endIdx - size // window: [startIdx, endIdx)
46
52
 
47
53
  // If the window is not fully available, exit early.
48
- if (startIdx < 0 || endIdx > main.len) return true
54
+ if (startIdx < 0 || endIdx > len) return true
49
55
 
50
- const highs = main.verticalOhlcv.high
51
- const lows = main.verticalOhlcv.low
56
+ const highs = verticalOhlcv.high
57
+ const lows = verticalOhlcv.low
52
58
 
53
59
  // **Update the maximum deque:**
54
60
  // Remove indices that are out of the current window.
@@ -86,13 +92,25 @@ export const donchianChannels = (main, index, size, offset, options) => {
86
92
  main.pushToMain({index, key: `${subPrefix}_lower`, value: lower})
87
93
 
88
94
  if (height) {
89
- const heightValue = ((upper - lower) / lower) * 100
95
+ let heightValue = ((upper - lower) / lower)
96
+
97
+ if(scale)
98
+ {
99
+ heightValue = calcMagnitude(heightValue, scale)
100
+ }
101
+
90
102
  main.pushToMain({index, key: `${subPrefix}_height`, value: heightValue})
91
103
  }
92
104
 
93
105
  // Process each range property.
94
106
  for (const rangeKey of range) {
95
- const rangeValue = (main.verticalOhlcv[rangeKey][index] - lower) / (upper - lower)
107
+ let rangeValue = (verticalOhlcv[rangeKey][index] - lower) / (upper - lower)
108
+
109
+ if(scale)
110
+ {
111
+ rangeValue = calcMagnitude(rangeValue, scale)
112
+ }
113
+
96
114
  main.pushToMain({index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue})
97
115
  }
98
116
 
@@ -1,73 +1,77 @@
1
- import {FasterEMA, FasterMACD} from 'trading-signals';
1
+ import { FasterEMA, FasterMACD } from 'trading-signals';
2
2
 
3
- const defaultTarget = 'close'
3
+ const defaultTarget = 'close';
4
4
 
5
5
  export const macd = (main, index, fast, slow, signal, options) => {
6
+ const { target } = options;
7
+ const { verticalOhlcv, instances } = main;
6
8
 
7
- const {target} = options
8
- const suffix = target === defaultTarget ? '' : `_${target}`;
9
- const indicatorKey = `${fast}_${slow}_${signal}${suffix}`;
10
-
11
- if (index === 0) {
12
-
13
- if(!main.verticalOhlcv.hasOwnProperty(target))
14
- {
15
- throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`)
16
- }
17
-
18
- const numberOfIndicators = main.inputParams.filter(o => o.key === 'macd').length;
19
- const prefix =
20
- numberOfIndicators > 1
21
- ? `macd_${indicatorKey}`
22
- : `macd${suffix}`;
23
-
24
- main.crossPairsList.push({fast: `${prefix}_diff`, slow: `${prefix}_dea`, isDefault: true});
25
-
26
- if(!main.instances.hasOwnProperty('macd'))
27
- {
28
- main.instances.macd = {
29
- numberOfIndicators,
30
- settings: {}
31
- }
32
- }
33
-
34
- main.instances.macd.settings[indicatorKey] = new FasterMACD(
35
- new FasterEMA(fast),
36
- new FasterEMA(slow),
37
- new FasterEMA(signal)
38
- );
39
-
40
- Object.assign(main.verticalOhlcv, {
41
- [`${prefix}_diff`]: [...main.nullArray],
42
- [`${prefix}_dea`]: [...main.nullArray],
43
- [`${prefix}_histogram`]: [...main.nullArray],
44
- })
45
- }
9
+ // Instance key (for internal storage) includes target if not default.
10
+ const instanceKey = `${fast}_${slow}_${signal}${target === defaultTarget ? '' : `_${target}`}`;
11
+
12
+ // On the first index, do the initialization.
13
+ if (index === 0) {
14
+ const { inputParams, crossPairsList, nullArray } = main;
46
15
 
47
-
48
- const subPrefix = main.instances.macd.numberOfIndicators > 1
49
- ? `macd_${indicatorKey}`
50
- : `macd${suffix}`;
51
-
52
- const macdInstance = main.instances.macd.settings[indicatorKey];
53
- const value = main.verticalOhlcv[target][index]
54
- macdInstance.update(value, main.lastIndexReplace);
55
-
56
- let macdResult;
57
-
58
- try {
59
- macdResult = macdInstance.getResult();
60
- } catch (err) {
61
- //do nothing
16
+ if (!verticalOhlcv.hasOwnProperty(target)) {
17
+ throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`);
62
18
  }
63
19
 
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.
22
+ const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd';
23
+
24
+ // Build the keys: if target is not the default, append it at the end.
25
+ const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}`;
26
+ const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}`;
27
+ const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}`;
64
28
 
65
- if (macdResult) {
29
+ crossPairsList.push({ fast: diffKey, slow: deaKey, isDefault: true });
66
30
 
67
- main.pushToMain({index, key: `${subPrefix}_diff`, value: macdResult.macd})
68
- main.pushToMain({index, key: `${subPrefix}_dea`, value: macdResult.signal})
69
- main.pushToMain({index, key: `${subPrefix}_histogram`, value: macdResult.histogram})
31
+ if (!instances.hasOwnProperty('macd')) {
32
+ instances.macd = {
33
+ numberOfIndicators,
34
+ settings: {}
35
+ };
70
36
  }
71
37
 
72
- return true;
38
+ instances.macd.settings[instanceKey] = new FasterMACD(
39
+ new FasterEMA(fast),
40
+ new FasterEMA(slow),
41
+ new FasterEMA(signal)
42
+ );
43
+
44
+ Object.assign(verticalOhlcv, {
45
+ [diffKey]: [...nullArray],
46
+ [deaKey]: [...nullArray],
47
+ [histogramKey]: [...nullArray],
48
+ });
49
+ }
50
+
51
+ const {numberOfIndicators, settings} = instances.macd
52
+
53
+ // Determine display prefix for updating values.
54
+ const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd';
55
+ const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}`;
56
+ const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}`;
57
+ const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}`;
58
+
59
+ const macdInstance = settings[instanceKey];
60
+ const value = verticalOhlcv[target][index];
61
+ macdInstance.update(value, main.lastIndexReplace);
62
+
63
+ let macdResult;
64
+ try {
65
+ macdResult = macdInstance.getResult();
66
+ } catch (err) {
67
+ // Do nothing if no result is available.
68
+ }
69
+
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
+ }
75
+
76
+ return true;
73
77
  };