ohlcv-indicators 3.5.52 → 3.5.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +48 -65
- package/package.json +1 -1
- package/src/core-functions/mainLoop.js +8 -3
- package/src/core-functions/pushToMain.js +1 -2
- package/src/machine-learning/classifier.js +56 -3
- package/src/moving-averages/bollingerBands.js +11 -8
- package/src/moving-averages/donchianChannel.js +8 -6
- package/src/moving-averages/heikenAshi.js +117 -93
- package/src/moving-averages/macd.js +5 -1
- package/src/moving-averages/movingAverages.js +5 -1
- package/src/moving-averages/vidya.js +7 -1
- package/src/oscillators/volumeDelta.js +74 -0
- package/src/studies/findCrosses.js +31 -5
- package/src/studies/lag.js +17 -12
- package/src/utilities/assignTypes.js +4 -6
- package/src/utilities/numberUtilities.js +22 -39
- package/src/utilities/precisionMultiplier.js +91 -16
- package/src/utilities/validators.js +1 -1
- package/src/utilities/verticalToHorizontal.js +16 -3
- package/src/studies/correlation.js +0 -98
|
@@ -1,23 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// ------------------------------
|
|
2
|
+
// calcPrecisionMultiplier (unchanged semantics)
|
|
3
|
+
// ------------------------------
|
|
4
|
+
export const calcPrecisionMultiplier = main => {
|
|
5
|
+
const { precision, firstRow, initialPriceBased } = main;
|
|
6
|
+
if (precision === false) return 1
|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
const multipliers = []
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
const err = 'If "precision" is set to true all the "open", "high", "low" and "close" values must be numeric strings like "123.45" with no commas or currency symbols.'
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
{
|
|
10
|
-
if(new Set(['open', 'high', 'low', 'close']).has(key) === false) continue
|
|
11
|
-
const [, decimals = ''] = String(value).split('.')
|
|
12
|
-
const decimalPrecision = Math.max(4, decimals.length)
|
|
13
|
-
const multiplier = decimalPrecision > 1 ? Math.pow(10, decimalPrecision - 1) : 1
|
|
12
|
+
for (const [key, strNum] of Object.entries(firstRow)) {
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
if(!initialPriceBased.has(key)) continue
|
|
15
|
+
|
|
16
|
+
if (typeof strNum !== 'string' || !/^-?\d+(\.\d+)?$/.test(strNum)) {
|
|
17
|
+
throw new Error(`${err}
|
|
18
|
+
Property "${key}" had the value "${strNum}" type ${typeof strNum}.`)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
if (!new Set(['open', 'high', 'low', 'close']).has(key)) continue;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const [, decimals = ''] = strNum.split('.')
|
|
24
|
+
const decimalPrecision = Math.max(4, decimals.length)
|
|
25
|
+
multipliers.push(Math.pow(10, decimalPrecision)) // returns Number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (multipliers.length === 0) return 1
|
|
29
|
+
return Math.max(...multipliers)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ------------------------------
|
|
33
|
+
// Precise BigInt-based helpers (no `n` literals)
|
|
34
|
+
// ------------------------------
|
|
35
|
+
export const addPrecisionExact = (strNum, mul) => {
|
|
36
|
+
if (typeof strNum !== 'string') throw new Error('addPrecisionExact expects a string when precision=true')
|
|
37
|
+
|
|
38
|
+
const negative = strNum.charAt(0) === '-'
|
|
39
|
+
const s = negative ? strNum.slice(1) : strNum
|
|
40
|
+
const [intPart, decPart = ''] = s.split('.')
|
|
41
|
+
|
|
42
|
+
// Compute decimals from mul (mul expected as 10^decimals)
|
|
43
|
+
const mulStr = String(mul);
|
|
44
|
+
const decimals = (mul === 1) ? 0 : (mulStr.length - 1)
|
|
45
|
+
|
|
46
|
+
const decPadded = decPart.padEnd(decimals, '0').slice(0, decimals); // pad or trim
|
|
47
|
+
const integerString = intPart + decPadded // e.g. "12" + "3400" => "123400"
|
|
48
|
+
|
|
49
|
+
// Use BigInt constructor (no n-literals)
|
|
50
|
+
let result = BigInt(integerString || '0'); // handle "0" or empty
|
|
51
|
+
if (negative) result = -result
|
|
52
|
+
return result
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const revertPrecisionExact = (num, mul) => {
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// Convert input to BigInt without using literal suffixes
|
|
59
|
+
const big = (typeof num === 'bigint') ? num : BigInt(parseInt(num))
|
|
60
|
+
const neg = big < BigInt(0)
|
|
61
|
+
const abs = neg ? -big : big
|
|
62
|
+
|
|
63
|
+
const mulBig = BigInt(mul);
|
|
64
|
+
if (mulBig === BigInt(1)) {
|
|
65
|
+
const s = abs.toString()
|
|
66
|
+
return neg ? `-${s}` : s
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const intPart = abs / mulBig
|
|
70
|
+
const fracPart = abs % mulBig
|
|
71
|
+
|
|
72
|
+
const decimals = String(mul).length - 1
|
|
73
|
+
const fracStr = fracPart.toString().padStart(decimals, '0')
|
|
74
|
+
|
|
75
|
+
// Remove trailing zeros in fractional part (optional)
|
|
76
|
+
const trimmedFrac = fracStr.replace(/0+$/, '')
|
|
77
|
+
const result = trimmedFrac.length === 0
|
|
78
|
+
? intPart.toString()
|
|
79
|
+
: `${intPart.toString()}.${trimmedFrac}`
|
|
80
|
+
|
|
81
|
+
return neg ? `-${result}` : result
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ------------------------------
|
|
85
|
+
// Convenience wrappers (Numbers) — may lose precision
|
|
86
|
+
// ------------------------------
|
|
87
|
+
export const addPrecisionAsNumber = (strNum, mul) => {
|
|
88
|
+
|
|
89
|
+
const big = addPrecisionExact(strNum, mul)
|
|
90
|
+
|
|
91
|
+
return Number(big)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const revertPrecisionAsNumber = (num, mul) => {
|
|
95
|
+
|
|
96
|
+
const asStr = revertPrecisionExact(num, mul)
|
|
97
|
+
return Number(asStr)
|
|
98
|
+
}
|
|
@@ -16,7 +16,7 @@ export const validateInputParams = (inputParams, len) => {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const isAlreadyComputed = main => {
|
|
19
|
-
if(main.isComputed === true) throw Error('ohlcv is already computed, you can not add new indicators after "compute", "getLastValues"
|
|
19
|
+
if(main.isComputed === true) throw Error('ohlcv is already computed, you can not add new indicators after "compute", "getLastValues" or "getData" methods are called.')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const validateArray = (arr, paramName, callerName) => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { dateOutputFormaters } from "./dateUtilities.js"
|
|
2
|
+
import { outputNumberFormatter } from "./numberUtilities.js"
|
|
2
3
|
|
|
3
4
|
export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, dateFormat}) => {
|
|
4
5
|
|
|
5
|
-
const {verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames, verticalOhlcvTempCols} = main
|
|
6
|
+
const {verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames, verticalOhlcvTempCols, priceBased, precision, precisionMultiplier} = main
|
|
7
|
+
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
if (verticalOhlcvKeyNames.length === 0) return []
|
|
8
11
|
|
|
@@ -10,8 +13,18 @@ export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, da
|
|
|
10
13
|
const maxStartIndex = Math.max(skipNullIndex, startIndex)
|
|
11
14
|
const diffLen = len - maxStartIndex
|
|
12
15
|
const result = Array.from({ length: diffLen }, () => ({}))
|
|
16
|
+
const formatter = {}
|
|
17
|
+
|
|
18
|
+
for(const key of verticalOhlcvKeyNames) {
|
|
19
|
+
if(precision && priceBased.has(key)) {
|
|
20
|
+
formatter[key] = (num, mul) => outputNumberFormatter.precisionNumberCleanString(num, mul)
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
formatter[key] = v => v
|
|
24
|
+
}
|
|
25
|
+
}
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
for(const [key, arr] of Object.entries(verticalOhlcv)){
|
|
15
28
|
if(verticalOhlcvTempCols.has(key)) continue
|
|
16
29
|
|
|
17
30
|
for (let i = maxStartIndex; i < len; i++)
|
|
@@ -22,7 +35,7 @@ export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, da
|
|
|
22
35
|
}
|
|
23
36
|
else
|
|
24
37
|
{
|
|
25
|
-
result[i - maxStartIndex][key] = arr[i]
|
|
38
|
+
result[i - maxStartIndex][key] = formatter[key](arr[i], precisionMultiplier)
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { FasterSMA } from "trading-signals";
|
|
2
|
-
|
|
3
|
-
// Normalize the array (z-score normalization)
|
|
4
|
-
const smoothNormalize = (arr, smoothing) => {
|
|
5
|
-
const len = arr.length;
|
|
6
|
-
const instance = new FasterSMA(smoothing);
|
|
7
|
-
const smoothedArr = new Array(len);
|
|
8
|
-
let sum = 0;
|
|
9
|
-
let sumSq = 0;
|
|
10
|
-
let count = 0;
|
|
11
|
-
|
|
12
|
-
for (let x = 0; x < len; x++) {
|
|
13
|
-
instance.update(arr[x]);
|
|
14
|
-
|
|
15
|
-
if (instance.isStable) {
|
|
16
|
-
const value = instance.getResult();
|
|
17
|
-
smoothedArr[x] = value;
|
|
18
|
-
sum += value;
|
|
19
|
-
sumSq += value * value;
|
|
20
|
-
count++;
|
|
21
|
-
} else {
|
|
22
|
-
smoothedArr[x] = null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const meanValue = sum / count;
|
|
27
|
-
const variance = (sumSq / count) - (meanValue * meanValue);
|
|
28
|
-
const stdDev = Math.sqrt(variance);
|
|
29
|
-
|
|
30
|
-
return smoothedArr.map(val => (val !== null ? (val - meanValue) / stdDev : null));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Calculate Pearson Correlation
|
|
34
|
-
export const correlation = (x, y, smoothing = 5) => {
|
|
35
|
-
if (x.length !== y.length) {
|
|
36
|
-
throw new Error('Both arrays must have the same length');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const normX = smoothNormalize(x, smoothing);
|
|
40
|
-
const normY = smoothNormalize(y, smoothing);
|
|
41
|
-
const n = x.length;
|
|
42
|
-
const result = [];
|
|
43
|
-
|
|
44
|
-
let sumX = 0, sumY = 0, sumXY = 0;
|
|
45
|
-
let sumXSquare = 0, sumYSquare = 0;
|
|
46
|
-
let count = 0;
|
|
47
|
-
|
|
48
|
-
// Find the first index where both normX and normY have valid values
|
|
49
|
-
let firstValidIndex = -1;
|
|
50
|
-
for (let i = 0; i < n; i++) {
|
|
51
|
-
if (normX[i] !== null && normY[i] !== null) {
|
|
52
|
-
firstValidIndex = i;
|
|
53
|
-
break;
|
|
54
|
-
} else {
|
|
55
|
-
result.push(0); // Fill initial invalid indices with 0
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (firstValidIndex === -1) {
|
|
60
|
-
// No valid data points
|
|
61
|
-
return new Array(n).fill(0);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
for (let i = firstValidIndex; i < n; i++) {
|
|
65
|
-
const xVal = normX[i];
|
|
66
|
-
const yVal = normY[i];
|
|
67
|
-
|
|
68
|
-
if (xVal === null || yVal === null) {
|
|
69
|
-
// Reset sums and counts when encountering null values
|
|
70
|
-
sumX = sumY = sumXY = sumXSquare = sumYSquare = count = 0;
|
|
71
|
-
result.push(0);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
sumX += xVal;
|
|
76
|
-
sumY += yVal;
|
|
77
|
-
sumXY += xVal * yVal;
|
|
78
|
-
sumXSquare += xVal * xVal;
|
|
79
|
-
sumYSquare += yVal * yVal;
|
|
80
|
-
count++;
|
|
81
|
-
|
|
82
|
-
if (count < 2) {
|
|
83
|
-
result.push(0); // Not enough data points to calculate correlation
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const numerator = (count * sumXY) - (sumX * sumY);
|
|
88
|
-
const denominator = Math.sqrt(
|
|
89
|
-
(count * sumXSquare - sumX * sumX) *
|
|
90
|
-
(count * sumYSquare - sumY * sumY)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const correlation = denominator === 0 ? 0 : numerator / denominator;
|
|
94
|
-
result.push(correlation);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return result;
|
|
98
|
-
}
|