ohlcv-indicators 2.1.9 → 2.2.1
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/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +31 -34
- package/package.json +1 -1
- package/src/moving-averages/bollingerBands.js +78 -74
- package/src/moving-averages/donchianChannel.js +72 -64
- package/src/moving-averages/macd.js +17 -17
- package/src/moving-averages/movingAverages.js +77 -73
- package/src/moving-averages/relativeVolume.js +37 -55
- package/src/oscillators/rsi.js +56 -62
- package/src/studies/lag.js +4 -2
- package/src/utilities/dataParsingUtilities.js +3 -1
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
30
|
-
instances.bollinger_bands
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
74
|
-
const subPrefix = prefix
|
|
70
|
+
const subPrefix = prefix;
|
|
75
71
|
|
|
76
|
-
//
|
|
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
|
-
|
|
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
|
|
82
|
+
// If not available, result stays null.
|
|
86
83
|
}
|
|
87
|
-
if (!result) return true
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
95
|
+
// Process height if requested.
|
|
97
96
|
if (height) {
|
|
98
|
-
let heightValue =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 =
|
|
111
|
-
|
|
112
|
-
if(
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
|
55
|
-
const startIdx = endIdx - size
|
|
56
|
-
|
|
57
|
-
// If the window is not fully available,
|
|
58
|
-
if (startIdx < 0 || endIdx > len)
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
minDeque.push(currentBarIdx)
|
|
87
|
+
minDeque.push(currentBarIdx);
|
|
86
88
|
|
|
87
|
-
//
|
|
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
|
-
|
|
95
|
-
main.pushToMain({index, key: `${subPrefix}
|
|
96
|
-
main.pushToMain({index, key: `${subPrefix}
|
|
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 =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 =
|
|
112
|
-
|
|
113
|
-
if(
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
//
|
|
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 } = 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
|
-
//
|
|
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
|
|
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
|
-
|
|
50
|
+
priceBased.push(diffKey, deaKey, histogramKey)
|
|
51
|
+
}
|
|
52
52
|
|
|
53
|
-
|
|
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
|
-
//
|
|
67
|
+
// If the result is unavailable, macdResult remains null.
|
|
68
|
+
macdResult = null;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
};
|