ohlcv-indicators 1.5.2 → 1.5.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/index.js
CHANGED
|
@@ -7,8 +7,10 @@ import { volumeProfile } from './src/studies/volumeProfile.js'
|
|
|
7
7
|
import {crossPairs} from './src/studies/findCrosses.js'
|
|
8
8
|
import { candles } from './src/studies/candles.js'
|
|
9
9
|
import { orb } from './src/studies/orb.js'
|
|
10
|
+
import { donchianChannels } from './src/moving-averages/donchianChannel.js'
|
|
10
11
|
import {Big} from 'trading-signals';
|
|
11
12
|
import { parseOhlcvToVertical } from './src/utilities/parsing-utilities.js'
|
|
13
|
+
import { candlesStudies } from './src/studies/candleStudies.js'
|
|
12
14
|
|
|
13
15
|
export default class OHLCV_INDICATORS {
|
|
14
16
|
constructor({input, precision = false, ticker = 'undefined'}) {
|
|
@@ -205,4 +207,18 @@ export default class OHLCV_INDICATORS {
|
|
|
205
207
|
|
|
206
208
|
return this
|
|
207
209
|
}
|
|
210
|
+
donchianChannels(period, offset)
|
|
211
|
+
{
|
|
212
|
+
const result = donchianChannels(this, period, offset)
|
|
213
|
+
Object.assign(this.indicators, result)
|
|
214
|
+
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
candlesStudies(period)
|
|
218
|
+
{
|
|
219
|
+
const result = candlesStudies(this, period)
|
|
220
|
+
Object.assign(this.indicators, result)
|
|
221
|
+
|
|
222
|
+
return this
|
|
223
|
+
}
|
|
208
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "A JavaScript library featuring optimized trading indicators for OHLCV arrays [{open, high, low, close, value}]. This library is optimized for backtesting and trading bot development.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const donchianChannels = (main, period, offset) => {
|
|
2
|
+
|
|
3
|
+
const {verticalOhlcv, precision, len} = main
|
|
4
|
+
const {high, low} = verticalOhlcv
|
|
5
|
+
|
|
6
|
+
return getDonchianChannels(high, low, period, offset, precision, len)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const getDonchianChannels = (high, low, period, offset, precision, len) => {
|
|
10
|
+
const donchian_channel_upper = new Array(len).fill(null)
|
|
11
|
+
const donchian_channel_lower = new Array(len).fill(null)
|
|
12
|
+
const donchian_channel_basis = new Array(len).fill(null)
|
|
13
|
+
|
|
14
|
+
const maxQueue = []
|
|
15
|
+
const minQueue = []
|
|
16
|
+
|
|
17
|
+
const avg = (precision)
|
|
18
|
+
? ((h, l) => (h.plus(l)).div(2))
|
|
19
|
+
: ((h, l) => (h + l) / 2)
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < len; i++) {
|
|
22
|
+
|
|
23
|
+
while (maxQueue.length && maxQueue[0] <= i - period) {
|
|
24
|
+
maxQueue.shift()
|
|
25
|
+
}
|
|
26
|
+
// Remove smaller values from the end
|
|
27
|
+
while (maxQueue.length && high[maxQueue[maxQueue.length - 1]] <= high[i]) {
|
|
28
|
+
maxQueue.pop()
|
|
29
|
+
}
|
|
30
|
+
maxQueue.push(i)
|
|
31
|
+
|
|
32
|
+
while (minQueue.length && minQueue[0] <= i - period) {
|
|
33
|
+
minQueue.shift()
|
|
34
|
+
}
|
|
35
|
+
while (minQueue.length && low[minQueue[minQueue.length - 1]] >= low[i]) {
|
|
36
|
+
minQueue.pop()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
minQueue.push(i)
|
|
40
|
+
|
|
41
|
+
if (i >= period - 1) {
|
|
42
|
+
const upperValue = high[maxQueue[0]]
|
|
43
|
+
const lowerValue = low[minQueue[0]]
|
|
44
|
+
const basisValue = avg(upperValue, lowerValue)
|
|
45
|
+
|
|
46
|
+
const indexWithOffset = i + offset
|
|
47
|
+
|
|
48
|
+
if (indexWithOffset >= 0 && indexWithOffset < len) {
|
|
49
|
+
donchian_channel_upper[indexWithOffset] = upperValue
|
|
50
|
+
donchian_channel_lower[indexWithOffset] = lowerValue
|
|
51
|
+
donchian_channel_basis[indexWithOffset] = basisValue
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
donchian_channel_upper,
|
|
58
|
+
donchian_channel_lower,
|
|
59
|
+
donchian_channel_basis
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {SMA, FasterSMA} from 'trading-signals';
|
|
2
|
+
|
|
3
|
+
export const candlesStudies = (main, period) => {
|
|
4
|
+
|
|
5
|
+
const {inputOhlcv, len} = main
|
|
6
|
+
|
|
7
|
+
const cols = getCandlesStudies(inputOhlcv, period, len)
|
|
8
|
+
return cols
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getCandlesStudies = (inputOhlcv, period = 20, len) => {
|
|
12
|
+
|
|
13
|
+
let getSize = (a, b) => Math.abs(a - b)
|
|
14
|
+
|
|
15
|
+
let candle_direction = new Array(len).fill(null)
|
|
16
|
+
|
|
17
|
+
//body
|
|
18
|
+
let candle_body_size = new Array(len).fill(null)
|
|
19
|
+
let candle_body_size_mean = new Array(len).fill(null)
|
|
20
|
+
let candle_body_size_label = new Array(len).fill(null)
|
|
21
|
+
|
|
22
|
+
//top
|
|
23
|
+
let candle_top_size_mean = new Array(len).fill(null)
|
|
24
|
+
let candle_top_size = new Array(len).fill(null)
|
|
25
|
+
let candle_top_size_label = new Array(len).fill(null)
|
|
26
|
+
|
|
27
|
+
//bottom
|
|
28
|
+
let candle_bottom_size_mean = new Array(len).fill(null)
|
|
29
|
+
let candle_bottom_size = new Array(len).fill(null)
|
|
30
|
+
let candle_bottom_size_label = new Array(len).fill(null)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
//instances
|
|
34
|
+
let bodyInstance = new FasterSMA(period)
|
|
35
|
+
let topInstance = new FasterSMA(period)
|
|
36
|
+
let bottomInstance = new FasterSMA(period)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
for(let x = 0; x < len; x++)
|
|
40
|
+
{
|
|
41
|
+
const {open, high, low, close} = inputOhlcv[x]
|
|
42
|
+
let bodySizeMean = null
|
|
43
|
+
let topSizeMean = null
|
|
44
|
+
let bottomSizeMean = null
|
|
45
|
+
const bodySize = getSize(open, close)
|
|
46
|
+
let topSize
|
|
47
|
+
let bottomSize
|
|
48
|
+
let candleDirection = (close > open) ? 'up' : 'down'
|
|
49
|
+
|
|
50
|
+
if(candleDirection === 'up')
|
|
51
|
+
{
|
|
52
|
+
topSize = getSize(high, close)
|
|
53
|
+
bottomSize = getSize(open, low)
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
{
|
|
57
|
+
topSize = getSize(high, open)
|
|
58
|
+
bottomSize = getSize(close, low)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
bodyInstance.update(bodySize)
|
|
62
|
+
topInstance.update(topSize)
|
|
63
|
+
bottomInstance.update(bottomSize)
|
|
64
|
+
|
|
65
|
+
try
|
|
66
|
+
{
|
|
67
|
+
bodySizeMean = bodyInstance.getResult()
|
|
68
|
+
topSizeMean = topInstance.getResult()
|
|
69
|
+
bottomSizeMean = bottomInstance.getResult()
|
|
70
|
+
}
|
|
71
|
+
catch(err)
|
|
72
|
+
{
|
|
73
|
+
bodySizeMean = null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
candle_direction[x] = candleDirection
|
|
77
|
+
|
|
78
|
+
//body
|
|
79
|
+
candle_body_size[x] = bodySize
|
|
80
|
+
candle_body_size_mean[x] = bodySizeMean
|
|
81
|
+
candle_body_size_label[x] = labelSize(bodySize, bodySizeMean)
|
|
82
|
+
|
|
83
|
+
//top
|
|
84
|
+
candle_top_size[x] = topSize
|
|
85
|
+
candle_top_size_mean[x] = topSizeMean
|
|
86
|
+
candle_top_size_label[x] = labelSize(topSize, topSizeMean)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
//bottom
|
|
90
|
+
candle_bottom_size[x] = bottomSize
|
|
91
|
+
candle_bottom_size_mean[x] = bottomSizeMean
|
|
92
|
+
candle_bottom_size_label[x] = labelSize(bottomSize, bottomSizeMean)
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
candle_direction,
|
|
98
|
+
candle_body_size_mean,
|
|
99
|
+
candle_body_size,
|
|
100
|
+
candle_body_size_label,
|
|
101
|
+
candle_top_size_mean,
|
|
102
|
+
candle_top_size,
|
|
103
|
+
candle_top_size_label,
|
|
104
|
+
candle_bottom_size_mean,
|
|
105
|
+
candle_bottom_size,
|
|
106
|
+
candle_bottom_size_label
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const labelSize = (value, mean) => {
|
|
111
|
+
|
|
112
|
+
if(value === null || mean === null) return null
|
|
113
|
+
if(value > mean * 1.25) return 'large'
|
|
114
|
+
else if(value < mean * 0.75) return 'small'
|
|
115
|
+
else return 'medium'
|
|
116
|
+
}
|
|
@@ -27,7 +27,6 @@ export const findCrosses = ({fast, slow, precision}) => {
|
|
|
27
27
|
lt = (a, b) => a < b
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
30
|
const state = new Array(dataLength).fill(null)
|
|
32
31
|
|
|
33
32
|
for(let x = 0; x < dataLength; x++)
|
|
@@ -37,17 +36,32 @@ export const findCrosses = ({fast, slow, precision}) => {
|
|
|
37
36
|
let value
|
|
38
37
|
|
|
39
38
|
if(f === null || s === null) value = 'neutral'
|
|
40
|
-
else if(eq(f, s))
|
|
39
|
+
else if(eq(f, s)){
|
|
40
|
+
if(gt(fast[x-1], slow[x-1]))
|
|
41
|
+
{
|
|
42
|
+
value = 'up'
|
|
43
|
+
}
|
|
44
|
+
else if(lt(fast[x-1], slow[x-1]))
|
|
45
|
+
{
|
|
46
|
+
value = 'down'
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
{
|
|
50
|
+
value = 'neutral'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
41
53
|
else if(gt(f, s)) value = 'up'
|
|
42
54
|
else if(lt(f, s)) value = 'down'
|
|
43
55
|
|
|
44
56
|
state[x] = value
|
|
45
57
|
}
|
|
46
58
|
|
|
59
|
+
|
|
60
|
+
|
|
47
61
|
const groups = groupConsecutiveValues(state)
|
|
48
62
|
const groupLengths = groups.map(g => g.length)
|
|
49
63
|
|
|
50
|
-
//console.log(groups)
|
|
64
|
+
//console.log(JSON.stringify(groups))
|
|
51
65
|
|
|
52
66
|
return groups.map((group, gIndex) => {
|
|
53
67
|
let initialValue = groupLengths[gIndex]
|
|
@@ -124,26 +138,39 @@ export const crossPairs = (main, arr) => {
|
|
|
124
138
|
|
|
125
139
|
const splitCount = arr => (typeof count !== 'undefined') ? (arr.slice(-count)).filter(o => o === 1 || o === -1).length : 0
|
|
126
140
|
|
|
127
|
-
//
|
|
141
|
+
//cross already exists
|
|
128
142
|
if(verticalOhlcv[crossName])
|
|
129
143
|
{
|
|
130
144
|
const cross = verticalOhlcv[crossName]
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
x[crossName] = cross
|
|
146
|
+
|
|
147
|
+
if(typeof count !== 'undefined')
|
|
148
|
+
{
|
|
149
|
+
c[countName] = splitCount(cross)
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
}
|
|
153
|
+
//cross created from 2 columns
|
|
134
154
|
else if (verticalOhlcv[fast] && verticalOhlcv[slow]) {
|
|
135
155
|
|
|
136
156
|
const cross = findCrosses({fast: verticalOhlcv[fast], slow: verticalOhlcv[slow], precision});
|
|
137
|
-
|
|
138
|
-
c[countName] = splitCount(cross)
|
|
139
157
|
x[crossName] = cross
|
|
140
158
|
|
|
141
|
-
|
|
159
|
+
if(typeof count !== 'undefined')
|
|
160
|
+
{
|
|
161
|
+
c[countName] = splitCount(cross)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
//cross created from 1 column and an number
|
|
166
|
+
else if (verticalOhlcv[fast] && slowNumArrCache[slow]) {
|
|
142
167
|
const cross = findCrosses({fast: verticalOhlcv[fast], slow: slowNumArrCache[slow], precision})
|
|
143
|
-
|
|
144
|
-
c[countName] = splitCount(cross)
|
|
145
168
|
x[crossName] = cross
|
|
146
169
|
|
|
170
|
+
if(typeof count !== 'undefined'){
|
|
171
|
+
c[countName] = splitCount(cross)
|
|
172
|
+
}
|
|
173
|
+
|
|
147
174
|
} else {
|
|
148
175
|
console.log(`Missing ohlcv properties for ${fast} or ${slow}`);
|
|
149
176
|
}
|
|
@@ -12,18 +12,20 @@ export const parseOhlcvToVertical = (input, len, big) => {
|
|
|
12
12
|
verticalOhlcv[key] = new Array(len)
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
for(let x = 0; x < len; x++)
|
|
17
|
+
{
|
|
18
|
+
const { open, high, low, close, volume, ...rest } = input[x]
|
|
19
|
+
verticalOhlcv.open[x] = big(open)
|
|
20
|
+
verticalOhlcv.high[x] = big(high)
|
|
21
|
+
verticalOhlcv.low[x] = big(low)
|
|
22
|
+
verticalOhlcv.close[x] = big(close)
|
|
23
|
+
verticalOhlcv.volume[x] = big(volume)
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
verticalOhlcv.close[index] = big(close)
|
|
21
|
-
verticalOhlcv.volume[index] = big(volume)
|
|
22
|
-
|
|
23
|
-
for (const key in rest) {
|
|
24
|
-
verticalOhlcv[key][index] = rest[key];
|
|
25
|
-
}
|
|
26
|
-
})
|
|
25
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
26
|
+
verticalOhlcv[key][x] = value
|
|
27
|
+
}
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
return verticalOhlcv
|
|
29
31
|
}
|