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.
Files changed (43) hide show
  1. package/AGENTS.md +113 -0
  2. package/README.md +456 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
  5. package/index.js +112 -84
  6. package/package.json +2 -5
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +35 -30
  9. package/src/core-functions/pushToMain.js +8 -27
  10. package/src/core-indicators/README.md +60 -0
  11. package/src/core-indicators/composites.js +136 -0
  12. package/src/core-indicators/index.js +5 -0
  13. package/src/core-indicators/indicator.js +81 -0
  14. package/src/core-indicators/movingAverages.js +88 -0
  15. package/src/core-indicators/rsi.js +41 -0
  16. package/src/core-indicators/volatility.js +107 -0
  17. package/src/moving-averages/bollingerBands.js +39 -31
  18. package/src/moving-averages/donchianChannel.js +62 -36
  19. package/src/moving-averages/heikenAshi.js +33 -52
  20. package/src/moving-averages/macd.js +10 -26
  21. package/src/moving-averages/movingAverages.js +19 -25
  22. package/src/moving-averages/relativeVolume.js +30 -24
  23. package/src/oscillators/mfi.js +56 -0
  24. package/src/oscillators/rsi.js +7 -26
  25. package/src/oscillators/stochastic.js +6 -19
  26. package/src/oscillators/volumeDelta.js +36 -28
  27. package/src/oscillators/volumeOscillator.js +24 -32
  28. package/src/studies/candleFeatures.js +40 -28
  29. package/src/studies/dateTime.js +54 -56
  30. package/src/studies/findCrosses.js +18 -16
  31. package/src/studies/lag.js +32 -20
  32. package/src/utilities/assignTypes.js +1 -1
  33. package/src/utilities/config.js +31 -0
  34. package/src/utilities/numberUtilities.js +5 -3
  35. package/src/utilities/precisionMultiplier.js +11 -6
  36. package/src/utilities/validators.js +40 -1
  37. package/src/utilities/verticalToHorizontal.js +26 -16
  38. package/src/volatility/adx.js +7 -20
  39. package/src/volatility/atr.js +10 -23
  40. package/test/test.js +1230 -0
  41. package/webpack.config.js +0 -11
  42. package/src/moving-averages/vidya.js +0 -181
  43. 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 mulStr = String(multiplier);
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 = (multiplier === 1) ? 0 : (String(multiplier).length - 1)
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 { params } of inputParams) {
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
- for (let i = maxStartIndex; i < len; i++)
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
- if(key === 'date')
35
+ const formatter = outputNumberFormatter.precisionNumberCleanString
36
+ for (let i = maxStartIndex; i < len; i++)
33
37
  {
34
- result[i - maxStartIndex][key] = dateOutputFormaters[dateFormat](arr[i])
35
- }
36
- else
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] = formatter[key](arr[i], precisionMultiplier)
48
+ result[i - maxStartIndex][key] = arr[i]
39
49
  }
40
50
  }
41
51
  }
@@ -1,5 +1,7 @@
1
- import { FasterADX } from 'trading-signals';
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
- const { len } = main;
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
- Object.assign(verticalOhlcv, {
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
- try {
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
+ };
@@ -1,29 +1,22 @@
1
1
 
2
- import {FasterATR, FasterWSMA} from 'trading-signals';
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, priceBased } = main
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, len} = main
13
+ const {instances, verticalOhlcv} = main
12
14
 
13
- instances[baseKeyName] = new FasterATR(size, FasterWSMA)
14
-
15
- const keyNames = [baseKeyName]
15
+ validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'atr')
16
16
 
17
- for(const k of keyNames)
18
- {
19
- verticalOhlcv[k] = new Float64Array(len).fill(NaN)
20
- priceBased.add(k)
21
- }
17
+ instances[baseKeyName] = new FasterATR(size, FasterWSMA)
22
18
 
23
- if(lag > 0)
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
- let currAtr = NaN;
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
- return true;
50
- }
37
+ }