ohlcv-indicators 3.1.9 → 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 -65
- package/package.json +2 -1
- package/src/moving-averages/bollingerBands.js +13 -2
- package/src/moving-averages/donchianChannel.js +11 -2
- 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 = [], scale = null, lag = 0} = options
|
|
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) {
|
|
@@ -366,6 +328,12 @@ export default class OHLCV_INDICATORS {
|
|
|
366
328
|
|
|
367
329
|
throw new Error(`"lag" value in rsi must be a number in bollingerBands.`);
|
|
368
330
|
}
|
|
331
|
+
|
|
332
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
333
|
+
|
|
334
|
+
throw new Error(`"autoMinMax" value in rsi must be a number in bollingerBands.`);
|
|
335
|
+
}
|
|
336
|
+
|
|
369
337
|
if (typeof height !== 'boolean') {
|
|
370
338
|
throw new Error('"height" must be a boolean in bollingerBands.');
|
|
371
339
|
}
|
|
@@ -377,7 +345,7 @@ export default class OHLCV_INDICATORS {
|
|
|
377
345
|
}
|
|
378
346
|
}
|
|
379
347
|
|
|
380
|
-
this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range, lag}]});
|
|
348
|
+
this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range, lag, autoMinMax}]});
|
|
381
349
|
|
|
382
350
|
return this;
|
|
383
351
|
}
|
|
@@ -391,7 +359,7 @@ export default class OHLCV_INDICATORS {
|
|
|
391
359
|
throw new Error('"size" must be a positive number in rsi.');
|
|
392
360
|
}
|
|
393
361
|
|
|
394
|
-
const {scale = null, target = 'close', lag = 0} = options
|
|
362
|
+
const {scale = null, target = 'close', lag = 0, autoMinMax = false} = options
|
|
395
363
|
|
|
396
364
|
if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
|
|
397
365
|
|
|
@@ -403,7 +371,12 @@ export default class OHLCV_INDICATORS {
|
|
|
403
371
|
throw new Error(`"lag" value in rsi must be a number in rsi.`);
|
|
404
372
|
}
|
|
405
373
|
|
|
406
|
-
|
|
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}]})
|
|
407
380
|
|
|
408
381
|
return this
|
|
409
382
|
}
|
|
@@ -418,7 +391,7 @@ export default class OHLCV_INDICATORS {
|
|
|
418
391
|
throw new Error('"offset" must be a number greater than or equal to 0 in donchianChannels.');
|
|
419
392
|
}
|
|
420
393
|
|
|
421
|
-
const { height = false, range = [], scale = null, lag = 0} = options;
|
|
394
|
+
const { height = false, range = [], scale = null, lag = 0, autoMinMax = false} = options;
|
|
422
395
|
|
|
423
396
|
if (!Array.isArray(range)) {
|
|
424
397
|
throw new Error('If set, "range" must be an array of column names in donchianChannels.');
|
|
@@ -427,6 +400,11 @@ export default class OHLCV_INDICATORS {
|
|
|
427
400
|
if (typeof lag !== 'number') {
|
|
428
401
|
throw new Error(`"lag" value in rsi must be a number in donchianChannels.`);
|
|
429
402
|
}
|
|
403
|
+
|
|
404
|
+
if (typeof autoMinMax !== 'boolean') {
|
|
405
|
+
|
|
406
|
+
throw new Error(`"autoMinMax" value in rsi must be a number in donchianChannels.`);
|
|
407
|
+
}
|
|
430
408
|
|
|
431
409
|
if (typeof height !== 'boolean') {
|
|
432
410
|
throw new Error('"height" must be a boolean in donchianChannels.');
|
|
@@ -440,37 +418,41 @@ export default class OHLCV_INDICATORS {
|
|
|
440
418
|
}
|
|
441
419
|
}
|
|
442
420
|
|
|
443
|
-
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 }] });
|
|
444
422
|
|
|
445
423
|
return this;
|
|
446
424
|
}
|
|
447
425
|
|
|
448
426
|
|
|
449
|
-
|
|
427
|
+
candleVectors(size = 20, options = {}) {
|
|
450
428
|
isAlreadyComputed(this);
|
|
451
429
|
|
|
452
430
|
if (typeof size !== 'number' || size <= 0) {
|
|
453
|
-
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.');
|
|
454
432
|
}
|
|
455
433
|
|
|
456
|
-
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;
|
|
457
435
|
|
|
458
436
|
|
|
459
437
|
if (typeof stdDev !== 'number' || stdDev <= 0) {
|
|
460
|
-
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.');
|
|
461
439
|
}
|
|
462
440
|
|
|
463
441
|
if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
|
|
464
442
|
|
|
465
|
-
throw new Error(`"scale" value in
|
|
443
|
+
throw new Error(`"scale" value in candleVectors must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
|
|
466
444
|
}
|
|
467
445
|
|
|
468
446
|
if (typeof patternSize !== 'number' || !Number.isInteger(patternSize) || patternSize < 1 ) {
|
|
469
447
|
|
|
470
|
-
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`);
|
|
471
453
|
}
|
|
472
454
|
|
|
473
|
-
this.inputParams.push({ key: '
|
|
455
|
+
this.inputParams.push({ key: 'candleVectors', params: [size, {stdDev, patternSize, lag, scale, autoMinMax}] });
|
|
474
456
|
return this;
|
|
475
457
|
}
|
|
476
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 = [], target, scale, lag }) => {
|
|
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,6 +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}`)
|
|
53
|
+
|
|
54
|
+
if(autoMinMax)
|
|
55
|
+
{
|
|
56
|
+
main.autoMinMaxKeys.push(`${prefix}_range_${rangeKey}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, [...nullArray]]))
|
|
@@ -106,7 +113,11 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
|
|
|
106
113
|
let rangeValue = null;
|
|
107
114
|
const priceValue = verticalOhlcv[rangeKey][index];
|
|
108
115
|
if (typeof priceValue === 'number' && typeof lower === 'number' && typeof upper === 'number' && (upper - lower) !== 0) {
|
|
109
|
-
rangeValue =
|
|
116
|
+
rangeValue = (autoMinMax)
|
|
117
|
+
? Math.max(Math.min((priceValue - lower) / (upper - lower), 1), 0)
|
|
118
|
+
: (priceValue - lower) / (upper - lower)
|
|
119
|
+
|
|
120
|
+
|
|
110
121
|
if (scale) {
|
|
111
122
|
rangeValue = calcMagnitude(rangeValue, scale);
|
|
112
123
|
}
|
|
@@ -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]]))
|
|
@@ -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 ? Math.min(magnitude, 1) : Math.max(-magnitude, -1)
|
|
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
|
{
|