ohlcv-indicators 3.3.4 → 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/index.js CHANGED
@@ -1,12 +1,10 @@
1
1
  import { mainLoop } from './src/core-functions/mainLoop.js'
2
2
  import { correlation } from './src/studies/correlation.js'
3
3
  import { isAlreadyComputed, validateArray, validateObject, validateArrayOptions, validateBoolean, validateNumber } from './src/utilities/validators.js'
4
- import { divideByMultiplier } from './src/utilities/numberUtilities.js'
5
4
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
6
5
  import { pushToMain } from './src/core-functions/pushToMain.js'
7
6
  import { assignTypes } from './src/utilities/assignTypes.js'
8
7
  import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
9
- import { selectDateFormatter, dateFormaters } from './src/utilities/dateUtilities.js'
10
8
 
11
9
  /**
12
10
  * Class OHLCV_INDICATORS
@@ -27,37 +25,31 @@ export default class OHLCV_INDICATORS {
27
25
 
28
26
  this.firstRow = input[0]
29
27
 
30
- this.inputCols = Object.keys(this.firstRow)
31
-
32
28
  const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
33
-
29
+
34
30
  this.inputTypes = inputTypes
35
31
  this.arrayTypes = arrayTypes
36
-
37
-
38
32
  if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
39
33
 
40
34
  this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
41
35
  this.isComputed = false
42
36
  this.input = input
43
- this.priceBased = ['open', 'high', 'low', 'close']
37
+ this.priceBased = new Set(['open', 'high', 'low', 'close'])
44
38
  this.len = input.length
45
39
  this.instances = {}
46
40
  this.crossPairsList = []
47
41
  this.verticalOhlcv = {}
42
+ this.verticalOhlcvKeyNames = []
48
43
 
49
- this.studies = {}
50
44
  this.utilities = {
51
45
  correlation
52
46
  }
53
47
 
54
48
  this.invalidValueIndex = -1
55
-
56
49
  this.precision = precision
57
50
  this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
58
51
  this.ScaledGroups = {}
59
52
 
60
-
61
53
  if(inputParams !== null)
62
54
  {
63
55
  validateArray(inputParams, 'inputParams', 'constructor')
@@ -70,7 +62,6 @@ export default class OHLCV_INDICATORS {
70
62
 
71
63
  this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
72
64
 
73
-
74
65
  return this
75
66
  }
76
67
 
@@ -95,7 +86,7 @@ export default class OHLCV_INDICATORS {
95
86
  let newArr = shouldSlice ? arr.slice(-sliceLength) : arr;
96
87
 
97
88
  // If this key is price based and precision is enabled, map over the array.
98
- if (precision && priceBased.includes(key)) {
89
+ if (precision && priceBased.has(key)) {
99
90
  newArr = newArr.map(v => (v == null ? null : v / precisionMultiplier));
100
91
  }
101
92
  result[key] = newArr;
@@ -110,9 +101,7 @@ export default class OHLCV_INDICATORS {
110
101
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
111
102
  this.compute()
112
103
 
113
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex} = this
114
-
115
- return verticalToHorizontal(verticalOhlcv, skipNull, precision, precisionMultiplier, priceBased, invalidValueIndex)
104
+ return verticalToHorizontal(skipNull, this, 0)
116
105
  }
117
106
 
118
107
 
@@ -120,16 +109,7 @@ export default class OHLCV_INDICATORS {
120
109
 
121
110
  this.compute()
122
111
 
123
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, len} = this
124
- const keyNames = Object.keys(verticalOhlcv)
125
- const row = {}
126
-
127
- for(const key of keyNames)
128
- {
129
- row[key] = verticalOhlcv[key][len - 1]
130
- }
131
-
132
- return (precision) ? divideByMultiplier({row, precisionMultiplier, priceBased}) : row
112
+ return verticalToHorizontal(false, this, this.len - 1)[0]
133
113
  }
134
114
 
135
115
  compute() {
@@ -150,6 +130,11 @@ export default class OHLCV_INDICATORS {
150
130
  if (this.len > 0) {
151
131
  mainLoop(this.input, this);
152
132
  this.isComputed = true;
133
+
134
+ //flushing after mainLoop
135
+ this.input = []
136
+ this.instances = {}
137
+ this.firstRow = []
153
138
  }
154
139
 
155
140
  return this;
@@ -184,13 +169,11 @@ export default class OHLCV_INDICATORS {
184
169
 
185
170
  this.inputParams.push({key: methodName, params: [colKeys, lookback]})
186
171
 
187
- for(let x = 0; x < colKeys.length; x++)
172
+ for(const key of colKeys)
188
173
  {
189
- const key = colKeys[x]
190
-
191
- if(this.priceBased.find(v => key === v))
174
+ if(this.priceBased.has(key))
192
175
  {
193
- this.priceBased.push(key)
176
+ this.priceBased.add(key);
194
177
  }
195
178
  }
196
179
 
@@ -201,7 +184,7 @@ export default class OHLCV_INDICATORS {
201
184
 
202
185
  const methodName = 'relativeVolume'
203
186
 
204
- if(!this.inputCols.includes('volume')) {
187
+ if(!this.inputTypes.hasOwnProperty('volume')) {
205
188
  throw new Error('If "relativeVolume" is called the input ohlcv must contain valid volume properties.')
206
189
  }
207
190
 
@@ -341,7 +324,7 @@ export default class OHLCV_INDICATORS {
341
324
  {
342
325
  const methodName = 'volumeOscillator'
343
326
 
344
- if(!this.inputCols.includes('volume')) {
327
+ if(!this.inputTypes.hasOwnProperty('volume')) {
345
328
  throw new Error('If "volumeOscillator" is called the input ohlcv must contain valid volume properties.')
346
329
  }
347
330
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.3.4",
3
+ "version": "3.3.5",
4
4
  "description": "A JavaScript library featuring optimized trading indicators for OHLCV arrays [{open, high, low, close, value}]. This library is optimized for backtesting and trading bot development.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -29,41 +29,22 @@ const indicatorFunctions = {
29
29
  Scaler
30
30
  };
31
31
 
32
- // Processes the inputParams into a list of indicator calls
33
- const processIndicatorCalls = inputParams =>
34
- Array.isArray(inputParams)
35
- ? inputParams.map(({ key, params }) => ({
36
- key,
37
- fn: indicatorFunctions[key],
38
- args: params
39
- }))
40
- : [];
41
32
 
42
33
  export const mainLoop = (input, main) => {
43
- const { len, inputParams, priceBased, precisionMultiplier, arrayTypes } = main;
34
+ const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes } = main;
44
35
 
45
- validateInputParams(main)
36
+ validateInputParams({inputParams, len})
46
37
 
47
- for(const key of Object.keys(main.inputTypes))
38
+ for(const key of Object.keys(inputTypes))
48
39
  {
49
- main.verticalOhlcv[key] = buildArray(arrayTypes[key], len)
40
+ verticalOhlcv[key] = buildArray(arrayTypes[key], len)
50
41
  }
51
42
 
52
- // Prepare the list of indicator function calls
53
- const indicatorCalls = processIndicatorCalls(inputParams);
54
-
55
43
  // Process each row in the input
56
- for (let x = 0; x < len; x++) {
57
- const curr = input[x]
58
-
59
- processThisRow({x, main, curr, indicatorCalls, priceBased, precisionMultiplier})
60
- }
61
- }
62
-
63
- const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMultiplier}) => {
64
-
65
-
66
- for(const [key, formaterKey] of Object.entries(main.inputTypes))
44
+ for (let index = 0; index < len; index++) {
45
+ const curr = input[index]
46
+
47
+ for(const [key, formaterKey] of Object.entries(inputTypes))
67
48
  {
68
49
  const value = curr[key]
69
50
 
@@ -71,30 +52,34 @@ const processThisRow = ({x, main, curr, indicatorCalls, priceBased, precisionMul
71
52
  {
72
53
  let formatedValue = numberFormater[formaterKey](value)
73
54
 
74
- if(precisionMultiplier > 1 && priceBased.includes(key))
55
+ if(precisionMultiplier > 1 && priceBased.has(key))
75
56
  {
76
57
  formatedValue = formatedValue * precisionMultiplier
77
58
  }
78
59
 
79
- main.pushToMain({index: x, key, value: formatedValue})
60
+ main.pushToMain({index, key, value: formatedValue})
80
61
  }
81
62
  else if(dateFormaters.hasOwnProperty(formaterKey))
82
63
  {
83
- main.pushToMain({index: x, key, value: dateFormaters[formaterKey](value)})
64
+ main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
84
65
  }
85
66
  else
86
67
  {
87
- main.pushToMain({index: x, key, value})
68
+ main.pushToMain({index, key, value})
88
69
  }
89
70
  }
71
+
72
+ // Run all indicator functions except for the ones processed later
73
+ for (const { key, params } of inputParams) {
74
+ if (key === 'lag' || key === 'crossPairs') continue;
75
+ // resolve fn on-demand, no per-item object allocation here
76
+ indicatorFunctions[key](main, index, ...params)
77
+ }
78
+
79
+ // Process these indicators separately (ensuring their execution order)
80
+ lag(main, index)
81
+ crossPairs(main, index)
82
+ }
90
83
 
91
- // Run all indicator functions except for the ones processed later
92
- for (const { key, fn, args } of indicatorCalls) {
93
- if (['crossPairs', 'lag'].includes(key)) continue;
94
- fn(main, x, ...args)
95
- }
96
-
97
- // Process these indicators separately (ensuring their execution order)
98
- lag(main, x)
99
- crossPairs(main, x)
100
- }
84
+ verticalOhlcvKeyNames.push(...Object.keys(verticalOhlcv))
85
+ }
@@ -45,7 +45,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
45
45
 
46
46
  // Set up additional arrays for each range property.
47
47
  for (const rangeKey of range) {
48
- if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
48
+ if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
49
49
  throw new Error(`Invalid range item value "${rangeKey}" property for bollingerBands. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
50
50
  }
51
51
  keyNames.push(`${prefix}_range_${rangeKey}`)
@@ -60,7 +60,9 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
60
60
  main.lag(keyNames, lag)
61
61
  }
62
62
 
63
- priceBased.push(`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`);
63
+ [`${prefix}_upper`, `${prefix}_middle`, `${prefix}_lower`].forEach(v => {
64
+ priceBased.add(v)
65
+ })
64
66
 
65
67
  for(const key of keyNames)
66
68
  {
@@ -27,7 +27,7 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
27
27
 
28
28
  // Set up additional arrays for each range property.
29
29
  for (const rangeKey of range) {
30
- if (!(rangeKey in verticalOhlcv) || !priceBased.includes(rangeKey)) {
30
+ if (!(rangeKey in verticalOhlcv) || !priceBased.has(rangeKey)) {
31
31
  throw new Error(`Invalid range item value "${rangeKey}" property for donchianChannels. Only price based key names are accepted:\n${JSON.stringify(priceBased)}`);
32
32
  }
33
33
  keyNames.push(`${prefix}_range_${rangeKey}`)
@@ -42,7 +42,10 @@ export const donchianChannels = (main, index, size, offset, { height, range, lag
42
42
  main.lag(keyNames, lag)
43
43
  }
44
44
 
45
- priceBased.push(`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`);
45
+
46
+ [`${prefix}_upper`, `${prefix}_basis`, `${prefix}_lower`].forEach(v => {
47
+ priceBased.add(v)
48
+ })
46
49
 
47
50
  instances.donchian_channel.settings[indicatorKey] = {
48
51
  maxDeque: [], // will hold indices for highs in descending order
@@ -47,7 +47,9 @@ export const macd = (main, index, fast, slow, signal, {target, lag}) => {
47
47
  [histogramKey]: new Float64Array(len).fill(NaN),
48
48
  });
49
49
 
50
- priceBased.push(diffKey, deaKey, histogramKey)
50
+ [diffKey, deaKey, histogramKey].forEach(v => {
51
+ priceBased.add(v)
52
+ })
51
53
 
52
54
  const keyNames = [diffKey, deaKey, histogramKey]
53
55
 
@@ -28,7 +28,8 @@ export const movingAverages = (main, index, indicatorName, size, { target, lag }
28
28
  };
29
29
 
30
30
  verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
31
- priceBased.push(keyName);
31
+
32
+ priceBased.add(keyName);
32
33
 
33
34
  if(lag > 0)
34
35
  {
@@ -17,10 +17,10 @@ export const lag = (main, index) => {
17
17
  {
18
18
  for (const colKey of colKeys)
19
19
  {
20
- if(priceBased.includes(colKey))
20
+ if(priceBased.has(colKey))
21
21
  {
22
22
  for (let step = 1; step <= lookback; step++) {
23
- priceBased.push(`${colKey}_lag_${step}`)
23
+ priceBased.add(`${colKey}_lag_${step}`)
24
24
  }
25
25
  }
26
26
  }
@@ -37,7 +37,7 @@ export const Scaler = (
37
37
  if (!verticalOhlcv.hasOwnProperty(target)) {
38
38
  throw new Error(`Target property "${target}" not found in verticalOhlcv`);
39
39
  }
40
- if (!priceBased.includes(target)) {
40
+ if (!priceBased.has(target)) {
41
41
  throw new Error(`Column "${target}" is not priceBased and cannot be scaled.`);
42
42
  }
43
43
 
@@ -1,7 +1,7 @@
1
1
  import { classifyNum } from "./numberUtilities.js";
2
2
  import { selectDateFormatter } from "./dateUtilities.js";
3
3
 
4
- const numberKeys = ['open', 'high', 'low', 'close']
4
+ const numberKeys = new Set(['open', 'high', 'low', 'close'])
5
5
 
6
6
  export const buildArray = (arrayType, len) => {
7
7
  if(arrayType === 'Array') return new Array(len).fill(null)
@@ -24,7 +24,7 @@ export const assignTypes = firstRow => {
24
24
  inputTypes[colName] = classifyNum(cellValue, true)
25
25
  arrayTypes[colName] = 'Int32Array'
26
26
  }
27
- else if(numberKeys.includes(colName))
27
+ else if(numberKeys.has(colName))
28
28
  {
29
29
  inputTypes[colName] = classifyNum(cellValue, true)
30
30
  arrayTypes[colName] = 'Float64Array'
@@ -15,7 +15,7 @@ export const divideByMultiplier = ({row, precisionMultiplier, priceBased}) => {
15
15
 
16
16
  let newValue = value
17
17
 
18
- if(priceBased.includes(key))
18
+ if(priceBased.has(key))
19
19
  {
20
20
  newValue = newValue / precisionMultiplier
21
21
  }
@@ -4,7 +4,7 @@ export const calcPrecisionMultiplier = (main, firstRow) => {
4
4
  if(precision === false) return 1
5
5
 
6
6
  let output = 1
7
- const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.includes(k)).map(([_, v]) => v)
7
+ const priceBasedValues = Object.entries(firstRow).filter(([k, _]) => priceBased.has(k)).map(([_, v]) => v)
8
8
 
9
9
 
10
10
  for(const value of priceBasedValues)
@@ -1,6 +1,4 @@
1
- export const validateInputParams = main => {
2
-
3
- const {inputParams, len} = main
1
+ export const validateInputParams = ({inputParams, len}) => {
4
2
 
5
3
  for (const { params } of inputParams) {
6
4
  for (const v of params) {
@@ -1,31 +1,29 @@
1
1
  export const verticalToHorizontal = (
2
- obj,
3
2
  skipNull = false,
4
- precision,
5
- precisionMultiplier,
6
- priceBased,
7
- invalidValueIndex = 0
3
+ main,
4
+ startIndex = 0
8
5
  ) => {
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
6
 
7
+ const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex, len, verticalOhlcvKeyNames} = main
8
+
9
+ if (verticalOhlcvKeyNames.length === 0) return []
17
10
 
18
- for (let i = startIndex; i < endIndex; i++) {
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++) {
19
17
  const row = {};
20
- for (let j = 0; j < keys.length; j++) {
21
- const key = keys[j]
22
- const value = obj[key][i]
18
+ for (let j = 0; j < verticalOhlcvKeyNames.length; j++) {
19
+ const key = verticalOhlcvKeyNames[j]
20
+ const value = verticalOhlcv[key][i]
23
21
 
24
- row[key] = (precision && priceBased.includes(key)) ? value / precisionMultiplier : value
22
+ row[key] = (precision && priceBased.has(key)) ? value / precisionMultiplier : value
25
23
 
26
24
  }
27
25
 
28
- result[i - startIndex] = row
26
+ result[i - maxStartIndex] = row
29
27
  }
30
28
 
31
29
  return result