ohlcv-indicators 3.3.4 → 3.3.5
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 +1105 -1188
- package/index.js +16 -33
- package/package.json +1 -1
- package/src/core-functions/mainLoop.js +26 -41
- package/src/moving-averages/bollingerBands.js +4 -2
- package/src/moving-averages/donchianChannel.js +5 -2
- package/src/moving-averages/macd.js +3 -1
- package/src/moving-averages/movingAverages.js +2 -1
- package/src/studies/lag.js +2 -2
- package/src/studies/scaler.js +1 -1
- package/src/utilities/assignTypes.js +2 -2
- package/src/utilities/numberUtilities.js +1 -1
- package/src/utilities/precisionMultiplier.js +1 -1
- package/src/utilities/validators.js +1 -3
- package/src/utilities/verticalToHorizontal.js +16 -18
package/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { mainLoop } from './src/core-functions/mainLoop.js'
|
|
2
2
|
import { correlation } from './src/studies/correlation.js'
|
|
3
3
|
import { isAlreadyComputed, validateArray, validateObject, validateArrayOptions, validateBoolean, validateNumber } from './src/utilities/validators.js'
|
|
4
|
-
import { divideByMultiplier } from './src/utilities/numberUtilities.js'
|
|
5
4
|
import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
|
|
6
5
|
import { pushToMain } from './src/core-functions/pushToMain.js'
|
|
7
6
|
import { assignTypes } from './src/utilities/assignTypes.js'
|
|
8
7
|
import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
|
|
9
|
-
import { selectDateFormatter, dateFormaters } from './src/utilities/dateUtilities.js'
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Class OHLCV_INDICATORS
|
|
@@ -27,37 +25,31 @@ export default class OHLCV_INDICATORS {
|
|
|
27
25
|
|
|
28
26
|
this.firstRow = input[0]
|
|
29
27
|
|
|
30
|
-
this.inputCols = Object.keys(this.firstRow)
|
|
31
|
-
|
|
32
28
|
const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
|
|
33
|
-
|
|
29
|
+
|
|
34
30
|
this.inputTypes = inputTypes
|
|
35
31
|
this.arrayTypes = arrayTypes
|
|
36
|
-
|
|
37
|
-
|
|
38
32
|
if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
|
|
39
33
|
|
|
40
34
|
this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
|
|
41
35
|
this.isComputed = false
|
|
42
36
|
this.input = input
|
|
43
|
-
this.priceBased = ['open', 'high', 'low', 'close']
|
|
37
|
+
this.priceBased = new Set(['open', 'high', 'low', 'close'])
|
|
44
38
|
this.len = input.length
|
|
45
39
|
this.instances = {}
|
|
46
40
|
this.crossPairsList = []
|
|
47
41
|
this.verticalOhlcv = {}
|
|
42
|
+
this.verticalOhlcvKeyNames = []
|
|
48
43
|
|
|
49
|
-
this.studies = {}
|
|
50
44
|
this.utilities = {
|
|
51
45
|
correlation
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
this.invalidValueIndex = -1
|
|
55
|
-
|
|
56
49
|
this.precision = precision
|
|
57
50
|
this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
|
|
58
51
|
this.ScaledGroups = {}
|
|
59
52
|
|
|
60
|
-
|
|
61
53
|
if(inputParams !== null)
|
|
62
54
|
{
|
|
63
55
|
validateArray(inputParams, 'inputParams', 'constructor')
|
|
@@ -70,7 +62,6 @@ export default class OHLCV_INDICATORS {
|
|
|
70
62
|
|
|
71
63
|
this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
|
|
72
64
|
|
|
73
|
-
|
|
74
65
|
return this
|
|
75
66
|
}
|
|
76
67
|
|
|
@@ -95,7 +86,7 @@ export default class OHLCV_INDICATORS {
|
|
|
95
86
|
let newArr = shouldSlice ? arr.slice(-sliceLength) : arr;
|
|
96
87
|
|
|
97
88
|
// If this key is price based and precision is enabled, map over the array.
|
|
98
|
-
if (precision && priceBased.
|
|
89
|
+
if (precision && priceBased.has(key)) {
|
|
99
90
|
newArr = newArr.map(v => (v == null ? null : v / precisionMultiplier));
|
|
100
91
|
}
|
|
101
92
|
result[key] = newArr;
|
|
@@ -110,9 +101,7 @@ export default class OHLCV_INDICATORS {
|
|
|
110
101
|
//getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
|
|
111
102
|
this.compute()
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return verticalToHorizontal(verticalOhlcv, skipNull, precision, precisionMultiplier, priceBased, invalidValueIndex)
|
|
104
|
+
return verticalToHorizontal(skipNull, this, 0)
|
|
116
105
|
}
|
|
117
106
|
|
|
118
107
|
|
|
@@ -120,16 +109,7 @@ export default class OHLCV_INDICATORS {
|
|
|
120
109
|
|
|
121
110
|
this.compute()
|
|
122
111
|
|
|
123
|
-
|
|
124
|
-
const keyNames = Object.keys(verticalOhlcv)
|
|
125
|
-
const row = {}
|
|
126
|
-
|
|
127
|
-
for(const key of keyNames)
|
|
128
|
-
{
|
|
129
|
-
row[key] = verticalOhlcv[key][len - 1]
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return (precision) ? divideByMultiplier({row, precisionMultiplier, priceBased}) : row
|
|
112
|
+
return verticalToHorizontal(false, this, this.len - 1)[0]
|
|
133
113
|
}
|
|
134
114
|
|
|
135
115
|
compute() {
|
|
@@ -150,6 +130,11 @@ export default class OHLCV_INDICATORS {
|
|
|
150
130
|
if (this.len > 0) {
|
|
151
131
|
mainLoop(this.input, this);
|
|
152
132
|
this.isComputed = true;
|
|
133
|
+
|
|
134
|
+
//flushing after mainLoop
|
|
135
|
+
this.input = []
|
|
136
|
+
this.instances = {}
|
|
137
|
+
this.firstRow = []
|
|
153
138
|
}
|
|
154
139
|
|
|
155
140
|
return this;
|
|
@@ -184,13 +169,11 @@ export default class OHLCV_INDICATORS {
|
|
|
184
169
|
|
|
185
170
|
this.inputParams.push({key: methodName, params: [colKeys, lookback]})
|
|
186
171
|
|
|
187
|
-
for(
|
|
172
|
+
for(const key of colKeys)
|
|
188
173
|
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if(this.priceBased.find(v => key === v))
|
|
174
|
+
if(this.priceBased.has(key))
|
|
192
175
|
{
|
|
193
|
-
this.priceBased.
|
|
176
|
+
this.priceBased.add(key);
|
|
194
177
|
}
|
|
195
178
|
}
|
|
196
179
|
|
|
@@ -201,7 +184,7 @@ export default class OHLCV_INDICATORS {
|
|
|
201
184
|
|
|
202
185
|
const methodName = 'relativeVolume'
|
|
203
186
|
|
|
204
|
-
if(!this.
|
|
187
|
+
if(!this.inputTypes.hasOwnProperty('volume')) {
|
|
205
188
|
throw new Error('If "relativeVolume" is called the input ohlcv must contain valid volume properties.')
|
|
206
189
|
}
|
|
207
190
|
|
|
@@ -341,7 +324,7 @@ export default class OHLCV_INDICATORS {
|
|
|
341
324
|
{
|
|
342
325
|
const methodName = 'volumeOscillator'
|
|
343
326
|
|
|
344
|
-
if(!this.
|
|
327
|
+
if(!this.inputTypes.hasOwnProperty('volume')) {
|
|
345
328
|
throw new Error('If "volumeOscillator" is called the input ohlcv must contain valid volume properties.')
|
|
346
329
|
}
|
|
347
330
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
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",
|
|
@@ -29,41 +29,22 @@ const indicatorFunctions = {
|
|
|
29
29
|
Scaler
|
|
30
30
|
};
|
|
31
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
32
|
|
|
42
33
|
export const mainLoop = (input, main) => {
|
|
43
|
-
const { len, inputParams, priceBased, precisionMultiplier, arrayTypes } = main;
|
|
34
|
+
const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes } = main;
|
|
44
35
|
|
|
45
|
-
validateInputParams(
|
|
36
|
+
validateInputParams({inputParams, len})
|
|
46
37
|
|
|
47
|
-
for(const key of Object.keys(
|
|
38
|
+
for(const key of Object.keys(inputTypes))
|
|
48
39
|
{
|
|
49
|
-
|
|
40
|
+
verticalOhlcv[key] = buildArray(arrayTypes[key], len)
|
|
50
41
|
}
|
|
51
42
|
|
|
52
|
-
// Prepare the list of indicator function calls
|
|
53
|
-
const indicatorCalls = processIndicatorCalls(inputParams);
|
|
54
|
-
|
|
55
43
|
// Process each row in the input
|
|
56
|
-
for (let
|
|
57
|
-
const curr = input[
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMultiplier}) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for(const [key, formaterKey] of Object.entries(main.inputTypes))
|
|
44
|
+
for (let index = 0; index < len; index++) {
|
|
45
|
+
const curr = input[index]
|
|
46
|
+
|
|
47
|
+
for(const [key, formaterKey] of Object.entries(inputTypes))
|
|
67
48
|
{
|
|
68
49
|
const value = curr[key]
|
|
69
50
|
|
|
@@ -71,30 +52,34 @@ const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMul
|
|
|
71
52
|
{
|
|
72
53
|
let formatedValue = numberFormater[formaterKey](value)
|
|
73
54
|
|
|
74
|
-
if(precisionMultiplier > 1 && priceBased.
|
|
55
|
+
if(precisionMultiplier > 1 && priceBased.has(key))
|
|
75
56
|
{
|
|
76
57
|
formatedValue = formatedValue * precisionMultiplier
|
|
77
58
|
}
|
|
78
59
|
|
|
79
|
-
main.pushToMain({index
|
|
60
|
+
main.pushToMain({index, key, value: formatedValue})
|
|
80
61
|
}
|
|
81
62
|
else if(dateFormaters.hasOwnProperty(formaterKey))
|
|
82
63
|
{
|
|
83
|
-
main.pushToMain({index
|
|
64
|
+
main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
|
|
84
65
|
}
|
|
85
66
|
else
|
|
86
67
|
{
|
|
87
|
-
main.pushToMain({index
|
|
68
|
+
main.pushToMain({index, key, value})
|
|
88
69
|
}
|
|
89
70
|
}
|
|
71
|
+
|
|
72
|
+
// Run all indicator functions except for the ones processed later
|
|
73
|
+
for (const { key, params } of inputParams) {
|
|
74
|
+
if (key === 'lag' || key === 'crossPairs') continue;
|
|
75
|
+
// resolve fn on-demand, no per-item object allocation here
|
|
76
|
+
indicatorFunctions[key](main, index, ...params)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Process these indicators separately (ensuring their execution order)
|
|
80
|
+
lag(main, index)
|
|
81
|
+
crossPairs(main, index)
|
|
82
|
+
}
|
|
90
83
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
}
|
|
84
|
+
verticalOhlcvKeyNames.push(...Object.keys(verticalOhlcv))
|
|
85
|
+
}
|
|
@@ -45,7 +45,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
45
45
|
|
|
46
46
|
// Set up additional arrays for each range property.
|
|
47
47
|
for (const rangeKey of range) {
|
|
48
|
-
if (!(rangeKey in verticalOhlcv) || !priceBased.
|
|
48
|
+
if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
|
|
49
49
|
throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
50
50
|
}
|
|
51
51
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
@@ -60,7 +60,9 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
60
60
|
main.lag(keyNames, lag)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
[`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`].forEach(v => {
|
|
64
|
+
priceBased.add(v)
|
|
65
|
+
})
|
|
64
66
|
|
|
65
67
|
for(const key of keyNames)
|
|
66
68
|
{
|
|
@@ -27,7 +27,7 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
|
|
|
27
27
|
|
|
28
28
|
// Set up additional arrays for each range property.
|
|
29
29
|
for (const rangeKey of range) {
|
|
30
|
-
if (!(rangeKey in verticalOhlcv) || !priceBased.
|
|
30
|
+
if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
|
|
31
31
|
throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
32
32
|
}
|
|
33
33
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
@@ -42,7 +42,10 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
|
|
|
42
42
|
main.lag(keyNames, lag)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
[`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`].forEach(v => {
|
|
47
|
+
priceBased.add(v)
|
|
48
|
+
})
|
|
46
49
|
|
|
47
50
|
instances.donchian_channel.settings[indicatorKey] = {
|
|
48
51
|
maxDeque: [], // will hold indices for highs in descending order
|
|
@@ -47,7 +47,9 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
|
|
|
47
47
|
[histogramKey]: new Float64Array(len).fill(NaN),
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
[diffKey, deaKey, histogramKey].forEach(v => {
|
|
51
|
+
priceBased.add(v)
|
|
52
|
+
})
|
|
51
53
|
|
|
52
54
|
const keyNames = [diffKey, deaKey, histogramKey]
|
|
53
55
|
|
package/src/studies/lag.js
CHANGED
|
@@ -17,10 +17,10 @@ export const lag = (main, index) => {
|
|
|
17
17
|
{
|
|
18
18
|
for (const colKey of colKeys)
|
|
19
19
|
{
|
|
20
|
-
if(priceBased.
|
|
20
|
+
if(priceBased.has(colKey))
|
|
21
21
|
{
|
|
22
22
|
for (let step = 1; step <= lookback; step++) {
|
|
23
|
-
priceBased.
|
|
23
|
+
priceBased.add(`${colKey}_lag_${step}`)
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/studies/scaler.js
CHANGED
|
@@ -37,7 +37,7 @@ export const Scaler = (
|
|
|
37
37
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
38
38
|
throw new Error(`Target property "${target}" not found in verticalOhlcv`);
|
|
39
39
|
}
|
|
40
|
-
if (!priceBased.
|
|
40
|
+
if (!priceBased.has(target)) {
|
|
41
41
|
throw new Error(`Column "${target}" is not priceBased and cannot be scaled.`);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { classifyNum } from "./numberUtilities.js";
|
|
2
2
|
import { selectDateFormatter } from "./dateUtilities.js";
|
|
3
3
|
|
|
4
|
-
const numberKeys = ['open', 'high', 'low', 'close']
|
|
4
|
+
const numberKeys = new Set(['open', 'high', 'low', 'close'])
|
|
5
5
|
|
|
6
6
|
export const buildArray = (arrayType, len) => {
|
|
7
7
|
if(arrayType === 'Array') return new Array(len).fill(null)
|
|
@@ -24,7 +24,7 @@ export const assignTypes = firstRow => {
|
|
|
24
24
|
inputTypes[colName] = classifyNum(cellValue, true)
|
|
25
25
|
arrayTypes[colName] = 'Int32Array'
|
|
26
26
|
}
|
|
27
|
-
else if(numberKeys.
|
|
27
|
+
else if(numberKeys.has(colName))
|
|
28
28
|
{
|
|
29
29
|
inputTypes[colName] = classifyNum(cellValue, true)
|
|
30
30
|
arrayTypes[colName] = 'Float64Array'
|
|
@@ -4,7 +4,7 @@ export const calcPrecisionMultiplier = (main, firstRow) => {
|
|
|
4
4
|
if(precision === false) return 1
|
|
5
5
|
|
|
6
6
|
let output = 1
|
|
7
|
-
const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.
|
|
7
|
+
const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.has(k)).map(([_, v]) => v)
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
for(const value of priceBasedValues)
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
export const verticalToHorizontal = (
|
|
2
|
-
obj,
|
|
3
2
|
skipNull = false,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
priceBased,
|
|
7
|
-
invalidValueIndex = 0
|
|
3
|
+
main,
|
|
4
|
+
startIndex = 0
|
|
8
5
|
) => {
|
|
9
|
-
const keys = Object.keys(obj);
|
|
10
|
-
if (keys.length === 0) return [];
|
|
11
|
-
|
|
12
|
-
const startIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0
|
|
13
|
-
const endIndex = obj[keys[0]].length
|
|
14
|
-
const len = endIndex - startIndex
|
|
15
|
-
const result = new Array(len)
|
|
16
6
|
|
|
7
|
+
const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames} = main
|
|
8
|
+
|
|
9
|
+
if (verticalOhlcvKeyNames.length === 0) return []
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
const skipNullIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0
|
|
12
|
+
const maxStartIndex = Math.max(skipNullIndex, startIndex)
|
|
13
|
+
const diffLen = len - maxStartIndex
|
|
14
|
+
const result = new Array(diffLen)
|
|
15
|
+
|
|
16
|
+
for (let i = maxStartIndex; i < len; i++) {
|
|
19
17
|
const row = {};
|
|
20
|
-
for (let j = 0; j <
|
|
21
|
-
const key =
|
|
22
|
-
const value =
|
|
18
|
+
for (let j = 0; j < verticalOhlcvKeyNames.length; j++) {
|
|
19
|
+
const key = verticalOhlcvKeyNames[j]
|
|
20
|
+
const value = verticalOhlcv[key][i]
|
|
23
21
|
|
|
24
|
-
row[key] = (precision && priceBased.
|
|
22
|
+
row[key] = (precision && priceBased.has(key)) ? value / precisionMultiplier : value
|
|
25
23
|
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
result[i -
|
|
26
|
+
result[i - maxStartIndex] = row
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
return result
|