ohlcv-indicators 3.4.1 → 3.4.3
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
|
@@ -16,6 +16,10 @@ import { assignTypes } from './src/utilities/assignTypes.js'
|
|
|
16
16
|
import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
|
|
17
17
|
import { buildArray } from './src/utilities/assignTypes.js'
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
//precomputed
|
|
21
|
+
import { precomputeMovingAverages } from './src/moving-averages/movingAverages.js'
|
|
22
|
+
|
|
19
23
|
/**
|
|
20
24
|
* Class OHLCV_INDICATORS
|
|
21
25
|
*
|
|
@@ -235,7 +239,9 @@ export default class OHLCV_INDICATORS {
|
|
|
235
239
|
|
|
236
240
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
const precomputed = precomputeMovingAverages({main: this, size, target, lag, methodName})
|
|
243
|
+
|
|
244
|
+
this.inputParams.push({key: methodName, params: [methodName, size, {target, lag, precomputed}]})
|
|
239
245
|
|
|
240
246
|
return this
|
|
241
247
|
}
|
|
@@ -252,7 +258,10 @@ export default class OHLCV_INDICATORS {
|
|
|
252
258
|
|
|
253
259
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
254
260
|
|
|
255
|
-
|
|
261
|
+
|
|
262
|
+
const precomputed = precomputeMovingAverages({main: this, size, target, lag, methodName})
|
|
263
|
+
|
|
264
|
+
this.inputParams.push({key: methodName, params: [methodName, size, {target, lag, precomputed}]})
|
|
256
265
|
|
|
257
266
|
return this
|
|
258
267
|
}
|
|
@@ -273,7 +282,10 @@ export default class OHLCV_INDICATORS {
|
|
|
273
282
|
|
|
274
283
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
275
284
|
|
|
276
|
-
|
|
285
|
+
const instanceKey = `${fast}_${slow}_${signal}${target === 'close' ? '' : `_${target}`}`
|
|
286
|
+
const precomputed = {instanceKey}
|
|
287
|
+
|
|
288
|
+
this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag, precomputed}]})
|
|
277
289
|
|
|
278
290
|
return this
|
|
279
291
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.3",
|
|
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,146 +1,125 @@
|
|
|
1
1
|
|
|
2
|
-
export const donchianChannels = (main, index, size, offset,
|
|
2
|
+
export const donchianChannels = (main, index, size, offset, options) => {
|
|
3
|
+
const {
|
|
4
|
+
verticalOhlcv,
|
|
5
|
+
instances,
|
|
6
|
+
len,
|
|
7
|
+
inputParams,
|
|
8
|
+
priceBased,
|
|
9
|
+
arrayTypes,
|
|
10
|
+
lag,
|
|
11
|
+
} = main;
|
|
12
|
+
const { height: includeHeight, range: rangeKeys } = options;
|
|
3
13
|
const indicatorKey = `${size}_${offset}`;
|
|
4
|
-
const { verticalOhlcv, instances, len } = main;
|
|
5
14
|
|
|
6
|
-
//
|
|
15
|
+
// ---- INIT (only at first bar) ----
|
|
7
16
|
if (index === 0) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (height) {
|
|
25
|
-
keyNames.push(`${prefix}_height`)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!instances.hasOwnProperty('donchian_channel')) {
|
|
30
|
-
instances.donchian_channel = { numberOfIndicators, settings: {} };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Set up additional arrays for each range property.
|
|
34
|
-
for (const rangeKey of range) {
|
|
35
|
-
if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
|
|
36
|
-
throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
17
|
+
// how many donchianChannels calls in this session?
|
|
18
|
+
const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length;
|
|
19
|
+
const basePrefix = 'donchian_channel';
|
|
20
|
+
const prefix = numberOfIndicators > 1
|
|
21
|
+
? `${basePrefix}_${indicatorKey}`
|
|
22
|
+
: basePrefix;
|
|
23
|
+
|
|
24
|
+
// build all output keys once
|
|
25
|
+
const keys = ['upper', 'basis', 'lower'].map(n => `${prefix}_${n}`);
|
|
26
|
+
if (includeHeight) keys.push(`${prefix}_height`);
|
|
27
|
+
for (const rk of rangeKeys) {
|
|
28
|
+
if (!(rk in verticalOhlcv) || !priceBased.has(rk)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Invalid range key "${rk}". Only price-based keys are allowed: ${[...priceBased]}`
|
|
31
|
+
);
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
keys.push(`${prefix}_range_${rk}`);
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if(lag > 0)
|
|
46
|
-
{
|
|
47
|
-
main.lag(keyNames, lag)
|
|
36
|
+
// bootstrap instance
|
|
37
|
+
if (!instances.donchian_channel) {
|
|
38
|
+
instances.donchian_channel = { numberOfIndicators, settings: {} };
|
|
48
39
|
}
|
|
40
|
+
instances.donchian_channel.numberOfIndicators = numberOfIndicators;
|
|
41
|
+
instances.donchian_channel.settings[indicatorKey] = { maxDeque: [], minDeque: [] };
|
|
49
42
|
|
|
43
|
+
// allocate & fill NaNs
|
|
44
|
+
Object.assign(
|
|
45
|
+
verticalOhlcv,
|
|
46
|
+
Object.fromEntries(keys.map(k => [k, new Float64Array(len).fill(NaN)]))
|
|
47
|
+
);
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
instances.donchian_channel.settings[indicatorKey] = {
|
|
56
|
-
maxDeque: [], // will hold indices for highs in descending order
|
|
57
|
-
minDeque: [] // will hold indices for lows in ascending order
|
|
58
|
-
};
|
|
49
|
+
// apply lag if requested
|
|
50
|
+
if (lag > 0) main.lag(keys, lag);
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
}
|
|
52
|
+
// mark price-based outputs
|
|
53
|
+
['upper','basis','lower'].forEach(n => priceBased.add(`${prefix}_${n}`));
|
|
54
|
+
keys.forEach(k => arrayTypes[k] = 'Float64Array');
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: NaN });
|
|
87
|
-
}
|
|
57
|
+
// ---- PER-BAR COMPUTATION ----
|
|
58
|
+
const { numberOfIndicators, settings } = instances.donchian_channel;
|
|
59
|
+
const prefix = numberOfIndicators > 1
|
|
60
|
+
? `donchian_channel_${indicatorKey}`
|
|
61
|
+
: 'donchian_channel';
|
|
62
|
+
|
|
63
|
+
const { maxDeque, minDeque } = settings[indicatorKey];
|
|
64
|
+
const current = index - offset;
|
|
65
|
+
const start = current - size + 1;
|
|
66
|
+
|
|
67
|
+
// prepare key lists again (cheap)
|
|
68
|
+
const outKeys = ['upper','basis','lower'].map(n => `${prefix}_${n}`);
|
|
69
|
+
if (includeHeight) outKeys.push(`${prefix}_height`);
|
|
70
|
+
const rangeOut = rangeKeys.map(rk => `${prefix}_range_${rk}`);
|
|
71
|
+
|
|
72
|
+
// not enough data → push NaNs
|
|
73
|
+
if (start < 0 || current + 1 > len) {
|
|
74
|
+
[...outKeys, ...rangeOut].forEach(key =>
|
|
75
|
+
main.pushToMain({ index, key, value: NaN })
|
|
76
|
+
);
|
|
88
77
|
return true;
|
|
89
78
|
}
|
|
90
79
|
|
|
91
|
-
const highs = verticalOhlcv
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
80
|
+
const { high: highs, low: lows } = verticalOhlcv;
|
|
81
|
+
|
|
82
|
+
// tiny helper to keep deques up-to-date
|
|
83
|
+
const update = (dq, arr, cmp) => {
|
|
84
|
+
while (dq.length && dq[0] < start) dq.shift();
|
|
85
|
+
while (dq.length && cmp(arr[dq.at(-1)], arr[current])) dq.pop();
|
|
86
|
+
dq.push(current);
|
|
87
|
+
};
|
|
88
|
+
update(maxDeque, highs, (a,b) => a <= b);
|
|
89
|
+
update(minDeque, lows, (a,b) => a >= b);
|
|
90
|
+
|
|
91
|
+
// single “bounds exist?” flag instead of repeated isNaN
|
|
92
|
+
const hasBounds = maxDeque.length && minDeque.length;
|
|
93
|
+
const upper = hasBounds ? highs[maxDeque[0]] : NaN;
|
|
94
|
+
const lower = hasBounds ? lows[minDeque[0]] : NaN;
|
|
95
|
+
const basis = hasBounds ? (upper + lower) / 2 : NaN;
|
|
96
|
+
|
|
97
|
+
main.pushToMain({ index, key: `${prefix}_upper`, value: upper });
|
|
98
|
+
main.pushToMain({ index, key: `${prefix}_basis`, value: basis });
|
|
99
|
+
main.pushToMain({ index, key: `${prefix}_lower`, value: lower });
|
|
100
|
+
|
|
101
|
+
if (includeHeight) {
|
|
102
|
+
// avoid divide-by-zero
|
|
103
|
+
const heightVal = hasBounds && lower
|
|
104
|
+
? (upper - lower) / lower
|
|
105
|
+
: NaN;
|
|
106
|
+
main.pushToMain({ index, key: `${prefix}_height`, value: heightVal });
|
|
100
107
|
}
|
|
101
|
-
maxDeque.push(currentBarIdx);
|
|
102
108
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
minDeque.shift();
|
|
106
|
-
}
|
|
107
|
-
while (minDeque.length && lows[minDeque[minDeque.length - 1]] >= lows[currentBarIdx]) {
|
|
108
|
-
minDeque.pop();
|
|
109
|
-
}
|
|
110
|
-
minDeque.push(currentBarIdx);
|
|
111
|
-
|
|
112
|
-
// Retrieve computed values with safety checks.
|
|
113
|
-
const upper = maxDeque.length ? highs[maxDeque[0]] : NaN;
|
|
114
|
-
const lower = minDeque.length ? lows[minDeque[0]] : NaN;
|
|
115
|
-
const basis = (!Number.isNaN(upper) && !Number.isNaN(lower)) ? (upper + lower) / 2 : NaN;
|
|
116
|
-
|
|
117
|
-
// Always push the main indicator values.
|
|
118
|
-
main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
|
|
119
|
-
main.pushToMain({ index, key: `${subPrefix}_basis`, value: basis });
|
|
120
|
-
main.pushToMain({ index, key: `${subPrefix}_lower`, value: lower });
|
|
121
|
-
|
|
122
|
-
// Process height if enabled.
|
|
123
|
-
if (height) {
|
|
124
|
-
let heightValue = NaN;
|
|
125
|
-
if (!Number.isNaN(upper) && !Number.isNaN(lower)) {
|
|
126
|
-
heightValue = ((upper - lower) / lower)
|
|
127
|
-
}
|
|
128
|
-
main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
|
|
129
|
-
}
|
|
109
|
+
// precompute spread once
|
|
110
|
+
const spread = hasBounds ? (upper - lower) : NaN;
|
|
130
111
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
|
|
112
|
+
// for each extra range key, use price===price to skip NaNs
|
|
113
|
+
|
|
114
|
+
for(let i = 0; i < rangeKeys.length; i++)
|
|
115
|
+
{
|
|
116
|
+
const rk = rangeKeys[i]
|
|
117
|
+
const price = verticalOhlcv[rk][index];
|
|
118
|
+
const val = hasBounds && price === price && spread
|
|
119
|
+
? (price - lower) / spread
|
|
120
|
+
: NaN;
|
|
121
|
+
main.pushToMain({ index, key: rangeOut[i], value: val });
|
|
143
122
|
}
|
|
144
123
|
|
|
145
124
|
return true;
|
|
146
|
-
}
|
|
125
|
+
};
|
|
@@ -2,55 +2,53 @@ import { FasterEMA, FasterMACD } from 'trading-signals';
|
|
|
2
2
|
|
|
3
3
|
const defaultTarget = 'close';
|
|
4
4
|
|
|
5
|
-
export const macd = (main, index, fast, slow, signal, {target, lag}) => {
|
|
5
|
+
export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}) => {
|
|
6
6
|
|
|
7
|
-
const { verticalOhlcv, instances } = main
|
|
8
|
-
|
|
9
|
-
// Create an instance key that includes the target if it isn't the default.
|
|
10
|
-
const instanceKey = `${fast}_${slow}_${signal}${target === defaultTarget ? '' : `_${target}`}`;
|
|
7
|
+
const { verticalOhlcv, instances } = main
|
|
8
|
+
const {instanceKey} = precomputed
|
|
11
9
|
|
|
12
10
|
// Initialization on the first index.
|
|
13
11
|
if (index === 0) {
|
|
14
|
-
const { inputParams, crossPairsList, priceBased, len , arrayTypes} = main
|
|
12
|
+
const { inputParams, crossPairsList, priceBased, len , arrayTypes} = main
|
|
15
13
|
|
|
16
14
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
17
|
-
throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`)
|
|
15
|
+
throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`)
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
let numberOfIndicators = 0
|
|
21
19
|
|
|
22
20
|
for (const o of inputParams) {
|
|
23
|
-
if (o.key === 'macd') numberOfIndicators
|
|
21
|
+
if (o.key === 'macd') numberOfIndicators++
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
// Choose a display prefix based on the number of indicators.
|
|
27
|
-
const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd'
|
|
25
|
+
const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd'
|
|
28
26
|
|
|
29
27
|
// Build the keys.
|
|
30
|
-
const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}
|
|
31
|
-
const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}
|
|
32
|
-
const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}
|
|
28
|
+
const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}`
|
|
29
|
+
const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}`
|
|
30
|
+
const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}`
|
|
33
31
|
|
|
34
|
-
crossPairsList.push({ fast: diffKey, slow: deaKey, isDefault: true })
|
|
32
|
+
crossPairsList.push({ fast: diffKey, slow: deaKey, isDefault: true })
|
|
35
33
|
|
|
36
34
|
if (!instances.hasOwnProperty('macd')) {
|
|
37
35
|
instances.macd = {
|
|
38
36
|
numberOfIndicators,
|
|
39
37
|
settings: {}
|
|
40
|
-
}
|
|
38
|
+
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
instances.macd.settings[instanceKey] = new FasterMACD(
|
|
44
42
|
new FasterEMA(fast),
|
|
45
43
|
new FasterEMA(slow),
|
|
46
44
|
new FasterEMA(signal)
|
|
47
|
-
)
|
|
45
|
+
)
|
|
48
46
|
|
|
49
47
|
Object.assign(verticalOhlcv, {
|
|
50
48
|
[diffKey]: new Float64Array(len).fill(NaN),
|
|
51
49
|
[deaKey]: new Float64Array(len).fill(NaN),
|
|
52
50
|
[histogramKey]: new Float64Array(len).fill(NaN),
|
|
53
|
-
})
|
|
51
|
+
})
|
|
54
52
|
|
|
55
53
|
[diffKey, deaKey, histogramKey].forEach(v => {
|
|
56
54
|
priceBased.add(v)
|
|
@@ -70,27 +68,27 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
|
|
|
70
68
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
const { numberOfIndicators, settings } = instances.macd
|
|
74
|
-
const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd'
|
|
75
|
-
const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}
|
|
76
|
-
const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}
|
|
77
|
-
const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}
|
|
71
|
+
const { numberOfIndicators, settings } = instances.macd
|
|
72
|
+
const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd'
|
|
73
|
+
const diffKey = target === defaultTarget ? `${displayPrefix}_diff` : `${displayPrefix}_diff_${target}`
|
|
74
|
+
const deaKey = target === defaultTarget ? `${displayPrefix}_dea` : `${displayPrefix}_dea_${target}`
|
|
75
|
+
const histogramKey = target === defaultTarget ? `${displayPrefix}_histogram` : `${displayPrefix}_histogram_${target}`
|
|
78
76
|
|
|
79
|
-
const macdInstance = settings[instanceKey]
|
|
80
|
-
const value = verticalOhlcv[target][index]
|
|
81
|
-
macdInstance.update(value)
|
|
77
|
+
const macdInstance = settings[instanceKey]
|
|
78
|
+
const value = verticalOhlcv[target][index]
|
|
79
|
+
macdInstance.update(value)
|
|
82
80
|
|
|
83
81
|
let macdResult = {}
|
|
84
82
|
try {
|
|
85
|
-
macdResult = macdInstance.getResult()
|
|
83
|
+
macdResult = macdInstance.getResult()
|
|
86
84
|
} catch (err) {
|
|
87
85
|
// If the result is unavailable, macdResult remains NaN.
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
// Always push values; use NaN as fallback when macdResult is missing.
|
|
91
|
-
main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN })
|
|
92
|
-
main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN })
|
|
93
|
-
main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN })
|
|
89
|
+
main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN })
|
|
90
|
+
main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN })
|
|
91
|
+
main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN })
|
|
94
92
|
|
|
95
|
-
return true
|
|
93
|
+
return true
|
|
96
94
|
};
|
|
@@ -1,48 +1,24 @@
|
|
|
1
1
|
|
|
2
|
-
import {FasterEMA, FasterSMA
|
|
2
|
+
import {FasterEMA, FasterSMA} from 'trading-signals';
|
|
3
3
|
|
|
4
4
|
const indicatorClasses = {ema: FasterEMA, sma: FasterSMA}
|
|
5
5
|
|
|
6
|
-
export const movingAverages = (main, index, indicatorName, size, { target, lag }) => {
|
|
7
|
-
const { verticalOhlcv, instances
|
|
8
|
-
|
|
9
|
-
typeof target === 'string' &&
|
|
10
|
-
verticalOhlcv.hasOwnProperty(target) &&
|
|
11
|
-
target !== 'close'
|
|
12
|
-
? `_${target}`
|
|
13
|
-
: '';
|
|
14
|
-
const keyName = `${indicatorName}_${size}${suffix}`;
|
|
6
|
+
export const movingAverages = (main, index, indicatorName, size, { target, lag, precomputed }) => {
|
|
7
|
+
const { verticalOhlcv, instances } = main
|
|
8
|
+
const {keyName} = precomputed
|
|
15
9
|
|
|
16
10
|
if (index === 0) {
|
|
17
|
-
const { len, arrayTypes } = main;
|
|
18
11
|
|
|
19
12
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
20
13
|
throw new Error(
|
|
21
14
|
`Target property ${target} not found in verticalOhlcv for ${indicatorName}.`
|
|
22
15
|
);
|
|
23
16
|
}
|
|
24
|
-
|
|
25
|
-
// Create the main moving average instance.
|
|
26
|
-
instances[keyName] = {
|
|
27
|
-
maInstance: new indicatorClasses[indicatorName](size)
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
|
|
31
|
-
|
|
32
|
-
priceBased.add(keyName);
|
|
33
|
-
|
|
34
|
-
if(lag > 0)
|
|
35
|
-
{
|
|
36
|
-
main.lag([keyName], lag)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
arrayTypes[keyName] = 'Float64Array'
|
|
40
|
-
|
|
41
17
|
}
|
|
42
18
|
|
|
43
19
|
// Retrieve the current price value.
|
|
44
|
-
const value = verticalOhlcv[target][index]
|
|
45
|
-
const { maInstance } = instances[keyName]
|
|
20
|
+
const value = verticalOhlcv[target][index]
|
|
21
|
+
const { maInstance } = instances[keyName]
|
|
46
22
|
|
|
47
23
|
// Update the moving average instance.
|
|
48
24
|
maInstance.update(value);
|
|
@@ -50,11 +26,36 @@ export const movingAverages = (main, index, indicatorName, size, { target, lag }
|
|
|
50
26
|
try {
|
|
51
27
|
currMa = maInstance.getResult();
|
|
52
28
|
} catch (err) {
|
|
53
|
-
|
|
29
|
+
|
|
54
30
|
}
|
|
55
31
|
|
|
56
32
|
// Always push the MA value (even if NaN).
|
|
57
33
|
main.pushToMain({ index, key: keyName, value: currMa });
|
|
58
34
|
|
|
59
35
|
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const precomputeMovingAverages = ({main, size, target, lag, methodName}) => {
|
|
39
|
+
|
|
40
|
+
const {instances, verticalOhlcv, priceBased, arrayTypes, len} = main
|
|
41
|
+
const suffix = (target !== 'close') ? `_${target}` : ''
|
|
42
|
+
const keyName = `${methodName}_${size}${suffix}`
|
|
43
|
+
|
|
44
|
+
// Create the main moving average instance.
|
|
45
|
+
instances[keyName] = {
|
|
46
|
+
maInstance: new indicatorClasses[methodName](size)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
|
|
50
|
+
|
|
51
|
+
priceBased.add(keyName)
|
|
52
|
+
|
|
53
|
+
if(lag > 0)
|
|
54
|
+
{
|
|
55
|
+
main.lag([keyName], lag)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
arrayTypes[keyName] = 'Float64Array'
|
|
59
|
+
|
|
60
|
+
return {keyName}
|
|
60
61
|
}
|
|
@@ -4,13 +4,13 @@ export const volumeOscillator = (main, index, fast, slow, {lag}) => {
|
|
|
4
4
|
|
|
5
5
|
const {verticalOhlcv, instances} = main
|
|
6
6
|
const value = verticalOhlcv.volume[index]
|
|
7
|
-
const key =
|
|
7
|
+
const key = `volume_oscillator_${fast}_${slow}`
|
|
8
8
|
|
|
9
9
|
if (index === 0) {
|
|
10
10
|
|
|
11
11
|
const {crossPairsList, len, arrayTypes} = main
|
|
12
12
|
Object.assign(instances, {
|
|
13
|
-
|
|
13
|
+
[key]: {
|
|
14
14
|
fastEMA: new FasterEMA(fast),
|
|
15
15
|
slowEMA: new FasterEMA(slow)
|
|
16
16
|
}
|
package/src/studies/dateTime.js
CHANGED
|
@@ -16,7 +16,7 @@ export const dateTime = (main, index, {lag}) => {
|
|
|
16
16
|
}
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
const colKeys = ['day_of_the_week', 'day_of_the_month', 'week_of_the_month', 'hour', 'month', 'year', 'session_daily_index', 'session_intraday_index']
|
|
19
|
+
const colKeys = ['day_of_the_week', 'day_of_the_month', 'week_of_the_month', 'minute', 'hour', 'month', 'year', 'session_daily_index', 'session_intraday_index']
|
|
20
20
|
|
|
21
21
|
for(const key of colKeys)
|
|
22
22
|
{
|
|
@@ -41,6 +41,7 @@ export const dateTime = (main, index, {lag}) => {
|
|
|
41
41
|
month,
|
|
42
42
|
year,
|
|
43
43
|
hour,
|
|
44
|
+
minute
|
|
44
45
|
} = getDateInfo(currDate)
|
|
45
46
|
|
|
46
47
|
|
|
@@ -63,6 +64,7 @@ export const dateTime = (main, index, {lag}) => {
|
|
|
63
64
|
main.pushToMain({index, key: 'month', value: month})
|
|
64
65
|
main.pushToMain({index, key: 'year', value: year})
|
|
65
66
|
main.pushToMain({index, key: 'hour', value: hour})
|
|
67
|
+
main.pushToMain({index, key: 'minute', value: minute})
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
instances.dateTime.sessionIntradayIndexCount++
|