ohlcv-indicators 3.1.8 → 3.2.0
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 +3901 -1
- package/index.js +47 -68
- package/package.json +2 -1
- package/src/moving-averages/bollingerBands.js +12 -28
- package/src/moving-averages/donchianChannel.js +12 -3
- package/src/moving-averages/movingAverages.js +14 -4
- package/src/oscillators/rsi.js +8 -1
- package/src/studies/{candleStudies.js → candleVectors.js} +16 -4
- package/src/utilities/classification.js +9 -15
- package/src/utilities/parsing-utilities.js +2 -2
- package/src/utilities/pushToMain.js +61 -0
- package/webpack.config.js +15 -0
package/index.js
CHANGED
|
@@ -5,6 +5,16 @@ import { isAlreadyComputed } from './src/utilities/validators.js'
|
|
|
5
5
|
import { divideByMultiplier } from './src/utilities/numberUtilities.js'
|
|
6
6
|
import { getMovingAveragesParams } from './src/moving-averages/movingAverages.js'
|
|
7
7
|
import { verticalToHorizontal } from './src/utilities/dataParsingUtilities.js'
|
|
8
|
+
import { pushToMain } from './src/utilities/pushToMain.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Class OHLCV_INDICATORS
|
|
12
|
+
*
|
|
13
|
+
* This class provides methods for calculating and managing technical indicators
|
|
14
|
+
* on financial OHLCV (Open, High, Low, Close, Volume) data. It enables users
|
|
15
|
+
* to parallel compute various technical indicators in 1 single loop.
|
|
16
|
+
* OHLCV datasets.
|
|
17
|
+
*/
|
|
8
18
|
|
|
9
19
|
const validMagnitudeValues = [0.001, 0.002, 0.0025, 0.005, 0.01, 0.02, 0.025, 0.05, 0.1, 0.20, 0.25, 0.5, 1, 2, 2.5, 5, 10]
|
|
10
20
|
|
|
@@ -27,8 +37,6 @@ export default class OHLCV_INDICATORS {
|
|
|
27
37
|
this.crossPairsList = []
|
|
28
38
|
this.verticalOhlcv = {}
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
40
|
this.studies = {}
|
|
33
41
|
this.utilities = {
|
|
34
42
|
correlation
|
|
@@ -39,6 +47,7 @@ export default class OHLCV_INDICATORS {
|
|
|
39
47
|
this.precision = precision
|
|
40
48
|
this.precisionMultiplier = (this.precision === true) ? 0 : 1
|
|
41
49
|
|
|
50
|
+
this.autoMinMaxKeys = []
|
|
42
51
|
this.minMaxRanges = {}
|
|
43
52
|
|
|
44
53
|
|
|
@@ -51,60 +60,13 @@ export default class OHLCV_INDICATORS {
|
|
|
51
60
|
this.inputParams = []
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
pushToMain({index, key, value})
|
|
58
|
-
{
|
|
59
|
-
if(value === null || typeof value === 'undefined')
|
|
60
|
-
{
|
|
61
|
-
this.invalidValueIndex = index
|
|
62
|
-
|
|
63
|
-
for(let k in this.minMaxRanges)
|
|
64
|
-
{
|
|
65
|
-
this.minMaxRanges[k] = {min: Infinity, max: -Infinity}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
63
|
+
this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
|
|
68
64
|
|
|
69
|
-
this.verticalOhlcv[key][index] = value
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
if(key.includes('_diff_'))
|
|
74
|
-
{
|
|
75
|
-
if(!this.minMaxRanges.hasOwnProperty(key) || (this.minMaxRanges[key].min === Infinity || this.minMaxRanges[key].max === -Infinity))
|
|
76
|
-
{
|
|
77
|
-
this.minMaxRanges[key] = { min: -1, max: 1}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else if(key.startsWith('candle_'))
|
|
81
|
-
{
|
|
82
|
-
this.minMaxRanges[key] = { min: -1, max: 1}
|
|
83
|
-
}
|
|
84
|
-
else if(key.includes('_range_') && (key.startsWith('bollinger_bands_') || key.startsWith('donchian_channel_')))
|
|
85
|
-
{
|
|
86
|
-
this.minMaxRanges[key] = { min: 0, max: 1 }
|
|
87
|
-
}
|
|
88
|
-
else if(!key.includes('_x_')) {
|
|
89
|
-
const newValue = (this.precision && this.priceBased.includes(key)) ? (value / this.precisionMultiplier) : value
|
|
90
|
-
|
|
91
|
-
if(!this.minMaxRanges.hasOwnProperty(key))
|
|
92
|
-
{
|
|
93
|
-
this.minMaxRanges[key] = { min: Infinity, max: -Infinity }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if(newValue < this.minMaxRanges[key].min)
|
|
97
|
-
{
|
|
98
|
-
this.minMaxRanges[key].min = newValue
|
|
99
|
-
}
|
|
100
|
-
if(newValue > this.minMaxRanges[key].max)
|
|
101
|
-
{
|
|
102
|
-
this.minMaxRanges[key].max = newValue
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
66
|
+
return this
|
|
106
67
|
}
|
|
107
68
|
|
|
69
|
+
|
|
108
70
|
getDataAsCols(skipNull = true) {
|
|
109
71
|
this.compute();
|
|
110
72
|
|
|
@@ -350,7 +312,7 @@ export default class OHLCV_INDICATORS {
|
|
|
350
312
|
throw new Error('"options" must be an object in bollingerBands. eg: {target, height, range}');
|
|
351
313
|
}
|
|
352
314
|
|
|
353
|
-
const {target = 'close', height = false, range = [],
|
|
315
|
+
const {target = 'close', height = false, range = [], scale = null, lag = 0, autoMinMax} = options
|
|
354
316
|
|
|
355
317
|
// Validate size and times
|
|
356
318
|
if (typeof size !== 'number' || size <= 0) {
|
|
@@ -362,13 +324,16 @@ export default class OHLCV_INDICATORS {
|
|
|
362
324
|
if (!Array.isArray(range)) {
|
|
363
325
|
throw new Error('If set, "range" must be a array of column names in bollingerBands.');
|
|
364
326
|
}
|
|
365
|
-
if (!Array.isArray(zScore)) {
|
|
366
|
-
throw new Error('If set, "zScore" must be a array of column names in bollingerBands.');
|
|
367
|
-
}
|
|
368
327
|
if (typeof lag !== 'number') {
|
|
369
328
|
|
|
370
329
|
throw new Error(`"lag" value in rsi must be a number in bollingerBands.`);
|
|
371
330
|
}
|
|
331
|
+
|
|
332
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
333
|
+
|
|
334
|
+
throw new Error(`"autoMinMax" value in rsi must be a number in bollingerBands.`);
|
|
335
|
+
}
|
|
336
|
+
|
|
372
337
|
if (typeof height !== 'boolean') {
|
|
373
338
|
throw new Error('"height" must be a boolean in bollingerBands.');
|
|
374
339
|
}
|
|
@@ -380,7 +345,7 @@ export default class OHLCV_INDICATORS {
|
|
|
380
345
|
}
|
|
381
346
|
}
|
|
382
347
|
|
|
383
|
-
this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range,
|
|
348
|
+
this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range, lag, autoMinMax}]});
|
|
384
349
|
|
|
385
350
|
return this;
|
|
386
351
|
}
|
|
@@ -394,7 +359,7 @@ export default class OHLCV_INDICATORS {
|
|
|
394
359
|
throw new Error('"size" must be a positive number in rsi.');
|
|
395
360
|
}
|
|
396
361
|
|
|
397
|
-
const {scale = null, target = 'close', lag = 0} = options
|
|
362
|
+
const {scale = null, target = 'close', lag = 0, autoMinMax = false} = options
|
|
398
363
|
|
|
399
364
|
if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
|
|
400
365
|
|
|
@@ -406,7 +371,12 @@ export default class OHLCV_INDICATORS {
|
|
|
406
371
|
throw new Error(`"lag" value in rsi must be a number in rsi.`);
|
|
407
372
|
}
|
|
408
373
|
|
|
409
|
-
|
|
374
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
375
|
+
|
|
376
|
+
throw new Error(`"autoMinMax" value in rsi must be a number in rsi.`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this.inputParams.push({key: 'rsi', params: [size, {scale, target, lag, autoMinMax}]})
|
|
410
380
|
|
|
411
381
|
return this
|
|
412
382
|
}
|
|
@@ -421,7 +391,7 @@ export default class OHLCV_INDICATORS {
|
|
|
421
391
|
throw new Error('"offset" must be a number greater than or equal to 0 in donchianChannels.');
|
|
422
392
|
}
|
|
423
393
|
|
|
424
|
-
const { height = false, range = [], scale = null, lag = 0} = options;
|
|
394
|
+
const { height = false, range = [], scale = null, lag = 0, autoMinMax = false} = options;
|
|
425
395
|
|
|
426
396
|
if (!Array.isArray(range)) {
|
|
427
397
|
throw new Error('If set, "range" must be an array of column names in donchianChannels.');
|
|
@@ -430,6 +400,11 @@ export default class OHLCV_INDICATORS {
|
|
|
430
400
|
if (typeof lag !== 'number') {
|
|
431
401
|
throw new Error(`"lag" value in rsi must be a number in donchianChannels.`);
|
|
432
402
|
}
|
|
403
|
+
|
|
404
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
405
|
+
|
|
406
|
+
throw new Error(`"autoMinMax" value in rsi must be a number in donchianChannels.`);
|
|
407
|
+
}
|
|
433
408
|
|
|
434
409
|
if (typeof height !== 'boolean') {
|
|
435
410
|
throw new Error('"height" must be a boolean in donchianChannels.');
|
|
@@ -443,37 +418,41 @@ export default class OHLCV_INDICATORS {
|
|
|
443
418
|
}
|
|
444
419
|
}
|
|
445
420
|
|
|
446
|
-
this.inputParams.push({ key: 'donchianChannels', params: [size, offset, { height, range, scale, lag }] });
|
|
421
|
+
this.inputParams.push({ key: 'donchianChannels', params: [size, offset, { height, range, scale, lag, autoMinMax }] });
|
|
447
422
|
|
|
448
423
|
return this;
|
|
449
424
|
}
|
|
450
425
|
|
|
451
426
|
|
|
452
|
-
|
|
427
|
+
candleVectors(size = 20, options = {}) {
|
|
453
428
|
isAlreadyComputed(this);
|
|
454
429
|
|
|
455
430
|
if (typeof size !== 'number' || size <= 0) {
|
|
456
|
-
throw new Error('"size" must be a positive number greater than 0 in
|
|
431
|
+
throw new Error('"size" must be a positive number greater than 0 in candleVectors.');
|
|
457
432
|
}
|
|
458
433
|
|
|
459
|
-
const { stdDev = 0.5, lag = 0, scale = 0.05, patternSize = 1 } = options;
|
|
434
|
+
const { stdDev = 0.5, lag = 0, scale = 0.05, patternSize = 1, autoMinMax = false } = options;
|
|
460
435
|
|
|
461
436
|
|
|
462
437
|
if (typeof stdDev !== 'number' || stdDev <= 0) {
|
|
463
|
-
throw new Error('"stdDev" must be a positive number greater than 0 in
|
|
438
|
+
throw new Error('"stdDev" must be a positive number greater than 0 in candleVectors.');
|
|
464
439
|
}
|
|
465
440
|
|
|
466
441
|
if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
|
|
467
442
|
|
|
468
|
-
throw new Error(`"scale" value in
|
|
443
|
+
throw new Error(`"scale" value in candleVectors must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
|
|
469
444
|
}
|
|
470
445
|
|
|
471
446
|
if (typeof patternSize !== 'number' || !Number.isInteger(patternSize) || patternSize < 1 ) {
|
|
472
447
|
|
|
473
|
-
throw new Error(`"patternSize" value in
|
|
448
|
+
throw new Error(`"patternSize" value in candleVectors must be an integer greater than 0.`);
|
|
449
|
+
}
|
|
450
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
451
|
+
|
|
452
|
+
throw new Error(`"autoMinMax" value in candleVectors must be boolean`);
|
|
474
453
|
}
|
|
475
454
|
|
|
476
|
-
this.inputParams.push({ key: '
|
|
455
|
+
this.inputParams.push({ key: 'candleVectors', params: [size, {stdDev, patternSize, lag, scale, autoMinMax}] });
|
|
477
456
|
return this;
|
|
478
457
|
}
|
|
479
458
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
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",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"@babel/preset-env": "^7.25.3",
|
|
23
23
|
"babel-loader": "^9.1.3",
|
|
24
24
|
"dotenv": "^16.4.5",
|
|
25
|
+
"terser-webpack-plugin": "^5.3.14",
|
|
25
26
|
"webpack": "^5.93.0",
|
|
26
27
|
"webpack-cli": "^5.1.4"
|
|
27
28
|
}
|
|
@@ -3,7 +3,7 @@ import { calcMagnitude } from '../utilities/numberUtilities.js';
|
|
|
3
3
|
|
|
4
4
|
const defaultTarget = 'close'
|
|
5
5
|
|
|
6
|
-
export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
6
|
+
export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, scale, lag, autoMinMax }) => {
|
|
7
7
|
const { verticalOhlcv, instances, lastIndexReplace } = main;
|
|
8
8
|
const suffix = target === defaultTarget ? '' : `_${target}`;
|
|
9
9
|
const indicatorKey = `${size}_${stdDev}${suffix}`;
|
|
@@ -11,6 +11,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
11
11
|
|
|
12
12
|
// Initialization on the first call.
|
|
13
13
|
if (index === 0) {
|
|
14
|
+
|
|
14
15
|
const { priceBased, inputParams, nullArray, verticalOhlcv } = main;
|
|
15
16
|
|
|
16
17
|
if (!(target in verticalOhlcv)) {
|
|
@@ -49,14 +50,12 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
49
50
|
throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
50
51
|
}
|
|
51
52
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
52
|
-
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
throw new Error(`Invalid zScore item value "${zScoreKey}" for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
54
|
+
if(autoMinMax)
|
|
55
|
+
{
|
|
56
|
+
main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
|
|
58
57
|
}
|
|
59
|
-
|
|
58
|
+
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, [...nullArray]]))
|
|
@@ -104,7 +103,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
104
103
|
if (height) {
|
|
105
104
|
let heightValue = null;
|
|
106
105
|
if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
|
|
107
|
-
heightValue = calcMagnitude( ((upper - lower) / lower), 0.
|
|
106
|
+
heightValue = calcMagnitude( ((upper - lower) / lower), 0.001)
|
|
108
107
|
}
|
|
109
108
|
main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
|
|
110
109
|
}
|
|
@@ -114,7 +113,11 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
114
113
|
let rangeValue = null;
|
|
115
114
|
const priceValue = verticalOhlcv[rangeKey][index];
|
|
116
115
|
if (typeof priceValue === 'number' && typeof lower === 'number' && typeof upper === 'number' && (upper - lower) !== 0) {
|
|
117
|
-
rangeValue = (
|
|
116
|
+
rangeValue = (autoMinMax)
|
|
117
|
+
? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
|
|
118
|
+
: (priceValue - lower) / (upper - lower)
|
|
119
|
+
|
|
120
|
+
|
|
118
121
|
if (scale) {
|
|
119
122
|
rangeValue = calcMagnitude(rangeValue, scale);
|
|
120
123
|
}
|
|
@@ -122,24 +125,5 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
122
125
|
main.pushToMain({ index, key: `${subPrefix}_range_${rangeKey}`, value: rangeValue });
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
// Process each zScore property.
|
|
126
|
-
for (const zScoreKey of zScore) {
|
|
127
|
-
let zScoreValue = null;
|
|
128
|
-
const priceValue = verticalOhlcv[zScoreKey][index];
|
|
129
|
-
if (
|
|
130
|
-
typeof priceValue === 'number' &&
|
|
131
|
-
typeof middle === 'number' &&
|
|
132
|
-
typeof upper === 'number' &&
|
|
133
|
-
typeof lower === 'number' &&
|
|
134
|
-
(upper - lower) !== 0
|
|
135
|
-
) {
|
|
136
|
-
zScoreValue = (2 * stdDev * (priceValue - middle)) / (upper - lower);
|
|
137
|
-
if (scale) {
|
|
138
|
-
zScoreValue = calcMagnitude(zScoreValue, scale);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
main.pushToMain({ index, key: `${subPrefix}_zscore_${zScoreKey}`, value: zScoreValue });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
128
|
return true;
|
|
145
129
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { calcMagnitude } from "../utilities/numberUtilities.js"
|
|
2
2
|
|
|
3
|
-
export const donchianChannels = (main, index, size, offset, { height, range, scale, lag }) => {
|
|
3
|
+
export const donchianChannels = (main, index, size, offset, { height, range, scale, lag, autoMinMax }) => {
|
|
4
4
|
const indicatorKey = `${size}_${offset}`;
|
|
5
5
|
const { verticalOhlcv, instances, len } = main;
|
|
6
6
|
|
|
@@ -32,6 +32,12 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
32
32
|
throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
|
|
33
33
|
}
|
|
34
34
|
keyNames.push(`${prefix}_range_${rangeKey}`)
|
|
35
|
+
|
|
36
|
+
if(autoMinMax)
|
|
37
|
+
{
|
|
38
|
+
main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, [...nullArray]]))
|
|
@@ -110,7 +116,7 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
110
116
|
if (height) {
|
|
111
117
|
let heightValue = null;
|
|
112
118
|
if (typeof upper === 'number' && typeof lower === 'number' && lower !== 0) {
|
|
113
|
-
heightValue = calcMagnitude( ((upper - lower) / lower), 0.
|
|
119
|
+
heightValue = calcMagnitude( ((upper - lower) / lower), 0.001)
|
|
114
120
|
}
|
|
115
121
|
main.pushToMain({ index, key: `${subPrefix}_height`, value: heightValue });
|
|
116
122
|
}
|
|
@@ -125,7 +131,10 @@ export const donchianChannels = (main, index, size, offset, { height, range, sca
|
|
|
125
131
|
typeof lower === 'number' &&
|
|
126
132
|
(upper - lower) !== 0
|
|
127
133
|
) {
|
|
128
|
-
rangeValue = (
|
|
134
|
+
rangeValue = (autoMinMax)
|
|
135
|
+
? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
|
|
136
|
+
: (priceValue - lower) / (upper - lower)
|
|
137
|
+
|
|
129
138
|
if (scale) {
|
|
130
139
|
rangeValue = calcMagnitude(rangeValue, scale);
|
|
131
140
|
}
|
|
@@ -4,7 +4,7 @@ import { classifyBoll } from '../utilities/classification.js'
|
|
|
4
4
|
|
|
5
5
|
const indicatorClasses = {ema: FasterEMA, sma: FasterSMA}
|
|
6
6
|
|
|
7
|
-
export const movingAverages = (main, index, indicatorName, size, { target, diff }) => {
|
|
7
|
+
export const movingAverages = (main, index, indicatorName, size, { target, diff, autoMinMax }) => {
|
|
8
8
|
const { verticalOhlcv, instances, priceBased, lastIndexReplace } = main;
|
|
9
9
|
let suffix =
|
|
10
10
|
typeof target === 'string' &&
|
|
@@ -43,6 +43,11 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
|
|
|
43
43
|
for (const targetKey of diff.targets) {
|
|
44
44
|
verticalOhlcv[`${diffKeyName}_${targetKey}`] = [...nullArray];
|
|
45
45
|
|
|
46
|
+
if(diff.autoMinMax)
|
|
47
|
+
{
|
|
48
|
+
main.autoMinMaxKeys.push(`${diffKeyName}_${targetKey}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
if (diff.lag > 0) {
|
|
47
52
|
lagDiffArr.push(`${diffKeyName}_${targetKey}`);
|
|
48
53
|
}
|
|
@@ -93,7 +98,7 @@ export const movingAverages = (main, index, indicatorName, size, { target, diff
|
|
|
93
98
|
diffBoll = null;
|
|
94
99
|
}
|
|
95
100
|
|
|
96
|
-
const classified = classifyBoll(diffValue, diffBoll, diff.scale);
|
|
101
|
+
const classified = classifyBoll(diffValue, diffBoll, diff.scale, diff.autoMinMax);
|
|
97
102
|
main.pushToMain({
|
|
98
103
|
index,
|
|
99
104
|
key: `${diffKeyName}_${targetKey}`,
|
|
@@ -134,7 +139,8 @@ export const getMovingAveragesParams = (indicatorName, size, options, validMagni
|
|
|
134
139
|
scale = 0.05,
|
|
135
140
|
targets = ['close'],
|
|
136
141
|
size: diffSize = size,
|
|
137
|
-
lag = 0
|
|
142
|
+
lag = 0,
|
|
143
|
+
autoMinMax = false
|
|
138
144
|
} = diff;
|
|
139
145
|
|
|
140
146
|
if (typeof stdDev !== 'number' || stdDev <= 0) {
|
|
@@ -157,7 +163,11 @@ export const getMovingAveragesParams = (indicatorName, size, options, validMagni
|
|
|
157
163
|
throw new Error(`"diff.lag" must be an integer greater than 0 in ${indicatorName}.`);
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
|
|
166
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
167
|
+
throw new Error(`"diff.autoMinMax" must be an boolean in ${indicatorName}.`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
optionArgs.diff = { stdDev, scale, size: diffSize, targets, lag, autoMinMax};
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
return optionArgs;
|
package/src/oscillators/rsi.js
CHANGED
|
@@ -4,7 +4,7 @@ import { calcMagnitude } from '../utilities/numberUtilities.js';
|
|
|
4
4
|
|
|
5
5
|
const defaultTarget = 'close';
|
|
6
6
|
|
|
7
|
-
export const rsi = (main, index, size, { scale, target, lag }) => {
|
|
7
|
+
export const rsi = (main, index, size, { scale, target, lag, autoMinMax }) => {
|
|
8
8
|
const { verticalOhlcv, instances, lastIndexReplace } = main;
|
|
9
9
|
const suffix = target === defaultTarget ? '' : `_${target}`;
|
|
10
10
|
const rsiKey = `rsi_${size}${suffix}`;
|
|
@@ -12,6 +12,7 @@ export const rsi = (main, index, size, { scale, target, lag }) => {
|
|
|
12
12
|
|
|
13
13
|
// Initialization on the first index.
|
|
14
14
|
if (index === 0) {
|
|
15
|
+
|
|
15
16
|
const { crossPairsList, nullArray } = main;
|
|
16
17
|
|
|
17
18
|
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
@@ -34,6 +35,12 @@ export const rsi = (main, index, size, { scale, target, lag }) => {
|
|
|
34
35
|
{
|
|
35
36
|
main.lag([rsiKey, rsiSmaKey], lag)
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
if(autoMinMax)
|
|
40
|
+
{
|
|
41
|
+
main.autoMinMaxKeys.push(rsiKey, rsiSmaKey)
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
const value = verticalOhlcv[target][index];
|
|
@@ -7,13 +7,15 @@ const diff = (a, b) => (a - b) / b
|
|
|
7
7
|
// Las diferencias estarán basadas en (objetivo - lower) / (upper - lower). Esto arrojará un valor entre -1 y 1
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const candleVectors = (main, index, size, {stdDev, patternSize, lag, scale, autoMinMax}) => {
|
|
11
11
|
|
|
12
12
|
const { verticalOhlcv, instances, lastIndexReplace } = main
|
|
13
|
-
const calcSize = (di, bo) => classifyBoll(di, bo, scale)
|
|
13
|
+
const calcSize = (di, bo) => classifyBoll(di, bo, scale, autoMinMax)
|
|
14
14
|
|
|
15
15
|
if(index === 0)
|
|
16
16
|
{
|
|
17
|
+
|
|
18
|
+
console.log({autoMinMax})
|
|
17
19
|
const {nullArray} = main
|
|
18
20
|
|
|
19
21
|
const bodyVectors = [
|
|
@@ -42,6 +44,11 @@ export const candleStudies = (main, index, size, {stdDev, patternSize, lag, scal
|
|
|
42
44
|
{
|
|
43
45
|
lookBackVectorKeyNames.push(`candle_change_${x+1}_${currK}_${prevK}`)
|
|
44
46
|
lookBackVectorsSetup[`candle_change_${x+1}_${currK}_${prevK}`] = [...nullArray]
|
|
47
|
+
|
|
48
|
+
if(autoMinMax)
|
|
49
|
+
{
|
|
50
|
+
main.autoMinMaxKeys.push(`candle_change_${x+1}_${currK}_${prevK}`)
|
|
51
|
+
}
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -53,6 +60,11 @@ export const candleStudies = (main, index, size, {stdDev, patternSize, lag, scal
|
|
|
53
60
|
{
|
|
54
61
|
bodyVectorKeyNames.push(`candle_body_${a}_${b}`)
|
|
55
62
|
bodyVectorSetup[`candle_body_${a}_${b}`] = [...nullArray]
|
|
63
|
+
|
|
64
|
+
if(autoMinMax)
|
|
65
|
+
{
|
|
66
|
+
main.autoMinMaxKeys.push(`candle_body_${a}_${b}`)
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
const keyNames = [...bodyVectorKeyNames, ...lookBackVectorKeyNames]
|
|
@@ -60,7 +72,7 @@ export const candleStudies = (main, index, size, {stdDev, patternSize, lag, scal
|
|
|
60
72
|
Object.assign(verticalOhlcv, {...bodyVectorSetup, ...lookBackVectorsSetup})
|
|
61
73
|
|
|
62
74
|
Object.assign(instances, {
|
|
63
|
-
|
|
75
|
+
candleVectors: {
|
|
64
76
|
bodyVectors,
|
|
65
77
|
lookBackVectors,
|
|
66
78
|
keyNames,
|
|
@@ -78,7 +90,7 @@ export const candleStudies = (main, index, size, {stdDev, patternSize, lag, scal
|
|
|
78
90
|
if (index === 0) return true
|
|
79
91
|
|
|
80
92
|
|
|
81
|
-
const {bodyVectors, lookBackVectors, bodyInstance} = instances.
|
|
93
|
+
const {bodyVectors, lookBackVectors, bodyInstance} = instances.candleVectors
|
|
82
94
|
|
|
83
95
|
let bodyBoll = null
|
|
84
96
|
const currOpen = verticalOhlcv.open[index]
|
|
@@ -20,7 +20,7 @@ import { calcMagnitude } from "./numberUtilities.js"
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
export const classifyBoll = (value, bollingerBands, scale = 0.05) => {
|
|
23
|
+
export const classifyBoll = (value, bollingerBands, scale = 0.05, autoMinMax = false) => {
|
|
24
24
|
|
|
25
25
|
// Validate that value and bollingerBands are not null/undefined
|
|
26
26
|
if (value == null || bollingerBands == null) return null
|
|
@@ -28,23 +28,17 @@ export const classifyBoll = (value, bollingerBands, scale = 0.05) => {
|
|
|
28
28
|
const positive = value >= 0
|
|
29
29
|
const absValue = Math.abs(value)
|
|
30
30
|
|
|
31
|
-
const { upper,
|
|
32
|
-
|
|
33
|
-
// Handle values outside the Bollinger bands
|
|
34
|
-
if (absValue < lower) return 0
|
|
35
|
-
if (absValue > upper) return positive ? 1 : -1
|
|
36
|
-
|
|
37
|
-
// Use a tolerance for floating point comparisons
|
|
38
|
-
const epsilon = Number.EPSILON * Math.max(absValue, Math.abs(middle))
|
|
39
|
-
if (Math.abs(absValue - middle) < epsilon) return positive ? 0.5 : -0.5
|
|
40
|
-
|
|
41
|
-
// Prevent division by zero if upper and lower are equal
|
|
42
|
-
if (upper === lower) return null
|
|
31
|
+
const { upper, lower } = bollingerBands
|
|
43
32
|
|
|
44
33
|
const rangeValue = (absValue - lower) / (upper - lower)
|
|
45
34
|
let magnitude = calcMagnitude(rangeValue, scale)
|
|
46
35
|
|
|
47
|
-
|
|
36
|
+
if(autoMinMax)
|
|
37
|
+
{
|
|
38
|
+
return positive ? Math.min(magnitude, 1) : Math.max(-magnitude, -1)
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return positive ? magnitude : -magnitude
|
|
42
|
+
}
|
|
48
43
|
|
|
49
|
-
return positive ? magnitude : -magnitude
|
|
50
44
|
}
|
|
@@ -6,7 +6,7 @@ import { relativeVolume } from "../moving-averages/relativeVolume.js";
|
|
|
6
6
|
import { donchianChannels } from "../moving-averages/donchianChannel.js";
|
|
7
7
|
import { bollingerBands } from "../moving-averages/bollingerBands.js";
|
|
8
8
|
import { volumeOscillator } from "../oscillators/volumeOscillator.js";
|
|
9
|
-
import {
|
|
9
|
+
import { candleVectors } from "../studies/candleVectors.js";
|
|
10
10
|
import { lag } from "../studies/lag.js";
|
|
11
11
|
import { crossPairs } from "../studies/findCrosses.js";
|
|
12
12
|
import { dateTime } from "../studies/dateTime.js";
|
|
@@ -25,7 +25,7 @@ const indicatorFunctions = {
|
|
|
25
25
|
donchianChannels,
|
|
26
26
|
bollingerBands,
|
|
27
27
|
volumeOscillator,
|
|
28
|
-
|
|
28
|
+
candleVectors
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
// Processes the inputParams into a list of indicator calls
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const pushToMain = ({main, index, key, value}) => {
|
|
2
|
+
|
|
3
|
+
const {precision, priceBased, minMaxRanges, verticalOhlcv, autoMinMaxKeys} = main
|
|
4
|
+
let {invalidValueIndex } = main
|
|
5
|
+
|
|
6
|
+
if(value === null || typeof value === 'undefined')
|
|
7
|
+
{
|
|
8
|
+
invalidValueIndex = index
|
|
9
|
+
|
|
10
|
+
for(let k in minMaxRanges)
|
|
11
|
+
{
|
|
12
|
+
minMaxRanges[k] = {min: Infinity, max: -Infinity}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
verticalOhlcv[key][index] = value
|
|
17
|
+
|
|
18
|
+
if(typeof value === 'number' && !key.includes('_x_'))
|
|
19
|
+
{
|
|
20
|
+
const keyInAutoMinMax = autoMinMaxKeys.includes(key)
|
|
21
|
+
|
|
22
|
+
if(key.includes('_diff_') && keyInAutoMinMax)
|
|
23
|
+
{
|
|
24
|
+
if(!minMaxRanges.hasOwnProperty(key) || (minMaxRanges[key].min === Infinity || minMaxRanges[key].max === -Infinity))
|
|
25
|
+
{
|
|
26
|
+
minMaxRanges[key] = { min: -1, max: 1}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if(key.startsWith('candle_') && keyInAutoMinMax)
|
|
30
|
+
{
|
|
31
|
+
minMaxRanges[key] = { min: -1, max: 1}
|
|
32
|
+
}
|
|
33
|
+
else if(key.includes('_range_') && keyInAutoMinMax)
|
|
34
|
+
{
|
|
35
|
+
minMaxRanges[key] = { min: 0, max: 1 }
|
|
36
|
+
}
|
|
37
|
+
else if(key.startsWith('rsi_') && keyInAutoMinMax)
|
|
38
|
+
{
|
|
39
|
+
minMaxRanges[key] = { min: 0, max: 1 }
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const newValue = (precision && priceBased.includes(key)) ? (value / precisionMultiplier) : value
|
|
43
|
+
|
|
44
|
+
if(!minMaxRanges.hasOwnProperty(key))
|
|
45
|
+
{
|
|
46
|
+
minMaxRanges[key] = { min: Infinity, max: -Infinity }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if(newValue < minMaxRanges[key].min)
|
|
50
|
+
{
|
|
51
|
+
minMaxRanges[key].min = newValue
|
|
52
|
+
}
|
|
53
|
+
if(newValue > minMaxRanges[key].max)
|
|
54
|
+
{
|
|
55
|
+
minMaxRanges[key].max = newValue
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return true
|
|
61
|
+
}
|
package/webpack.config.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { resolve, dirname } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
+
import TerserPlugin from 'terser-webpack-plugin';
|
|
4
|
+
|
|
3
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4
6
|
|
|
5
7
|
const entry = './index.js';
|
|
@@ -14,6 +16,19 @@ export default {
|
|
|
14
16
|
output,
|
|
15
17
|
mode: 'production',
|
|
16
18
|
target: 'web',
|
|
19
|
+
optimization: { //TerserPlugin start
|
|
20
|
+
minimize: true,
|
|
21
|
+
minimizer: [
|
|
22
|
+
new TerserPlugin({
|
|
23
|
+
terserOptions: {
|
|
24
|
+
output: {
|
|
25
|
+
beautify: true, // Beautify the output
|
|
26
|
+
comments: true, // Preserve comments
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
}, // TerserPlugin end
|
|
17
32
|
module: {
|
|
18
33
|
rules: [
|
|
19
34
|
{
|