ohlcv-indicators 1.9.2 → 1.9.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/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +70 -142
- package/package.json +1 -1
- package/src/moving-averages/bollingerBands.js +42 -80
- package/src/moving-averages/donchianChannel.js +43 -56
- package/src/moving-averages/ema.js +15 -40
- package/src/moving-averages/macd.js +43 -46
- package/src/moving-averages/relativeVolume.js +28 -39
- package/src/moving-averages/sma.js +15 -35
- package/src/oscillators/rsi.js +34 -52
- package/src/oscillators/volumeOscillator.js +32 -36
- package/src/studies/candleStudies.js +156 -168
- package/src/studies/lag.js +27 -0
- package/src/utilities/parsing-utilities.js +143 -125
- package/src/utilities/setIndicatorsFromInputParams.js +1 -3
- package/src/oscillators/orb.js +0 -41
|
@@ -1,47 +1,43 @@
|
|
|
1
|
-
import { FasterEMA } from 'trading-signals'
|
|
2
|
-
import { findCrosses } from "../studies/findCrosses.js";
|
|
1
|
+
import { FasterEMA } from 'trading-signals';
|
|
3
2
|
|
|
3
|
+
export const volumeOscillator = (main, index, fastSize = 5, slowSize = 10) => {
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const { verticalOhlcv } = main;
|
|
7
|
-
const { volume } = verticalOhlcv;
|
|
5
|
+
const value = main.verticalOhlcv.volume[index]
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (let i = 0; i < volume.length; i++) {
|
|
19
|
-
|
|
20
|
-
fastEMA.update(volume[i])
|
|
21
|
-
slowEMA.update(volume[i])
|
|
7
|
+
if (!main.instances.hasOwnProperty(`volume_oscillator`)) {
|
|
8
|
+
main.instances[`volume_oscillator`] = {
|
|
9
|
+
fastEMA: new FasterEMA(fastSize),
|
|
10
|
+
slowEMA: new FasterEMA(slowSize)
|
|
11
|
+
};
|
|
12
|
+
main.verticalOhlcv[`volume_oscillator`] = new Array(main.len).fill(null);
|
|
13
|
+
main.autoCrossPairsList.push({ fast: `volume_oscillator`, slow: 0 });
|
|
14
|
+
}
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
let slowValue = null
|
|
16
|
+
const { fastEMA, slowEMA } = main.instances[`volume_oscillator`];
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (err) {
|
|
29
|
-
fastValue = null
|
|
30
|
-
}
|
|
18
|
+
fastEMA.update(value);
|
|
19
|
+
slowEMA.update(value);
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch (err) {
|
|
35
|
-
slowValue = null
|
|
36
|
-
}
|
|
21
|
+
let fastValue = null;
|
|
22
|
+
let slowValue = null;
|
|
37
23
|
|
|
38
|
-
|
|
24
|
+
try {
|
|
25
|
+
fastValue = fastEMA.getResult();
|
|
26
|
+
} catch (err) {
|
|
27
|
+
fastValue = null;
|
|
28
|
+
}
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
try {
|
|
31
|
+
slowValue = slowEMA.getResult();
|
|
32
|
+
} catch (err) {
|
|
33
|
+
slowValue = null;
|
|
42
34
|
}
|
|
43
35
|
|
|
44
|
-
|
|
36
|
+
if (typeof fastValue === 'number' && typeof slowValue === 'number' && slowValue !== 0) {
|
|
37
|
+
main.verticalOhlcv[`volume_oscillator`][index] = 100 * (fastValue - slowValue) / slowValue
|
|
38
|
+
} else {
|
|
39
|
+
main.verticalOhlcv[`volume_oscillator`][index] = null;
|
|
40
|
+
}
|
|
45
41
|
|
|
46
|
-
return
|
|
47
|
-
}
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
@@ -1,184 +1,172 @@
|
|
|
1
|
-
import {FasterSMA} from 'trading-signals'
|
|
2
|
-
import { classifySize } from '../utilities/classification.js'
|
|
3
|
-
|
|
4
|
-
export const candlesStudies = (main,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//instances
|
|
28
|
-
let topInstance
|
|
29
|
-
let bottomInstance
|
|
30
|
-
let gapInstance
|
|
31
|
-
let bodySizeInstance
|
|
32
|
-
let shadowSizeInstance
|
|
33
|
-
let closeDirectionInstance
|
|
34
|
-
let highDirectionInstance
|
|
35
|
-
let lowDirectionInstance
|
|
36
|
-
let openDirectionInstance
|
|
37
|
-
|
|
38
|
-
if(classify)
|
|
39
|
-
{
|
|
40
|
-
topInstance = new FasterSMA(period)
|
|
41
|
-
bottomInstance = new FasterSMA(period)
|
|
42
|
-
gapInstance = new FasterSMA(period)
|
|
43
|
-
bodySizeInstance = new FasterSMA(period)
|
|
44
|
-
shadowSizeInstance = new FasterSMA(period)
|
|
45
|
-
closeDirectionInstance = new FasterSMA(period)
|
|
46
|
-
highDirectionInstance = new FasterSMA(period)
|
|
47
|
-
lowDirectionInstance = new FasterSMA(period)
|
|
48
|
-
openDirectionInstance = new FasterSMA(period)
|
|
1
|
+
import { FasterSMA } from 'trading-signals';
|
|
2
|
+
import { classifySize } from '../utilities/classification.js';
|
|
3
|
+
|
|
4
|
+
export const candlesStudies = (main, index, size, classify = true, classificationLevels = {}) => {
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const { changeLevel = 7, sizeLevel = 5 } = classificationLevels;
|
|
9
|
+
|
|
10
|
+
// Ensure arrays exist in main.verticalOhlcv
|
|
11
|
+
const fields = [
|
|
12
|
+
'candle_gap_size',
|
|
13
|
+
'candle_body_size',
|
|
14
|
+
'candle_top_size',
|
|
15
|
+
'candle_bottom_size',
|
|
16
|
+
'candle_shadow_size',
|
|
17
|
+
'candle_close_direction',
|
|
18
|
+
'candle_high_direction',
|
|
19
|
+
'candle_low_direction',
|
|
20
|
+
'candle_open_direction'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const field of fields) {
|
|
24
|
+
if (!main.verticalOhlcv.hasOwnProperty(field)) {
|
|
25
|
+
main.verticalOhlcv[field] = new Array(main.len).fill(null);
|
|
26
|
+
}
|
|
49
27
|
}
|
|
50
28
|
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const candleBodySize = Math.abs(close - open)
|
|
75
|
-
const shadowSize = Math.abs(curr.high - curr.low)
|
|
76
|
-
|
|
77
|
-
//sizes
|
|
78
|
-
const topSize = high - Math.max(open, close)
|
|
79
|
-
const bottomSize = Math.min(open, close) - low
|
|
80
|
-
const gapSize = Math.abs(prev.close - curr.open)
|
|
81
|
-
const closeDirection = Math.abs(curr.close - prev.close)
|
|
82
|
-
const highDirection = Math.abs(curr.high - prev.high)
|
|
83
|
-
const lowDirection = Math.abs(curr.low - prev.low)
|
|
84
|
-
const openDirection = Math.abs(curr.open - prev.open)
|
|
85
|
-
|
|
86
|
-
if(classify)
|
|
87
|
-
{
|
|
88
|
-
//instances
|
|
89
|
-
topInstance.update(Math.abs(topSize))
|
|
90
|
-
bottomInstance.update(Math.abs(bottomSize))
|
|
91
|
-
shadowSizeInstance.update(Math.abs(shadowSize))
|
|
92
|
-
|
|
93
|
-
gapInstance.update(gapSize)
|
|
94
|
-
bodySizeInstance.update(candleBodySize)
|
|
95
|
-
closeDirectionInstance.update(closeDirection)
|
|
96
|
-
highDirectionInstance.update(highDirection)
|
|
97
|
-
lowDirectionInstance.update(lowDirection)
|
|
98
|
-
openDirectionInstance.update(openDirection)
|
|
99
|
-
|
|
100
|
-
try
|
|
101
|
-
{
|
|
102
|
-
topSizeMean = topInstance.getResult()
|
|
103
|
-
bottomSizeMean = bottomInstance.getResult()
|
|
104
|
-
gapMean = gapInstance.getResult()
|
|
105
|
-
bodySizeMean = bodySizeInstance.getResult()
|
|
106
|
-
shadowSizeMean = shadowSizeInstance.getResult()
|
|
107
|
-
closeDirectionMean = closeDirectionInstance.getResult()
|
|
108
|
-
highDirectionMean = highDirectionInstance.getResult()
|
|
109
|
-
lowDirectionMean = lowDirectionInstance.getResult()
|
|
110
|
-
openDirectionMean = openDirectionInstance.getResult()
|
|
111
|
-
}
|
|
112
|
-
catch(err)
|
|
113
|
-
{
|
|
114
|
-
bodySizeMean = null
|
|
115
|
-
gapMean = null
|
|
116
|
-
topSizeMean = null
|
|
117
|
-
bottomSizeMean = null
|
|
118
|
-
shadowSizeMean = null
|
|
119
|
-
closeDirectionMean = null
|
|
120
|
-
highDirectionMean = null
|
|
121
|
-
lowDirectionMean = null
|
|
122
|
-
openDirectionMean = null
|
|
29
|
+
// Ensure instances exist if classification is enabled
|
|
30
|
+
if (classify) {
|
|
31
|
+
const instanceNames = [
|
|
32
|
+
'topInstance',
|
|
33
|
+
'bottomInstance',
|
|
34
|
+
'gapInstance',
|
|
35
|
+
'bodySizeInstance',
|
|
36
|
+
'shadowSizeInstance',
|
|
37
|
+
'closeDirectionInstance',
|
|
38
|
+
'highDirectionInstance',
|
|
39
|
+
'lowDirectionInstance',
|
|
40
|
+
'openDirectionInstance'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Create a namespace for candle studies if it doesn't exist
|
|
44
|
+
if (!main.instances.hasOwnProperty('candleStudies')) {
|
|
45
|
+
main.instances.candleStudies = {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create instances if they don't exist
|
|
49
|
+
for (const name of instanceNames) {
|
|
50
|
+
if (!main.instances.candleStudies.hasOwnProperty(name)) {
|
|
51
|
+
main.instances.candleStudies[name] = new FasterSMA(size);
|
|
123
52
|
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
124
55
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
56
|
+
// If we do not have a previous candle, we cannot compute differences
|
|
57
|
+
const prevOpen = main.verticalOhlcv.open[index-1]
|
|
58
|
+
const prevHigh = main.verticalOhlcv.high[index-1]
|
|
59
|
+
const prevLow = main.verticalOhlcv.low[index-1]
|
|
60
|
+
const prevClose = main.verticalOhlcv.close[index-1]
|
|
61
|
+
|
|
62
|
+
if ([prevOpen, prevHigh, prevLow, prevClose].some(v => typeof v === 'undefined')) return true;
|
|
63
|
+
|
|
64
|
+
const currOpen = main.verticalOhlcv.open[index]
|
|
65
|
+
const currHigh = main.verticalOhlcv.high[index]
|
|
66
|
+
const currLow = main.verticalOhlcv.low[index]
|
|
67
|
+
const currClose = main.verticalOhlcv.close[index]
|
|
68
|
+
|
|
69
|
+
// Determine candle direction and sizes
|
|
70
|
+
const isUp = currClose > currOpen;
|
|
71
|
+
const candleBodySize = Math.abs(currClose - currOpen);
|
|
72
|
+
const shadowSize = Math.abs(currHigh - currLow);
|
|
73
|
+
|
|
74
|
+
const topSize = currHigh - Math.max(currOpen, currClose);
|
|
75
|
+
const bottomSize = Math.min(currOpen, currClose) - currLow;
|
|
76
|
+
const gapSize = Math.abs(prevClose - currOpen);
|
|
77
|
+
const closeDirection = Math.abs(currClose - prevClose);
|
|
78
|
+
const highDirection = Math.abs(currHigh - prevHigh);
|
|
79
|
+
const lowDirection = Math.abs(currLow - prevLow);
|
|
80
|
+
const openDirection = Math.abs(currOpen - prevOpen);
|
|
81
|
+
|
|
82
|
+
let gapMean = null;
|
|
83
|
+
let bottomSizeMean = null;
|
|
84
|
+
let topSizeMean = null;
|
|
85
|
+
let bodySizeMean = null;
|
|
86
|
+
let shadowSizeMean = null;
|
|
87
|
+
let closeDirectionMean = null;
|
|
88
|
+
let highDirectionMean = null;
|
|
89
|
+
let lowDirectionMean = null;
|
|
90
|
+
let openDirectionMean = null;
|
|
91
|
+
|
|
92
|
+
if (classify) {
|
|
93
|
+
const {
|
|
94
|
+
topInstance,
|
|
95
|
+
bottomInstance,
|
|
96
|
+
gapInstance,
|
|
97
|
+
bodySizeInstance,
|
|
98
|
+
shadowSizeInstance,
|
|
99
|
+
closeDirectionInstance,
|
|
100
|
+
highDirectionInstance,
|
|
101
|
+
lowDirectionInstance,
|
|
102
|
+
openDirectionInstance
|
|
103
|
+
} = main.instances.candleStudies;
|
|
104
|
+
|
|
105
|
+
// Update instances with current absolute values
|
|
106
|
+
topInstance.update(Math.abs(topSize));
|
|
107
|
+
bottomInstance.update(Math.abs(bottomSize));
|
|
108
|
+
shadowSizeInstance.update(Math.abs(shadowSize));
|
|
109
|
+
gapInstance.update(gapSize);
|
|
110
|
+
bodySizeInstance.update(candleBodySize);
|
|
111
|
+
closeDirectionInstance.update(closeDirection);
|
|
112
|
+
highDirectionInstance.update(highDirection);
|
|
113
|
+
lowDirectionInstance.update(lowDirection);
|
|
114
|
+
openDirectionInstance.update(openDirection);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
topSizeMean = topInstance.getResult();
|
|
118
|
+
bottomSizeMean = bottomInstance.getResult();
|
|
119
|
+
gapMean = gapInstance.getResult();
|
|
120
|
+
bodySizeMean = bodySizeInstance.getResult();
|
|
121
|
+
shadowSizeMean = shadowSizeInstance.getResult();
|
|
122
|
+
closeDirectionMean = closeDirectionInstance.getResult();
|
|
123
|
+
highDirectionMean = highDirectionInstance.getResult();
|
|
124
|
+
lowDirectionMean = lowDirectionInstance.getResult();
|
|
125
|
+
openDirectionMean = openDirectionInstance.getResult();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// If there's not enough data, means remain null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main.verticalOhlcv['candle_body_size'][index] = isUp
|
|
131
|
+
? classifySize(candleBodySize, bodySizeMean, 2, changeLevel)
|
|
128
132
|
: -Math.abs(classifySize(candleBodySize, bodySizeMean, 2, changeLevel)) || 0;
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
? classifySize(gapSize, gapMean, 2, changeLevel)
|
|
134
|
+
main.verticalOhlcv['candle_gap_size'][index] = isUp
|
|
135
|
+
? classifySize(gapSize, gapMean, 2, changeLevel)
|
|
132
136
|
: -Math.abs(classifySize(gapSize, gapMean, 2, changeLevel)) || 0;
|
|
133
137
|
|
|
134
|
-
|
|
135
|
-
? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
|
|
138
|
+
main.verticalOhlcv['candle_close_direction'][index] = isUp
|
|
139
|
+
? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
|
|
136
140
|
: -Math.abs(classifySize(closeDirection, closeDirectionMean, 2, changeLevel)) || 0;
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
? classifySize(highDirection, highDirectionMean, 2, changeLevel)
|
|
140
|
-
: -Math.abs(classifySize(highDirection, highDirectionMean, 2, changeLevel)) || 0;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
? classifySize(lowDirection, lowDirectionMean, 2, changeLevel)
|
|
142
|
+
main.verticalOhlcv['candle_high_direction'][index] = isUp
|
|
143
|
+
? classifySize(highDirection, highDirectionMean, 2, changeLevel)
|
|
144
|
+
: -Math.abs(classifySize(highDirection, highDirectionMean, 2, changeLevel)) || 0;
|
|
145
|
+
|
|
146
|
+
main.verticalOhlcv['candle_low_direction'][index] = isUp
|
|
147
|
+
? classifySize(lowDirection, lowDirectionMean, 2, changeLevel)
|
|
145
148
|
: -Math.abs(classifySize(lowDirection, lowDirectionMean, 2, changeLevel)) || 0;
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
? classifySize(openDirection, openDirectionMean, 2, changeLevel)
|
|
150
|
+
main.verticalOhlcv['candle_open_direction'][index] = isUp
|
|
151
|
+
? classifySize(openDirection, openDirectionMean, 2, changeLevel)
|
|
149
152
|
: -Math.abs(classifySize(openDirection, openDirectionMean, 2, changeLevel)) || 0;
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
candle_top_size[x] = topSize
|
|
167
|
-
candle_bottom_size[x] = bottomSize
|
|
168
|
-
candle_shadow_size[x] = shadowSize
|
|
169
|
-
}
|
|
170
|
-
|
|
154
|
+
main.verticalOhlcv['candle_top_size'][index] = classifySize(topSize, topSizeMean, 0.5, sizeLevel);
|
|
155
|
+
main.verticalOhlcv['candle_bottom_size'][index] = classifySize(bottomSize, bottomSizeMean, 0.5, sizeLevel);
|
|
156
|
+
main.verticalOhlcv['candle_shadow_size'][index] = classifySize(shadowSize, shadowSizeMean, 0.5, sizeLevel);
|
|
157
|
+
} else {
|
|
158
|
+
// Without classification, just store raw values
|
|
159
|
+
main.verticalOhlcv['candle_body_size'][index] = isUp ? candleBodySize : -candleBodySize;
|
|
160
|
+
main.verticalOhlcv['candle_gap_size'][index] = isUp ? gapSize : -gapSize;
|
|
161
|
+
main.verticalOhlcv['candle_close_direction'][index] = isUp ? closeDirection : -closeDirection;
|
|
162
|
+
main.verticalOhlcv['candle_high_direction'][index] = isUp ? highDirection : -highDirection;
|
|
163
|
+
main.verticalOhlcv['candle_low_direction'][index] = isUp ? lowDirection : -lowDirection;
|
|
164
|
+
main.verticalOhlcv['candle_open_direction'][index] = isUp ? openDirection : -openDirection;
|
|
165
|
+
|
|
166
|
+
main.verticalOhlcv['candle_top_size'][index] = topSize;
|
|
167
|
+
main.verticalOhlcv['candle_bottom_size'][index] = bottomSize;
|
|
168
|
+
main.verticalOhlcv['candle_shadow_size'][index] = shadowSize;
|
|
171
169
|
}
|
|
172
170
|
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
candle_body_size,
|
|
176
|
-
candle_top_size,
|
|
177
|
-
candle_bottom_size,
|
|
178
|
-
candle_shadow_size,
|
|
179
|
-
candle_close_direction,
|
|
180
|
-
candle_high_direction,
|
|
181
|
-
candle_low_direction,
|
|
182
|
-
candle_open_direction
|
|
183
|
-
}
|
|
184
|
-
}
|
|
171
|
+
return true;
|
|
172
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const lag = (main, index, colKeys = ['close'], lags = 1) => {
|
|
2
|
+
|
|
3
|
+
const { verticalOhlcv, len } = main;
|
|
4
|
+
|
|
5
|
+
// Ensure arrays exist for each lag key before we populate
|
|
6
|
+
for (let x = 0; x < colKeys.length; x++) {
|
|
7
|
+
for (let lag = 1; lag <= lags; lag++) {
|
|
8
|
+
const key = `${colKeys[x]}_lag_${lag}`;
|
|
9
|
+
// If the array does not exist, initialize it
|
|
10
|
+
if (!verticalOhlcv.hasOwnProperty(key)) {
|
|
11
|
+
verticalOhlcv[key] = new Array(len).fill(null);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Compute the lagged index
|
|
15
|
+
const laggedIndex = index - lag;
|
|
16
|
+
|
|
17
|
+
// If laggedIndex is negative, we don't have a previous value
|
|
18
|
+
const value = (laggedIndex >= 0)
|
|
19
|
+
? verticalOhlcv[colKeys[x]][laggedIndex]
|
|
20
|
+
: null;
|
|
21
|
+
|
|
22
|
+
verticalOhlcv[key][index] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true;
|
|
27
|
+
};
|