ohlcv-indicators 3.5.68 → 3.5.69
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
|
@@ -17,8 +17,6 @@ import { dateOutputFormaters } from './src/utilities/dateUtilities.js'
|
|
|
17
17
|
import { defaultMapColsCallback } from './src/studies/mapCols.js'
|
|
18
18
|
import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
|
|
19
19
|
|
|
20
|
-
import { normalizeMinMax } from './src/machine-learning/ml-utilities.js'
|
|
21
|
-
|
|
22
20
|
/**
|
|
23
21
|
* Class OHLCV_INDICATORS
|
|
24
22
|
*
|
|
@@ -28,13 +26,14 @@ import { normalizeMinMax } from './src/machine-learning/ml-utilities.js'
|
|
|
28
26
|
*/
|
|
29
27
|
|
|
30
28
|
export default class OHLCV_INDICATORS {
|
|
31
|
-
constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, precision = false}) {
|
|
29
|
+
constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, precision = false, useFullNames = false}) {
|
|
32
30
|
|
|
33
31
|
validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
|
|
34
32
|
if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
|
|
35
33
|
|
|
36
34
|
validateNumber(chunkProcess, {min: 100, max: 50000, allowDecimals: false}, 'chunkProcess', 'constructor')
|
|
37
35
|
validateBoolean(precision, 'precision', 'contructor')
|
|
36
|
+
validateBoolean(precision, 'useFullNames', 'contructor')
|
|
38
37
|
|
|
39
38
|
this.chunkProcess = chunkProcess
|
|
40
39
|
|
|
@@ -42,6 +41,7 @@ export default class OHLCV_INDICATORS {
|
|
|
42
41
|
this.len = this.input.length
|
|
43
42
|
this.firstRow = this.input[0]
|
|
44
43
|
this.precision = precision
|
|
44
|
+
this.useFullNames = useFullNames
|
|
45
45
|
|
|
46
46
|
const initialPriceBasedArr = ['open', 'high', 'low', 'close', 'mid_price']
|
|
47
47
|
this.initialPriceBased = new Set(initialPriceBasedArr)
|
|
@@ -410,14 +410,11 @@ export default class OHLCV_INDICATORS {
|
|
|
410
410
|
validateNumber(stdDev, {min: 0.01, max: 50, allowDecimals: true}, 'stdDev', methodName)
|
|
411
411
|
validateObject(options, 'options', methodName)
|
|
412
412
|
|
|
413
|
-
const {
|
|
413
|
+
const { lag = 0} = options
|
|
414
414
|
|
|
415
|
-
validateString(target, 'options.target', methodName)
|
|
416
|
-
validateArray(range, 'options.range', methodName)
|
|
417
415
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
418
|
-
validateBoolean(height, 'options.height', methodName)
|
|
419
416
|
|
|
420
|
-
this.inputParams.push({key: methodName, params: [size, stdDev, {
|
|
417
|
+
this.inputParams.push({key: methodName, params: [size, stdDev, {lag}]});
|
|
421
418
|
|
|
422
419
|
return this;
|
|
423
420
|
}
|
|
@@ -450,13 +447,11 @@ export default class OHLCV_INDICATORS {
|
|
|
450
447
|
validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
|
|
451
448
|
|
|
452
449
|
validateObject(options, 'options', methodName)
|
|
453
|
-
const {
|
|
450
|
+
const { lag = 0} = options;
|
|
454
451
|
|
|
455
|
-
validateArray(range, 'options.range', methodName)
|
|
456
452
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
457
|
-
validateBoolean(height, 'options.height', methodName)
|
|
458
453
|
|
|
459
|
-
this.inputParams.push({ key: methodName, order: 0, params: [size, offset, {
|
|
454
|
+
this.inputParams.push({ key: methodName, order: 0, params: [size, offset, {lag}] });
|
|
460
455
|
|
|
461
456
|
return this;
|
|
462
457
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.69",
|
|
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,126 +1,80 @@
|
|
|
1
1
|
import { FasterBollingerBands } from 'trading-signals'
|
|
2
|
-
const defaultTarget = 'close'
|
|
3
2
|
|
|
4
|
-
export const bollingerBands = (main, index, size, stdDev, {
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const indicatorKey = `${size}_${stdDev}`;
|
|
3
|
+
export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
|
|
4
|
+
const { verticalOhlcv, instances, useFullNames } = main
|
|
5
|
+
const indicatorKey = `${size}_${stdDev}`
|
|
8
6
|
const prefix = 'bollinger_bands'
|
|
7
|
+
const target = 'close'
|
|
9
8
|
|
|
10
9
|
// Initialization on the first call.
|
|
11
10
|
if (index === 0) {
|
|
12
|
-
|
|
13
|
-
const {inputParams, verticalOhlcv, len, priceBased } = main;
|
|
11
|
+
const { inputParams, verticalOhlcv, len, priceBased } = main
|
|
14
12
|
|
|
15
13
|
if (!(target in verticalOhlcv)) {
|
|
16
|
-
throw new Error(`bollingerBands could not find target "${target}"`)
|
|
14
|
+
throw new Error(`bollingerBands could not find target "${target}"`)
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
let numberOfIndicators = 0
|
|
20
18
|
|
|
21
19
|
for (const o of inputParams) {
|
|
22
|
-
if (o.key === 'bollingerBands') numberOfIndicators
|
|
20
|
+
if (o.key === 'bollingerBands') numberOfIndicators++
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
// Only create the container if it doesn't already exist.
|
|
26
23
|
if (!instances.bollinger_bands) {
|
|
27
24
|
instances.bollinger_bands = {
|
|
28
25
|
numberOfIndicators,
|
|
29
26
|
settings: {}
|
|
30
|
-
}
|
|
27
|
+
}
|
|
31
28
|
}
|
|
32
|
-
// Add (or override) the indicator instance keyed by indicatorKey.
|
|
33
|
-
instances.bollinger_bands.settings[indicatorKey] = new FasterBollingerBands(size, stdDev);
|
|
34
|
-
|
|
35
|
-
let suffix = ''
|
|
36
|
-
if((numberOfIndicators > 1)) suffix += `_${indicatorKey}`
|
|
37
|
-
if(target !== defaultTarget) suffix += `_${target}`
|
|
38
|
-
|
|
39
|
-
const keyNames = [
|
|
40
|
-
`${prefix}_upper`,
|
|
41
|
-
`${prefix}_middle`,
|
|
42
|
-
`${prefix}_lower`,
|
|
43
|
-
].map(v => `${v}${suffix}`)
|
|
44
29
|
|
|
30
|
+
instances.bollinger_bands.settings[indicatorKey] = new FasterBollingerBands(size, stdDev)
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
keyNames.push(
|
|
48
|
-
`${prefix}_height${suffix}`
|
|
49
|
-
)
|
|
50
|
-
}
|
|
32
|
+
const suffix = (numberOfIndicators > 1 || useFullNames) ? `_${indicatorKey}` : ''
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
keyNames.push(`${prefix}_range_${rangeKey}${suffix}`)
|
|
58
|
-
|
|
59
|
-
}
|
|
34
|
+
const keyNames = [
|
|
35
|
+
`${prefix}_upper${suffix}`,
|
|
36
|
+
`${prefix}_middle${suffix}`,
|
|
37
|
+
`${prefix}_lower${suffix}`,
|
|
38
|
+
]
|
|
60
39
|
|
|
61
|
-
if(priceBased.has(target)) {
|
|
62
|
-
for(const k of keyNames) {
|
|
40
|
+
if (priceBased.has(target)) {
|
|
41
|
+
for (const k of keyNames) {
|
|
63
42
|
priceBased.add(k)
|
|
64
43
|
}
|
|
65
44
|
}
|
|
66
45
|
|
|
46
|
+
const verticalOhlcvSetup = Object.fromEntries(
|
|
47
|
+
keyNames.map(v => [v, new Float64Array(len).fill(NaN)])
|
|
48
|
+
)
|
|
67
49
|
|
|
68
|
-
|
|
69
|
-
Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
|
|
50
|
+
Object.assign(verticalOhlcv, { ...verticalOhlcvSetup })
|
|
70
51
|
|
|
71
|
-
if(lag > 0)
|
|
72
|
-
{
|
|
52
|
+
if (lag > 0) {
|
|
73
53
|
main.lag(keyNames, lag)
|
|
74
54
|
}
|
|
75
55
|
}
|
|
76
56
|
|
|
77
|
-
const {numberOfIndicators} = instances.bollinger_bands
|
|
57
|
+
const { numberOfIndicators } = instances.bollinger_bands
|
|
58
|
+
const suffix = (numberOfIndicators > 1 || useFullNames) ? `_${indicatorKey}` : ''
|
|
78
59
|
|
|
79
|
-
let suffix = ''
|
|
80
|
-
if((numberOfIndicators > 1)) suffix += `_${indicatorKey}`
|
|
81
|
-
if(target !== defaultTarget) suffix += `_${target}`
|
|
82
|
-
|
|
83
|
-
// Update the indicator with the current value.
|
|
84
60
|
const instance = instances.bollinger_bands.settings[indicatorKey]
|
|
85
61
|
const value = verticalOhlcv[target][index]
|
|
86
62
|
instance.update(value)
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
let result = {};
|
|
64
|
+
let result = {}
|
|
90
65
|
try {
|
|
91
|
-
result = instance.getResult()
|
|
66
|
+
result = instance.getResult()
|
|
92
67
|
} catch (err) {
|
|
93
68
|
// If not available, result stays {}.
|
|
94
69
|
}
|
|
95
70
|
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const lower = result?.lower ?? NaN;
|
|
100
|
-
|
|
101
|
-
// Always push the indicator outputs.
|
|
102
|
-
main.pushToMain({ index, key: `${prefix}_upper${suffix}`, value: upper });
|
|
103
|
-
main.pushToMain({ index, key: `${prefix}_middle${suffix}`, value: middle });
|
|
104
|
-
main.pushToMain({ index, key: `${prefix}_lower${suffix}`, value: lower });
|
|
105
|
-
|
|
106
|
-
// Process height if requested.
|
|
107
|
-
if (height) {
|
|
108
|
-
let heightValue = NaN;
|
|
109
|
-
if (!Number.isNaN(lower) && !Number.isNaN(upper)) {
|
|
110
|
-
heightValue = ((upper - lower) / lower)
|
|
111
|
-
}
|
|
112
|
-
main.pushToMain({ index, key: `${prefix}_height${suffix}`, value: heightValue });
|
|
113
|
-
}
|
|
71
|
+
const upper = result?.upper ?? NaN
|
|
72
|
+
const middle = result?.middle ?? NaN
|
|
73
|
+
const lower = result?.lower ?? NaN
|
|
114
74
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const priceValue = verticalOhlcv[rangeKey][index];
|
|
119
|
-
if (!Number.isNaN(priceValue) && !Number.isNaN(lower) && !Number.isNaN(upper)) {
|
|
120
|
-
rangeValue = (priceValue - lower) / (upper - lower)
|
|
121
|
-
}
|
|
122
|
-
main.pushToMain({ index, key: `${prefix}_range_${rangeKey}${suffix}`, value: rangeValue });
|
|
123
|
-
}
|
|
75
|
+
main.pushToMain({ index, key: `${prefix}_upper${suffix}`, value: upper })
|
|
76
|
+
main.pushToMain({ index, key: `${prefix}_middle${suffix}`, value: middle })
|
|
77
|
+
main.pushToMain({ index, key: `${prefix}_lower${suffix}`, value: lower })
|
|
124
78
|
|
|
125
|
-
return true
|
|
126
|
-
}
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
@@ -1,120 +1,82 @@
|
|
|
1
|
-
|
|
2
1
|
export const donchianChannels = (main, index, size, offset, options) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const {verticalOhlcv, instances, len, inputParams, lag, priceBased} = main
|
|
7
|
-
const { height: includeHeight, range: rangeKeys } = options
|
|
2
|
+
const { verticalOhlcv, instances, len, inputParams, priceBased, useFullNames } = main
|
|
3
|
+
const { lag: outputLag = 0 } = options
|
|
8
4
|
const indicatorKey = `${size}_${offset}`
|
|
9
5
|
|
|
10
6
|
// ---- INIT (only at first bar) ----
|
|
11
7
|
if (index === 0) {
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// build all output keys once
|
|
20
|
-
const keys = ['upper', 'basis', 'lower'].map(n => `${prefix}_${n}`);
|
|
21
|
-
if (includeHeight) keys.push(`${prefix}_height`);
|
|
22
|
-
for (const rk of rangeKeys) {
|
|
23
|
-
if (!(rk in verticalOhlcv)) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Invalid range key "${rk}".`
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
keys.push(`${prefix}_range_${rk}`);
|
|
29
|
-
}
|
|
8
|
+
const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length
|
|
9
|
+
const useIndexedKeys = numberOfIndicators > 1 || useFullNames
|
|
10
|
+
|
|
11
|
+
const getKey = name =>
|
|
12
|
+
useIndexedKeys
|
|
13
|
+
? `donchian_channel_${name}_${indicatorKey}`
|
|
14
|
+
: `donchian_channel_${name}`
|
|
30
15
|
|
|
31
|
-
|
|
16
|
+
const keys = ['upper', 'basis', 'lower'].map(getKey)
|
|
17
|
+
|
|
18
|
+
for (const k of keys) {
|
|
32
19
|
priceBased.add(k)
|
|
33
20
|
}
|
|
34
21
|
|
|
35
|
-
// bootstrap instance
|
|
36
22
|
if (!instances.donchian_channel) {
|
|
37
|
-
instances.donchian_channel = { numberOfIndicators, settings: {} }
|
|
23
|
+
instances.donchian_channel = { numberOfIndicators, settings: {} }
|
|
38
24
|
}
|
|
39
|
-
instances.donchian_channel.numberOfIndicators = numberOfIndicators;
|
|
40
|
-
instances.donchian_channel.settings[indicatorKey] = { maxDeque: [], minDeque: [] };
|
|
41
25
|
|
|
42
|
-
|
|
26
|
+
instances.donchian_channel.numberOfIndicators = numberOfIndicators
|
|
27
|
+
instances.donchian_channel.settings[indicatorKey] = { maxDeque: [], minDeque: [] }
|
|
28
|
+
|
|
43
29
|
Object.assign(
|
|
44
30
|
verticalOhlcv,
|
|
45
31
|
Object.fromEntries(keys.map(k => [k, new Float64Array(len).fill(NaN)]))
|
|
46
|
-
)
|
|
32
|
+
)
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
if (outputLag > 0) {
|
|
35
|
+
main.lag(keys, outputLag)
|
|
36
|
+
}
|
|
50
37
|
}
|
|
51
38
|
|
|
52
39
|
// ---- PER-BAR COMPUTATION ----
|
|
53
|
-
const { numberOfIndicators, settings } = instances.donchian_channel
|
|
54
|
-
const
|
|
55
|
-
? `donchian_channel_${indicatorKey}`
|
|
56
|
-
: 'donchian_channel';
|
|
40
|
+
const { numberOfIndicators, settings } = instances.donchian_channel
|
|
41
|
+
const useIndexedKeys = numberOfIndicators > 1 || useFullNames
|
|
57
42
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
const getKey = name =>
|
|
44
|
+
useIndexedKeys
|
|
45
|
+
? `donchian_channel_${name}_${indicatorKey}`
|
|
46
|
+
: `donchian_channel_${name}`
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
48
|
+
const { maxDeque, minDeque } = settings[indicatorKey]
|
|
49
|
+
const current = index - offset
|
|
50
|
+
const start = current - size + 1
|
|
51
|
+
|
|
52
|
+
const outKeys = ['upper', 'basis', 'lower'].map(getKey)
|
|
66
53
|
|
|
67
|
-
// not enough data → push NaNs
|
|
68
54
|
if (start < 0 || current + 1 > len) {
|
|
69
|
-
|
|
55
|
+
outKeys.forEach(key =>
|
|
70
56
|
main.pushToMain({ index, key, value: NaN })
|
|
71
|
-
)
|
|
72
|
-
return true
|
|
57
|
+
)
|
|
58
|
+
return true
|
|
73
59
|
}
|
|
74
60
|
|
|
75
|
-
const { high: highs, low: lows } = verticalOhlcv
|
|
61
|
+
const { high: highs, low: lows } = verticalOhlcv
|
|
76
62
|
|
|
77
|
-
// tiny helper to keep deques up-to-date
|
|
78
63
|
const update = (dq, arr, cmp) => {
|
|
79
|
-
while (dq.length && dq[0] < start)
|
|
80
|
-
while (dq.length && cmp(arr[dq.at(-1)], arr[current])) dq.pop()
|
|
81
|
-
dq.push(current)
|
|
82
|
-
};
|
|
83
|
-
update(maxDeque, highs, (a,b) => a <= b);
|
|
84
|
-
update(minDeque, lows, (a,b) => a >= b);
|
|
85
|
-
|
|
86
|
-
// single “bounds exist?” flag instead of repeated isNaN
|
|
87
|
-
const hasBounds = maxDeque.length && minDeque.length;
|
|
88
|
-
const upper = hasBounds ? highs[maxDeque[0]] : NaN;
|
|
89
|
-
const lower = hasBounds ? lows[minDeque[0]] : NaN;
|
|
90
|
-
const basis = hasBounds ? (upper + lower) / 2 : NaN;
|
|
91
|
-
|
|
92
|
-
main.pushToMain({ index, key: `${prefix}_upper`, value: upper });
|
|
93
|
-
main.pushToMain({ index, key: `${prefix}_basis`, value: basis });
|
|
94
|
-
main.pushToMain({ index, key: `${prefix}_lower`, value: lower });
|
|
95
|
-
|
|
96
|
-
if (includeHeight) {
|
|
97
|
-
// avoid divide-by-zero
|
|
98
|
-
const heightVal = hasBounds && lower
|
|
99
|
-
? (upper - lower) / lower
|
|
100
|
-
: NaN;
|
|
101
|
-
main.pushToMain({ index, key: `${prefix}_height`, value: heightVal });
|
|
64
|
+
while (dq.length && dq[0] < start) dq.shift()
|
|
65
|
+
while (dq.length && cmp(arr[dq.at(-1)], arr[current])) dq.pop()
|
|
66
|
+
dq.push(current)
|
|
102
67
|
}
|
|
103
68
|
|
|
104
|
-
|
|
105
|
-
|
|
69
|
+
update(maxDeque, highs, (a, b) => a <= b)
|
|
70
|
+
update(minDeque, lows, (a, b) => a >= b)
|
|
106
71
|
|
|
107
|
-
|
|
72
|
+
const hasBounds = maxDeque.length && minDeque.length
|
|
73
|
+
const upper = hasBounds ? highs[maxDeque[0]] : NaN
|
|
74
|
+
const lower = hasBounds ? lows[minDeque[0]] : NaN
|
|
75
|
+
const basis = hasBounds ? (upper + lower) / 2 : NaN
|
|
108
76
|
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
const price = verticalOhlcv[rk][index];
|
|
113
|
-
const val = hasBounds && price === price && spread
|
|
114
|
-
? (price - lower) / spread
|
|
115
|
-
: NaN;
|
|
116
|
-
main.pushToMain({ index, key: rangeOut[i], value: val });
|
|
117
|
-
}
|
|
77
|
+
main.pushToMain({ index, key: getKey('upper'), value: upper })
|
|
78
|
+
main.pushToMain({ index, key: getKey('basis'), value: basis })
|
|
79
|
+
main.pushToMain({ index, key: getKey('lower'), value: lower })
|
|
118
80
|
|
|
119
|
-
return true
|
|
120
|
-
}
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
@@ -2,14 +2,25 @@ import { FasterEMA } from 'trading-signals';
|
|
|
2
2
|
|
|
3
3
|
export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag = 0, bothNull = false } = {}) => {
|
|
4
4
|
const { verticalOhlcv, instances, len, priceBased } = main;
|
|
5
|
-
const
|
|
5
|
+
const indicatorKey = `${smoothLength}_${afterSmoothLength}`;
|
|
6
6
|
const keys = ['open', 'high', 'low', 'close'];
|
|
7
7
|
|
|
8
|
+
const getKey = key =>
|
|
9
|
+
bothNull
|
|
10
|
+
? `heiken_ashi_${key}`
|
|
11
|
+
: `heiken_ashi_${key}_${indicatorKey}`;
|
|
12
|
+
|
|
13
|
+
const crossKey = bothNull
|
|
14
|
+
? 'heiken_ashi_cross'
|
|
15
|
+
: `heiken_ashi_cross_${indicatorKey}`;
|
|
16
|
+
|
|
17
|
+
const instanceKey = bothNull
|
|
18
|
+
? 'heiken_ashi'
|
|
19
|
+
: `heiken_ashi_${indicatorKey}`;
|
|
20
|
+
|
|
8
21
|
// ---- INIT ----
|
|
9
22
|
if (index === 0) {
|
|
10
|
-
|
|
11
|
-
instances[prefix] = {
|
|
12
|
-
// Only allocate EMAs when smoothing is requested
|
|
23
|
+
instances[instanceKey] = {
|
|
13
24
|
emaPre: !bothNull
|
|
14
25
|
? Object.fromEntries(keys.map(k => [k, new FasterEMA(smoothLength)]))
|
|
15
26
|
: null,
|
|
@@ -21,13 +32,13 @@ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag =
|
|
|
21
32
|
isTrendUp: false,
|
|
22
33
|
};
|
|
23
34
|
|
|
24
|
-
const keyNames = keys.map(
|
|
35
|
+
const keyNames = keys.map(getKey);
|
|
25
36
|
const verticalOhlcvSetup = Object.fromEntries(
|
|
26
|
-
[...keyNames,
|
|
37
|
+
[...keyNames, crossKey].map(v => [v, new Float64Array(len).fill(NaN)])
|
|
27
38
|
);
|
|
28
39
|
|
|
29
|
-
for(const k of keyNames) {
|
|
30
|
-
priceBased.add(k)
|
|
40
|
+
for (const k of keyNames) {
|
|
41
|
+
priceBased.add(k);
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
Object.assign(verticalOhlcv, { ...verticalOhlcvSetup });
|
|
@@ -42,7 +53,7 @@ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag =
|
|
|
42
53
|
const high = verticalOhlcv.high[index];
|
|
43
54
|
const low = verticalOhlcv.low[index];
|
|
44
55
|
const close = verticalOhlcv.close[index];
|
|
45
|
-
const inst = instances[
|
|
56
|
+
const inst = instances[instanceKey];
|
|
46
57
|
|
|
47
58
|
let sOpen, sHigh, sLow, sClose;
|
|
48
59
|
|
|
@@ -59,17 +70,16 @@ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag =
|
|
|
59
70
|
sLow = inst.emaPre.low.getResult();
|
|
60
71
|
sClose = inst.emaPre.close.getResult();
|
|
61
72
|
} catch {
|
|
62
|
-
return true;
|
|
73
|
+
return true;
|
|
63
74
|
}
|
|
64
75
|
} else {
|
|
65
|
-
// No pre-smoothing: use raw OHLC
|
|
66
76
|
sOpen = open;
|
|
67
77
|
sHigh = high;
|
|
68
78
|
sLow = low;
|
|
69
79
|
sClose = close;
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
// ---- HEIKEN ASHI CORE
|
|
82
|
+
// ---- HEIKEN ASHI CORE ----
|
|
73
83
|
const haClose = (sOpen + sHigh + sLow + sClose) / 4;
|
|
74
84
|
const haOpen = (Number.isNaN(inst.prevHaOpen) || Number.isNaN(inst.prevHaClose))
|
|
75
85
|
? (sOpen + sClose) / 2
|
|
@@ -77,7 +87,6 @@ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag =
|
|
|
77
87
|
const haHigh = Math.max(sHigh, haOpen, haClose);
|
|
78
88
|
const haLow = Math.min(sLow, haOpen, haClose);
|
|
79
89
|
|
|
80
|
-
// Store for next round
|
|
81
90
|
inst.prevHaOpen = haOpen;
|
|
82
91
|
inst.prevHaClose = haClose;
|
|
83
92
|
|
|
@@ -96,20 +105,19 @@ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag =
|
|
|
96
105
|
smLow = inst.emaPost.low.getResult();
|
|
97
106
|
smClose = inst.emaPost.close.getResult();
|
|
98
107
|
} catch {
|
|
99
|
-
return true;
|
|
108
|
+
return true;
|
|
100
109
|
}
|
|
101
110
|
} else {
|
|
102
|
-
// No post-smoothing: final values are plain HA
|
|
103
111
|
smOpen = haOpen;
|
|
104
112
|
smHigh = haHigh;
|
|
105
113
|
smLow = haLow;
|
|
106
114
|
smClose = haClose;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
// ---- TREND/CROSS LOGIC
|
|
110
|
-
const prevCross = index > 0 ? verticalOhlcv[
|
|
111
|
-
const prevHaOpenArr = index > 0 ? verticalOhlcv[
|
|
112
|
-
const prevHaCloseArr = index > 0 ? verticalOhlcv[
|
|
117
|
+
// ---- TREND/CROSS LOGIC ----
|
|
118
|
+
const prevCross = index > 0 ? verticalOhlcv[crossKey][index - 1] : 0;
|
|
119
|
+
const prevHaOpenArr = index > 0 ? verticalOhlcv[getKey('open')][index - 1] : NaN;
|
|
120
|
+
const prevHaCloseArr = index > 0 ? verticalOhlcv[getKey('close')][index - 1] : NaN;
|
|
113
121
|
|
|
114
122
|
const crossUp = !Number.isNaN(prevHaCloseArr) && prevHaCloseArr <= prevHaOpenArr && smClose > smOpen;
|
|
115
123
|
const crossDown = !Number.isNaN(prevHaCloseArr) && prevHaCloseArr >= prevHaOpenArr && smClose < smOpen;
|
|
@@ -127,11 +135,11 @@ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, { lag =
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
// ---- PUSH OUTPUTS ----
|
|
130
|
-
main.pushToMain({ index, key:
|
|
131
|
-
main.pushToMain({ index, key:
|
|
132
|
-
main.pushToMain({ index, key:
|
|
133
|
-
main.pushToMain({ index, key:
|
|
134
|
-
main.pushToMain({ index, key:
|
|
138
|
+
main.pushToMain({ index, key: getKey('open'), value: smOpen });
|
|
139
|
+
main.pushToMain({ index, key: getKey('high'), value: smHigh });
|
|
140
|
+
main.pushToMain({ index, key: getKey('low'), value: smLow });
|
|
141
|
+
main.pushToMain({ index, key: getKey('close'), value: smClose });
|
|
142
|
+
main.pushToMain({ index, key: crossKey, value: cross });
|
|
135
143
|
|
|
136
144
|
return true;
|
|
137
|
-
};
|
|
145
|
+
};
|
|
@@ -1,13 +1,10 @@
|
|
|
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
|
-
export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const { verticalOhlcv, instances, priceBased } = main
|
|
10
|
-
const {instanceKey} = precomputed
|
|
5
|
+
export const macd = (main, index, fast, slow, signal, { target, lag, precomputed }) => {
|
|
6
|
+
const { verticalOhlcv, instances, priceBased, useFullNames } = main
|
|
7
|
+
const { instanceKey } = precomputed
|
|
11
8
|
|
|
12
9
|
// Initialization on the first index.
|
|
13
10
|
if (index === 0) {
|
|
@@ -23,13 +20,25 @@ export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}
|
|
|
23
20
|
if (o.key === 'macd') numberOfIndicators++
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
const
|
|
23
|
+
const useIndexedKeys = numberOfIndicators > 1 || useFullNames
|
|
24
|
+
const indicatorSuffix = `${fast}_${slow}_${signal}`
|
|
25
|
+
|
|
26
|
+
const getKey = name => {
|
|
27
|
+
if (target === defaultTarget) {
|
|
28
|
+
return useIndexedKeys
|
|
29
|
+
? `macd_${name}_${indicatorSuffix}`
|
|
30
|
+
: `macd_${name}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return useIndexedKeys
|
|
34
|
+
? `macd_${name}_${target}_${indicatorSuffix}`
|
|
35
|
+
: `macd_${name}_${target}`
|
|
36
|
+
}
|
|
28
37
|
|
|
29
38
|
// Build the keys.
|
|
30
|
-
const diffKey =
|
|
31
|
-
const deaKey =
|
|
32
|
-
const histogramKey =
|
|
39
|
+
const diffKey = getKey('diff')
|
|
40
|
+
const deaKey = getKey('dea')
|
|
41
|
+
const histogramKey = getKey('histogram')
|
|
33
42
|
|
|
34
43
|
if (!instances.hasOwnProperty('macd')) {
|
|
35
44
|
instances.macd = {
|
|
@@ -52,22 +61,34 @@ export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}
|
|
|
52
61
|
|
|
53
62
|
const keyNames = [diffKey, deaKey, histogramKey]
|
|
54
63
|
|
|
55
|
-
for(const k of keyNames) {
|
|
64
|
+
for (const k of keyNames) {
|
|
56
65
|
priceBased.add(k)
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
if(lag > 0)
|
|
60
|
-
{
|
|
68
|
+
if (lag > 0) {
|
|
61
69
|
main.lag(keyNames, lag)
|
|
62
70
|
}
|
|
63
|
-
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
const { numberOfIndicators, settings } = instances.macd
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const
|
|
74
|
+
const useIndexedKeys = numberOfIndicators > 1 || useFullNames
|
|
75
|
+
const indicatorSuffix = `${fast}_${slow}_${signal}`
|
|
76
|
+
|
|
77
|
+
const getKey = name => {
|
|
78
|
+
if (target === defaultTarget) {
|
|
79
|
+
return useIndexedKeys
|
|
80
|
+
? `macd_${name}_${indicatorSuffix}`
|
|
81
|
+
: `macd_${name}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return useIndexedKeys
|
|
85
|
+
? `macd_${name}_${target}_${indicatorSuffix}`
|
|
86
|
+
: `macd_${name}_${target}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const diffKey = getKey('diff')
|
|
90
|
+
const deaKey = getKey('dea')
|
|
91
|
+
const histogramKey = getKey('histogram')
|
|
71
92
|
|
|
72
93
|
const macdInstance = settings[instanceKey]
|
|
73
94
|
const value = verticalOhlcv[target][index]
|
|
@@ -86,4 +107,4 @@ export const macd = (main, index, fast, slow, signal, {target, lag, precomputed}
|
|
|
86
107
|
main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN })
|
|
87
108
|
|
|
88
109
|
return true
|
|
89
|
-
}
|
|
110
|
+
}
|
|
@@ -4,11 +4,11 @@ export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag})
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
const { verticalOhlcv, instances } = main;
|
|
7
|
+
const { verticalOhlcv, instances , useFullNames} = main;
|
|
8
8
|
|
|
9
|
-
const paramsKey =
|
|
10
|
-
const stochD = `
|
|
11
|
-
const stochK = `
|
|
9
|
+
const paramsKey = ((kPeriod === 14 && kSlowingPeriod === 3 && dPeriod === 3) && !useFullNames) ? '' : `_${kPeriod}_${kSlowingPeriod}_${dPeriod}`
|
|
10
|
+
const stochD = `stoch_d${paramsKey}`;
|
|
11
|
+
const stochK = `stoch_k${paramsKey}`;
|
|
12
12
|
const instanceKey = paramsKey
|
|
13
13
|
|
|
14
14
|
// Initialization on the first index.
|