ohlcv-indicators 3.3.2 → 3.3.5
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 +1731 -1879
- package/index.js +170 -292
- package/package.json +1 -3
- package/src/core-functions/mainLoop.js +85 -0
- package/src/core-functions/pushToMain.js +15 -0
- package/src/moving-averages/bollingerBands.js +26 -32
- package/src/moving-averages/donchianChannel.js +31 -36
- package/src/moving-averages/macd.js +30 -16
- package/src/moving-averages/movingAverages.js +15 -129
- package/src/moving-averages/relativeVolume.js +20 -14
- package/src/oscillators/rsi.js +21 -25
- package/src/oscillators/volumeOscillator.js +20 -19
- package/src/studies/dateTime.js +22 -20
- package/src/studies/findCrosses.js +17 -17
- package/src/studies/lag.js +22 -13
- package/src/studies/scaler.js +112 -0
- package/src/utilities/assignTypes.js +39 -0
- package/src/utilities/dateUtilities.js +79 -0
- package/src/utilities/numberUtilities.js +51 -10
- package/src/utilities/precisionMultiplier.js +24 -0
- package/src/utilities/validators.js +77 -8
- package/src/utilities/verticalToHorizontal.js +30 -0
- package/src/studies/candleVectors.js +0 -121
- package/src/studies/priceVariations.js +0 -23
- package/src/utilities/classification.js +0 -75
- package/src/utilities/dataParsingUtilities.js +0 -32
- package/src/utilities/parsing-utilities.js +0 -189
- package/src/utilities/pushToMain.js +0 -63
|
@@ -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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
|
@@ -17,7 +15,7 @@ export const divideByMultiplier = ({row, precisionMultiplier, priceBased}) => {
|
|
|
17
15
|
|
|
18
16
|
let newValue = value
|
|
19
17
|
|
|
20
|
-
if(priceBased.
|
|
18
|
+
if(priceBased.has(key))
|
|
21
19
|
{
|
|
22
20
|
newValue = newValue / precisionMultiplier
|
|
23
21
|
}
|
|
@@ -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.has(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,9 +1,4 @@
|
|
|
1
|
-
export const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export const validateInputParams = main => {
|
|
5
|
-
|
|
6
|
-
const {inputParams, len} = main
|
|
1
|
+
export const validateInputParams = ({inputParams, len}) => {
|
|
7
2
|
|
|
8
3
|
for (const { params } of inputParams) {
|
|
9
4
|
for (const v of params) {
|
|
@@ -16,5 +11,79 @@ export const validateInputParams = main => {
|
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
export const isAlreadyComputed = main => {
|
|
19
|
-
if(main.
|
|
20
|
-
}
|
|
14
|
+
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.')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const validateArray = (arr, paramName, callerName) => {
|
|
18
|
+
|
|
19
|
+
if(!Array.isArray(arr))
|
|
20
|
+
{
|
|
21
|
+
throw new Error(`Param "${paramName}" must be an array in "${callerName}".`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const validateObject = (obj, paramName, callerName) => {
|
|
28
|
+
|
|
29
|
+
if(!Boolean(obj) || !typeof obj == 'object')
|
|
30
|
+
{
|
|
31
|
+
throw new Error(`Param "${paramName}" must be an object in "${callerName}".`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const validateArrayOptions = (arrayOptions, value, paramName, callerName) => {
|
|
38
|
+
|
|
39
|
+
validateArray(arrayOptions, 'arrayOptions', `${callerName}.${paramName}.validateArrayOptions`)
|
|
40
|
+
|
|
41
|
+
if(!arrayOptions.includes(value.toString()))
|
|
42
|
+
{
|
|
43
|
+
throw new Error(`Param "${paramName}" must be any of the following values [${arrayOptions.join(', ')}] in "${callerName}".`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const validateBoolean = (value, paramName, callerName) => {
|
|
50
|
+
|
|
51
|
+
if(typeof value !== 'boolean')
|
|
52
|
+
{
|
|
53
|
+
throw new Error(`Param "${paramName}" must be an boolean in "${callerName}".`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const validateNumber = (value, options, paramName, callerName) => {
|
|
60
|
+
|
|
61
|
+
validateObject(options, 'options', `${callerName}.${paramName}.validateNumber`)
|
|
62
|
+
|
|
63
|
+
const {allowDecimals = false, min, max} = options
|
|
64
|
+
|
|
65
|
+
const label = (allowDecimals) ? 'number' : 'integer'
|
|
66
|
+
|
|
67
|
+
if(typeof value !== 'number' || (allowDecimals === false && !Number.isInteger(value)))
|
|
68
|
+
{
|
|
69
|
+
throw new Error(`Param "${paramName}" must be an integer in "${callerName}".`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if(typeof min !== 'number' || typeof max !== 'number' || min >= max)
|
|
73
|
+
{
|
|
74
|
+
throw new Error(`Param "min" must be a valid ${label} lower than "max" "${callerName}.${paramName}.validateNumber".`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if(value < min)
|
|
78
|
+
{
|
|
79
|
+
throw new Error(`Param "${paramName}" must be an ${label} greater than or equal to ${min} in "${callerName}".`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if(value > max)
|
|
83
|
+
{
|
|
84
|
+
throw new Error(`Param "${paramName}" must be an ${label} lower than or equal to ${max} in "${callerName}".`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const verticalToHorizontal = (
|
|
2
|
+
skipNull = false,
|
|
3
|
+
main,
|
|
4
|
+
startIndex = 0
|
|
5
|
+
) => {
|
|
6
|
+
|
|
7
|
+
const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames} = main
|
|
8
|
+
|
|
9
|
+
if (verticalOhlcvKeyNames.length === 0) return []
|
|
10
|
+
|
|
11
|
+
const skipNullIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0
|
|
12
|
+
const maxStartIndex = Math.max(skipNullIndex, startIndex)
|
|
13
|
+
const diffLen = len - maxStartIndex
|
|
14
|
+
const result = new Array(diffLen)
|
|
15
|
+
|
|
16
|
+
for (let i = maxStartIndex; i < len; i++) {
|
|
17
|
+
const row = {};
|
|
18
|
+
for (let j = 0; j < verticalOhlcvKeyNames.length; j++) {
|
|
19
|
+
const key = verticalOhlcvKeyNames[j]
|
|
20
|
+
const value = verticalOhlcv[key][i]
|
|
21
|
+
|
|
22
|
+
row[key] = (precision && priceBased.has(key)) ? value / precisionMultiplier : value
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
result[i - maxStartIndex] = row
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result
|
|
30
|
+
};
|
|
@@ -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,32 +0,0 @@
|
|
|
1
|
-
export const verticalToHorizontal = (
|
|
2
|
-
obj,
|
|
3
|
-
skipNull = false,
|
|
4
|
-
precision,
|
|
5
|
-
precisionMultiplier,
|
|
6
|
-
priceBased,
|
|
7
|
-
invalidValueIndex = 0
|
|
8
|
-
) => {
|
|
9
|
-
const keys = Object.keys(obj);
|
|
10
|
-
if (keys.length === 0) return [];
|
|
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);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
19
|
-
const row = {};
|
|
20
|
-
for (let j = 0; j < keys.length; j++) {
|
|
21
|
-
const key = keys[j];
|
|
22
|
-
const value = obj[key][i];
|
|
23
|
-
|
|
24
|
-
row[key] = (precision && priceBased.includes(key)) ? value / precisionMultiplier : value
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
result[i - startIndex] = row;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return result;
|
|
32
|
-
};
|
|
@@ -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
|
-
};
|