ohlcv-indicators 1.9.2 → 1.9.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 +1 -1
- package/index.js +70 -142
- package/package.json +1 -1
- package/src/moving-averages/bollingerBands.js +49 -75
- package/src/moving-averages/donchianChannel.js +47 -56
- package/src/moving-averages/ema.js +15 -40
- package/src/moving-averages/macd.js +30 -37
- package/src/moving-averages/relativeVolume.js +31 -39
- package/src/moving-averages/sma.js +15 -35
- package/src/oscillators/rsi.js +38 -50
- package/src/oscillators/volumeOscillator.js +34 -34
- package/src/studies/candleStudies.js +140 -167
- package/src/studies/findCrosses.js +95 -117
- package/src/studies/lag.js +27 -0
- package/src/utilities/parsing-utilities.js +197 -144
- package/src/utilities/setIndicatorsFromInputParams.js +1 -3
- package/src/oscillators/orb.js +0 -41
|
@@ -1,47 +1,47 @@
|
|
|
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
|
-
|
|
7
|
+
if (!main.instances.hasOwnProperty(`volume_oscillator`)) {
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Object.assign(main.instances, {
|
|
10
|
+
volume_oscillator: {
|
|
11
|
+
fastEMA: new FasterEMA(fastSize),
|
|
12
|
+
slowEMA: new FasterEMA(slowSize)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const volume_oscillator = new Array(volume.length).fill(null)
|
|
17
|
-
|
|
18
|
-
for (let i = 0; i < volume.length; i++) {
|
|
19
|
-
|
|
20
|
-
fastEMA.update(volume[i])
|
|
21
|
-
slowEMA.update(volume[i])
|
|
16
|
+
main.verticalOhlcv[`volume_oscillator`] = new Array(main.len).fill(null);
|
|
17
|
+
main.autoCrossPairsList.push({ fast: `volume_oscillator`, slow: 0 });
|
|
18
|
+
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
let slowValue = null
|
|
20
|
+
const { fastEMA, slowEMA } = main.instances[`volume_oscillator`];
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (err) {
|
|
29
|
-
fastValue = null
|
|
30
|
-
}
|
|
22
|
+
fastEMA.update(value);
|
|
23
|
+
slowEMA.update(value);
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch (err) {
|
|
35
|
-
slowValue = null
|
|
36
|
-
}
|
|
25
|
+
let fastValue = null;
|
|
26
|
+
let slowValue = null;
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
try {
|
|
29
|
+
fastValue = fastEMA.getResult();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
fastValue = null;
|
|
32
|
+
}
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
try {
|
|
35
|
+
slowValue = slowEMA.getResult();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
slowValue = null;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
if (typeof fastValue === 'number' && typeof slowValue === 'number' && slowValue !== 0) {
|
|
41
|
+
main.verticalOhlcv[`volume_oscillator`][index] = 100 * (fastValue - slowValue) / slowValue
|
|
42
|
+
} else {
|
|
43
|
+
main.verticalOhlcv[`volume_oscillator`][index] = null;
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
return
|
|
47
|
-
}
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
@@ -1,184 +1,157 @@
|
|
|
1
|
-
import {FasterSMA} from 'trading-signals'
|
|
2
|
-
import { classifySize } from '../utilities/classification.js'
|
|
3
|
-
|
|
4
|
-
export const candlesStudies = (main,
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return cols
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const getCandlesStudies = (inputOhlcv, period = 20, len, classify = true, classificationLevels = {}) => {
|
|
13
|
-
|
|
14
|
-
const {changeLevel = 7, sizeLevel = 5} = classificationLevels
|
|
15
|
-
|
|
16
|
-
//close > open
|
|
17
|
-
let candle_body_size = new Array(len).fill(null) //scaled candle direction close - open
|
|
18
|
-
let candle_top_size = new Array(len).fill(null) //scaled top size (0, 0.5 and 1)
|
|
19
|
-
let candle_bottom_size = new Array(len).fill(null) //scaled bottom size(0, 0.5 and 1)
|
|
20
|
-
let candle_gap_size = new Array(len).fill(null) //scaled gap between the current open and previous close (-1 to 1)
|
|
21
|
-
let candle_shadow_size = new Array(len).fill(null) //scaled shadow size high - slow
|
|
22
|
-
let candle_close_direction = new Array(len).fill(null) //scaled shadow size curr.close - prev.close
|
|
23
|
-
let candle_high_direction = new Array(len).fill(null) //scaled shadow size curr.high - prev.high
|
|
24
|
-
let candle_low_direction = new Array(len).fill(null) //scaled shadow size curr.low - prev.low
|
|
25
|
-
let candle_open_direction = new Array(len).fill(null) //scaled shadow size curr.open - prev.open
|
|
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)
|
|
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
|
+
const { changeLevel = 7, sizeLevel = 5 } = classificationLevels;
|
|
7
|
+
|
|
8
|
+
if(index === 0)
|
|
39
9
|
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
10
|
+
Object.assign(main.verticalOhlcv, {
|
|
11
|
+
candle_gap_size: new Array(main.len).fill(null),
|
|
12
|
+
candle_body_size: new Array(main.len).fill(null),
|
|
13
|
+
candle_top_size: new Array(main.len).fill(null),
|
|
14
|
+
candle_bottom_size: new Array(main.len).fill(null),
|
|
15
|
+
candle_shadow_size: new Array(main.len).fill(null),
|
|
16
|
+
candle_close_direction: new Array(main.len).fill(null),
|
|
17
|
+
candle_high_direction: new Array(main.len).fill(null),
|
|
18
|
+
candle_low_direction: new Array(main.len).fill(null),
|
|
19
|
+
candle_open_direction: new Array(main.len).fill(null),
|
|
20
|
+
})
|
|
49
21
|
}
|
|
50
22
|
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
let bodySizeMean = null
|
|
66
|
-
let shadowSizeMean = null
|
|
67
|
-
let closeDirectionMean = null
|
|
68
|
-
let highDirectionMean = null
|
|
69
|
-
let lowDirectionMean = null
|
|
70
|
-
let openDirectionMean = null
|
|
71
|
-
|
|
72
|
-
//directions
|
|
73
|
-
const isUp = close > open
|
|
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
|
|
23
|
+
// Ensure instances exist if classification is enabled
|
|
24
|
+
if (classify && index === 0) {
|
|
25
|
+
|
|
26
|
+
Object.assign(main.instances, {
|
|
27
|
+
candleStudies: {
|
|
28
|
+
topInstance: new FasterSMA(size),
|
|
29
|
+
bottomInstance: new FasterSMA(size),
|
|
30
|
+
gapInstance: new FasterSMA(size),
|
|
31
|
+
bodySizeInstance: new FasterSMA(size),
|
|
32
|
+
shadowSizeInstance: new FasterSMA(size),
|
|
33
|
+
closeDirectionInstance: new FasterSMA(size),
|
|
34
|
+
highDirectionInstance: new FasterSMA(size),
|
|
35
|
+
lowDirectionInstance: new FasterSMA(size),
|
|
36
|
+
openDirectionInstance: new FasterSMA(size)
|
|
123
37
|
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
124
40
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
41
|
+
// If we do not have a previous candle, we cannot compute differences
|
|
42
|
+
const prevOpen = main.verticalOhlcv.open[index-1]
|
|
43
|
+
const prevHigh = main.verticalOhlcv.high[index-1]
|
|
44
|
+
const prevLow = main.verticalOhlcv.low[index-1]
|
|
45
|
+
const prevClose = main.verticalOhlcv.close[index-1]
|
|
46
|
+
|
|
47
|
+
if ([prevOpen, prevHigh, prevLow, prevClose].some(v => typeof v === 'undefined')) return true;
|
|
48
|
+
|
|
49
|
+
const currOpen = main.verticalOhlcv.open[index]
|
|
50
|
+
const currHigh = main.verticalOhlcv.high[index]
|
|
51
|
+
const currLow = main.verticalOhlcv.low[index]
|
|
52
|
+
const currClose = main.verticalOhlcv.close[index]
|
|
53
|
+
|
|
54
|
+
// Determine candle direction and sizes
|
|
55
|
+
const isUp = currClose > currOpen;
|
|
56
|
+
const candleBodySize = Math.abs(currClose - currOpen);
|
|
57
|
+
const shadowSize = Math.abs(currHigh - currLow);
|
|
58
|
+
|
|
59
|
+
const topSize = currHigh - Math.max(currOpen, currClose);
|
|
60
|
+
const bottomSize = Math.min(currOpen, currClose) - currLow;
|
|
61
|
+
const gapSize = Math.abs(prevClose - currOpen);
|
|
62
|
+
const closeDirection = Math.abs(currClose - prevClose);
|
|
63
|
+
const highDirection = Math.abs(currHigh - prevHigh);
|
|
64
|
+
const lowDirection = Math.abs(currLow - prevLow);
|
|
65
|
+
const openDirection = Math.abs(currOpen - prevOpen);
|
|
66
|
+
|
|
67
|
+
let gapMean = null;
|
|
68
|
+
let bottomSizeMean = null;
|
|
69
|
+
let topSizeMean = null;
|
|
70
|
+
let bodySizeMean = null;
|
|
71
|
+
let shadowSizeMean = null;
|
|
72
|
+
let closeDirectionMean = null;
|
|
73
|
+
let highDirectionMean = null;
|
|
74
|
+
let lowDirectionMean = null;
|
|
75
|
+
let openDirectionMean = null;
|
|
76
|
+
|
|
77
|
+
if (classify) {
|
|
78
|
+
const {
|
|
79
|
+
topInstance,
|
|
80
|
+
bottomInstance,
|
|
81
|
+
gapInstance,
|
|
82
|
+
bodySizeInstance,
|
|
83
|
+
shadowSizeInstance,
|
|
84
|
+
closeDirectionInstance,
|
|
85
|
+
highDirectionInstance,
|
|
86
|
+
lowDirectionInstance,
|
|
87
|
+
openDirectionInstance
|
|
88
|
+
} = main.instances.candleStudies;
|
|
89
|
+
|
|
90
|
+
// Update instances with current absolute values
|
|
91
|
+
topInstance.update(Math.abs(topSize));
|
|
92
|
+
bottomInstance.update(Math.abs(bottomSize));
|
|
93
|
+
shadowSizeInstance.update(Math.abs(shadowSize));
|
|
94
|
+
gapInstance.update(gapSize);
|
|
95
|
+
bodySizeInstance.update(candleBodySize);
|
|
96
|
+
closeDirectionInstance.update(closeDirection);
|
|
97
|
+
highDirectionInstance.update(highDirection);
|
|
98
|
+
lowDirectionInstance.update(lowDirection);
|
|
99
|
+
openDirectionInstance.update(openDirection);
|
|
100
|
+
|
|
101
|
+
try {
|
|
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
|
+
} catch (err) {
|
|
112
|
+
// If there's not enough data, means remain null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main.verticalOhlcv.candle_body_size[index] = isUp
|
|
116
|
+
? classifySize(candleBodySize, bodySizeMean, 2, changeLevel)
|
|
128
117
|
: -Math.abs(classifySize(candleBodySize, bodySizeMean, 2, changeLevel)) || 0;
|
|
129
118
|
|
|
130
|
-
|
|
131
|
-
? classifySize(gapSize, gapMean, 2, changeLevel)
|
|
119
|
+
main.verticalOhlcv.candle_gap_size[index] = isUp
|
|
120
|
+
? classifySize(gapSize, gapMean, 2, changeLevel)
|
|
132
121
|
: -Math.abs(classifySize(gapSize, gapMean, 2, changeLevel)) || 0;
|
|
133
122
|
|
|
134
|
-
|
|
135
|
-
? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
|
|
123
|
+
main.verticalOhlcv.candle_close_direction[index] = isUp
|
|
124
|
+
? classifySize(closeDirection, closeDirectionMean, 2, changeLevel)
|
|
136
125
|
: -Math.abs(classifySize(closeDirection, closeDirectionMean, 2, changeLevel)) || 0;
|
|
137
126
|
|
|
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)
|
|
127
|
+
main.verticalOhlcv.candle_high_direction[index] = isUp
|
|
128
|
+
? classifySize(highDirection, highDirectionMean, 2, changeLevel)
|
|
129
|
+
: -Math.abs(classifySize(highDirection, highDirectionMean, 2, changeLevel)) || 0;
|
|
130
|
+
|
|
131
|
+
main.verticalOhlcv.candle_low_direction[index] = isUp
|
|
132
|
+
? classifySize(lowDirection, lowDirectionMean, 2, changeLevel)
|
|
145
133
|
: -Math.abs(classifySize(lowDirection, lowDirectionMean, 2, changeLevel)) || 0;
|
|
146
134
|
|
|
147
|
-
|
|
148
|
-
? classifySize(openDirection, openDirectionMean, 2, changeLevel)
|
|
135
|
+
main.verticalOhlcv.candle_open_direction[index] = isUp
|
|
136
|
+
? classifySize(openDirection, openDirectionMean, 2, changeLevel)
|
|
149
137
|
: -Math.abs(classifySize(openDirection, openDirectionMean, 2, changeLevel)) || 0;
|
|
150
138
|
|
|
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
|
-
|
|
139
|
+
main.verticalOhlcv.candle_top_size[index] = classifySize(topSize, topSizeMean, 0.5, sizeLevel);
|
|
140
|
+
main.verticalOhlcv.candle_bottom_size[index] = classifySize(bottomSize, bottomSizeMean, 0.5, sizeLevel);
|
|
141
|
+
main.verticalOhlcv.candle_shadow_size[index] = classifySize(shadowSize, shadowSizeMean, 0.5, sizeLevel);
|
|
142
|
+
} else {
|
|
143
|
+
// Without classification, just store raw values
|
|
144
|
+
main.verticalOhlcv.candle_body_size[index] = isUp ? candleBodySize : -candleBodySize;
|
|
145
|
+
main.verticalOhlcv.candle_gap_size[index] = isUp ? gapSize : -gapSize;
|
|
146
|
+
main.verticalOhlcv.candle_close_direction[index] = isUp ? closeDirection : -closeDirection;
|
|
147
|
+
main.verticalOhlcv.candle_high_direction[index] = isUp ? highDirection : -highDirection;
|
|
148
|
+
main.verticalOhlcv.candle_low_direction[index] = isUp ? lowDirection : -lowDirection;
|
|
149
|
+
main.verticalOhlcv.candle_open_direction[index] = isUp ? openDirection : -openDirection;
|
|
150
|
+
|
|
151
|
+
main.verticalOhlcv.candle_top_size[index] = topSize;
|
|
152
|
+
main.verticalOhlcv.candle_bottom_size[index] = bottomSize;
|
|
153
|
+
main.verticalOhlcv.candle_shadow_size[index] = shadowSize;
|
|
171
154
|
}
|
|
172
155
|
|
|
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
|
-
}
|
|
156
|
+
return true;
|
|
157
|
+
};
|
|
@@ -3,8 +3,69 @@ const gt = (f, s) => f > s
|
|
|
3
3
|
const lt = (f, s) => f < s
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
export const crossPairs = (main, arr) => {
|
|
8
|
+
|
|
9
|
+
const {verticalOhlcv} = main
|
|
10
|
+
|
|
11
|
+
const x = {}
|
|
12
|
+
const c = {}
|
|
13
|
+
|
|
14
|
+
for (const { fast, slow, count } of arr) {
|
|
15
|
+
// Validate parameters
|
|
16
|
+
if (!fast || !slow) continue;
|
|
17
|
+
|
|
18
|
+
// Prepare slowNumArr if 'slow' is a number
|
|
19
|
+
if (typeof slow === 'number') {
|
|
20
|
+
|
|
21
|
+
main.verticalOhlcv[slow] = new Array(main.len).fill(slow)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const crossName = `${fast}_x_${slow}`
|
|
25
|
+
const countName = `${fast}_c_${slow}`
|
|
26
|
+
|
|
27
|
+
const splitCount = arr => (typeof count !== 'undefined') ? (arr.slice(-count)).filter(o => o === 1 || o === -1).length : 0
|
|
28
|
+
|
|
29
|
+
//cross already exists
|
|
30
|
+
if(verticalOhlcv[crossName])
|
|
31
|
+
{
|
|
32
|
+
const cross = verticalOhlcv[crossName]
|
|
33
|
+
x[crossName] = cross
|
|
34
|
+
|
|
35
|
+
if(typeof count !== 'undefined')
|
|
36
|
+
{
|
|
37
|
+
c[countName] = splitCount(cross)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
//cross created from 2 columns
|
|
42
|
+
else if ((verticalOhlcv[fast] || fast === 'price') && verticalOhlcv[slow]) {
|
|
43
|
+
|
|
44
|
+
const cross = (fast === 'price')
|
|
45
|
+
? findCrosses({
|
|
46
|
+
fast: verticalOhlcv.close,
|
|
47
|
+
high: verticalOhlcv.high,
|
|
48
|
+
low: verticalOhlcv.low,
|
|
49
|
+
slow: verticalOhlcv[slow]}
|
|
50
|
+
)
|
|
51
|
+
: findCrosses({fast: verticalOhlcv[fast], slow: verticalOhlcv[slow]})
|
|
52
|
+
|
|
53
|
+
x[crossName] = cross
|
|
54
|
+
|
|
55
|
+
if(typeof count !== 'undefined')
|
|
56
|
+
{
|
|
57
|
+
c[countName] = splitCount(cross)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {x, c}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
6
67
|
// Function to find crosses between two arrays: fast and slow
|
|
7
|
-
|
|
68
|
+
const findCrosses = ({fast, high, low, slow}) => {
|
|
8
69
|
// Map states based on comparison of fast and slow arrays
|
|
9
70
|
|
|
10
71
|
const dataLength = fast.length
|
|
@@ -80,131 +141,48 @@ export const findCrosses = ({fast, high, low, slow}) => {
|
|
|
80
141
|
|
|
81
142
|
|
|
82
143
|
const flatGroupsToNumArr = groups => {
|
|
83
|
-
|
|
84
|
-
const groupLengths = groups.map(g => g.length)
|
|
85
|
-
|
|
86
|
-
return groups.map((group, gIndex) => {
|
|
87
|
-
let initialValue = groupLengths[gIndex]
|
|
88
|
-
|
|
89
|
-
return group.map((s, i) => {
|
|
90
|
-
let v = initialValue - i
|
|
91
|
-
|
|
92
|
-
if(s === 0.5)
|
|
93
|
-
{
|
|
94
|
-
v = 0
|
|
95
|
-
}
|
|
96
|
-
if(s === 1)
|
|
97
|
-
{
|
|
98
|
-
v = v * 1
|
|
99
|
-
}
|
|
100
|
-
if(s === 0)
|
|
101
|
-
{
|
|
102
|
-
v = v * -1
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return v
|
|
106
|
-
}).reverse()
|
|
107
|
-
}).flat()
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Helper function to group consecutive values
|
|
112
|
-
export const groupConsecutiveValues = arr => {
|
|
113
|
-
if (arr.length === 0) return []
|
|
114
|
-
|
|
115
|
-
return arr.reduce((acc, currentValue, index, array) => {
|
|
116
|
-
if (index === 0) {
|
|
117
|
-
// Start the first group with the first element
|
|
118
|
-
acc.push([currentValue])
|
|
119
|
-
} else {
|
|
120
|
-
// Get the previous value
|
|
121
|
-
const prevValue = array[index - 1]
|
|
122
|
-
const currentGroup = acc[acc.length - 1]
|
|
123
|
-
|
|
124
|
-
// Check the direction of the previous and current values
|
|
125
|
-
if (currentValue !== prevValue) {
|
|
126
|
-
// Start a new group if the direction changes
|
|
127
|
-
acc.push([currentValue]);
|
|
128
|
-
} else {
|
|
129
|
-
// Otherwise, add to the current group
|
|
130
|
-
currentGroup.push(currentValue);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return acc
|
|
134
|
-
}, [])
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
export const crossPairs = (main, arr) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const {verticalOhlcv, len} = main
|
|
144
|
-
const slowNumArrCache = {};
|
|
145
|
-
const x = {}
|
|
146
|
-
const c = {}
|
|
147
|
-
|
|
148
|
-
for (const { fast, slow, count } of arr) {
|
|
149
|
-
// Validate parameters
|
|
150
|
-
if (!fast || !slow) continue;
|
|
144
|
+
const result = [];
|
|
151
145
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
for (const group of groups) {
|
|
147
|
+
const length = group.length;
|
|
148
|
+
|
|
149
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
150
|
+
const s = group[i];
|
|
151
|
+
const offset = length - i;
|
|
152
|
+
result.push(
|
|
153
|
+
s === 0.5 ? 0 : offset * (s === 1 ? 1 : -1)
|
|
154
|
+
);
|
|
156
155
|
}
|
|
156
|
+
}
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const splitCount = arr => (typeof count !== 'undefined') ? (arr.slice(-count)).filter(o => o === 1 || o === -1).length : 0
|
|
162
|
-
|
|
163
|
-
//cross already exists
|
|
164
|
-
if(verticalOhlcv[crossName])
|
|
165
|
-
{
|
|
166
|
-
const cross = verticalOhlcv[crossName]
|
|
167
|
-
x[crossName] = cross
|
|
168
|
-
|
|
169
|
-
if(typeof count !== 'undefined')
|
|
170
|
-
{
|
|
171
|
-
c[countName] = splitCount(cross)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
//cross created from 2 columns
|
|
176
|
-
else if ((verticalOhlcv[fast] || fast === 'price') && verticalOhlcv[slow]) {
|
|
177
|
-
|
|
178
|
-
const cross = (fast === 'price')
|
|
179
|
-
? findCrosses({
|
|
180
|
-
fast: verticalOhlcv.close,
|
|
181
|
-
high: verticalOhlcv.high,
|
|
182
|
-
low: verticalOhlcv.low,
|
|
183
|
-
slow: verticalOhlcv[slow]}
|
|
184
|
-
)
|
|
185
|
-
: findCrosses({fast: verticalOhlcv[fast], slow: verticalOhlcv[slow]})
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
186
160
|
|
|
187
|
-
x[crossName] = cross
|
|
188
161
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
162
|
+
// Helper function to group consecutive values
|
|
163
|
+
const groupConsecutiveValues = (arr) => {
|
|
164
|
+
const length = arr.length;
|
|
165
|
+
if (length === 0) return [];
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
else if (verticalOhlcv[fast] && slowNumArrCache[slow]) {
|
|
197
|
-
const cross = findCrosses({fast: verticalOhlcv[fast], slow: slowNumArrCache[slow]})
|
|
198
|
-
x[crossName] = cross
|
|
167
|
+
const result = [];
|
|
168
|
+
let currentGroup = [arr[0]];
|
|
199
169
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
170
|
+
for (let i = 1; i < length; i++) {
|
|
171
|
+
const currentValue = arr[i];
|
|
172
|
+
const prevValue = arr[i - 1];
|
|
203
173
|
|
|
174
|
+
if (currentValue === prevValue) {
|
|
175
|
+
// Same value, continue the current group
|
|
176
|
+
currentGroup.push(currentValue);
|
|
204
177
|
} else {
|
|
205
|
-
|
|
178
|
+
// Value changed, finalize the current group and start a new one
|
|
179
|
+
result.push(currentGroup);
|
|
180
|
+
currentGroup = [currentValue];
|
|
206
181
|
}
|
|
207
182
|
}
|
|
208
183
|
|
|
209
|
-
|
|
210
|
-
|
|
184
|
+
// Push the last group
|
|
185
|
+
result.push(currentGroup);
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
};
|
|
@@ -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
|
+
};
|