ohlcv-indicators 3.5.4 → 3.5.42

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,77 +1,44 @@
1
- import { buildArray } from "../utilities/assignTypes.js";
2
-
3
- export const lag = (main, index) => {
4
-
5
- const {instances, verticalOhlcv, len, arrayTypes, priceBased} = main
6
-
7
- if(index === 0)
8
- {
9
- const {inputParams} = main
10
-
11
- const params = []
12
-
13
- for (const o of inputParams) {
14
- if (o.key === 'lag') {
15
- params.push(o.params)
16
- }
17
- }
18
-
19
- for (const [colKeys, lookback] of params)
20
- {
21
- for (const colKey of colKeys)
22
- {
23
- if(priceBased.has(colKey))
24
- {
25
- for (let step = 1; step <= lookback; step++) {
26
- priceBased.add(`${colKey}_lag_${step}`)
27
- }
28
- }
29
- }
30
- }
31
-
32
- instances.lag = {
33
- lagParams: params
34
- }
1
+ import { buildArray } from "../utilities/assignTypes.js"
2
+
3
+ export const lag = (main, index, colKeys, lookback) => {
4
+
5
+
6
+
7
+ const { verticalOhlcv, len, arrayTypes } = main
8
+
9
+ if(index === 0)
10
+ {
11
+ for (const colKey of colKeys) {
12
+ if (!arrayTypes.hasOwnProperty(colKey)) {
13
+ throw new Error(
14
+ `Lag processing invoked by "${colKey}" expected arrayTypes to have a "${colKey}" property, but it wasn’t found.`
15
+ )
16
+ }
17
+
18
+ for (let step = 1; step <= lookback; step++) {
19
+ const key = `${colKey}_lag_${step}`
20
+ verticalOhlcv[key] = buildArray(arrayTypes[colKey], len)
21
+ arrayTypes[key] = arrayTypes[colKey]
22
+ }
35
23
  }
36
-
37
- const {lagParams} = instances.lag
38
-
39
- for (const [colKeys, lookback] of lagParams) {
40
-
41
- for (const colKey of colKeys) {
42
- const currentColumn = verticalOhlcv[colKey];
43
-
44
- // Initialize lagged arrays only on the first index
45
- if (index === 0) {
46
- for (let step = 1; step <= lookback; step++) {
47
- const key = `${colKey}_lag_${step}`;
48
-
49
- if(!arrayTypes.hasOwnProperty(colKey))
50
- {
51
- throw new Error(`Lag processing invoked by "${colKey}" expected arrayTypes to have a "${colKey}" property, but it wasn’t found.`)
52
- }
24
+ }
53
25
 
54
- verticalOhlcv[key] = buildArray(arrayTypes[colKey], len)
55
- arrayTypes[key] = arrayTypes[colKey]
56
- }
57
- }
26
+ for (const colKey of colKeys) {
27
+ const currentColumn = verticalOhlcv[colKey]
58
28
 
59
- // Populate lagged values
60
- for (let step = 1; step <= lookback; step++) {
61
- const key = `${colKey}_lag_${step}`;
62
- const laggedIndex = index - step;
29
+ // Populate the lagged values each tick:
30
+ for (let step = 1; step <= lookback; step++) {
31
+ const key = `${colKey}_lag_${step}`
32
+ const laggedIndex = index - step
33
+ const value =
34
+ laggedIndex < 0 || currentColumn[laggedIndex] === undefined
35
+ ? null
36
+ : currentColumn[laggedIndex]
63
37
 
64
- const value = (laggedIndex <= 0 || typeof currentColumn[laggedIndex] === 'undefined')
65
- ? null
66
- : currentColumn[laggedIndex]
38
+ main.pushToMain({ index, key, value })
67
39
 
68
- main.pushToMain({index, key, value})
69
- }
70
- }
71
40
  }
72
-
73
-
74
-
41
+ }
75
42
 
76
- return true;
77
- };
43
+ return true
44
+ }
@@ -0,0 +1,47 @@
1
+ export const mapCols = (main, index, newCols, callback, {lag}) => {
2
+
3
+
4
+
5
+ const {verticalOhlcv, len, arrayTypes} = main
6
+
7
+ if(index === 0)
8
+ {
9
+
10
+ for(const key of newCols)
11
+ {
12
+ if(verticalOhlcv.hasOwnProperty(key)) {
13
+ throw new Error(`New property "${key}" already exist in "verticalOhlcv" and can not be modified using mapCols.`)
14
+ }
15
+
16
+ verticalOhlcv[key] = new Array(len).fill(0)
17
+ arrayTypes[key] = 'Array'
18
+ }
19
+
20
+ if(lag > 0)
21
+ {
22
+ main.lag(newCols, lag)
23
+ }
24
+ }
25
+
26
+ const cols = callback({index, main})
27
+
28
+ if(cols == null) return true
29
+
30
+ for(const [key, value] of Object.entries(cols))
31
+ {
32
+ main.pushToMain({ index, key, value })
33
+ }
34
+ }
35
+
36
+ export const defaultMapColsCallback = ({main, index}) => {
37
+ const {verticalOhlcv} = main
38
+
39
+ const currClose = verticalOhlcv.close[index]
40
+ const prevClose = verticalOhlcv.close[index-1]
41
+
42
+ if(typeof prevClose === 'undefined') return null
43
+
44
+ return {
45
+ change: (currClose - prevClose) / prevClose
46
+ }
47
+ }
@@ -4,12 +4,28 @@ import { selectDateFormatter } from "./dateUtilities.js";
4
4
  const numberKeys = new Set(['open', 'high', 'low', 'close'])
5
5
 
6
6
  export const buildArray = (arrayType, len) => {
7
- if(arrayType === 'Array') return new Array(len).fill(null)
8
- if(arrayType === 'Float64Array') return new Float64Array(len).fill(NaN)
9
- if(arrayType === 'Int32Array') return new Int32Array(len).fill(NaN)
7
+ const ctorMap = {
8
+ Array: Array,
9
+ Float64Array: Float64Array,
10
+ Float32Array: Float32Array,
11
+ Int32Array: Int32Array,
12
+ Uint8Array: Uint8Array,
13
+ Int8Array: Int8Array,
14
+ Int16Array: Int16Array,
15
+ }
16
+
17
+ const Ctor = ctorMap[arrayType];
18
+ if (!Ctor || typeof Ctor.from !== 'function') {
19
+ throw new Error(`Unsupported array type: ${arrayType}`);
20
+ }
21
+ // Arrays get nulls, typed-arrays get NaNs
22
+ const fillValue = arrayType === 'Array' ? null : NaN;
23
+ // Ctor.from({ length }, mapFn) works for Array *and* all TypedArrays
24
+ return Ctor.from({ length: len }, () => fillValue);
10
25
  }
11
26
 
12
- export const assignTypes = firstRow => {
27
+ export const assignTypes = main => {
28
+ const {firstRow, notNumberKeys} = main
13
29
  const inputTypes = {}
14
30
  const arrayTypes = {}
15
31
 
@@ -18,11 +34,13 @@ export const assignTypes = firstRow => {
18
34
  if (colName === 'date') {
19
35
  inputTypes[colName] = selectDateFormatter(cellValue, true)
20
36
  arrayTypes[colName] = 'Array'
37
+ notNumberKeys.add(colName)
21
38
  }
22
39
  else if(colName === 'volume')
23
40
  {
24
41
  inputTypes[colName] = classifyNum(cellValue, true)
25
42
  arrayTypes[colName] = 'Int32Array'
43
+ notNumberKeys.add(colName)
26
44
  }
27
45
  else if(numberKeys.has(colName))
28
46
  {
@@ -32,6 +50,12 @@ export const assignTypes = firstRow => {
32
50
  const thisType = classifyNum(cellValue, false)
33
51
  inputTypes[colName] = thisType
34
52
  arrayTypes[colName] = (thisType === 'number') ? 'Float64Array' : 'Array'
53
+
54
+ if(!thisType.startsWith('number'))
55
+ {
56
+ notNumberKeys.add(colName)
57
+ }
58
+
35
59
  }
36
60
  }
37
61
 
@@ -7,32 +7,6 @@ export const numberFormater = {
7
7
  numberDirtyString: n => Number(sanitizeDirtyNumberString(n))
8
8
  }
9
9
 
10
- export const divideByMultiplier = ({row, precisionMultiplier, priceBased}) => {
11
- const output = {}
12
-
13
- for(const [key, value] of Object.entries(row))
14
- {
15
-
16
- let newValue = value
17
-
18
- if(priceBased.has(key))
19
- {
20
- newValue = newValue / precisionMultiplier
21
- }
22
-
23
- output[key] = newValue
24
- }
25
-
26
- return output
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
10
 
37
11
  export const classifyNum = (num, throwError = false) => {
38
12
  // 1) true numbers
@@ -0,0 +1,51 @@
1
+
2
+ import { validateArray, validateString } from "./validators.js"
3
+
4
+ export const getOrderFromArray = (arr, methodName) => {
5
+
6
+ validateArray(arr, 'arr', 'getOrderFromArray')
7
+ validateString(methodName, 'methodName', 'getOrderFromArray')
8
+
9
+ const level2Methods = ['regressor', 'classifier', 'pca']
10
+ const baseKeys = ['open', 'high', 'low', 'close', 'volume', 'date']
11
+
12
+ // 1. Level-2: does this method live in your level-2 list?
13
+ const isSecondary = level2Methods.includes(methodName);
14
+
15
+ // 2. Level-0: is every field one of the primitives *and* not level-2?
16
+ const isPrimary = arr.every(v => baseKeys.includes(v)) && !isSecondary;
17
+
18
+ if(isPrimary)
19
+ {
20
+ return 0
21
+ }
22
+
23
+ let order = 1
24
+ const level1Prefixes = ['_x_', 'zscore_', 'minmax_', 'lag_']
25
+
26
+ for(const k of level1Prefixes)
27
+ {
28
+ if(arr.some(v => v.includes(k)))
29
+ {
30
+ order++
31
+ }
32
+ }
33
+
34
+ if(!isSecondary)
35
+ {
36
+ return order
37
+ }
38
+
39
+ const level2Prefixes = ['prediction_', 'reg_', 'cla_', 'pca_']
40
+ order = 10
41
+
42
+ for(const k of level2Prefixes)
43
+ {
44
+ if(arr.some(v => v.includes(k)))
45
+ {
46
+ order++
47
+ }
48
+ }
49
+
50
+ return order
51
+ }
@@ -1,14 +1,13 @@
1
1
  export const calcPrecisionMultiplier = (main, firstRow) => {
2
- const {precision, priceBased} = main
2
+ const {precision} = main
3
3
 
4
4
  if(precision === false) return 1
5
5
 
6
6
  let output = 1
7
- const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.has(k)).map(([_, v]) => v)
8
7
 
9
-
10
- for(const value of priceBasedValues)
8
+ for(const [key, value] of Object.entries(firstRow))
11
9
  {
10
+ if(new Set(['open', 'high', 'low', 'close']).has(key) === false) continue
12
11
  const [, decimals = ''] = String(value).split('.')
13
12
  const decimalPrecision = Math.max(4, decimals.length)
14
13
  const multiplier = decimalPrecision > 1 ? Math.pow(10, decimalPrecision - 1) : 1
@@ -132,4 +132,11 @@ export const validateArrayOfRanges = (range, paramName, callerName) => {
132
132
  }
133
133
 
134
134
  return true
135
+ }
136
+
137
+ export const validateString = (txt, paramName, callerName) => {
138
+
139
+ if(typeof txt !== 'string' || !txt.length) throw new Error(`Invalid "${paramName}" property in "${callerName}". Only strings are accepted.`)
140
+
141
+ return true
135
142
  }
@@ -2,7 +2,7 @@ import { dateOutputFormaters } from "./dateUtilities.js"
2
2
 
3
3
  export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, dateFormat}) => {
4
4
 
5
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames, verticalOhlcvTempCols} = main
5
+ const {precisionMultiplier, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames, verticalOhlcvTempCols, notNumberKeys} = main
6
6
 
7
7
  if (verticalOhlcvKeyNames.length === 0) return []
8
8
 
@@ -13,7 +13,7 @@ export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, da
13
13
 
14
14
  for(const [key, arr] of Object.entries(verticalOhlcv)){
15
15
  if(verticalOhlcvTempCols.has(key)) continue
16
- const shouldApplyPrecision = priceBased.has(key) && precision
16
+ const shouldApplyPrecision = precision && !notNumberKeys.has(key)
17
17
 
18
18
  for (let i = maxStartIndex; i < len; i++)
19
19
  {
@@ -7,7 +7,7 @@ export const atr = (main, index, size, {lag, percentage, upper, lower}) => {
7
7
 
8
8
  if (index === 0) {
9
9
 
10
- const {instances, verticalOhlcv, arrayTypes, len, priceBased} = main
10
+ const {instances, verticalOhlcv, arrayTypes, len} = main
11
11
 
12
12
  instances[baseKeyName] = new FasterATR(size, FasterWSMA)
13
13
 
@@ -18,12 +18,7 @@ export const atr = (main, index, size, {lag, percentage, upper, lower}) => {
18
18
  if(lower !== null) keyNames.push(`${baseKeyName}_lower`)
19
19
 
20
20
  for(const k of keyNames)
21
- {
22
- if(!k.endsWith('_percentage'))
23
- {
24
- priceBased.add(k)
25
- }
26
-
21
+ {
27
22
  verticalOhlcv[k] = new Float64Array(len).fill(NaN)
28
23
  arrayTypes[k] = 'Float64Array'
29
24
  }