ohlcv-indicators 3.5.52 → 3.5.54
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 +48 -65
- package/package.json +1 -1
- package/src/core-functions/mainLoop.js +8 -3
- package/src/core-functions/pushToMain.js +1 -2
- package/src/machine-learning/classifier.js +56 -3
- package/src/moving-averages/bollingerBands.js +11 -8
- package/src/moving-averages/donchianChannel.js +8 -6
- package/src/moving-averages/heikenAshi.js +117 -93
- package/src/moving-averages/macd.js +5 -1
- package/src/moving-averages/movingAverages.js +5 -1
- package/src/moving-averages/vidya.js +7 -1
- package/src/oscillators/volumeDelta.js +74 -0
- package/src/studies/findCrosses.js +31 -5
- package/src/studies/lag.js +17 -12
- package/src/utilities/assignTypes.js +4 -6
- package/src/utilities/numberUtilities.js +22 -39
- package/src/utilities/precisionMultiplier.js +91 -16
- package/src/utilities/validators.js +1 -1
- package/src/utilities/verticalToHorizontal.js +16 -3
- package/src/studies/correlation.js +0 -98
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { mainLoop } from './src/core-functions/mainLoop.js'
|
|
2
|
-
import { correlation } from './src/studies/correlation.js'
|
|
3
2
|
import {
|
|
4
3
|
isAlreadyComputed,
|
|
5
4
|
validateArray,
|
|
@@ -14,10 +13,10 @@ import {
|
|
|
14
13
|
import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
|
|
15
14
|
import { pushToMain } from './src/core-functions/pushToMain.js'
|
|
16
15
|
import { assignTypes } from './src/utilities/assignTypes.js'
|
|
17
|
-
import { buildArray, getArrayType } from './src/utilities/assignTypes.js'
|
|
18
16
|
import { dateOutputFormaters } from './src/utilities/dateUtilities.js'
|
|
19
17
|
import { defaultMapColsCallback } from './src/studies/mapCols.js'
|
|
20
18
|
import { getOrderFromArray } from './src/utilities/order.js'
|
|
19
|
+
import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
|
|
21
20
|
|
|
22
21
|
import {
|
|
23
22
|
defaultYCallback,
|
|
@@ -38,24 +37,32 @@ import { normalizeMinMax } from './src/machine-learning/ml-utilities.js'
|
|
|
38
37
|
*/
|
|
39
38
|
|
|
40
39
|
export default class OHLCV_INDICATORS {
|
|
41
|
-
constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, ML = {}}) {
|
|
40
|
+
constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, ML = {}, precision = false}) {
|
|
42
41
|
|
|
43
42
|
validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
|
|
44
43
|
if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
|
|
45
44
|
|
|
46
45
|
validateNumber(chunkProcess, {min: 100, max: 50000, allowDecimals: false}, 'chunkProcess', 'constructor')
|
|
47
46
|
validateObject(ML, 'ML', 'constructor')
|
|
47
|
+
validateBoolean(precision, 'precision', 'contructor')
|
|
48
48
|
|
|
49
49
|
this.chunkProcess = chunkProcess
|
|
50
50
|
|
|
51
51
|
this.input = [...input]
|
|
52
52
|
this.len = this.input.length
|
|
53
53
|
this.firstRow = this.input[0]
|
|
54
|
-
|
|
54
|
+
this.precision = precision
|
|
55
|
+
|
|
56
|
+
const initialPriceBasedArr = ['open', 'high', 'low', 'close']
|
|
57
|
+
this.initialPriceBased = new Set(initialPriceBasedArr)
|
|
58
|
+
this.priceBased = new Set(initialPriceBasedArr)
|
|
59
|
+
this.precisionMultiplier = calcPrecisionMultiplier(this)
|
|
60
|
+
|
|
55
61
|
const {inputTypes, arrayTypes} = assignTypes(this)
|
|
56
62
|
|
|
57
63
|
this.inputTypes = inputTypes
|
|
58
64
|
this.arrayTypes = arrayTypes
|
|
65
|
+
|
|
59
66
|
if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
|
|
60
67
|
|
|
61
68
|
this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
|
|
@@ -65,10 +72,7 @@ export default class OHLCV_INDICATORS {
|
|
|
65
72
|
this.verticalOhlcv = {}
|
|
66
73
|
this.verticalOhlcvKeyNames = []
|
|
67
74
|
this.verticalOhlcvTempCols = new Set()
|
|
68
|
-
|
|
69
|
-
this.utilities = {
|
|
70
|
-
correlation
|
|
71
|
-
}
|
|
75
|
+
this.utilities = {}
|
|
72
76
|
|
|
73
77
|
this.invalidValueIndex = -1
|
|
74
78
|
this.scaledGroups = {}
|
|
@@ -105,50 +109,6 @@ export default class OHLCV_INDICATORS {
|
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
|
|
108
|
-
getDataAsCols(options = {}) {
|
|
109
|
-
this.compute();
|
|
110
|
-
|
|
111
|
-
const {skipNull = true, dateFormat = 'string'} = options
|
|
112
|
-
|
|
113
|
-
validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getDataAsCols')
|
|
114
|
-
validateObject(options, 'options', 'getDataAsCols')
|
|
115
|
-
|
|
116
|
-
const {
|
|
117
|
-
len,
|
|
118
|
-
invalidValueIndex,
|
|
119
|
-
verticalOhlcv,
|
|
120
|
-
verticalOhlcvTempCols,
|
|
121
|
-
} = this
|
|
122
|
-
const result = {}
|
|
123
|
-
|
|
124
|
-
const startIndex = skipNull ? invalidValueIndex + 1 : 0
|
|
125
|
-
const newLen = len - startIndex
|
|
126
|
-
|
|
127
|
-
for (const [key, arr] of Object.entries(verticalOhlcv)) {
|
|
128
|
-
|
|
129
|
-
if(verticalOhlcvTempCols.has(key)) continue
|
|
130
|
-
|
|
131
|
-
const thisArrType = getArrayType(key, verticalOhlcv[key])
|
|
132
|
-
|
|
133
|
-
result[key] = buildArray(thisArrType, newLen)
|
|
134
|
-
|
|
135
|
-
for (let x = startIndex; x < len; x++)
|
|
136
|
-
{
|
|
137
|
-
if(key === 'date')
|
|
138
|
-
{
|
|
139
|
-
result[key][x] = dateOutputFormaters[dateFormat](arr[x])
|
|
140
|
-
}
|
|
141
|
-
else{
|
|
142
|
-
result[key][x] = arr[x]
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
112
|
getData(options = {}) {
|
|
153
113
|
|
|
154
114
|
//getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
|
|
@@ -223,9 +183,9 @@ export default class OHLCV_INDICATORS {
|
|
|
223
183
|
validateArray(arr, 'arr', methodName)
|
|
224
184
|
validateObject(options, 'options', methodName)
|
|
225
185
|
|
|
226
|
-
const {limit =
|
|
186
|
+
const {limit = null, oneHot = false} = options
|
|
227
187
|
|
|
228
|
-
validateNumber(limit, {min: 2, max: this.len}, 'options.limit', methodName)
|
|
188
|
+
if(limit !== null) validateNumber(limit, {min: 2, max: this.len}, 'options.limit', methodName)
|
|
229
189
|
validateBoolean(oneHot, 'options.oneHot', methodName)
|
|
230
190
|
|
|
231
191
|
const orderArr = []
|
|
@@ -292,6 +252,27 @@ export default class OHLCV_INDICATORS {
|
|
|
292
252
|
return this
|
|
293
253
|
}
|
|
294
254
|
|
|
255
|
+
volumeDelta(options = {}) {
|
|
256
|
+
|
|
257
|
+
const methodName = 'volumeDelta'
|
|
258
|
+
|
|
259
|
+
if(!this.inputTypes.hasOwnProperty('volume')) {
|
|
260
|
+
throw new Error('If "volumeDelta" is called the input ohlcv must contain valid volume properties.')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
isAlreadyComputed(this)
|
|
264
|
+
|
|
265
|
+
validateObject(options, 'options', methodName)
|
|
266
|
+
|
|
267
|
+
const { lag = 0} = options;
|
|
268
|
+
|
|
269
|
+
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
270
|
+
|
|
271
|
+
this.inputParams.push({key: methodName, order: 0, params: [{lag}]})
|
|
272
|
+
|
|
273
|
+
return this
|
|
274
|
+
}
|
|
275
|
+
|
|
295
276
|
atr(size = 14, options = {}) {
|
|
296
277
|
|
|
297
278
|
const methodName = 'atr'
|
|
@@ -355,13 +336,18 @@ export default class OHLCV_INDICATORS {
|
|
|
355
336
|
return this
|
|
356
337
|
}
|
|
357
338
|
|
|
358
|
-
heikenAshi(smoothLength =
|
|
339
|
+
heikenAshi(smoothLength = null, afterSmoothLength = null, options = {}) {
|
|
359
340
|
const methodName = 'heikenAshi'
|
|
360
341
|
|
|
361
342
|
isAlreadyComputed(this)
|
|
362
343
|
|
|
363
|
-
|
|
364
|
-
|
|
344
|
+
const bothNull = smoothLength === null && afterSmoothLength === null;
|
|
345
|
+
|
|
346
|
+
if(bothNull === false) {
|
|
347
|
+
validateNumber(smoothLength, {min: 1, max: this.len, allowDecimals: false}, 'smoothLength', methodName)
|
|
348
|
+
validateNumber(afterSmoothLength, {min: 1, max: this.len, allowDecimals: false}, 'afterSmoothLength', methodName)
|
|
349
|
+
}
|
|
350
|
+
|
|
365
351
|
validateObject(options, 'options', methodName)
|
|
366
352
|
|
|
367
353
|
const {lag = 0} = options
|
|
@@ -370,7 +356,7 @@ export default class OHLCV_INDICATORS {
|
|
|
370
356
|
|
|
371
357
|
const order = 0
|
|
372
358
|
|
|
373
|
-
this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag}]})
|
|
359
|
+
this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull}]})
|
|
374
360
|
|
|
375
361
|
return this
|
|
376
362
|
}
|
|
@@ -474,17 +460,16 @@ export default class OHLCV_INDICATORS {
|
|
|
474
460
|
validateNumber(stdDev, {min: 0.01, max: 50, allowDecimals: true}, 'stdDev', methodName)
|
|
475
461
|
validateObject(options, 'options', methodName)
|
|
476
462
|
|
|
477
|
-
const {target = 'close', height = false, range = [], lag = 0
|
|
463
|
+
const {target = 'close', height = false, range = [], lag = 0} = options
|
|
478
464
|
|
|
479
465
|
validateString(target, 'options.target', methodName)
|
|
480
466
|
validateArray(range, 'options.range', methodName)
|
|
481
467
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
482
468
|
validateBoolean(height, 'options.height', methodName)
|
|
483
|
-
if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
|
|
484
469
|
|
|
485
470
|
const order = getOrderFromArray([target], methodName)
|
|
486
471
|
|
|
487
|
-
this.inputParams.push({key: methodName, order, params: [size, stdDev, {target, height, range, lag
|
|
472
|
+
this.inputParams.push({key: methodName, order, params: [size, stdDev, {target, height, range, lag}]});
|
|
488
473
|
|
|
489
474
|
return this;
|
|
490
475
|
}
|
|
@@ -532,19 +517,17 @@ export default class OHLCV_INDICATORS {
|
|
|
532
517
|
|
|
533
518
|
isAlreadyComputed(this)
|
|
534
519
|
|
|
535
|
-
|
|
536
520
|
validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
|
|
537
521
|
validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
|
|
538
522
|
|
|
539
523
|
validateObject(options, 'options', methodName)
|
|
540
|
-
const { height = false, range = [], lag = 0
|
|
524
|
+
const { height = false, range = [], lag = 0} = options;
|
|
541
525
|
|
|
542
526
|
validateArray(range, 'options.range', methodName)
|
|
543
527
|
validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
|
|
544
528
|
validateBoolean(height, 'options.height', methodName)
|
|
545
|
-
if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
|
|
546
529
|
|
|
547
|
-
this.inputParams.push({ key: methodName, order: 0, params: [size, offset, { height, range, lag
|
|
530
|
+
this.inputParams.push({ key: methodName, order: 0, params: [size, offset, { height, range, lag}] });
|
|
548
531
|
|
|
549
532
|
return this;
|
|
550
533
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.54",
|
|
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",
|
|
@@ -10,6 +10,7 @@ import { relativeVolume } from "../moving-averages/relativeVolume.js";
|
|
|
10
10
|
import { donchianChannels } from "../moving-averages/donchianChannel.js";
|
|
11
11
|
import { bollingerBands } from "../moving-averages/bollingerBands.js";
|
|
12
12
|
import { volumeOscillator } from "../oscillators/volumeOscillator.js";
|
|
13
|
+
import { volumeDelta } from "../oscillators/volumeDelta.js";
|
|
13
14
|
import { lag } from "../studies/lag.js";
|
|
14
15
|
import { crossPairs } from "../studies/findCrosses.js";
|
|
15
16
|
import { dateTime } from "../studies/dateTime.js";
|
|
@@ -18,7 +19,7 @@ import { atr } from "../volatility/atr.js";
|
|
|
18
19
|
import { mapCols } from "../studies/mapCols.js";
|
|
19
20
|
|
|
20
21
|
import { buildArray } from "../utilities/assignTypes.js";
|
|
21
|
-
import {
|
|
22
|
+
import { inputNumberFormatter } from "../utilities/numberUtilities.js";
|
|
22
23
|
import { dateFormaters } from "../utilities/dateUtilities.js";
|
|
23
24
|
|
|
24
25
|
import { pca } from "../machine-learning/pca.js";
|
|
@@ -36,6 +37,7 @@ const mainFunctions = {
|
|
|
36
37
|
sma: movingAverages,
|
|
37
38
|
vidya,
|
|
38
39
|
macd,
|
|
40
|
+
volumeDelta,
|
|
39
41
|
relativeVolume,
|
|
40
42
|
donchianChannels,
|
|
41
43
|
bollingerBands,
|
|
@@ -55,6 +57,7 @@ export const mainLoop = (input, main) => {
|
|
|
55
57
|
inputTypes,
|
|
56
58
|
chunkProcess,
|
|
57
59
|
processSecondaryLoop,
|
|
60
|
+
precisionMultiplier
|
|
58
61
|
} = main;
|
|
59
62
|
|
|
60
63
|
validateInputParams(inputParams, len)
|
|
@@ -75,9 +78,11 @@ export const mainLoop = (input, main) => {
|
|
|
75
78
|
{
|
|
76
79
|
const value = curr[key]
|
|
77
80
|
|
|
78
|
-
if(
|
|
81
|
+
if(inputNumberFormatter.hasOwnProperty(formaterKey))
|
|
79
82
|
{
|
|
80
|
-
|
|
83
|
+
if(typeof value === 'undefined') console.log({index, formaterKey, key})
|
|
84
|
+
|
|
85
|
+
const formatedValue = inputNumberFormatter[formaterKey](value, precisionMultiplier)
|
|
81
86
|
|
|
82
87
|
main.pushToMain({index, key, value: formatedValue})
|
|
83
88
|
}
|
|
@@ -25,8 +25,7 @@ export const areKeyValuesValid = (main, index, keyNames) => {
|
|
|
25
25
|
|
|
26
26
|
const val = verticalOhlcv[key][index]
|
|
27
27
|
|
|
28
|
-
if(
|
|
29
|
-
{
|
|
28
|
+
if (val == null || Number.isNaN(val)) {
|
|
30
29
|
output = false
|
|
31
30
|
break
|
|
32
31
|
}
|
|
@@ -5,6 +5,51 @@ import { countUniqueLabels, oversampleXY } from "./balancing.js"
|
|
|
5
5
|
|
|
6
6
|
const algo = 'classifier'
|
|
7
7
|
|
|
8
|
+
class Store {
|
|
9
|
+
constructor(args = {}) {
|
|
10
|
+
Object.entries(args).forEach(([k, v]) => { this[k] = v })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// internal helpers
|
|
14
|
+
_unwrap = v => Array.isArray(v) ? v[0] : v
|
|
15
|
+
_num = (v, def = 0) => {
|
|
16
|
+
const n = Number(this._unwrap(v))
|
|
17
|
+
return Number.isFinite(n) ? n : def
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// set: supports value OR updater function
|
|
21
|
+
// if you pass a function, we feed it the current *numeric* value
|
|
22
|
+
set = (key, valueOrFn) => {
|
|
23
|
+
if (typeof valueOrFn === 'function') {
|
|
24
|
+
const next = valueOrFn(this._num(this[key], 0))
|
|
25
|
+
this[key] = next // <- change to [next] if you want to keep the array wrapper
|
|
26
|
+
return next
|
|
27
|
+
} else {
|
|
28
|
+
this[key] = valueOrFn // <- change to [valueOrFn] if you want to keep the array wrapper
|
|
29
|
+
return valueOrFn
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// convenience numerics
|
|
34
|
+
inc = (key, delta = 1) => this.set(key, v => v + delta)
|
|
35
|
+
add = (key, delta = 0) => this.inc(key, delta) // alias
|
|
36
|
+
getNum = (key, def = 0) => this._num(this[key], def)
|
|
37
|
+
|
|
38
|
+
get = (key) => this._unwrap(this[key])
|
|
39
|
+
getAll = () => {
|
|
40
|
+
|
|
41
|
+
const output = {}
|
|
42
|
+
|
|
43
|
+
for(const [key, valueOrFn] of Object.entries(this)) {
|
|
44
|
+
if(typeof valueOrFn === 'function') continue
|
|
45
|
+
output[key] = valueOrFn
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
return {...output}
|
|
50
|
+
} // shallow clone of data-only fields
|
|
51
|
+
}
|
|
52
|
+
|
|
8
53
|
export const classifier = (
|
|
9
54
|
main,
|
|
10
55
|
index,
|
|
@@ -39,7 +84,15 @@ export const classifier = (
|
|
|
39
84
|
const expectedLoops = (flatY) ? predictions : 1 //
|
|
40
85
|
|
|
41
86
|
instances[algo][prefix] = {
|
|
42
|
-
|
|
87
|
+
store: new Store({
|
|
88
|
+
strategy: null,
|
|
89
|
+
total: 0,
|
|
90
|
+
win: 0,
|
|
91
|
+
loss: 0,
|
|
92
|
+
strategyGainLoss: 0,
|
|
93
|
+
idx: 0,
|
|
94
|
+
initialBalance: verticalOhlcv.close[index]
|
|
95
|
+
}),
|
|
43
96
|
expectedLoops,
|
|
44
97
|
isTrained: new Array(expectedLoops).fill(false),
|
|
45
98
|
uniqueLabels: new Array(expectedLoops).fill(0),
|
|
@@ -66,7 +119,7 @@ export const classifier = (
|
|
|
66
119
|
{
|
|
67
120
|
return
|
|
68
121
|
}
|
|
69
|
-
else if(filterCallback(verticalOhlcv, index) === false)
|
|
122
|
+
else if(filterCallback(verticalOhlcv, index, instances[algo][prefix].store) === false)
|
|
70
123
|
{
|
|
71
124
|
return
|
|
72
125
|
}
|
|
@@ -138,7 +191,7 @@ export const classifier = (
|
|
|
138
191
|
if(!trainX) return
|
|
139
192
|
|
|
140
193
|
//if univariable Y (flatY) a model is created for each prediction
|
|
141
|
-
const trainY = yCallback(index, verticalOhlcv, horizon)
|
|
194
|
+
const trainY = yCallback(index, verticalOhlcv, horizon, instances[algo][prefix].store)
|
|
142
195
|
|
|
143
196
|
if (trainY !== null && (!Array.isArray(trainY) || trainY.length !== predictions)) {
|
|
144
197
|
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { FasterBollingerBands } from 'trading-signals'
|
|
2
|
-
import { roundDecimalPlaces } from '../utilities/numberUtilities.js';
|
|
3
|
-
|
|
4
2
|
const defaultTarget = 'close'
|
|
5
3
|
|
|
6
|
-
export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, lag
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, lag }) => {
|
|
9
5
|
|
|
10
6
|
const { verticalOhlcv, instances } = main;
|
|
11
7
|
const indicatorKey = `${size}_${stdDev}`;
|
|
@@ -14,7 +10,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
14
10
|
// Initialization on the first call.
|
|
15
11
|
if (index === 0) {
|
|
16
12
|
|
|
17
|
-
const {inputParams, verticalOhlcv, len } = main;
|
|
13
|
+
const {inputParams, verticalOhlcv, len, priceBased } = main;
|
|
18
14
|
|
|
19
15
|
if (!(target in verticalOhlcv)) {
|
|
20
16
|
throw new Error(`bollingerBands could not find target "${target}"`);
|
|
@@ -62,6 +58,13 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
62
58
|
|
|
63
59
|
}
|
|
64
60
|
|
|
61
|
+
if(priceBased.has(target)) {
|
|
62
|
+
for(const k of keyNames) {
|
|
63
|
+
priceBased.add(k)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
65
68
|
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, new Float64Array(len).fill(NaN)]))
|
|
66
69
|
Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
|
|
67
70
|
|
|
@@ -106,7 +109,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
106
109
|
if (!Number.isNaN(lower) && !Number.isNaN(upper)) {
|
|
107
110
|
heightValue = ((upper - lower) / lower)
|
|
108
111
|
}
|
|
109
|
-
main.pushToMain({ index, key: `${prefix}_height${suffix}`, value:
|
|
112
|
+
main.pushToMain({ index, key: `${prefix}_height${suffix}`, value: heightValue });
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
// Process each range property.
|
|
@@ -116,7 +119,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
116
119
|
if (!Number.isNaN(priceValue) && !Number.isNaN(lower) && !Number.isNaN(upper)) {
|
|
117
120
|
rangeValue = (priceValue - lower) / (upper - lower)
|
|
118
121
|
}
|
|
119
|
-
main.pushToMain({ index, key: `${prefix}_range_${rangeKey}${suffix}`, value:
|
|
122
|
+
main.pushToMain({ index, key: `${prefix}_range_${rangeKey}${suffix}`, value: rangeValue });
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
return true;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
import { roundDecimalPlaces } from "../utilities/numberUtilities.js";
|
|
3
|
-
|
|
4
2
|
export const donchianChannels = (main, index, size, offset, options) => {
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
|
|
8
|
-
const {verticalOhlcv, instances, len, inputParams, lag} = main
|
|
9
|
-
const { height: includeHeight, range: rangeKeys
|
|
6
|
+
const {verticalOhlcv, instances, len, inputParams, lag, priceBased} = main
|
|
7
|
+
const { height: includeHeight, range: rangeKeys } = options
|
|
10
8
|
const indicatorKey = `${size}_${offset}`
|
|
11
9
|
|
|
12
10
|
// ---- INIT (only at first bar) ----
|
|
@@ -30,6 +28,10 @@ export const donchianChannels = (main, index, size, offset, options) => {
|
|
|
30
28
|
keys.push(`${prefix}_range_${rk}`);
|
|
31
29
|
}
|
|
32
30
|
|
|
31
|
+
for(const k of keys) {
|
|
32
|
+
priceBased.add(k)
|
|
33
|
+
}
|
|
34
|
+
|
|
33
35
|
// bootstrap instance
|
|
34
36
|
if (!instances.donchian_channel) {
|
|
35
37
|
instances.donchian_channel = { numberOfIndicators, settings: {} };
|
|
@@ -96,7 +98,7 @@ export const donchianChannels = (main, index, size, offset, options) => {
|
|
|
96
98
|
const heightVal = hasBounds && lower
|
|
97
99
|
? (upper - lower) / lower
|
|
98
100
|
: NaN;
|
|
99
|
-
main.pushToMain({ index, key: `${prefix}_height`, value:
|
|
101
|
+
main.pushToMain({ index, key: `${prefix}_height`, value: heightVal });
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
// precompute spread once
|
|
@@ -111,7 +113,7 @@ export const donchianChannels = (main, index, size, offset, options) => {
|
|
|
111
113
|
const val = hasBounds && price === price && spread
|
|
112
114
|
? (price - lower) / spread
|
|
113
115
|
: NaN;
|
|
114
|
-
main.pushToMain({ index, key: rangeOut[i], value:
|
|
116
|
+
main.pushToMain({ index, key: rangeOut[i], value: val });
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
return true;
|