ohlcv-indicators 3.3.2 → 3.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ohlcv-indicators.min.js +1646 -1711
- package/index.js +163 -268
- package/package.json +1 -3
- package/src/core-functions/mainLoop.js +100 -0
- package/src/core-functions/pushToMain.js +15 -0
- package/src/moving-averages/bollingerBands.js +22 -30
- package/src/moving-averages/donchianChannel.js +26 -34
- package/src/moving-averages/macd.js +27 -15
- package/src/moving-averages/movingAverages.js +15 -130
- package/src/moving-averages/relativeVolume.js +20 -14
- package/src/oscillators/rsi.js +21 -25
- package/src/oscillators/volumeOscillator.js +20 -19
- package/src/studies/dateTime.js +22 -20
- package/src/studies/findCrosses.js +17 -17
- package/src/studies/lag.js +21 -12
- package/src/studies/scaler.js +112 -0
- package/src/utilities/assignTypes.js +39 -0
- package/src/utilities/dateUtilities.js +79 -0
- package/src/utilities/numberUtilities.js +50 -9
- package/src/utilities/precisionMultiplier.js +24 -0
- package/src/utilities/validators.js +76 -5
- package/src/utilities/{dataParsingUtilities.js → verticalToHorizontal.js} +8 -8
- package/src/studies/candleVectors.js +0 -121
- package/src/studies/priceVariations.js +0 -23
- package/src/utilities/classification.js +0 -75
- package/src/utilities/parsing-utilities.js +0 -189
- package/src/utilities/pushToMain.js +0 -63
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { validateInputParams } from "../utilities/validators.js";
|
|
2
|
+
import { rsi } from "../oscillators/rsi.js";
|
|
3
|
+
import { movingAverages } from "../moving-averages/movingAverages.js";
|
|
4
|
+
import { macd } from "../moving-averages/macd.js";
|
|
5
|
+
import { relativeVolume } from "../moving-averages/relativeVolume.js";
|
|
6
|
+
import { donchianChannels } from "../moving-averages/donchianChannel.js";
|
|
7
|
+
import { bollingerBands } from "../moving-averages/bollingerBands.js";
|
|
8
|
+
import { volumeOscillator } from "../oscillators/volumeOscillator.js";
|
|
9
|
+
import { lag } from "../studies/lag.js";
|
|
10
|
+
import { crossPairs } from "../studies/findCrosses.js";
|
|
11
|
+
import { dateTime } from "../studies/dateTime.js";
|
|
12
|
+
import { Scaler } from "../studies/scaler.js";
|
|
13
|
+
|
|
14
|
+
import { buildArray } from "../utilities/assignTypes.js";
|
|
15
|
+
import { numberFormater } from "../utilities/numberUtilities.js";
|
|
16
|
+
import { dateFormaters } from "../utilities/dateUtilities.js";
|
|
17
|
+
|
|
18
|
+
// Map indicator keys to their respective functions
|
|
19
|
+
const indicatorFunctions = {
|
|
20
|
+
dateTime,
|
|
21
|
+
rsi,
|
|
22
|
+
ema: movingAverages,
|
|
23
|
+
sma: movingAverages,
|
|
24
|
+
macd,
|
|
25
|
+
relativeVolume,
|
|
26
|
+
donchianChannels,
|
|
27
|
+
bollingerBands,
|
|
28
|
+
volumeOscillator,
|
|
29
|
+
Scaler
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Processes the inputParams into a list of indicator calls
|
|
33
|
+
const processIndicatorCalls = inputParams =>
|
|
34
|
+
Array.isArray(inputParams)
|
|
35
|
+
? inputParams.map(({ key, params }) => ({
|
|
36
|
+
key,
|
|
37
|
+
fn: indicatorFunctions[key],
|
|
38
|
+
args: params
|
|
39
|
+
}))
|
|
40
|
+
: [];
|
|
41
|
+
|
|
42
|
+
export const mainLoop = (input, main) => {
|
|
43
|
+
const { len, inputParams, priceBased, precisionMultiplier, arrayTypes } = main;
|
|
44
|
+
|
|
45
|
+
validateInputParams(main)
|
|
46
|
+
|
|
47
|
+
for(const key of Object.keys(main.inputTypes))
|
|
48
|
+
{
|
|
49
|
+
main.verticalOhlcv[key] = buildArray(arrayTypes[key], len)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Prepare the list of indicator function calls
|
|
53
|
+
const indicatorCalls = processIndicatorCalls(inputParams);
|
|
54
|
+
|
|
55
|
+
// Process each row in the input
|
|
56
|
+
for (let x = 0; x < len; x++) {
|
|
57
|
+
const curr = input[x]
|
|
58
|
+
|
|
59
|
+
processThisRow({x, main, curr, indicatorCalls, priceBased, precisionMultiplier})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMultiplier}) => {
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
for(const [key, formaterKey] of Object.entries(main.inputTypes))
|
|
67
|
+
{
|
|
68
|
+
const value = curr[key]
|
|
69
|
+
|
|
70
|
+
if(numberFormater.hasOwnProperty(formaterKey))
|
|
71
|
+
{
|
|
72
|
+
let formatedValue = numberFormater[formaterKey](value)
|
|
73
|
+
|
|
74
|
+
if(precisionMultiplier > 1 && priceBased.includes(key))
|
|
75
|
+
{
|
|
76
|
+
formatedValue = formatedValue * precisionMultiplier
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main.pushToMain({index: x, key, value: formatedValue})
|
|
80
|
+
}
|
|
81
|
+
else if(dateFormaters.hasOwnProperty(formaterKey))
|
|
82
|
+
{
|
|
83
|
+
main.pushToMain({index: x, key, value: dateFormaters[formaterKey](value)})
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
{
|
|
87
|
+
main.pushToMain({index: x, key, value})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Run all indicator functions except for the ones processed later
|
|
92
|
+
for (const { key, fn, args } of indicatorCalls) {
|
|
93
|
+
if (['crossPairs', 'lag'].includes(key)) continue;
|
|
94
|
+
fn(main, x, ...args)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Process these indicators separately (ensuring their execution order)
|
|
98
|
+
lag(main, x)
|
|
99
|
+
crossPairs(main, x)
|
|
100
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const pushToMain = ({main, index, key, value}) => {
|
|
2
|
+
|
|
3
|
+
const {verticalOhlcv} = main
|
|
4
|
+
|
|
5
|
+
if(value === null || typeof value === 'undefined' || Number.isNaN(value))
|
|
6
|
+
{
|
|
7
|
+
main.invalidValueIndex = index
|
|
8
|
+
verticalOhlcv[key][index] = value
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
verticalOhlcv[key][index] = value
|
|
13
|
+
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { FasterBollingerBands } from 'trading-signals'
|
|
2
|
-
import { calcMagnitude } from '../utilities/numberUtilities.js';
|
|
3
2
|
|
|
4
3
|
const defaultTarget = 'close'
|
|
5
4
|
|
|
6
|
-
export const bollingerBands = (main, index, size, stdDev, { height, range = [], target,
|
|
7
|
-
const { verticalOhlcv, instances
|
|
5
|
+
export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, lag }) => {
|
|
6
|
+
const { verticalOhlcv, instances } = main;
|
|
8
7
|
const suffix = target === defaultTarget ? '' : `_${target}`;
|
|
9
8
|
const indicatorKey = `${size}_${stdDev}${suffix}`;
|
|
10
9
|
let prefix;
|
|
@@ -12,7 +11,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
12
11
|
// Initialization on the first call.
|
|
13
12
|
if (index === 0) {
|
|
14
13
|
|
|
15
|
-
const { priceBased, inputParams,
|
|
14
|
+
const { priceBased, inputParams, verticalOhlcv, len, arrayTypes } = main;
|
|
16
15
|
|
|
17
16
|
if (!(target in verticalOhlcv)) {
|
|
18
17
|
throw new Error(`bollingerBands could not find target "${target}"`);
|
|
@@ -51,14 +50,9 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
51
50
|
}
|
|
52
51
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
53
52
|
|
|
54
|
-
if(autoMinMax)
|
|
55
|
-
{
|
|
56
|
-
main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v,
|
|
55
|
+
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, new Float64Array(len).fill(NaN)]))
|
|
62
56
|
Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
|
|
63
57
|
|
|
64
58
|
if(lag > 0)
|
|
@@ -67,6 +61,11 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`);
|
|
64
|
+
|
|
65
|
+
for(const key of keyNames)
|
|
66
|
+
{
|
|
67
|
+
arrayTypes[key] = 'Float64Array'
|
|
68
|
+
}
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
// Derive prefix for subsequent calls if not set.
|
|
@@ -79,20 +78,20 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
79
78
|
// Update the indicator with the current value.
|
|
80
79
|
const instance = instances.bollinger_bands.settings[indicatorKey];
|
|
81
80
|
const value = verticalOhlcv[target][index];
|
|
82
|
-
instance.update(value
|
|
81
|
+
instance.update(value);
|
|
83
82
|
|
|
84
83
|
// Attempt to retrieve the result.
|
|
85
|
-
let result =
|
|
84
|
+
let result = {};
|
|
86
85
|
try {
|
|
87
86
|
result = instance.getResult();
|
|
88
87
|
} catch (err) {
|
|
89
|
-
// If not available, result stays
|
|
88
|
+
// If not available, result stays {}.
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
// Use
|
|
93
|
-
const upper = result?.upper ??
|
|
94
|
-
const middle = result?.middle ??
|
|
95
|
-
const lower = result?.lower ??
|
|
91
|
+
// Use NaN fallbacks for the primary values.
|
|
92
|
+
const upper = result?.upper ?? NaN;
|
|
93
|
+
const middle = result?.middle ?? NaN;
|
|
94
|
+
const lower = result?.lower ?? NaN;
|
|
96
95
|
|
|
97
96
|
// Always push the indicator outputs.
|
|
98
97
|
main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
|
|
@@ -101,26 +100,19 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
101
100
|
|
|
102
101
|
// Process height if requested.
|
|
103
102
|
if (height) {
|
|
104
|
-
let heightValue =
|
|
105
|
-
if (
|
|
106
|
-
heightValue =
|
|
103
|
+
let heightValue = NaN;
|
|
104
|
+
if (!Number.isNaN(lower) && !Number.isNaN(upper)) {
|
|
105
|
+
heightValue = ((upper - lower) / lower)
|
|
107
106
|
}
|
|
108
107
|
main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
// Process each range property.
|
|
112
111
|
for (const rangeKey of range) {
|
|
113
|
-
let rangeValue =
|
|
112
|
+
let rangeValue = NaN;
|
|
114
113
|
const priceValue = verticalOhlcv[rangeKey][index];
|
|
115
|
-
if (
|
|
116
|
-
rangeValue = (
|
|
117
|
-
? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
|
|
118
|
-
: (priceValue - lower) / (upper - lower)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (scale) {
|
|
122
|
-
rangeValue = calcMagnitude(rangeValue, scale);
|
|
123
|
-
}
|
|
114
|
+
if (!Number.isNaN(priceValue) && !Number.isNaN(lower) && !Number.isNaN(upper)) {
|
|
115
|
+
rangeValue = (priceValue - lower) / (upper - lower)
|
|
124
116
|
}
|
|
125
117
|
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
|
|
126
118
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { calcMagnitude } from "../utilities/numberUtilities.js"
|
|
2
1
|
|
|
3
|
-
export const donchianChannels = (main, index, size, offset, { height, range,
|
|
2
|
+
export const donchianChannels = (main, index, size, offset, { height, range, lag }) => {
|
|
4
3
|
const indicatorKey = `${size}_${offset}`;
|
|
5
4
|
const { verticalOhlcv, instances, len } = main;
|
|
6
5
|
|
|
7
6
|
// Initialization: create output arrays and indicator instance on the first call.
|
|
8
7
|
if (index === 0) {
|
|
9
|
-
const { inputParams, priceBased,
|
|
8
|
+
const { inputParams, priceBased, arrayTypes} = main;
|
|
10
9
|
const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length;
|
|
11
10
|
const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel';
|
|
12
11
|
|
|
@@ -33,14 +32,9 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
33
32
|
}
|
|
34
33
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
35
34
|
|
|
36
|
-
if(autoMinMax)
|
|
37
|
-
{
|
|
38
|
-
main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
35
|
}
|
|
42
36
|
|
|
43
|
-
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v,
|
|
37
|
+
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, new Float64Array(len).fill(NaN)]))
|
|
44
38
|
Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
|
|
45
39
|
|
|
46
40
|
if(lag > 0)
|
|
@@ -54,6 +48,11 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
54
48
|
maxDeque: [], // will hold indices for highs in descending order
|
|
55
49
|
minDeque: [] // will hold indices for lows in ascending order
|
|
56
50
|
};
|
|
51
|
+
|
|
52
|
+
for(const key of keyNames)
|
|
53
|
+
{
|
|
54
|
+
arrayTypes[key] = 'Float64Array'
|
|
55
|
+
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
const numberOfIndicators = instances.donchian_channel.numberOfIndicators;
|
|
@@ -67,16 +66,16 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
67
66
|
const endIdx = currentBarIdx + 1; // currentBarIdx is inclusive
|
|
68
67
|
const startIdx = endIdx - size; // window: [startIdx, endIdx)
|
|
69
68
|
|
|
70
|
-
// If the window is not fully available, push
|
|
69
|
+
// If the window is not fully available, push NaN values for all computed outputs.
|
|
71
70
|
if (startIdx < 0 || endIdx > len) {
|
|
72
|
-
main.pushToMain({ index, key: `${subPrefix}_upper`, value:
|
|
73
|
-
main.pushToMain({ index, key: `${subPrefix}_basis`, value:
|
|
74
|
-
main.pushToMain({ index, key: `${subPrefix}_lower`, value:
|
|
71
|
+
main.pushToMain({ index, key: `${subPrefix}_upper`, value: NaN });
|
|
72
|
+
main.pushToMain({ index, key: `${subPrefix}_basis`, value: NaN });
|
|
73
|
+
main.pushToMain({ index, key: `${subPrefix}_lower`, value: NaN });
|
|
75
74
|
if (height) {
|
|
76
|
-
main.pushToMain({ index, key: `${subPrefix}_height`, value:
|
|
75
|
+
main.pushToMain({ index, key: `${subPrefix}_height`, value: NaN });
|
|
77
76
|
}
|
|
78
77
|
for (const rangeKey of range) {
|
|
79
|
-
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value:
|
|
78
|
+
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: NaN });
|
|
80
79
|
}
|
|
81
80
|
return true;
|
|
82
81
|
}
|
|
@@ -103,9 +102,9 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
103
102
|
minDeque.push(currentBarIdx);
|
|
104
103
|
|
|
105
104
|
// Retrieve computed values with safety checks.
|
|
106
|
-
const upper = maxDeque.length ? highs[maxDeque[0]] :
|
|
107
|
-
const lower = minDeque.length ? lows[minDeque[0]] :
|
|
108
|
-
const basis = (
|
|
105
|
+
const upper = maxDeque.length ? highs[maxDeque[0]] : NaN;
|
|
106
|
+
const lower = minDeque.length ? lows[minDeque[0]] : NaN;
|
|
107
|
+
const basis = (!Number.isNaN(upper) && !Number.isNaN(lower)) ? (upper + lower) / 2 : NaN;
|
|
109
108
|
|
|
110
109
|
// Always push the main indicator values.
|
|
111
110
|
main.pushToMain({ index, key: `${subPrefix}_upper`, value: upper });
|
|
@@ -114,30 +113,23 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
114
113
|
|
|
115
114
|
// Process height if enabled.
|
|
116
115
|
if (height) {
|
|
117
|
-
let heightValue =
|
|
118
|
-
if (
|
|
119
|
-
heightValue =
|
|
116
|
+
let heightValue = NaN;
|
|
117
|
+
if (!Number.isNaN(upper) && !Number.isNaN(lower)) {
|
|
118
|
+
heightValue = ((upper - lower) / lower)
|
|
120
119
|
}
|
|
121
120
|
main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
// Process each range property.
|
|
125
124
|
for (const rangeKey of range) {
|
|
126
|
-
let rangeValue =
|
|
127
|
-
const priceValue = verticalOhlcv[rangeKey][index]
|
|
125
|
+
let rangeValue = NaN;
|
|
126
|
+
const priceValue = verticalOhlcv[rangeKey][index]
|
|
128
127
|
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
(upper - lower) !== 0
|
|
128
|
+
!Number.isNaN(priceValue) &&
|
|
129
|
+
!Number.isNaN(upper) &&
|
|
130
|
+
!Number.isNaN(lower)
|
|
133
131
|
) {
|
|
134
|
-
rangeValue = (
|
|
135
|
-
? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
|
|
136
|
-
: (priceValue - lower) / (upper - lower)
|
|
137
|
-
|
|
138
|
-
if (scale) {
|
|
139
|
-
rangeValue = calcMagnitude(rangeValue, scale);
|
|
140
|
-
}
|
|
132
|
+
rangeValue = (priceValue - lower) / (upper - lower)
|
|
141
133
|
}
|
|
142
134
|
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
|
|
143
135
|
}
|
|
@@ -2,16 +2,16 @@ import { FasterEMA, FasterMACD } from 'trading-signals';
|
|
|
2
2
|
|
|
3
3
|
const defaultTarget = 'close';
|
|
4
4
|
|
|
5
|
-
export const macd = (main, index, fast, slow, signal,
|
|
6
|
-
|
|
7
|
-
const { verticalOhlcv, instances
|
|
5
|
+
export const macd = (main, index, fast, slow, signal, {target, lag}) => {
|
|
6
|
+
|
|
7
|
+
const { verticalOhlcv, instances } = main;
|
|
8
8
|
|
|
9
9
|
// Create an instance key that includes the target if it isn't the default.
|
|
10
10
|
const instanceKey = `${fast}_${slow}_${signal}${target === defaultTarget ? '' : `_${target}`}`;
|
|
11
11
|
|
|
12
12
|
// Initialization on the first index.
|
|
13
13
|
if (index === 0) {
|
|
14
|
-
const { inputParams, crossPairsList,
|
|
14
|
+
const { inputParams, crossPairsList, priceBased, len , arrayTypes} = main;
|
|
15
15
|
|
|
16
16
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
17
17
|
throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`);
|
|
@@ -42,12 +42,25 @@ export const macd = (main, index, fast, slow, signal, options) => {
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
Object.assign(verticalOhlcv, {
|
|
45
|
-
[diffKey]:
|
|
46
|
-
[deaKey]:
|
|
47
|
-
[histogramKey]:
|
|
45
|
+
[diffKey]: new Float64Array(len).fill(NaN),
|
|
46
|
+
[deaKey]: new Float64Array(len).fill(NaN),
|
|
47
|
+
[histogramKey]: new Float64Array(len).fill(NaN),
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
priceBased.push(diffKey, deaKey, histogramKey)
|
|
51
|
+
|
|
52
|
+
const keyNames = [diffKey, deaKey, histogramKey]
|
|
53
|
+
|
|
54
|
+
if(lag > 0)
|
|
55
|
+
{
|
|
56
|
+
main.lag(keyNames, lag)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for(const key of keyNames)
|
|
60
|
+
{
|
|
61
|
+
arrayTypes[key] = 'Float64Array'
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
const { numberOfIndicators, settings } = instances.macd;
|
|
@@ -58,20 +71,19 @@ export const macd = (main, index, fast, slow, signal, options) => {
|
|
|
58
71
|
|
|
59
72
|
const macdInstance = settings[instanceKey];
|
|
60
73
|
const value = verticalOhlcv[target][index];
|
|
61
|
-
macdInstance.update(value
|
|
74
|
+
macdInstance.update(value);
|
|
62
75
|
|
|
63
|
-
let macdResult =
|
|
76
|
+
let macdResult = {}
|
|
64
77
|
try {
|
|
65
78
|
macdResult = macdInstance.getResult();
|
|
66
79
|
} catch (err) {
|
|
67
|
-
// If the result is unavailable, macdResult remains
|
|
68
|
-
macdResult = null;
|
|
80
|
+
// If the result is unavailable, macdResult remains NaN.
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
// Always push values; use
|
|
72
|
-
main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd :
|
|
73
|
-
main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal :
|
|
74
|
-
main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram :
|
|
83
|
+
// Always push values; use NaN as fallback when macdResult is missing.
|
|
84
|
+
main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN });
|
|
85
|
+
main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN });
|
|
86
|
+
main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN });
|
|
75
87
|
|
|
76
88
|
return true;
|
|
77
89
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
import {FasterEMA, FasterSMA, FasterBollingerBands} from 'trading-signals';
|
|
3
|
-
import { classifyBoll, calcZScore } from '../utilities/classification.js'
|
|
4
3
|
|
|
5
4
|
const indicatorClasses = {ema: FasterEMA, sma: FasterSMA}
|
|
6
5
|
|
|
7
|
-
export const movingAverages = (main, index, indicatorName, size, { target,
|
|
8
|
-
const { verticalOhlcv, instances, priceBased
|
|
6
|
+
export const movingAverages = (main, index, indicatorName, size, { target, lag }) => {
|
|
7
|
+
const { verticalOhlcv, instances, priceBased } = main;
|
|
9
8
|
let suffix =
|
|
10
9
|
typeof target === 'string' &&
|
|
11
10
|
verticalOhlcv.hasOwnProperty(target) &&
|
|
@@ -13,10 +12,9 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
|
|
|
13
12
|
? `_${target}`
|
|
14
13
|
: '';
|
|
15
14
|
const keyName = `${indicatorName}_${size}${suffix}`;
|
|
16
|
-
const diffKeyName = `${indicatorName}_${size}_diff${suffix}`;
|
|
17
15
|
|
|
18
16
|
if (index === 0) {
|
|
19
|
-
const {
|
|
17
|
+
const { len, arrayTypes } = main;
|
|
20
18
|
|
|
21
19
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
22
20
|
throw new Error(
|
|
@@ -29,146 +27,33 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
|
|
|
29
27
|
maInstance: new indicatorClasses[indicatorName](size)
|
|
30
28
|
};
|
|
31
29
|
|
|
32
|
-
verticalOhlcv[keyName] =
|
|
33
|
-
|
|
34
|
-
// If diff configuration is provided, create the diff indicator.
|
|
35
|
-
if (diff !== null) {
|
|
36
|
-
instances[keyName].diffInstance = new FasterBollingerBands(
|
|
37
|
-
diff.size,
|
|
38
|
-
diff.stdDev
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const lagDiffArr = [];
|
|
42
|
-
|
|
43
|
-
for (const targetKey of diff.targets) {
|
|
44
|
-
verticalOhlcv[`${diffKeyName}_${targetKey}`] = [...nullArray];
|
|
45
|
-
|
|
46
|
-
if(diff.autoMinMax)
|
|
47
|
-
{
|
|
48
|
-
main.autoMinMaxKeys.push(`${diffKeyName}_${targetKey}`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (diff.lag > 0) {
|
|
52
|
-
lagDiffArr.push(`${diffKeyName}_${targetKey}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
30
|
+
verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
|
|
31
|
+
priceBased.push(keyName);
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
if(lag > 0)
|
|
34
|
+
{
|
|
35
|
+
main.lag([keyName], lag)
|
|
59
36
|
}
|
|
60
37
|
|
|
61
|
-
|
|
38
|
+
arrayTypes[keyName] = 'Float64Array'
|
|
39
|
+
|
|
62
40
|
}
|
|
63
41
|
|
|
64
42
|
// Retrieve the current price value.
|
|
65
43
|
const value = verticalOhlcv[target][index];
|
|
66
|
-
const { maInstance
|
|
44
|
+
const { maInstance } = instances[keyName];
|
|
67
45
|
|
|
68
46
|
// Update the moving average instance.
|
|
69
|
-
maInstance.update(value
|
|
70
|
-
let currMa =
|
|
47
|
+
maInstance.update(value);
|
|
48
|
+
let currMa = NaN;
|
|
71
49
|
try {
|
|
72
50
|
currMa = maInstance.getResult();
|
|
73
51
|
} catch (err) {
|
|
74
|
-
currMa =
|
|
52
|
+
currMa = NaN;
|
|
75
53
|
}
|
|
76
54
|
|
|
77
|
-
// Always push the MA value (even if
|
|
55
|
+
// Always push the MA value (even if NaN).
|
|
78
56
|
main.pushToMain({ index, key: keyName, value: currMa });
|
|
79
57
|
|
|
80
|
-
// Process the diff values if a diff is requested.
|
|
81
|
-
if (diff !== null) {
|
|
82
|
-
for (const targetKey of diff.targets) {
|
|
83
|
-
// Compute diffValue only if we have a valid MA value.
|
|
84
|
-
let diffValue = null;
|
|
85
|
-
if (currMa !== null && typeof currMa === 'number') {
|
|
86
|
-
diffValue = verticalOhlcv[targetKey][index] - currMa;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Update the diff indicator only if diffValue is a valid number.
|
|
90
|
-
if (diffValue !== null && typeof diffValue === 'number') {
|
|
91
|
-
diffInstance.update(Math.abs(diffValue), lastIndexReplace);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let diffBoll = null;
|
|
95
|
-
try {
|
|
96
|
-
diffBoll = diffInstance.getResult();
|
|
97
|
-
} catch (err) {
|
|
98
|
-
diffBoll = null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const classified = classifyBoll(diffValue, diffBoll, diff.scale, diff.autoMinMax);
|
|
102
|
-
main.pushToMain({
|
|
103
|
-
index,
|
|
104
|
-
key: `${diffKeyName}_${targetKey}`,
|
|
105
|
-
value: classified
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
58
|
return true;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
export const getMovingAveragesParams = (indicatorName, size, options, validMagnitudeValues) => {
|
|
115
|
-
if (!options || typeof options !== 'object') {
|
|
116
|
-
throw new Error(`"options" must be an object in ${indicatorName}. eg: {target, height, range}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let { target = 'close', diff = null } = options;
|
|
120
|
-
|
|
121
|
-
if(typeof target !== 'string')
|
|
122
|
-
{
|
|
123
|
-
throw new Error(`"target" must be a valid keyName ${indicatorName}.`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (typeof size !== 'number' || size <= 0) {
|
|
127
|
-
throw new Error(`"size" must be a positive number in ${indicatorName}.`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
//diff can be null, true, false or an object with optional {stdDev, scale, targets, and size}
|
|
131
|
-
const optionArgs = { target, diff: null };
|
|
132
|
-
|
|
133
|
-
if (diff !== null && (typeof diff === 'object' || (typeof diff === 'boolean' && diff === true))) {
|
|
134
|
-
|
|
135
|
-
diff = (diff === true) ? ({}) : diff
|
|
136
|
-
|
|
137
|
-
const {
|
|
138
|
-
stdDev = 2,
|
|
139
|
-
scale = 0.05,
|
|
140
|
-
targets = ['close'],
|
|
141
|
-
size: diffSize = size,
|
|
142
|
-
lag = 0,
|
|
143
|
-
autoMinMax = false
|
|
144
|
-
} = diff;
|
|
145
|
-
|
|
146
|
-
if (typeof stdDev !== 'number' || stdDev <= 0) {
|
|
147
|
-
throw new Error(`"diff.stdDev" must be a number greater than 0 in ${indicatorName}.`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (typeof scale !== 'number' || !validMagnitudeValues.includes(scale)) {
|
|
151
|
-
throw new Error(`"diff.scale" must be a number between 0.01 and 1 in ${indicatorName}.`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (typeof diffSize !== 'number' || !Number.isInteger(diffSize) || diffSize <= 0) {
|
|
155
|
-
throw new Error(`"diff.size" must be an integer greater than or equal to size in ${indicatorName}.`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!Array.isArray(targets)) {
|
|
159
|
-
throw new Error(`"diff.targest" must be an array of keyNames with at least 1 item in ${indicatorName}.`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (typeof lag !== 'number' || !Number.isInteger(lag) || lag < 0) {
|
|
163
|
-
throw new Error(`"diff.lag" must be an integer greater than 0 in ${indicatorName}.`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (typeof autoMinMax !== 'boolean') {
|
|
167
|
-
throw new Error(`"diff.autoMinMax" must be an boolean in ${indicatorName}.`);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
optionArgs.diff = { stdDev, scale, size: diffSize, targets, lag, autoMinMax};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return optionArgs;
|
|
174
59
|
}
|
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
import { FasterSMA } from 'trading-signals';
|
|
2
|
-
import { calcMagnitude } from '../utilities/numberUtilities.js';
|
|
3
2
|
|
|
4
|
-
export const relativeVolume = (main, index, size
|
|
3
|
+
export const relativeVolume = (main, index, size, {lag}) => {
|
|
4
|
+
|
|
5
5
|
const key = `relative_volume_${size}`;
|
|
6
|
-
const { instances, verticalOhlcv
|
|
6
|
+
const { instances, verticalOhlcv } = main;
|
|
7
7
|
|
|
8
8
|
if (index === 0) {
|
|
9
|
-
const {
|
|
9
|
+
const { len, arrayTypes } = main;
|
|
10
|
+
|
|
10
11
|
instances[key] = {
|
|
11
12
|
instance: new FasterSMA(size),
|
|
12
|
-
prevRelativeVolumeSma:
|
|
13
|
+
prevRelativeVolumeSma: NaN
|
|
13
14
|
};
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
verticalOhlcv[key] = new Float64Array(len).fill(NaN);
|
|
17
|
+
|
|
18
|
+
if(lag > 0)
|
|
19
|
+
{
|
|
20
|
+
main.lag([key], lag)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
arrayTypes[key] = 'Float64Array'
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
const value = verticalOhlcv.volume[index];
|
|
18
27
|
const smaInstance = instances[key].instance;
|
|
19
|
-
smaInstance.update(value
|
|
28
|
+
smaInstance.update(value);
|
|
20
29
|
|
|
21
|
-
let smaValue =
|
|
30
|
+
let smaValue = NaN;
|
|
22
31
|
try {
|
|
23
32
|
smaValue = smaInstance.getResult();
|
|
24
33
|
} catch (err) {
|
|
25
|
-
|
|
34
|
+
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
const prevSma = instances[key].prevRelativeVolumeSma;
|
|
29
|
-
let currRelativeVolume =
|
|
38
|
+
let currRelativeVolume = NaN;
|
|
30
39
|
|
|
31
40
|
// Only calculate relative volume if both current SMA and previous SMA are valid numbers and prevSma is not zero.
|
|
32
|
-
if (
|
|
41
|
+
if (!Number.isNaN(smaValue) && !Number.isNaN(prevSma)) {
|
|
33
42
|
currRelativeVolume = value / prevSma;
|
|
34
|
-
if (scale) {
|
|
35
|
-
currRelativeVolume = calcMagnitude(currRelativeVolume, scale);
|
|
36
|
-
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
main.pushToMain({ index, key, value: currRelativeVolume });
|