ohlcv-indicators 3.3.2 → 3.3.4

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.
@@ -1,14 +1,12 @@
1
+ // Helper to clean non-numeric characters (except "-" at the start and decimal point)
2
+ export const sanitizeDirtyNumberString = str => str.replace(/(?!^-)[^0-9.]/g, '');
1
3
 
2
- export const calcMagnitude = (number, magnitudeLevel = 0.1) => {
3
-
4
- const factor = 1 / magnitudeLevel
5
-
6
- return Math.round(number * factor) / factor
4
+ export const numberFormater = {
5
+ number: n => n,
6
+ numberCleanString: n => Number(n),
7
+ numberDirtyString: n => Number(sanitizeDirtyNumberString(n))
7
8
  }
8
9
 
9
- // Helper to clean non-numeric characters (except "-" at the start and decimal point)
10
- export const cleanNumStr = str => str.replace(/(?!^-)[^0-9.]/g, '');
11
-
12
10
  export const divideByMultiplier = ({row, precisionMultiplier, priceBased}) => {
13
11
  const output = {}
14
12
 
@@ -26,4 +24,47 @@ export const divideByMultiplier = ({row, precisionMultiplier, priceBased}) => {
26
24
  }
27
25
 
28
26
  return output
29
- }
27
+ }
28
+
29
+ // Parses a number from either a numeric or string input, applying a multiplier to fix precision issues
30
+ export const parseNumber = (num, type, precisionMultiplier) => {
31
+ const addMultiplier = cNum => precisionMultiplier > 1 ? cNum * precisionMultiplier : cNum;
32
+
33
+ return addMultiplier(numberFormater[type](num))
34
+
35
+ };
36
+
37
+ export const classifyNum = (num, throwError = false) => {
38
+ // 1) true numbers
39
+ if (typeof num === 'number' && !Number.isNaN(num)) {
40
+ return 'number';
41
+ }
42
+
43
+ // 2) strings
44
+ if (typeof num === 'string') {
45
+ const str = num.trim();
46
+ const cleanRe = /^-?\d+(\.\d+)?$/;
47
+
48
+ // a) clean numeric string: only digits and an optional single period
49
+ if (cleanRe.test(str)) {
50
+ return 'numberCleanString';
51
+ }
52
+
53
+ // b) dirty numeric string: contains non-digits (dollar, commas, letters, etc.)
54
+ // but once you strip those out, you're left with a valid number
55
+ const stripped = str.replace(/[^0-9.]/g, '');
56
+ if (stripped && cleanRe.test(stripped)) {
57
+ return 'numberDirtyString';
58
+ }
59
+ }
60
+
61
+
62
+ if(throwError)
63
+ {
64
+ throw new TypeError(
65
+ 'Invalid input: expected a number or numeric string'
66
+ );
67
+ }
68
+
69
+ return typeof num
70
+ };
@@ -0,0 +1,24 @@
1
+ export const calcPrecisionMultiplier = (main, firstRow) => {
2
+ const {precision, priceBased} = main
3
+
4
+ if(precision === false) return 1
5
+
6
+ let output = 1
7
+ const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.includes(k)).map(([_, v]) => v)
8
+
9
+
10
+ for(const value of priceBasedValues)
11
+ {
12
+ const [, decimals = ''] = String(value).split('.')
13
+ const decimalPrecision = Math.max(4, decimals.length)
14
+ const multiplier = decimalPrecision > 1 ? Math.pow(10, decimalPrecision - 1) : 1
15
+
16
+ if(multiplier > output)
17
+ {
18
+ output = multiplier
19
+ }
20
+ }
21
+
22
+ return output
23
+
24
+ }
@@ -1,6 +1,3 @@
1
- export const validateDate = dateString => typeof dateString === 'string' && /^\d{4}-\d{2}-\d{2}/.test(dateString)
2
-
3
-
4
1
  export const validateInputParams = main => {
5
2
 
6
3
  const {inputParams, len} = main
@@ -16,5 +13,79 @@ export const validateInputParams = main => {
16
13
  }
17
14
 
18
15
  export const isAlreadyComputed = main => {
19
- if(main.lastComputedIndex !== 0) throw Error('ohlcv is already computed, you can not add new indicators after "compute", "getLastValues" , "getDataAsCols" or "getData" methods are called.')
20
- }
16
+ if(main.isComputed === true) throw Error('ohlcv is already computed, you can not add new indicators after "compute", "getLastValues" , "getDataAsCols" or "getData" methods are called.')
17
+ }
18
+
19
+ export const validateArray = (arr, paramName, callerName) => {
20
+
21
+ if(!Array.isArray(arr))
22
+ {
23
+ throw new Error(`Param "${paramName}" must be an array in "${callerName}".`)
24
+ }
25
+
26
+ return true
27
+ }
28
+
29
+ export const validateObject = (obj, paramName, callerName) => {
30
+
31
+ if(!Boolean(obj) || !typeof obj == 'object')
32
+ {
33
+ throw new Error(`Param "${paramName}" must be an object in "${callerName}".`)
34
+ }
35
+
36
+ return true
37
+ }
38
+
39
+ export const validateArrayOptions = (arrayOptions, value, paramName, callerName) => {
40
+
41
+ validateArray(arrayOptions, 'arrayOptions', `${callerName}.${paramName}.validateArrayOptions`)
42
+
43
+ if(!arrayOptions.includes(value.toString()))
44
+ {
45
+ throw new Error(`Param "${paramName}" must be any of the following values [${arrayOptions.join(', ')}] in "${callerName}".`)
46
+ }
47
+
48
+ return true
49
+ }
50
+
51
+ export const validateBoolean = (value, paramName, callerName) => {
52
+
53
+ if(typeof value !== 'boolean')
54
+ {
55
+ throw new Error(`Param "${paramName}" must be an boolean in "${callerName}".`)
56
+ }
57
+
58
+ return true
59
+ }
60
+
61
+ export const validateNumber = (value, options, paramName, callerName) => {
62
+
63
+ validateObject(options, 'options', `${callerName}.${paramName}.validateNumber`)
64
+
65
+ const {allowDecimals = false, min, max} = options
66
+
67
+ const label = (allowDecimals) ? 'number' : 'integer'
68
+
69
+ if(typeof value !== 'number' || (allowDecimals === false && !Number.isInteger(value)))
70
+ {
71
+ throw new Error(`Param "${paramName}" must be an integer in "${callerName}".`)
72
+ }
73
+
74
+ if(typeof min !== 'number' || typeof max !== 'number' || min >= max)
75
+ {
76
+ throw new Error(`Param "min" must be a valid ${label} lower than "max" "${callerName}.${paramName}.validateNumber".`)
77
+ }
78
+
79
+ if(value < min)
80
+ {
81
+ throw new Error(`Param "${paramName}" must be an ${label} greater than or equal to ${min} in "${callerName}".`)
82
+ }
83
+
84
+ if(value > max)
85
+ {
86
+ throw new Error(`Param "${paramName}" must be an ${label} lower than or equal to ${max} in "${callerName}".`)
87
+ }
88
+
89
+ return true
90
+ }
91
+
@@ -9,24 +9,24 @@ export const verticalToHorizontal = (
9
9
  const keys = Object.keys(obj);
10
10
  if (keys.length === 0) return [];
11
11
 
12
- const startIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0;
13
- const endIndex = obj[keys[0]].length;
14
- const len = endIndex - startIndex;
15
- const result = new Array(len);
12
+ const startIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0
13
+ const endIndex = obj[keys[0]].length
14
+ const len = endIndex - startIndex
15
+ const result = new Array(len)
16
16
 
17
17
 
18
18
  for (let i = startIndex; i < endIndex; i++) {
19
19
  const row = {};
20
20
  for (let j = 0; j < keys.length; j++) {
21
- const key = keys[j];
22
- const value = obj[key][i];
21
+ const key = keys[j]
22
+ const value = obj[key][i]
23
23
 
24
24
  row[key] = (precision && priceBased.includes(key)) ? value / precisionMultiplier : value
25
25
 
26
26
  }
27
27
 
28
- result[i - startIndex] = row;
28
+ result[i - startIndex] = row
29
29
  }
30
30
 
31
- return result;
31
+ return result
32
32
  };
@@ -1,121 +0,0 @@
1
- const diff = (a, b) => (a - b)
2
-
3
- import { calcZScore } from "../utilities/classification.js"
4
-
5
- export const candleVectors = (main, index, size, {patternSize, lag, autoMinMax}) => {
6
-
7
- const { verticalOhlcv, instances, lastIndexReplace } = main
8
-
9
- if(index === 0)
10
- {
11
- const {nullArray} = main
12
-
13
- const bodyVectors = [
14
-
15
- ['close', 'open'], ['close', 'high'], ['close', 'low'],
16
- ['open', 'high'], ['open', 'low'],
17
- ['high', 'low']
18
-
19
- ]
20
-
21
- //curr vs prev
22
- const lookBackVectors = [
23
- ['open', 'open'],
24
- ['high', 'high'],
25
- ['low', 'low'],
26
- ['close', 'close'],
27
- ...bodyVectors
28
- ]
29
-
30
- const lookBackVectorKeyNames = []
31
- const lookBackVectorsSetup = {}
32
-
33
- for(let x = 0; x < patternSize; x++)
34
- {
35
- for(const [currK, prevK] of lookBackVectors)
36
- {
37
- lookBackVectorKeyNames.push(`candle_change_${x+1}_${currK}_${prevK}`)
38
- lookBackVectorsSetup[`candle_change_${x+1}_${currK}_${prevK}`] = [...nullArray]
39
-
40
- if(autoMinMax)
41
- {
42
- main.autoMinMaxKeys.push(`candle_change_${x+1}_${currK}_${prevK}`)
43
- }
44
- }
45
- }
46
-
47
-
48
- const bodyVectorKeyNames = []
49
- const bodyVectorSetup = {}
50
-
51
- for(const [a, b] of bodyVectors)
52
- {
53
- bodyVectorKeyNames.push(`candle_body_${a}_${b}`)
54
- bodyVectorSetup[`candle_body_${a}_${b}`] = [...nullArray]
55
-
56
- if(autoMinMax)
57
- {
58
- main.autoMinMaxKeys.push(`candle_body_${a}_${b}`)
59
- }
60
- }
61
-
62
- const keyNames = [...bodyVectorKeyNames, ...lookBackVectorKeyNames]
63
-
64
- Object.assign(verticalOhlcv, {...bodyVectorSetup, ...lookBackVectorsSetup})
65
-
66
- Object.assign(instances, {
67
- candleVectors: {
68
- bodyVectors,
69
- lookBackVectors,
70
- keyNames,
71
- arrayChunk: {...Object.fromEntries(keyNames.map(v => [v, []]))}
72
- }
73
- })
74
-
75
- if(lag > 0)
76
- {
77
- main.lag(keyNames, lag)
78
- }
79
-
80
- }
81
-
82
- const {bodyVectors, lookBackVectors, arrayChunk} = instances.candleVectors
83
-
84
- for(let x = 0; x < bodyVectors.length; x++)
85
- {
86
- const [k2, k1] = bodyVectors[x]
87
- const v2 = verticalOhlcv[k2][index]
88
- const v1 = verticalOhlcv[k1][index]
89
- const key = `candle_body_${k2}_${k1}`
90
- const difference = diff(v2, v1)
91
-
92
- const zScore = calcZScore(arrayChunk, key, size, difference, lastIndexReplace)
93
-
94
- main.pushToMain({index, key, value: zScore})
95
- }
96
-
97
- for(let x = 0; x < patternSize; x++)
98
- {
99
- for(const [currK, prevK] of lookBackVectors)
100
- {
101
- const currV = verticalOhlcv[currK][index]
102
- const prevV = verticalOhlcv[prevK][index-(x+1)]
103
- const key = `candle_change_${x+1}_${currK}_${prevK}`
104
-
105
- if(typeof prevV === 'undefined')
106
- {
107
- main.pushToMain({index, key, value: null})
108
- continue
109
- }
110
-
111
- const difference = diff(currV, prevV)
112
- const zScore = calcZScore(arrayChunk, key, size, difference, lastIndexReplace)
113
-
114
- main.pushToMain({index, key, value: zScore})
115
- }
116
- }
117
-
118
- return true
119
- }
120
-
121
-
@@ -1,23 +0,0 @@
1
- export const priceVariations = (main, index) => {
2
-
3
-
4
- if(index === 0)
5
- {
6
- main.priceBased = [...new Set([...main.priceBased, 'mid_price_open_close', 'mid_price_high_low'])]
7
-
8
- Object.assign(main.verticalOhlcv, {
9
- mid_price_open_close: [...main.nullArray],
10
- mid_price_high_low: [...main.nullArray]
11
- })
12
-
13
-
14
- }
15
-
16
- const open = main.verticalOhlcv.open[index]
17
- const high = main.verticalOhlcv.high[index]
18
- const low = main.verticalOhlcv.low[index]
19
- const close = main.verticalOhlcv.close[index]
20
-
21
- main.pushToMain({index, key: 'mid_price_open_close', value: (open + close) / 2})
22
- main.pushToMain({index, key: 'mid_price_high_low', value: (high + low) / 2})
23
- }
@@ -1,75 +0,0 @@
1
- import { calcMagnitude } from "./numberUtilities.js"
2
-
3
- /**
4
- * Classifies a given value relative to provided Bollinger band thresholds.
5
- *
6
- * The function evaluates the input value against the lower, middle, and upper thresholds
7
- * of the Bollinger bands. If the value is below the lower threshold, it returns 0.
8
- * If the value exceeds the upper threshold, it returns 1 (or -1 if the value is negative).
9
- * For values within the bands, it calculates a normalized magnitude between 0 and 1 (or 0 and -1 for negatives)
10
- * based on the position within the range, rounded to one decimal place.
11
- * Additionally, if the value is nearly equal to the middle threshold (within a small tolerance),
12
- * the function returns 0.5 or -0.5 depending on the sign of the value.
13
- *
14
- * @param {number} value - The value to classify.
15
- * @param {Object} bollingerBands - An object containing Bollinger band thresholds.
16
- * @param {number} bollingerBands.upper - The upper band threshold.
17
- * @param {number} bollingerBands.middle - The middle band value.
18
- * @param {number} bollingerBands.lower - The lower band threshold.
19
- * @returns {number|null} The classified value, or null if inputs are invalid or if upper equals lower.
20
- */
21
-
22
-
23
- export const classifyBoll = (value, bollingerBands, scale = 0.05, autoMinMax = false) => {
24
-
25
- // Validate that value and bollingerBands are not null/undefined
26
- if (value == null || bollingerBands == null) return null
27
-
28
- const positive = value >= 0
29
- const absValue = Math.abs(value)
30
-
31
- const { upper, lower } = bollingerBands
32
-
33
- const rangeValue = (absValue - lower) / (upper - lower)
34
- let magnitude = calcMagnitude(rangeValue, scale)
35
-
36
- if(magnitude === 0)
37
- {
38
- return (positive) ? 0.01 : -0.01
39
- }
40
-
41
- if(autoMinMax)
42
- {
43
- return positive ? Math.min(magnitude, 1) : Math.max(-magnitude, -1)
44
- }
45
- else {
46
- return positive ? magnitude : -magnitude
47
- }
48
-
49
- }
50
-
51
-
52
- export const calcZScore = (arrayChunk, key, size, difference, lastIndexReplace) => {
53
- const positive = difference > 0
54
- const absValue = Math.abs(difference)
55
-
56
- if (lastIndexReplace) {
57
- arrayChunk[key][arrayChunk[key].length - 1] = absValue
58
- } else {
59
- arrayChunk[key].push(absValue)
60
- }
61
-
62
- if (arrayChunk[key].length > size) {
63
- arrayChunk[key].shift() // Maintain array size
64
- }
65
-
66
- if (arrayChunk[key].length < size) return null
67
-
68
- const mean = arrayChunk[key].reduce((sum, value) => sum + value, 0) / size
69
- const stdDev = Math.sqrt(
70
- arrayChunk[key].reduce((sum, value) => sum + (value - mean) ** 2, 0) / size
71
- )
72
-
73
- const zScore = (absValue - mean) / stdDev
74
- return (positive) ? zScore : -zScore
75
- }
@@ -1,189 +0,0 @@
1
- import { validateInputParams } from "./validators.js";
2
- import { rsi } from "../oscillators/rsi.js";
3
- import { movingAverages } from "../moving-averages/movingAverages.js";
4
- import { macd } from "../moving-averages/macd.js";
5
- import { relativeVolume } from "../moving-averages/relativeVolume.js";
6
- import { donchianChannels } from "../moving-averages/donchianChannel.js";
7
- import { bollingerBands } from "../moving-averages/bollingerBands.js";
8
- import { volumeOscillator } from "../oscillators/volumeOscillator.js";
9
- import { candleVectors } from "../studies/candleVectors.js";
10
- import { lag } from "../studies/lag.js";
11
- import { crossPairs } from "../studies/findCrosses.js";
12
- import { dateTime } from "../studies/dateTime.js";
13
- import { priceVariations } from "../studies/priceVariations.js";
14
-
15
- import { cleanNumStr } from "./numberUtilities.js";
16
-
17
- // Map indicator keys to their respective functions
18
- const indicatorFunctions = {
19
- dateTime,
20
- priceVariations,
21
- rsi,
22
- movingAverages,
23
- macd,
24
- relativeVolume,
25
- donchianChannels,
26
- bollingerBands,
27
- volumeOscillator,
28
- candleVectors
29
- };
30
-
31
- // Processes the inputParams into a list of indicator calls
32
- const processIndicatorCalls = inputParams =>
33
- Array.isArray(inputParams)
34
- ? inputParams.map(({ key, params }) => ({
35
- key,
36
- fn: indicatorFunctions[key],
37
- args: params
38
- }))
39
- : [];
40
-
41
- // Sets the input types for each key based on the first row of data
42
- const setInputTypes = (row0, main) => {
43
- const isValidNumberString = str => /^(\d+(\.\d*)?|\.\d+)$/.test(str);
44
-
45
- for (const key of Object.keys(main.inputTypes)) {
46
- if (!(key in row0)) {
47
- throw new Error(`Property "${key}" not found in ohlcv array. ${main.ticker}`);
48
- }
49
-
50
- let value = row0[key];
51
-
52
-
53
-
54
- if (typeof value === 'number' && value > 0) {
55
- main.inputTypes[key] = 'number';
56
- } else if (typeof value === 'string' && isValidNumberString(value)) {
57
- main.inputTypes[key] = 'string';
58
- value = cleanNumStr(value);
59
- } else {
60
- throw new Error(`Invalid property "${key}" in ohlcv array. ${main.ticker}`);
61
- }
62
-
63
- // Determine precisionMultiplier if not already set
64
- if (main.precisionMultiplier === 0 && main.precision === true) {
65
- const [, decimals = ''] = String(value).split('.');
66
- const decimalPrecision = Math.max(4, decimals.length);
67
- main.precisionMultiplier = decimalPrecision > 1 ? Math.pow(10, decimalPrecision - 1) : 1;
68
- }
69
- }
70
- };
71
-
72
- // Parses a number from either a numeric or string input, applying a multiplier to fix precision issues
73
- const parseNumber = (num, type, precisionMultiplier) => {
74
- const addMultiplier = cNum =>
75
- precisionMultiplier > 1 ? cNum * precisionMultiplier : cNum;
76
-
77
- if (type === 'number') {
78
- return addMultiplier(num);
79
- } else if (type === 'string') {
80
- const cleanedStr = cleanNumStr(num);
81
- return addMultiplier(Number(cleanedStr));
82
- } else
83
- {
84
- return 0 // Optionally: throw an error if type is not recognized
85
- }
86
-
87
- };
88
-
89
- const parseVolume = (volume, type) => {
90
-
91
- if(type === 'number') return volume
92
- else if(type === 'string'){
93
- return Number(cleanNumStr(volume))
94
- }
95
- else
96
- {
97
- return 0 // Optionally: throw an error if type is not recognized
98
- }
99
- }
100
-
101
- export const parseOhlcvToVertical = (input, main, startIndex = 0) => {
102
- const { len, inputParams, hasVolume } = main;
103
-
104
- //do not move nullArray from here
105
- main.nullArray = new Array(len).fill(null);
106
-
107
- const BASE_KEYS = ['open', 'high', 'low', 'close'];
108
-
109
- if(hasVolume)
110
- {
111
- BASE_KEYS.push('volume')
112
- }
113
-
114
- const baseKeysSet = new Set(BASE_KEYS);
115
-
116
- if (startIndex === 0) {
117
- // Validate parameters and initialize base keys in verticalOhlcv
118
- validateInputParams(main);
119
- BASE_KEYS.forEach(key => {
120
- main.verticalOhlcv[key] = [...main.nullArray];
121
- });
122
-
123
- // Identify any additional keys from the first input row and initialize them
124
- const inputKeys = Object.keys(input[0]);
125
- const otherKeys = inputKeys.filter(key => !baseKeysSet.has(key));
126
- otherKeys.forEach(key => {
127
- main.verticalOhlcv[key] = [...main.nullArray];
128
- });
129
- main.otherKeys = otherKeys;
130
-
131
- // Set the input types once using the first row
132
- setInputTypes(input[0], main);
133
- } else if (!main.otherKeys) {
134
- throw new Error(
135
- "otherKeys not found in main. Ensure you run with startIndex=0 first to initialize."
136
- );
137
- }
138
-
139
- // Prepare the list of indicator function calls
140
- const indicatorCalls = processIndicatorCalls(inputParams);
141
-
142
- // Process each row in the input
143
- for (let x = startIndex; x < len; x++) {
144
- const current = input[x];
145
- // Destructure the base keys and use the rest for other properties
146
- //sets volume to 0
147
- const { open, high, low, close, ...rest } = current
148
- const {precisionMultiplier} = main
149
-
150
- const parsedOpen = parseNumber(open, main.inputTypes.open, precisionMultiplier)
151
- const parsedHigh = parseNumber(high, main.inputTypes.high, precisionMultiplier)
152
- const parsedLow = parseNumber(low, main.inputTypes.low, precisionMultiplier)
153
- const parsedClose = parseNumber(close, main.inputTypes.close, precisionMultiplier)
154
-
155
-
156
- main.pushToMain({index: x, key: 'open', value: parsedOpen})
157
- main.pushToMain({index: x, key: 'high', value: parsedHigh})
158
- main.pushToMain({index: x, key: 'low', value: parsedLow})
159
- main.pushToMain({index: x, key: 'close', value: parsedClose})
160
-
161
- if(hasVolume)
162
- {
163
- const {volume} = current
164
- const parsedVolume = parseVolume (volume, main.inputTypes.volume)
165
- main.pushToMain({index: x, key: 'volume', value: parsedVolume})
166
- } else{
167
- delete rest.volume
168
- }
169
-
170
-
171
-
172
- // Populate any extra keys identified during initialization
173
- for (const key of main.otherKeys) {
174
- main.pushToMain({index: x, key, value: rest[key]})
175
- }
176
-
177
- // Run all indicator functions except for the ones processed later
178
- for (const { key, fn, args } of indicatorCalls) {
179
- if (['crossPairs', 'lag'].includes(key)) continue;
180
- fn(main, x, ...args);
181
- }
182
-
183
- // Process these indicators separately (ensuring their execution order)
184
- lag(main, x);
185
- crossPairs(main, x);
186
-
187
- main.lastComputedIndex++;
188
- }
189
- };
@@ -1,63 +0,0 @@
1
- export const pushToMain = ({main, index, key, value}) => {
2
-
3
- const {precision, priceBased, minMaxRanges, verticalOhlcv, autoMinMaxKeys, precisionMultiplier} = main
4
-
5
- if(value === null || typeof value === 'undefined')
6
- {
7
- main.invalidValueIndex = index //do not use let for main.invalidValueIndex
8
-
9
- for(const k of Object.keys(minMaxRanges))
10
- {
11
- minMaxRanges[k] = {min: Infinity, max: -Infinity}
12
- }
13
-
14
- verticalOhlcv[key][index] = null
15
- return false
16
- }
17
-
18
- verticalOhlcv[key][index] = value
19
-
20
- if(typeof value === 'number' && !key.includes('_x_'))
21
- {
22
- const keyInAutoMinMax = autoMinMaxKeys.includes(key)
23
-
24
- if(key.includes('_diff_') && keyInAutoMinMax)
25
- {
26
- if(!minMaxRanges.hasOwnProperty(key) || (minMaxRanges[key].min === Infinity || minMaxRanges[key].max === -Infinity))
27
- {
28
- minMaxRanges[key] = { min: -1, max: 1}
29
- }
30
- }
31
- else if(key.startsWith('candle_') && keyInAutoMinMax)
32
- {
33
- minMaxRanges[key] = { min: -3, max: 3}
34
- }
35
- else if(key.includes('_range_') && keyInAutoMinMax)
36
- {
37
- minMaxRanges[key] = { min: 0, max: 1 }
38
- }
39
- else if(key.startsWith('rsi_') && keyInAutoMinMax)
40
- {
41
- minMaxRanges[key] = { min: 0, max: 1 }
42
- }
43
- else {
44
- const newValue = (precision && priceBased.includes(key)) ? (value / precisionMultiplier) : value
45
-
46
- if(!minMaxRanges.hasOwnProperty(key))
47
- {
48
- minMaxRanges[key] = { min: Infinity, max: -Infinity }
49
- }
50
-
51
- if(newValue < minMaxRanges[key].min)
52
- {
53
- minMaxRanges[key].min = newValue
54
- }
55
- if(newValue > minMaxRanges[key].max)
56
- {
57
- minMaxRanges[key].max = newValue
58
- }
59
- }
60
- }
61
-
62
- return true
63
- }