ohlcv-indicators 3.5.78 → 3.5.80
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/AGENTS.md +113 -0
- package/README.md +456 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
- package/index.js +112 -84
- package/package.json +2 -5
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +35 -30
- package/src/core-functions/pushToMain.js +8 -27
- package/src/core-indicators/README.md +60 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +81 -0
- package/src/core-indicators/movingAverages.js +88 -0
- package/src/core-indicators/rsi.js +41 -0
- package/src/core-indicators/volatility.js +107 -0
- package/src/moving-averages/bollingerBands.js +39 -31
- package/src/moving-averages/donchianChannel.js +62 -36
- package/src/moving-averages/heikenAshi.js +33 -52
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +19 -25
- package/src/moving-averages/relativeVolume.js +30 -24
- package/src/oscillators/mfi.js +56 -0
- package/src/oscillators/rsi.js +7 -26
- package/src/oscillators/stochastic.js +6 -19
- package/src/oscillators/volumeDelta.js +36 -28
- package/src/oscillators/volumeOscillator.js +24 -32
- package/src/studies/candleFeatures.js +40 -28
- package/src/studies/dateTime.js +54 -56
- package/src/studies/findCrosses.js +18 -16
- package/src/studies/lag.js +32 -20
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +31 -0
- package/src/utilities/numberUtilities.js +5 -3
- package/src/utilities/precisionMultiplier.js +11 -6
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +26 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +1230 -0
- package/webpack.config.js +0 -11
- package/src/moving-averages/vidya.js +0 -181
- package/src/studies/mapCols.js +0 -46
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { addPrecisionAsNumber, revertPrecisionAsNumber } from "./precisionMultiplier.js";
|
|
2
2
|
|
|
3
|
+
export const isPositiveInteger = n => Number.isInteger(n) && n >= 0;
|
|
4
|
+
|
|
3
5
|
export const inputNumberFormatter = {
|
|
4
6
|
number: n => n,
|
|
5
7
|
numberCleanString: n => Number(n),
|
|
6
|
-
precisionNumberCleanString: (strNum, multiplier) => addPrecisionAsNumber(strNum, multiplier)
|
|
8
|
+
precisionNumberCleanString: (strNum, multiplier, cachedDecimals) => addPrecisionAsNumber(strNum, multiplier, cachedDecimals)
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export const outputNumberFormatter = {
|
|
10
12
|
number: n => n,
|
|
11
13
|
numberCleanString: n => n,
|
|
12
|
-
precisionNumberCleanString: (strNum, multiplier) => revertPrecisionAsNumber(strNum, multiplier)
|
|
14
|
+
precisionNumberCleanString: (strNum, multiplier, cachedDecimals) => revertPrecisionAsNumber(strNum, multiplier, cachedDecimals)
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export const classifyNum = (num, throwError = false, precision = false) => {
|
|
@@ -32,4 +34,4 @@ export const classifyNum = (num, throwError = false, precision = false) => {
|
|
|
32
34
|
|
|
33
35
|
if(throwError) throw new TypeError('Invalid input: expected a number or numeric string')
|
|
34
36
|
return typeof num
|
|
35
|
-
}
|
|
37
|
+
}
|
|
@@ -51,8 +51,14 @@ export const calcPrecisionMultiplier = main => {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
// Keep the existing decimal-width rule, including scientific-notation multipliers.
|
|
55
|
+
export const getPrecisionDecimals = multiplier => {
|
|
56
|
+
const mulStr = String(multiplier)
|
|
57
|
+
return multiplier === 1 ? 0 : mulStr.length - 1
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
//this function converts the strNum into a integer * multiplier
|
|
55
|
-
export const addPrecisionAsNumber = (strNum, multiplier) => {
|
|
61
|
+
export const addPrecisionAsNumber = (strNum, multiplier, cachedDecimals) => {
|
|
56
62
|
if (typeof strNum !== 'string') throw new Error('addPrecisionExact expects a string when precision=true')
|
|
57
63
|
|
|
58
64
|
const negative = strNum.charAt(0) === '-'
|
|
@@ -60,8 +66,7 @@ export const addPrecisionAsNumber = (strNum, multiplier) => {
|
|
|
60
66
|
const [intPart, decPart = ''] = s.split('.')
|
|
61
67
|
|
|
62
68
|
// Compute decimals from multiplier (multiplier expected as 10^decimals)
|
|
63
|
-
const
|
|
64
|
-
const decimals = (multiplier === 1) ? 0 : (mulStr.length - 1)
|
|
69
|
+
const decimals = cachedDecimals === undefined ? getPrecisionDecimals(multiplier) : cachedDecimals
|
|
65
70
|
|
|
66
71
|
const decPadded = decPart.padEnd(decimals, '0').slice(0, decimals); // pad or trim
|
|
67
72
|
const integerString = intPart + decPadded // e.g. "12" + "3400" => "123400"
|
|
@@ -75,7 +80,7 @@ export const addPrecisionAsNumber = (strNum, multiplier) => {
|
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
//this function converts the outputs the number generated in addPrecisionAsNumber / multiplier
|
|
78
|
-
export const revertPrecisionAsNumber = (num, multiplier) => {
|
|
83
|
+
export const revertPrecisionAsNumber = (num, multiplier, cachedDecimals) => {
|
|
79
84
|
if (typeof num !== 'number' || !Number.isFinite(num)) {
|
|
80
85
|
throw new Error('revertPrecisionAsNumber expects a finite number')
|
|
81
86
|
}
|
|
@@ -86,7 +91,7 @@ export const revertPrecisionAsNumber = (num, multiplier) => {
|
|
|
86
91
|
// Make sure we're working with an integer, mirroring addPrecisionAsNumber
|
|
87
92
|
abs = Math.trunc(abs)
|
|
88
93
|
|
|
89
|
-
const decimals =
|
|
94
|
+
const decimals = cachedDecimals === undefined ? getPrecisionDecimals(multiplier) : cachedDecimals
|
|
90
95
|
|
|
91
96
|
if (decimals === 0) {
|
|
92
97
|
// No fractional part; just restore sign
|
|
@@ -106,4 +111,4 @@ export const revertPrecisionAsNumber = (num, multiplier) => {
|
|
|
106
111
|
: `${intPart}.${fracStr}`
|
|
107
112
|
|
|
108
113
|
return Number(negative ? `-${resultStr}` : resultStr)
|
|
109
|
-
}
|
|
114
|
+
}
|
|
@@ -5,7 +5,14 @@ export const validateInputParams = (inputParams, len) => {
|
|
|
5
5
|
throw new Error('Property "inputParams" must be an "Array" or "Set".')
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
for (const
|
|
8
|
+
for (const job of inputParams) {
|
|
9
|
+
if (!job || typeof job.key !== 'string' || !Array.isArray(job.params)) {
|
|
10
|
+
throw new TypeError('Each inputParams entry must have an indicator key and a params array.')
|
|
11
|
+
}
|
|
12
|
+
if (job.order !== undefined && !Number.isFinite(job.order)) {
|
|
13
|
+
throw new TypeError('Indicator order must be a finite number when provided.')
|
|
14
|
+
}
|
|
15
|
+
const { params } = job
|
|
9
16
|
for (const v of params) {
|
|
10
17
|
if (typeof v === 'number' && v > len) {
|
|
11
18
|
console.log(v, len);
|
|
@@ -16,6 +23,7 @@ export const validateInputParams = (inputParams, len) => {
|
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
export const isAlreadyComputed = main => {
|
|
26
|
+
if(main.isComputing === true) throw Error('You cannot add indicators while computation is in progress.')
|
|
19
27
|
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
28
|
}
|
|
21
29
|
|
|
@@ -117,3 +125,34 @@ export const validateString = (txt, paramName, callerName) => {
|
|
|
117
125
|
|
|
118
126
|
return true
|
|
119
127
|
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
export const validateInputValues = (
|
|
131
|
+
requiredParams,
|
|
132
|
+
verticalOhlcv,
|
|
133
|
+
index,
|
|
134
|
+
callbackName
|
|
135
|
+
) => {
|
|
136
|
+
if (index !== 0) return true;
|
|
137
|
+
|
|
138
|
+
const invalidParams = [];
|
|
139
|
+
|
|
140
|
+
for (const key of Object.keys(requiredParams)) {
|
|
141
|
+
const value = verticalOhlcv[key]?.[index];
|
|
142
|
+
|
|
143
|
+
const isValid = key === "volume"
|
|
144
|
+
? Number.isInteger(value) && value >= 0
|
|
145
|
+
: Number.isFinite(value) && value > 0;
|
|
146
|
+
|
|
147
|
+
if (!isValid) invalidParams.push(key);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (invalidParams.length > 0) {
|
|
151
|
+
throw new TypeError(
|
|
152
|
+
`Invalid or missing input value(s) [${invalidParams.join(", ")}] ` +
|
|
153
|
+
`at index ${index} in "${callbackName}".`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dateOutputFormaters } from "./dateUtilities.js"
|
|
2
2
|
import { outputNumberFormatter } from "./numberUtilities.js"
|
|
3
|
+
import { getPrecisionDecimals } from "./precisionMultiplier.js"
|
|
3
4
|
|
|
4
5
|
export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, dateFormat}) => {
|
|
5
6
|
|
|
@@ -8,34 +9,43 @@ export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, da
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
if (verticalOhlcvKeyNames.length === 0) return []
|
|
12
|
+
|
|
13
|
+
const precisionDecimals = precision && typeof precisionMultiplier === 'number'
|
|
14
|
+
? getPrecisionDecimals(precisionMultiplier)
|
|
15
|
+
: undefined
|
|
11
16
|
|
|
12
17
|
const skipNullIndex = skipNull && invalidValueIndex >= 0 ? invalidValueIndex + 1 : 0
|
|
13
18
|
const maxStartIndex = Math.max(skipNullIndex, startIndex)
|
|
14
19
|
const diffLen = len - maxStartIndex
|
|
15
20
|
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
|
-
}
|
|
26
21
|
|
|
27
22
|
for(const [key, arr] of Object.entries(verticalOhlcv)){
|
|
28
23
|
if(verticalOhlcvTempCols.has(key)) continue
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
if(key === 'date')
|
|
26
|
+
{
|
|
27
|
+
const formatter = dateOutputFormaters[dateFormat]
|
|
28
|
+
for (let i = maxStartIndex; i < len; i++)
|
|
29
|
+
{
|
|
30
|
+
result[i - maxStartIndex][key] = formatter(arr[i])
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if(precision && priceBased.has(key))
|
|
31
34
|
{
|
|
32
|
-
|
|
35
|
+
const formatter = outputNumberFormatter.precisionNumberCleanString
|
|
36
|
+
for (let i = maxStartIndex; i < len; i++)
|
|
33
37
|
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
const value = arr[i]
|
|
39
|
+
result[i - maxStartIndex][key] = value == null || Number.isNaN(value)
|
|
40
|
+
? value
|
|
41
|
+
: formatter(value, precisionMultiplier, precisionDecimals)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
{
|
|
46
|
+
for (let i = maxStartIndex; i < len; i++)
|
|
37
47
|
{
|
|
38
|
-
result[i - maxStartIndex][key] =
|
|
48
|
+
result[i - maxStartIndex][key] = arr[i]
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
}
|
package/src/volatility/adx.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { FasterADX } from '
|
|
1
|
+
import { FasterADX } from '../core-indicators/index.js';
|
|
2
2
|
import { mathLog } from '../utilities/math.js';
|
|
3
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
4
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
3
5
|
|
|
4
6
|
export const adx = (main, index, size, { lag, retLogs }) => {
|
|
5
7
|
const { verticalOhlcv, instances, useFullNames } = main;
|
|
@@ -10,7 +12,7 @@ export const adx = (main, index, size, { lag, retLogs }) => {
|
|
|
10
12
|
|
|
11
13
|
// Initialization on the first index.
|
|
12
14
|
if (index === 0) {
|
|
13
|
-
|
|
15
|
+
validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'adx');
|
|
14
16
|
|
|
15
17
|
if (!verticalOhlcv.hasOwnProperty('high')) {
|
|
16
18
|
throw new Error('Property high not found in verticalOhlcv for adx.');
|
|
@@ -28,32 +30,17 @@ export const adx = (main, index, size, { lag, retLogs }) => {
|
|
|
28
30
|
[instanceKey]: new FasterADX(size)
|
|
29
31
|
});
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
[adxKey]: new Float64Array(len).fill(NaN)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (lag > 0) {
|
|
37
|
-
main.lag([adxKey], lag);
|
|
38
|
-
}
|
|
33
|
+
initializeColumns(main, [{ key: adxKey }], { lag });
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
const high = verticalOhlcv.high[index];
|
|
42
37
|
const low = verticalOhlcv.low[index];
|
|
43
38
|
const close = verticalOhlcv.close[index];
|
|
44
39
|
|
|
45
|
-
let currentAdx = NaN;
|
|
46
|
-
|
|
47
40
|
// Update the ADX indicator.
|
|
48
41
|
instances[instanceKey].update({ high, low, close });
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
currentAdx = instances[instanceKey].getResult();
|
|
52
|
-
|
|
53
|
-
instances[instanceKey].get
|
|
54
|
-
} catch (err) {
|
|
55
|
-
currentAdx = NaN;
|
|
56
|
-
}
|
|
43
|
+
const currentAdx = instances[instanceKey].isStable ? instances[instanceKey].getResult() : NaN;
|
|
57
44
|
|
|
58
45
|
|
|
59
46
|
const adxVal = Number.isNaN(currentAdx)
|
|
@@ -62,4 +49,4 @@ export const adx = (main, index, size, { lag, retLogs }) => {
|
|
|
62
49
|
|
|
63
50
|
|
|
64
51
|
main.pushToMain({ index, key: adxKey, value: adxVal });
|
|
65
|
-
};
|
|
52
|
+
};
|
package/src/volatility/atr.js
CHANGED
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
|
|
2
|
-
import {FasterATR, FasterWSMA} from '
|
|
2
|
+
import {FasterATR, FasterWSMA} from '../core-indicators/index.js';
|
|
3
3
|
import { mathLog } from '../utilities/math.js';
|
|
4
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
5
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
4
6
|
|
|
5
7
|
export const atr = (main, index, size, {lag, retLogs}) => {
|
|
6
|
-
const { verticalOhlcv, instances
|
|
8
|
+
const { verticalOhlcv, instances } = main
|
|
7
9
|
const baseKeyName = retLogs ? `ret_log_atr_${size}` : `atr_${size}`
|
|
8
10
|
|
|
9
11
|
if (index === 0) {
|
|
10
12
|
|
|
11
|
-
const {instances, verticalOhlcv
|
|
13
|
+
const {instances, verticalOhlcv} = main
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const keyNames = [baseKeyName]
|
|
15
|
+
validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'atr')
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
verticalOhlcv[k] = new Float64Array(len).fill(NaN)
|
|
20
|
-
priceBased.add(k)
|
|
21
|
-
}
|
|
17
|
+
instances[baseKeyName] = new FasterATR(size, FasterWSMA)
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
main.lag(keyNames, lag)
|
|
26
|
-
}
|
|
19
|
+
initializeColumns(main, [{ key: baseKeyName, priceBased: !retLogs }], { lag })
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
// Retrieve the current price value.
|
|
@@ -36,15 +29,9 @@ export const atr = (main, index, size, {lag, retLogs}) => {
|
|
|
36
29
|
|
|
37
30
|
// Update the moving average instance.
|
|
38
31
|
instance.update(curr);
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
currAtr = instance.getResult();
|
|
42
|
-
} catch (err) {
|
|
43
|
-
|
|
44
|
-
}
|
|
32
|
+
const currAtr = instance.isStable ? instance.getResult() : NaN;
|
|
45
33
|
|
|
46
34
|
// Always push the MA value (even if NaN).
|
|
47
35
|
main.pushToMain({ index, key: baseKeyName, value: Number.isNaN(currAtr) ? NaN : (retLogs ? mathLog(currAtr, curr.close) : currAtr) });
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
}
|
|
37
|
+
}
|