ohlcv-indicators 3.5.78 → 3.5.79
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/AGENTS.md +105 -0
- package/README.md +415 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
- package/index.js +93 -55
- package/package.json +1 -1
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +47 -22
- package/src/core-functions/pushToMain.js +1 -3
- package/src/core-indicators/README.md +61 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +82 -0
- package/src/core-indicators/movingAverages.js +88 -0
- package/src/core-indicators/rsi.js +41 -0
- package/src/core-indicators/volatility.js +107 -0
- package/src/moving-averages/bollingerBands.js +11 -26
- package/src/moving-averages/donchianChannel.js +30 -29
- package/src/moving-averages/heikenAshi.js +23 -33
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +7 -23
- package/src/moving-averages/relativeVolume.js +23 -21
- package/src/oscillators/rsi.js +7 -26
- package/src/oscillators/stochastic.js +6 -19
- package/src/oscillators/volumeDelta.js +36 -28
- package/src/oscillators/volumeOscillator.js +29 -31
- package/src/studies/candleFeatures.js +12 -20
- package/src/studies/dateTime.js +22 -27
- package/src/studies/findCrosses.js +16 -7
- package/src/studies/lag.js +26 -13
- package/src/studies/mapCols.js +14 -15
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +64 -0
- package/src/utilities/numberUtilities.js +2 -0
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +21 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +533 -0
- package/src/moving-averages/vidya.js +0 -181
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { FasterEMA } from '
|
|
1
|
+
import { FasterEMA } from '../core-indicators/index.js';
|
|
2
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
3
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
2
4
|
|
|
3
5
|
const isBadNumber = v => v == null || !Number.isFinite(v);
|
|
4
6
|
|
|
@@ -9,7 +11,7 @@ export const heikenAshi = (
|
|
|
9
11
|
afterSmoothLength,
|
|
10
12
|
{ lag = 0, bothNull = false, retLogs = true } = {}
|
|
11
13
|
) => {
|
|
12
|
-
const { verticalOhlcv, instances,
|
|
14
|
+
const { verticalOhlcv, instances, scaledGroups } = main;
|
|
13
15
|
const indicatorKey = `${smoothLength}_${afterSmoothLength}`;
|
|
14
16
|
const ohlcKeys = ['open', 'high', 'low', 'close'];
|
|
15
17
|
|
|
@@ -40,6 +42,8 @@ export const heikenAshi = (
|
|
|
40
42
|
|
|
41
43
|
// ---- INIT ----
|
|
42
44
|
if (index === 0) {
|
|
45
|
+
validateInputValues({ open: true, high: true, low: true, close: true }, verticalOhlcv, index, 'heikenAshi');
|
|
46
|
+
|
|
43
47
|
instances[instanceKey] = {
|
|
44
48
|
emaPre: !bothNull
|
|
45
49
|
? Object.fromEntries(ohlcKeys.map(k => [k, new FasterEMA(smoothLength)]))
|
|
@@ -56,19 +60,11 @@ export const heikenAshi = (
|
|
|
56
60
|
|
|
57
61
|
const keyNames = featureKeys.map(getKey);
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
[...keyNames, crossKey].map(k => [k, new Float64Array(len).fill(NaN)])
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
Object.assign(verticalOhlcv, verticalOhlcvSetup);
|
|
63
|
+
initializeColumns(main, [...keyNames, crossKey].map(key => ({ key })), { lag });
|
|
64
64
|
|
|
65
65
|
if (scaledGroups) {
|
|
66
66
|
scaledGroups.heikenAshi = keyNames;
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
if (lag > 0) {
|
|
70
|
-
main.lag(keyNames, lag);
|
|
71
|
-
}
|
|
72
68
|
}
|
|
73
69
|
|
|
74
70
|
// ---- FETCH RAW INPUTS ----
|
|
@@ -84,7 +80,7 @@ export const heikenAshi = (
|
|
|
84
80
|
isBadNumber(low) ||
|
|
85
81
|
isBadNumber(close)
|
|
86
82
|
) {
|
|
87
|
-
return
|
|
83
|
+
return;
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
let sOpen, sHigh, sLow, sClose;
|
|
@@ -96,14 +92,12 @@ export const heikenAshi = (
|
|
|
96
92
|
inst.emaPre.low.update(low);
|
|
97
93
|
inst.emaPre.close.update(close);
|
|
98
94
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
95
|
+
if (!inst.emaPre.open.isStable || !inst.emaPre.high.isStable ||
|
|
96
|
+
!inst.emaPre.low.isStable || !inst.emaPre.close.isStable) return;
|
|
97
|
+
sOpen = inst.emaPre.open.getResult();
|
|
98
|
+
sHigh = inst.emaPre.high.getResult();
|
|
99
|
+
sLow = inst.emaPre.low.getResult();
|
|
100
|
+
sClose = inst.emaPre.close.getResult();
|
|
107
101
|
} else {
|
|
108
102
|
sOpen = open;
|
|
109
103
|
sHigh = high;
|
|
@@ -117,7 +111,7 @@ export const heikenAshi = (
|
|
|
117
111
|
isBadNumber(sLow) ||
|
|
118
112
|
isBadNumber(sClose)
|
|
119
113
|
) {
|
|
120
|
-
return
|
|
114
|
+
return;
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
// ---- HEIKEN ASHI CORE ----
|
|
@@ -145,14 +139,12 @@ export const heikenAshi = (
|
|
|
145
139
|
inst.emaPost.low.update(haLow);
|
|
146
140
|
inst.emaPost.close.update(haClose);
|
|
147
141
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
142
|
+
if (!inst.emaPost.open.isStable || !inst.emaPost.high.isStable ||
|
|
143
|
+
!inst.emaPost.low.isStable || !inst.emaPost.close.isStable) return;
|
|
144
|
+
smOpen = inst.emaPost.open.getResult();
|
|
145
|
+
smHigh = inst.emaPost.high.getResult();
|
|
146
|
+
smLow = inst.emaPost.low.getResult();
|
|
147
|
+
smClose = inst.emaPost.close.getResult();
|
|
156
148
|
} else {
|
|
157
149
|
smOpen = haOpen;
|
|
158
150
|
smHigh = haHigh;
|
|
@@ -170,7 +162,7 @@ export const heikenAshi = (
|
|
|
170
162
|
smLow <= 0 ||
|
|
171
163
|
smClose <= 0
|
|
172
164
|
) {
|
|
173
|
-
return
|
|
165
|
+
return;
|
|
174
166
|
}
|
|
175
167
|
|
|
176
168
|
// ---- TREND/CROSS LOGIC ----
|
|
@@ -229,6 +221,4 @@ export const heikenAshi = (
|
|
|
229
221
|
|
|
230
222
|
// Do not add return logs to cross.
|
|
231
223
|
main.pushToMain({ index, key: crossKey, value: cross });
|
|
232
|
-
|
|
233
|
-
return true;
|
|
234
|
-
};
|
|
224
|
+
};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { FasterEMA, FasterMACD } from '
|
|
1
|
+
import { FasterEMA, FasterMACD } from '../core-indicators/index.js'
|
|
2
|
+
import { validateInputValues } from '../utilities/validators.js'
|
|
3
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
2
4
|
|
|
3
5
|
const defaultTarget = 'close'
|
|
4
6
|
|
|
5
7
|
export const macd = (main, index, fast, slow, signal, { target, lag, precomputed }) => {
|
|
6
|
-
const { verticalOhlcv, instances,
|
|
8
|
+
const { verticalOhlcv, instances, useFullNames } = main
|
|
7
9
|
const { instanceKey } = precomputed
|
|
8
10
|
|
|
9
11
|
// Initialization on the first index.
|
|
10
12
|
if (index === 0) {
|
|
11
|
-
|
|
13
|
+
validateInputValues({ [target]: true }, verticalOhlcv, index, 'macd')
|
|
14
|
+
|
|
15
|
+
const { inputParams } = main
|
|
12
16
|
|
|
13
17
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
14
18
|
throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`)
|
|
@@ -53,21 +57,8 @@ export const macd = (main, index, fast, slow, signal, { target, lag, precomputed
|
|
|
53
57
|
new FasterEMA(signal)
|
|
54
58
|
)
|
|
55
59
|
|
|
56
|
-
Object.assign(verticalOhlcv, {
|
|
57
|
-
[diffKey]: new Float64Array(len).fill(NaN),
|
|
58
|
-
[deaKey]: new Float64Array(len).fill(NaN),
|
|
59
|
-
[histogramKey]: new Float64Array(len).fill(NaN),
|
|
60
|
-
})
|
|
61
|
-
|
|
62
60
|
const keyNames = [diffKey, deaKey, histogramKey]
|
|
63
|
-
|
|
64
|
-
for (const k of keyNames) {
|
|
65
|
-
priceBased.add(k)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (lag > 0) {
|
|
69
|
-
main.lag(keyNames, lag)
|
|
70
|
-
}
|
|
61
|
+
initializeColumns(main, keyNames.map(key => ({ key, priceBased: true })), { lag })
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
const { numberOfIndicators, settings } = instances.macd
|
|
@@ -94,17 +85,10 @@ export const macd = (main, index, fast, slow, signal, { target, lag, precomputed
|
|
|
94
85
|
const value = verticalOhlcv[target][index]
|
|
95
86
|
macdInstance.update(value)
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
macdResult = macdInstance.getResult()
|
|
100
|
-
} catch (err) {
|
|
101
|
-
// If the result is unavailable, macdResult remains NaN.
|
|
102
|
-
}
|
|
88
|
+
const macdResult = macdInstance.isStable ? macdInstance.getResult() : null
|
|
103
89
|
|
|
104
90
|
// Always push values; use NaN as fallback when macdResult is missing.
|
|
105
91
|
main.pushToMain({ index, key: diffKey, value: macdResult ? macdResult.macd : NaN })
|
|
106
92
|
main.pushToMain({ index, key: deaKey, value: macdResult ? macdResult.signal : NaN })
|
|
107
93
|
main.pushToMain({ index, key: histogramKey, value: macdResult ? macdResult.histogram : NaN })
|
|
108
|
-
|
|
109
|
-
return true
|
|
110
|
-
}
|
|
94
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import {FasterEMA, FasterSMA} from '
|
|
2
|
+
import {FasterEMA, FasterSMA} from '../core-indicators/index.js';
|
|
3
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
4
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
3
5
|
|
|
4
6
|
const indicatorClasses = {
|
|
5
7
|
ema: FasterEMA,
|
|
@@ -13,8 +15,7 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
|
|
|
13
15
|
const keyName = `${methodName}_${size}${suffix}`
|
|
14
16
|
|
|
15
17
|
if (index === 0) {
|
|
16
|
-
|
|
17
|
-
const {len} = main
|
|
18
|
+
validateInputValues({ [target]: true }, verticalOhlcv, index, methodName)
|
|
18
19
|
|
|
19
20
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
20
21
|
throw new Error(
|
|
@@ -25,16 +26,7 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
|
|
|
25
26
|
// Create the main moving average instance.
|
|
26
27
|
instances[keyName] = new indicatorClasses[methodName](size)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if(priceBased.has(target)){
|
|
31
|
-
priceBased.add(keyName)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if(lag > 0)
|
|
35
|
-
{
|
|
36
|
-
main.lag([keyName], lag)
|
|
37
|
-
}
|
|
29
|
+
initializeColumns(main, [{ key: keyName, priceBased: priceBased.has(target) }], { lag })
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
// Retrieve the current price value
|
|
@@ -44,16 +36,8 @@ export const movingAverages = (main, index, methodName, size, { target, lag }) =
|
|
|
44
36
|
// Update the moving average instance.
|
|
45
37
|
instance.update(value)
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
currMa = instance.getResult()
|
|
51
|
-
} catch (err) {
|
|
52
|
-
|
|
53
|
-
}
|
|
39
|
+
const currMa = instance.isStable ? instance.getResult() : NaN
|
|
54
40
|
|
|
55
41
|
// Always push the MA value (even if NaN).
|
|
56
42
|
main.pushToMain({ index, key: keyName, value: currMa })
|
|
57
|
-
|
|
58
|
-
return true
|
|
59
|
-
}
|
|
43
|
+
}
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
import { FasterSMA } from '
|
|
1
|
+
import { FasterSMA } from '../core-indicators/index.js';
|
|
2
|
+
import { isPositiveInteger } from '../utilities/numberUtilities.js';
|
|
3
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
4
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
5
|
+
|
|
2
6
|
|
|
3
7
|
export const relativeVolume = (main, index, size, {lag}) => {
|
|
4
8
|
|
|
5
9
|
const key = `relative_volume_${size}`;
|
|
6
10
|
const { instances, verticalOhlcv } = main;
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
if (index === 0) {
|
|
9
|
-
|
|
14
|
+
validateInputValues({ volume: true }, verticalOhlcv, index, 'relativeVolume');
|
|
10
15
|
|
|
11
16
|
instances[key] = {
|
|
12
17
|
instance: new FasterSMA(size),
|
|
13
18
|
prevRelativeVolumeSma: NaN
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if(lag > 0)
|
|
19
|
-
{
|
|
20
|
-
main.lag([key], lag)
|
|
21
|
-
}
|
|
21
|
+
initializeColumns(main, [{ key }], { lag });
|
|
22
22
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
const smaInstance = instances[key].instance;
|
|
27
|
-
smaInstance.update(value);
|
|
28
|
-
|
|
29
|
-
let smaValue = NaN;
|
|
30
|
-
try {
|
|
31
|
-
smaValue = smaInstance.getResult();
|
|
32
|
-
} catch (err) {
|
|
25
|
+
const volume = verticalOhlcv.volume[index];
|
|
33
26
|
|
|
27
|
+
if(volume === 0) {
|
|
28
|
+
return;
|
|
34
29
|
}
|
|
30
|
+
|
|
31
|
+
const smaInstance = instances[key].instance;
|
|
32
|
+
smaInstance.update(volume);
|
|
33
|
+
|
|
34
|
+
const smaValue = smaInstance.isStable ? smaInstance.getResult() : NaN;
|
|
35
35
|
|
|
36
36
|
const prevSma = instances[key].prevRelativeVolumeSma;
|
|
37
|
-
let currRelativeVolume = NaN;
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
// Save the current SMA for the next nonzero-volume row.
|
|
39
|
+
instances[key].prevRelativeVolumeSma = smaValue;
|
|
40
|
+
|
|
41
|
+
if(Number.isNaN(smaValue) || Number.isNaN(prevSma)) {
|
|
42
|
+
return
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
let currRelativeVolume = volume / prevSma;
|
|
46
|
+
|
|
44
47
|
main.pushToMain({ index, key, value: currRelativeVolume });
|
|
45
48
|
instances[key].prevRelativeVolumeSma = smaValue;
|
|
46
49
|
|
|
47
|
-
return true;
|
|
48
50
|
};
|
package/src/oscillators/rsi.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FasterRSI } from '
|
|
2
|
-
import { FasterSMA } from 'trading-signals';
|
|
1
|
+
import { FasterRSI, FasterSMA } from '../core-indicators/index.js';
|
|
3
2
|
import { mathLog } from '../utilities/math.js';
|
|
3
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
4
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
4
5
|
|
|
5
6
|
const defaultTarget = 'close'
|
|
6
7
|
export const rsi = (main, index, size, { target, lag, retLogs }) => {
|
|
@@ -15,8 +16,7 @@ export const rsi = (main, index, size, { target, lag, retLogs }) => {
|
|
|
15
16
|
|
|
16
17
|
// Initialization on the first index.
|
|
17
18
|
if (index === 0) {
|
|
18
|
-
|
|
19
|
-
const {len } = main;
|
|
19
|
+
validateInputValues({ [target]: true }, verticalOhlcv, index, 'rsi');
|
|
20
20
|
|
|
21
21
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
22
22
|
throw new Error(`Target property ${target} not found in verticalOhlcv for rsi.`);
|
|
@@ -27,32 +27,17 @@ export const rsi = (main, index, size, { target, lag, retLogs }) => {
|
|
|
27
27
|
[rsiSmaKey]: new FasterSMA(size)
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
[rsiKey]: new Float64Array(len).fill(NaN),
|
|
32
|
-
[rsiSmaKey]: new Float64Array(len).fill(NaN),
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const baseKeys = [rsiKey, rsiSmaKey]
|
|
36
|
-
|
|
37
|
-
if (lag > 0) {
|
|
38
|
-
main.lag(baseKeys, lag);
|
|
39
|
-
}
|
|
30
|
+
initializeColumns(main, [{ key: rsiKey }, { key: rsiSmaKey }], { lag });
|
|
40
31
|
|
|
41
32
|
|
|
42
33
|
}
|
|
43
34
|
|
|
44
35
|
const value = verticalOhlcv[target][index];
|
|
45
|
-
let currentRsi = NaN;
|
|
46
|
-
let smoothedRsi = NaN;
|
|
47
36
|
|
|
48
37
|
// Update the RSI indicator.
|
|
49
38
|
instances[rsiKey].update(value);
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
currentRsi = instances[rsiKey].getResult();
|
|
53
|
-
} catch (err) {
|
|
54
|
-
currentRsi = NaN;
|
|
55
|
-
}
|
|
40
|
+
const currentRsi = instances[rsiKey].isStable ? instances[rsiKey].getResult() : NaN;
|
|
56
41
|
|
|
57
42
|
|
|
58
43
|
const rsiVal = Number.isNaN(currentRsi) ? NaN : (retLogs ? mathLog(currentRsi, 50) : currentRsi)
|
|
@@ -63,11 +48,7 @@ export const rsi = (main, index, size, { target, lag, retLogs }) => {
|
|
|
63
48
|
instances[rsiSmaKey].update(currentRsi);
|
|
64
49
|
}
|
|
65
50
|
|
|
66
|
-
|
|
67
|
-
smoothedRsi = instances[rsiSmaKey].getResult();
|
|
68
|
-
} catch (err) {
|
|
69
|
-
smoothedRsi = NaN;
|
|
70
|
-
}
|
|
51
|
+
const smoothedRsi = instances[rsiSmaKey].isStable ? instances[rsiSmaKey].getResult() : NaN;
|
|
71
52
|
|
|
72
53
|
const smoothedRsiVal = Number.isNaN(smoothedRsi) ? NaN : (retLogs ? mathLog(smoothedRsi, 50) : smoothedRsi)
|
|
73
54
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { FasterStochasticOscillator } from '
|
|
1
|
+
import { FasterStochasticOscillator } from '../core-indicators/index.js';
|
|
2
2
|
import { mathLog } from '../utilities/math.js';
|
|
3
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
4
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
3
5
|
|
|
4
6
|
export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}) => {
|
|
5
7
|
|
|
@@ -13,39 +15,24 @@ export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag,
|
|
|
13
15
|
|
|
14
16
|
// Initialization on the first index.
|
|
15
17
|
if (index === 0) {
|
|
16
|
-
|
|
17
|
-
const {len } = main;
|
|
18
|
+
validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'stochastic');
|
|
18
19
|
|
|
19
20
|
Object.assign(instances, {
|
|
20
21
|
[instanceKey]: new FasterStochasticOscillator(kPeriod, kSlowingPeriod, dPeriod)
|
|
21
22
|
})
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
[stochD]: new Float64Array(len).fill(NaN),
|
|
25
|
-
[stochK]: new Float64Array(len).fill(NaN),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const baseKeys = [stochD, stochK]
|
|
29
|
-
|
|
30
|
-
if (lag > 0) {
|
|
31
|
-
main.lag(baseKeys, lag);
|
|
32
|
-
}
|
|
24
|
+
initializeColumns(main, [{ key: stochD }, { key: stochK }], { lag });
|
|
33
25
|
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
const close = verticalOhlcv['close'][index]
|
|
37
29
|
const low = verticalOhlcv['low'][index]
|
|
38
30
|
const high = verticalOhlcv['high'][index]
|
|
39
|
-
let stockObj = null
|
|
40
31
|
|
|
41
32
|
// Update the stochastic indicator.
|
|
42
33
|
instances[instanceKey].update({close, low, high})
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
stockObj = instances[instanceKey].getResult()
|
|
46
|
-
} catch (err) {
|
|
47
|
-
stockObj = null
|
|
48
|
-
}
|
|
35
|
+
const stockObj = instances[instanceKey].isStable ? instances[instanceKey].getResult() : null
|
|
49
36
|
|
|
50
37
|
const kVal = stockObj ? (retLogs ? mathLog(stockObj.stochK, 50) : stockObj.stochK) : NaN
|
|
51
38
|
const dVal = stockObj ? (retLogs ? mathLog(stockObj.stochD, 50) : stockObj.stochD) : NaN
|
|
@@ -1,29 +1,41 @@
|
|
|
1
|
+
import { isPositiveInteger } from '../utilities/numberUtilities.js';
|
|
2
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
3
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
export const volumeDelta = (main, index, { lag = 0 }) => {
|
|
2
|
-
const { verticalOhlcv, instances
|
|
3
|
-
const
|
|
7
|
+
const { verticalOhlcv, instances } = main;
|
|
8
|
+
const key = 'volume_delta';
|
|
4
9
|
|
|
5
10
|
if (index === 0) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const keys = [
|
|
9
|
-
`${groupName}_open`,
|
|
10
|
-
`${groupName}_high`,
|
|
11
|
-
`${groupName}_low`,
|
|
12
|
-
`${groupName}_close`,
|
|
13
|
-
`${groupName}_cross`,
|
|
14
|
-
];
|
|
11
|
+
validateInputValues({ open: true, close: true, volume: true }, verticalOhlcv, index, 'volumeDelta');
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
if (!instances[key]) {
|
|
14
|
+
instances[key] = { isBuyVolume: true, cross: 0};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const keyNames = [
|
|
18
|
+
`${key}_high`,
|
|
19
|
+
`${key}_low`,
|
|
20
|
+
`${key}_close`,
|
|
21
|
+
`${key}_cross`,
|
|
22
|
+
];
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
if (lag > 0) main.lag(keys, lag);
|
|
24
|
+
initializeColumns(main, keyNames.map(key => ({ key })), { lag });
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
const inst = instances[
|
|
27
|
+
const inst = instances[key];
|
|
28
|
+
|
|
29
|
+
const {hasInvalidInputValue} = inst
|
|
24
30
|
|
|
25
31
|
// inputs
|
|
26
|
-
const
|
|
32
|
+
const volume = verticalOhlcv.volume[index];
|
|
33
|
+
|
|
34
|
+
if(volume === 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
const open = verticalOhlcv.open[index];
|
|
28
40
|
const close = verticalOhlcv.close[index];
|
|
29
41
|
const prevClose = index > 0 ? verticalOhlcv.close[index - 1] : NaN;
|
|
@@ -55,20 +67,16 @@ export const volumeDelta = (main, index, { lag = 0 }) => {
|
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
const delta = Number.isFinite(vol) ? (isBuy ? vol : -vol) : NaN;
|
|
70
|
+
const delta = isBuy ? volume : -volume;
|
|
60
71
|
|
|
61
72
|
// delta "candle"
|
|
62
|
-
const openVal = Number.isNaN(delta) ? NaN : 0;
|
|
63
73
|
const closeVal = delta;
|
|
64
|
-
const highVal =
|
|
65
|
-
const lowVal =
|
|
74
|
+
const highVal = Math.max(delta, 0);
|
|
75
|
+
const lowVal = Math.min(delta, 0);
|
|
66
76
|
|
|
67
|
-
main.pushToMain({ index, key: `${
|
|
68
|
-
main.pushToMain({ index, key: `${
|
|
69
|
-
main.pushToMain({ index, key: `${
|
|
70
|
-
main.pushToMain({ index, key: `${
|
|
71
|
-
main.pushToMain({ index, key: `${groupName}_cross`, value: inst.cross });
|
|
77
|
+
main.pushToMain({ index, key: `${key}_high`, value: highVal });
|
|
78
|
+
main.pushToMain({ index, key: `${key}_low`, value: lowVal });
|
|
79
|
+
main.pushToMain({ index, key: `${key}_close`, value: closeVal });
|
|
80
|
+
main.pushToMain({ index, key: `${key}_cross`, value: inst.cross });
|
|
72
81
|
|
|
73
|
-
return true;
|
|
74
82
|
};
|
|
@@ -1,59 +1,57 @@
|
|
|
1
|
-
import { FasterEMA } from '
|
|
1
|
+
import { FasterEMA } from '../core-indicators/index.js';
|
|
2
|
+
import { mathLog } from '../utilities/math.js';
|
|
3
|
+
import { isPositiveInteger } from '../utilities/numberUtilities.js';
|
|
4
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
5
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
2
6
|
|
|
3
|
-
export const volumeOscillator = (main, index, fast, slow, {lag}) => {
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
export const volumeOscillator = (main, index, fast, slow, {lag, retLogs}) => {
|
|
6
8
|
|
|
7
9
|
const {verticalOhlcv, instances} = main
|
|
8
|
-
const value = verticalOhlcv.volume[index]
|
|
9
10
|
const key = `volume_oscillator_${fast}_${slow}`
|
|
10
11
|
|
|
11
12
|
if (index === 0) {
|
|
13
|
+
validateInputValues({ volume: true }, verticalOhlcv, index, 'volumeOscillator');
|
|
12
14
|
|
|
13
|
-
const {len} = main
|
|
14
15
|
Object.assign(instances, {
|
|
15
16
|
[key]: {
|
|
16
17
|
fastEMA: new FasterEMA(fast),
|
|
17
|
-
slowEMA: new FasterEMA(slow)
|
|
18
|
+
slowEMA: new FasterEMA(slow),
|
|
18
19
|
}
|
|
19
20
|
})
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
main.lag([key], lag)
|
|
26
|
-
}
|
|
22
|
+
initializeColumns(main, [
|
|
23
|
+
{ key },
|
|
24
|
+
{ key: `ret_log_${key}`, enabled: retLogs }
|
|
25
|
+
], { lag });
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
const { fastEMA, slowEMA } = instances[key];
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
slowEMA.update(value);
|
|
30
|
+
const volume = verticalOhlcv.volume[index];
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
fastValue = fastEMA.getResult();
|
|
39
|
-
} catch (err) {
|
|
40
|
-
|
|
32
|
+
if(volume === 0) {
|
|
33
|
+
return;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} catch (err) {
|
|
36
|
+
fastEMA.update(volume);
|
|
37
|
+
slowEMA.update(volume);
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
const fastValue = fastEMA.isStable ? fastEMA.getResult() : NaN;
|
|
40
|
+
const slowValue = slowEMA.isStable ? slowEMA.getResult() : NaN;
|
|
48
41
|
|
|
49
42
|
let volumeOscValue = NaN
|
|
43
|
+
let volumeOscRetLog = NaN
|
|
50
44
|
|
|
51
|
-
if(
|
|
52
|
-
|
|
53
|
-
volumeOscValue = 100 * (fastValue - slowValue) / slowValue
|
|
45
|
+
if(Number.isNaN(fastValue) || Number.isNaN(slowValue)) {
|
|
46
|
+
return;
|
|
54
47
|
}
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
volumeOscValue = 100 * (fastValue - slowValue) / slowValue
|
|
50
|
+
|
|
51
|
+
if(retLogs) {
|
|
52
|
+
volumeOscRetLog = mathLog(fastValue, slowValue)
|
|
53
|
+
main.pushToMain({index, key: `ret_log_${key}`, value: volumeOscRetLog})
|
|
54
|
+
}
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
main.pushToMain({index, key, value: volumeOscValue})
|
|
59
57
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { validateInputValues } from '../utilities/validators.js'
|
|
2
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
2
3
|
|
|
3
4
|
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
5
|
|
|
@@ -9,19 +10,16 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
|
|
|
9
10
|
const prefix = (retLogs) ? 'ret_log_' : 'ret_'
|
|
10
11
|
|
|
11
12
|
if(index === 0) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
[`${prefix}lower_wick`]: new Float64Array(len).fill(NaN),
|
|
19
|
-
[`${prefix}gap`]: new Float64Array(len).fill(NaN),
|
|
20
|
-
[`${prefix}body`]: new Float64Array(len).fill(NaN),
|
|
21
|
-
[`${prefix}range`]: new Float64Array(len).fill(NaN)
|
|
22
|
-
}
|
|
13
|
+
validateInputValues({ open: true, high: true, low: true, close: true }, verticalOhlcv, index, 'candleFeatures')
|
|
14
|
+
|
|
15
|
+
const columns = [
|
|
16
|
+
'change', 'mid_price_change', 'upper_wick', 'lower_wick',
|
|
17
|
+
'gap', 'body', 'range'
|
|
18
|
+
].map(name => ({ key: `${prefix}${name}` }))
|
|
23
19
|
|
|
24
20
|
for(const target of colKeys) {
|
|
21
|
+
validateInputValues({ [target]: true }, verticalOhlcv, index, 'candleFeatures')
|
|
22
|
+
|
|
25
23
|
if(!verticalOhlcv.hasOwnProperty(target)) {
|
|
26
24
|
throw new Error(
|
|
27
25
|
`Target property in "options.colKey" array "${target}" not found in verticalOhlcv for "candleFeatures".`
|
|
@@ -34,16 +32,10 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
|
|
|
34
32
|
);
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
columns.push({ key: `${prefix}${target}` })
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
scaledGroups.candleFeatures = Object.keys(newCols)
|
|
43
|
-
|
|
44
|
-
if(lag) {
|
|
45
|
-
main.lag(Object.keys(newCols), lag)
|
|
46
|
-
}
|
|
38
|
+
scaledGroups.candleFeatures = initializeColumns(main, columns, { lag })
|
|
47
39
|
}
|
|
48
40
|
|
|
49
41
|
const prevClose = verticalOhlcv.close[index - 1]
|