ohlcv-indicators 3.3.4 → 3.3.6
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 +1165 -1079
- package/index.js +63 -63
- package/package.json +1 -1
- package/src/core-functions/mainLoop.js +26 -41
- package/src/moving-averages/bollingerBands.js +10 -3
- package/src/moving-averages/donchianChannel.js +11 -3
- package/src/moving-averages/macd.js +9 -2
- package/src/moving-averages/movingAverages.js +2 -1
- package/src/studies/findCrosses.js +14 -7
- package/src/studies/lag.js +8 -5
- 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 +27 -2
- package/src/utilities/verticalToHorizontal.js +20 -23
package/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { mainLoop } from './src/core-functions/mainLoop.js'
|
|
2
2
|
import { correlation } from './src/studies/correlation.js'
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
isAlreadyComputed,
|
|
5
|
+
validateArray,
|
|
6
|
+
validateObject,
|
|
7
|
+
validateArrayOptions,
|
|
8
|
+
validateBoolean,
|
|
9
|
+
validateNumber,
|
|
10
|
+
validateArrayOfRanges
|
|
11
|
+
} from './src/utilities/validators.js'
|
|
5
12
|
import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
|
|
6
13
|
import { pushToMain } from './src/core-functions/pushToMain.js'
|
|
7
14
|
import { assignTypes } from './src/utilities/assignTypes.js'
|
|
8
15
|
import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
|
|
9
|
-
import {
|
|
16
|
+
import { buildArray } from './src/utilities/assignTypes.js'
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* Class OHLCV_INDICATORS
|
|
@@ -27,40 +34,35 @@ export default class OHLCV_INDICATORS {
|
|
|
27
34
|
|
|
28
35
|
this.firstRow = input[0]
|
|
29
36
|
|
|
30
|
-
this.inputCols = Object.keys(this.firstRow)
|
|
31
|
-
|
|
32
37
|
const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
|
|
33
|
-
|
|
38
|
+
|
|
34
39
|
this.inputTypes = inputTypes
|
|
35
40
|
this.arrayTypes = arrayTypes
|
|
36
|
-
|
|
37
|
-
|
|
38
41
|
if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
|
|
39
42
|
|
|
40
43
|
this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
|
|
41
44
|
this.isComputed = false
|
|
42
45
|
this.input = input
|
|
43
|
-
this.priceBased = ['open', 'high', 'low', 'close']
|
|
46
|
+
this.priceBased = new Set(['open', 'high', 'low', 'close'])
|
|
44
47
|
this.len = input.length
|
|
45
48
|
this.instances = {}
|
|
46
49
|
this.crossPairsList = []
|
|
47
50
|
this.verticalOhlcv = {}
|
|
51
|
+
this.verticalOhlcvKeyNames = []
|
|
52
|
+
this.verticalOhlcvTempCols = new Set()
|
|
48
53
|
|
|
49
|
-
this.studies = {}
|
|
50
54
|
this.utilities = {
|
|
51
55
|
correlation
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
this.invalidValueIndex = -1
|
|
55
|
-
|
|
56
59
|
this.precision = precision
|
|
57
60
|
this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
|
|
58
61
|
this.ScaledGroups = {}
|
|
59
62
|
|
|
60
|
-
|
|
61
63
|
if(inputParams !== null)
|
|
62
64
|
{
|
|
63
|
-
|
|
65
|
+
validateInputParams(inputParams, this.len)
|
|
64
66
|
this.inputParams = inputParams
|
|
65
67
|
this.compute()
|
|
66
68
|
}
|
|
@@ -70,7 +72,6 @@ export default class OHLCV_INDICATORS {
|
|
|
70
72
|
|
|
71
73
|
this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
|
|
72
74
|
|
|
73
|
-
|
|
74
75
|
return this
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -84,21 +85,28 @@ export default class OHLCV_INDICATORS {
|
|
|
84
85
|
invalidValueIndex,
|
|
85
86
|
len,
|
|
86
87
|
verticalOhlcv,
|
|
87
|
-
priceBased
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
88
|
+
priceBased,
|
|
89
|
+
arrayTypes,
|
|
90
|
+
verticalOhlcvTempCols
|
|
91
|
+
} = this
|
|
92
|
+
const result = {}
|
|
93
|
+
const startIndex = skipNull ? invalidValueIndex + 1 : 0
|
|
94
|
+
const newLen = len - startIndex
|
|
92
95
|
|
|
93
96
|
for (const [key, arr] of Object.entries(verticalOhlcv)) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
|
|
98
|
+
if(verticalOhlcvTempCols.has(key)) continue
|
|
99
|
+
|
|
100
|
+
const shouldApplyPrecision = priceBased.has(key) && precision
|
|
101
|
+
result[key] = buildArray(arrayTypes[key], newLen)
|
|
102
|
+
|
|
103
|
+
for (let x = startIndex; x < len; x++)
|
|
104
|
+
{
|
|
105
|
+
result[key][x] = (shouldApplyPrecision)
|
|
106
|
+
? arr[x] / precisionMultiplier
|
|
107
|
+
: arr[x]
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
return result;
|
|
@@ -110,9 +118,7 @@ export default class OHLCV_INDICATORS {
|
|
|
110
118
|
//getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
|
|
111
119
|
this.compute()
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return verticalToHorizontal(verticalOhlcv, skipNull, precision, precisionMultiplier, priceBased, invalidValueIndex)
|
|
121
|
+
return verticalToHorizontal(skipNull, this, 0)
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
|
|
@@ -120,16 +126,7 @@ export default class OHLCV_INDICATORS {
|
|
|
120
126
|
|
|
121
127
|
this.compute()
|
|
122
128
|
|
|
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
|
|
129
|
+
return verticalToHorizontal(false, this, this.len - 1)[0]
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
compute() {
|
|
@@ -150,6 +147,11 @@ export default class OHLCV_INDICATORS {
|
|
|
150
147
|
if (this.len > 0) {
|
|
151
148
|
mainLoop(this.input, this);
|
|
152
149
|
this.isComputed = true;
|
|
150
|
+
|
|
151
|
+
//flushing after mainLoop
|
|
152
|
+
this.input = []
|
|
153
|
+
this.instances = {}
|
|
154
|
+
this.firstRow = []
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
return this;
|
|
@@ -184,13 +186,11 @@ export default class OHLCV_INDICATORS {
|
|
|
184
186
|
|
|
185
187
|
this.inputParams.push({key: methodName, params: [colKeys, lookback]})
|
|
186
188
|
|
|
187
|
-
for(
|
|
189
|
+
for(const key of colKeys)
|
|
188
190
|
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if(this.priceBased.find(v => key === v))
|
|
191
|
+
if(this.priceBased.has(key))
|
|
192
192
|
{
|
|
193
|
-
this.priceBased.
|
|
193
|
+
this.priceBased.add(key);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -201,7 +201,7 @@ export default class OHLCV_INDICATORS {
|
|
|
201
201
|
|
|
202
202
|
const methodName = 'relativeVolume'
|
|
203
203
|
|
|
204
|
-
if(!this.
|
|
204
|
+
if(!this.inputTypes.hasOwnProperty('volume')) {
|
|
205
205
|
throw new Error('If "relativeVolume" is called the input ohlcv must contain valid volume properties.')
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -212,7 +212,7 @@ export default class OHLCV_INDICATORS {
|
|
|
212
212
|
|
|
213
213
|
const { lag = 0} = options;
|
|
214
214
|
|
|
215
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
215
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
216
216
|
|
|
217
217
|
this.inputParams.push({key: methodName, params: [size, {lag}]})
|
|
218
218
|
|
|
@@ -230,7 +230,7 @@ export default class OHLCV_INDICATORS {
|
|
|
230
230
|
|
|
231
231
|
const {target = 'close', lag = 0} = options
|
|
232
232
|
|
|
233
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
233
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
234
234
|
|
|
235
235
|
this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
|
|
236
236
|
|
|
@@ -247,7 +247,7 @@ export default class OHLCV_INDICATORS {
|
|
|
247
247
|
|
|
248
248
|
const {target = 'close', lag = 0} = options
|
|
249
249
|
|
|
250
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
250
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
251
251
|
|
|
252
252
|
this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
|
|
253
253
|
|
|
@@ -268,7 +268,7 @@ export default class OHLCV_INDICATORS {
|
|
|
268
268
|
|
|
269
269
|
const {target = 'close', lag = 0} = options
|
|
270
270
|
|
|
271
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
271
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
272
272
|
|
|
273
273
|
this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag}]})
|
|
274
274
|
|
|
@@ -288,9 +288,9 @@ export default class OHLCV_INDICATORS {
|
|
|
288
288
|
const {target = 'close', height = false, range = [], lag = 0} = options
|
|
289
289
|
|
|
290
290
|
|
|
291
|
-
validateArray(range, 'range', methodName)
|
|
292
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
293
|
-
validateBoolean(height, 'height', methodName)
|
|
291
|
+
validateArray(range, 'options.range', methodName)
|
|
292
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
293
|
+
validateBoolean(height, 'options.height', methodName)
|
|
294
294
|
|
|
295
295
|
this.inputParams.push({key: methodName, params: [size, stdDev, {target, height, range, lag}]});
|
|
296
296
|
|
|
@@ -308,7 +308,7 @@ export default class OHLCV_INDICATORS {
|
|
|
308
308
|
|
|
309
309
|
const {target = 'close', lag = 0} = options
|
|
310
310
|
|
|
311
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
311
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
312
312
|
|
|
313
313
|
this.inputParams.push({key: methodName, params: [size, {target, lag}]})
|
|
314
314
|
|
|
@@ -327,9 +327,9 @@ export default class OHLCV_INDICATORS {
|
|
|
327
327
|
validateObject(options, 'options', methodName)
|
|
328
328
|
const { height = false, range = [], lag = 0} = options;
|
|
329
329
|
|
|
330
|
-
validateArray(range, 'range', methodName)
|
|
331
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
332
|
-
validateBoolean(height, 'height', methodName)
|
|
330
|
+
validateArray(range, 'options.range', methodName)
|
|
331
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
332
|
+
validateBoolean(height, 'options.height', methodName)
|
|
333
333
|
|
|
334
334
|
this.inputParams.push({ key: methodName, params: [size, offset, { height, range, lag }] });
|
|
335
335
|
|
|
@@ -341,7 +341,7 @@ export default class OHLCV_INDICATORS {
|
|
|
341
341
|
{
|
|
342
342
|
const methodName = 'volumeOscillator'
|
|
343
343
|
|
|
344
|
-
if(!this.
|
|
344
|
+
if(!this.inputTypes.hasOwnProperty('volume')) {
|
|
345
345
|
throw new Error('If "volumeOscillator" is called the input ohlcv must contain valid volume properties.')
|
|
346
346
|
}
|
|
347
347
|
|
|
@@ -353,7 +353,7 @@ export default class OHLCV_INDICATORS {
|
|
|
353
353
|
|
|
354
354
|
const {lag = 0} = options
|
|
355
355
|
|
|
356
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
356
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
357
357
|
|
|
358
358
|
this.inputParams.push({key: methodName, params: [fastSize, slowSize, {lag}]})
|
|
359
359
|
return this
|
|
@@ -369,7 +369,7 @@ export default class OHLCV_INDICATORS {
|
|
|
369
369
|
|
|
370
370
|
const {lag = 0} = options
|
|
371
371
|
|
|
372
|
-
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
|
|
372
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
373
373
|
|
|
374
374
|
this.inputParams.push({key: methodName, params: [{lag}]})
|
|
375
375
|
return this
|
|
@@ -386,9 +386,9 @@ export default class OHLCV_INDICATORS {
|
|
|
386
386
|
|
|
387
387
|
const {group = false, range = [0, 1], lag = 0, type = 'minmax'} = options
|
|
388
388
|
|
|
389
|
-
validateBoolean(group, 'group', methodName)
|
|
390
|
-
|
|
391
|
-
validateArrayOptions(['minmax', 'zscore'], type, 'type', methodName)
|
|
389
|
+
validateBoolean(group, 'options.group', methodName)
|
|
390
|
+
validateArrayOfRanges(range, 'options.range', methodName)
|
|
391
|
+
validateArrayOptions(['minmax', 'zscore'], type, 'options.type', methodName)
|
|
392
392
|
|
|
393
393
|
this.inputParams.push({key: methodName, params: [size, colKeys, type, group, range, lag]})
|
|
394
394
|
return this
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.6",
|
|
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
|
+
}
|
|
@@ -17,7 +17,12 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
17
17
|
throw new Error(`bollingerBands could not find target "${target}"`);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
let numberOfIndicators = 0
|
|
21
|
+
|
|
22
|
+
for (const o of inputParams) {
|
|
23
|
+
if (o.key === 'bollingerBands') numberOfIndicators++;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
prefix = numberOfIndicators > 1
|
|
22
27
|
? `bollinger_bands_${indicatorKey}`
|
|
23
28
|
: `bollinger_bands${suffix}`;
|
|
@@ -45,7 +50,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
45
50
|
|
|
46
51
|
// Set up additional arrays for each range property.
|
|
47
52
|
for (const rangeKey of range) {
|
|
48
|
-
if (!(rangeKey in verticalOhlcv) || !priceBased.
|
|
53
|
+
if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
|
|
49
54
|
throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
50
55
|
}
|
|
51
56
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
@@ -60,7 +65,9 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
60
65
|
main.lag(keyNames, lag)
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
[`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`].forEach(v => {
|
|
69
|
+
priceBased.add(v)
|
|
70
|
+
})
|
|
64
71
|
|
|
65
72
|
for(const key of keyNames)
|
|
66
73
|
{
|
|
@@ -6,7 +6,12 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
|
|
|
6
6
|
// Initialization: create output arrays and indicator instance on the first call.
|
|
7
7
|
if (index === 0) {
|
|
8
8
|
const { inputParams, priceBased, arrayTypes} = main;
|
|
9
|
-
|
|
9
|
+
let numberOfIndicators = 0
|
|
10
|
+
|
|
11
|
+
for (const o of inputParams) {
|
|
12
|
+
if (o.key === 'donchianChannels') numberOfIndicators++;
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
const prefix = numberOfIndicators > 1 ? `donchian_channel_${indicatorKey}` : 'donchian_channel';
|
|
11
16
|
|
|
12
17
|
const keyNames = [
|
|
@@ -27,7 +32,7 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
|
|
|
27
32
|
|
|
28
33
|
// Set up additional arrays for each range property.
|
|
29
34
|
for (const rangeKey of range) {
|
|
30
|
-
if (!(rangeKey in verticalOhlcv) || !priceBased.
|
|
35
|
+
if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
|
|
31
36
|
throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
32
37
|
}
|
|
33
38
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
@@ -42,7 +47,10 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
|
|
|
42
47
|
main.lag(keyNames, lag)
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
|
|
51
|
+
[`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`].forEach(v => {
|
|
52
|
+
priceBased.add(v)
|
|
53
|
+
})
|
|
46
54
|
|
|
47
55
|
instances.donchian_channel.settings[indicatorKey] = {
|
|
48
56
|
maxDeque: [], // will hold indices for highs in descending order
|
|
@@ -17,7 +17,12 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
|
|
|
17
17
|
throw new Error(`Target property ${target} not found in verticalOhlcv for macd.`);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
let numberOfIndicators = 0
|
|
21
|
+
|
|
22
|
+
for (const o of inputParams) {
|
|
23
|
+
if (o.key === 'macd') numberOfIndicators++;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
// Choose a display prefix based on the number of indicators.
|
|
22
27
|
const displayPrefix = numberOfIndicators > 1 ? `macd_${fast}_${slow}_${signal}` : 'macd';
|
|
23
28
|
|
|
@@ -47,7 +52,9 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
|
|
|
47
52
|
[histogramKey]: new Float64Array(len).fill(NaN),
|
|
48
53
|
});
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
[diffKey, deaKey, histogramKey].forEach(v => {
|
|
56
|
+
priceBased.add(v)
|
|
57
|
+
})
|
|
51
58
|
|
|
52
59
|
const keyNames = [diffKey, deaKey, histogramKey]
|
|
53
60
|
|
|
@@ -132,19 +132,25 @@ class CrossInstance {
|
|
|
132
132
|
|
|
133
133
|
export const crossPairs = (main, index) => {
|
|
134
134
|
|
|
135
|
-
const {verticalOhlcv, instances, len, arrayTypes} = main
|
|
135
|
+
const {verticalOhlcv, instances, len, arrayTypes, verticalOhlcvTempCols} = main
|
|
136
136
|
|
|
137
137
|
if(index === 0)
|
|
138
138
|
{
|
|
139
139
|
const {inputParams} = main
|
|
140
|
-
const findParams = inputParams.filter(o => o.key === 'crossPairs')
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
instances.crossPairsList = inputParams.reduce((acc, { key, params }) => {
|
|
142
|
+
if (key === 'crossPairs') {
|
|
143
|
+
for (const group of params) {
|
|
144
|
+
for (const pair of group) {
|
|
145
|
+
acc.push(pair)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return acc;
|
|
150
|
+
}, [])
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
const {crossPairsList} = instances
|
|
153
|
+
const {crossPairsList} = instances
|
|
148
154
|
|
|
149
155
|
for (const { fast, slow } of crossPairsList)
|
|
150
156
|
{
|
|
@@ -156,7 +162,8 @@ export const crossPairs = (main, index) => {
|
|
|
156
162
|
{
|
|
157
163
|
if(typeof slow === 'number')
|
|
158
164
|
{
|
|
159
|
-
|
|
165
|
+
verticalOhlcvTempCols.add(slow.toString())
|
|
166
|
+
verticalOhlcv[slow.toString()] = new Int32Array(len).fill(slow)
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
if(fast !== 'price' && !verticalOhlcv.hasOwnProperty(fast)) throw Error(`fast "${fast} not found in crossPairs"`)
|
package/src/studies/lag.js
CHANGED
|
@@ -7,20 +7,23 @@ export const lag = (main, index) => {
|
|
|
7
7
|
if(index === 0)
|
|
8
8
|
{
|
|
9
9
|
const {inputParams} = main
|
|
10
|
-
const findParams = inputParams.filter(o => o.key === 'lag')
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
const params = []
|
|
13
12
|
|
|
14
|
-
const
|
|
13
|
+
for (const o of inputParams) {
|
|
14
|
+
if (o.key === 'lag') {
|
|
15
|
+
params.push(o.params)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
15
18
|
|
|
16
19
|
for (const [colKeys, lookback] of params)
|
|
17
20
|
{
|
|
18
21
|
for (const colKey of colKeys)
|
|
19
22
|
{
|
|
20
|
-
if(priceBased.
|
|
23
|
+
if(priceBased.has(colKey))
|
|
21
24
|
{
|
|
22
25
|
for (let step = 1; step <= lookback; step++) {
|
|
23
|
-
priceBased.
|
|
26
|
+
priceBased.add(`${colKey}_lag_${step}`)
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
}
|
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,6 +1,9 @@
|
|
|
1
|
-
export const validateInputParams =
|
|
1
|
+
export const validateInputParams = (inputParams, len) => {
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
if(!Array.isArray(inputParams))
|
|
4
|
+
{
|
|
5
|
+
throw new Error('Property "inputParams" must be an "Array" or "Set".')
|
|
6
|
+
}
|
|
4
7
|
|
|
5
8
|
for (const { params } of inputParams) {
|
|
6
9
|
for (const v of params) {
|
|
@@ -89,3 +92,25 @@ export const validateNumber = (value, options, paramName, callerName) => {
|
|
|
89
92
|
return true
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
export const validateArrayOfRanges = (range, paramName, callerName) => {
|
|
96
|
+
|
|
97
|
+
validateArray(range, paramName, callerName)
|
|
98
|
+
|
|
99
|
+
if (range.length !== 2) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Invalid "${paramName}" array length: expected 2 items, but got ${range.length} in "${callerName}.${paramName}".`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const [min, max] = range
|
|
106
|
+
|
|
107
|
+
validateNumber(min, {min: -100, max, allowDecimals: false}, 'min', callerName)
|
|
108
|
+
validateNumber(max, {min: min, max: 100, allowDecimals: false}, 'max', callerName)
|
|
109
|
+
|
|
110
|
+
if(min === max)
|
|
111
|
+
{
|
|
112
|
+
throw new Error(`Invalid "min" can not be equal to "max" property in "${callerName}.${paramName}".`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true
|
|
116
|
+
}
|