ohlcv-indicators 3.3.2 → 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,11 +1,10 @@
1
- import { parseOhlcvToVertical } from './src/utilities/parsing-utilities.js'
1
+ import { mainLoop } from './src/core-functions/mainLoop.js'
2
2
  import { correlation } from './src/studies/correlation.js'
3
- import { validateDate } from './src/utilities/validators.js'
4
- import { isAlreadyComputed } from './src/utilities/validators.js'
5
- import { divideByMultiplier } from './src/utilities/numberUtilities.js'
6
- import { getMovingAveragesParams } from './src/moving-averages/movingAverages.js'
7
- import { verticalToHorizontal } from './src/utilities/dataParsingUtilities.js'
8
- import { pushToMain } from './src/utilities/pushToMain.js'
3
+ import { isAlreadyComputed, validateArray, validateObject, validateArrayOptions, validateBoolean, validateNumber } from './src/utilities/validators.js'
4
+ import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
5
+ import { pushToMain } from './src/core-functions/pushToMain.js'
6
+ import { assignTypes } from './src/utilities/assignTypes.js'
7
+ import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
9
8
 
10
9
  /**
11
10
  * Class OHLCV_INDICATORS
@@ -16,43 +15,44 @@ import { pushToMain } from './src/utilities/pushToMain.js'
16
15
  * OHLCV datasets.
17
16
  */
18
17
 
19
- const validMagnitudeValues = [0.001, 0.002, 0.0025, 0.005, 0.01, 0.02, 0.025, 0.05, 0.1, 0.20, 0.25, 0.5, 1, 2, 2.5, 5, 10]
20
-
21
18
  export default class OHLCV_INDICATORS {
22
19
  constructor({input, ticker = null, precision = true, inputParams = null}) {
23
20
 
24
- if(!Array.isArray(input)) throw Error('input OHLCV must be an array: ' + ticker)
21
+ validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
25
22
  if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
26
- if(!input[0].hasOwnProperty('close')) throw Error('input OHLCV array objects require at least close property: ' + ticker)
27
- this.hasVolume = ((typeof input[0].volume === 'number' && input[0].volume > 0) || (typeof input[0].volume === 'string' && input[0].volume)) ? true : false
28
23
 
24
+ validateBoolean(precision, 'precision', 'constructor')
25
+
26
+ this.firstRow = input[0]
27
+
28
+ const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
29
+
30
+ this.inputTypes = inputTypes
31
+ this.arrayTypes = arrayTypes
32
+ if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
33
+
34
+ this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
29
35
  this.isComputed = false
30
- this.lastComputedIndex = 0
31
36
  this.input = input
32
- this.inputTypes = (this.hasVolume) ? {open: '', high: '', low: '', close: '', volume: ''} : {open: '', high: '', low: '', close: ''}
33
- this.priceBased = ['open', 'high', 'low', 'close']
37
+ this.priceBased = new Set(['open', 'high', 'low', 'close'])
34
38
  this.len = input.length
35
- this.lastIndexReplace = false
36
39
  this.instances = {}
37
40
  this.crossPairsList = []
38
41
  this.verticalOhlcv = {}
42
+ this.verticalOhlcvKeyNames = []
39
43
 
40
- this.studies = {}
41
44
  this.utilities = {
42
45
  correlation
43
46
  }
44
47
 
45
48
  this.invalidValueIndex = -1
46
-
47
49
  this.precision = precision
48
- this.precisionMultiplier = (this.precision === true) ? 0 : 1
49
-
50
- this.autoMinMaxKeys = []
51
- this.minMaxRanges = {}
52
-
50
+ this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
51
+ this.ScaledGroups = {}
53
52
 
54
- if(Array.isArray(inputParams))
53
+ if(inputParams !== null)
55
54
  {
55
+ validateArray(inputParams, 'inputParams', 'constructor')
56
56
  this.inputParams = inputParams
57
57
  this.compute()
58
58
  }
@@ -62,7 +62,6 @@ export default class OHLCV_INDICATORS {
62
62
 
63
63
  this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
64
64
 
65
-
66
65
  return this
67
66
  }
68
67
 
@@ -82,13 +81,12 @@ export default class OHLCV_INDICATORS {
82
81
  const shouldSlice = invalidValueIndex >= 0 && skipNull;
83
82
  const sliceLength = len - (invalidValueIndex + 1);
84
83
 
85
-
86
84
  for (const [key, arr] of Object.entries(verticalOhlcv)) {
87
85
  // If slicing is needed, create a sliced copy, otherwise re-use the array.
88
86
  let newArr = shouldSlice ? arr.slice(-sliceLength) : arr;
89
87
 
90
88
  // If this key is price based and precision is enabled, map over the array.
91
- if (precision && priceBased.includes(key)) {
89
+ if (precision && priceBased.has(key)) {
92
90
  newArr = newArr.map(v => (v == null ? null : v / precisionMultiplier));
93
91
  }
94
92
  result[key] = newArr;
@@ -103,9 +101,7 @@ export default class OHLCV_INDICATORS {
103
101
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
104
102
  this.compute()
105
103
 
106
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, invalidValueIndex} = this
107
-
108
- return verticalToHorizontal(verticalOhlcv, skipNull, precision, precisionMultiplier, priceBased, invalidValueIndex)
104
+ return verticalToHorizontal(skipNull, this, 0)
109
105
  }
110
106
 
111
107
 
@@ -113,165 +109,130 @@ export default class OHLCV_INDICATORS {
113
109
 
114
110
  this.compute()
115
111
 
116
- const {precisionMultiplier, priceBased, precision, verticalOhlcv, len} = this
117
- const keyNames = Object.keys(verticalOhlcv)
118
- const row = {}
119
-
120
- for(const key of keyNames)
121
- {
122
- row[key] = verticalOhlcv[key][len - 1]
123
- }
124
-
125
- return (precision) ? divideByMultiplier({row, precisionMultiplier, priceBased}) : row
112
+ return verticalToHorizontal(false, this, this.len - 1)[0]
126
113
  }
127
114
 
128
- compute(change) {
129
-
130
- //if change is a valid object and change.date is after the last row in this.input[this.input.length - 1].date pushes change to this.input using the parseOhlcvToVertical function.
115
+ compute() {
131
116
 
132
-
133
- //stops the compute if compute is called from getData, getDataAsCols or getLastValues an isComputed is false
134
- if(this.isComputed === true && !change)
135
- {
136
- return this
117
+ // If we've already computed, bail out immediately
118
+ if (this.isComputed) {
119
+ return this;
137
120
  }
138
- else{
139
- this.isComputed = false
140
- }
141
-
142
- //checks the first row in OHLCV input to verify if the date property is valid
143
- this.isValidDate = this.input[0].hasOwnProperty('date') && validateDate(this.input[0].date)
144
-
145
- //compute method can be used to process indicators if no arguments are provided
146
- //compute method can be called to access the .verticalOhlcv object
147
- //compute method is called automatically if getLastValues or getDate methods are called
148
- if (this.len > this.lastComputedIndex && !change) {
149
- parseOhlcvToVertical(this.input, this, 0);
150
- }
151
-
152
- //compute method can be used to add new or update the last datapoints if a new OHLCV object is passed with a valid date property
153
- //valid date properties: "2024-12-16 15:45:00" or "2024-12-16"
154
- if (change && typeof change === 'object') {
155
-
156
- if (!['open', 'high', 'low', 'close', 'volume', 'date'].every(f => Object.keys(change).includes(f))) {
157
- throw Error('Invalid OHLCV object sent in "compute". Correct usage: .compute({open: 108.25, high: 108.410004, low: 108.25, close: 99999999, volume: 875903, date: "2024-12-16 15:45:00" || "2024-12-16"})');
158
- }
159
- if(!this.isValidDate)
160
- {
161
- throw Error('All the OHLCV rows require a valid date property to access the compute change method. Correct date format: "2024-12-16 15:45:00" or "2024-12-16"')
162
- }
163
- if(!validateDate(change.date))
164
- {
165
- throw Error('The date in the new OHLCV row is invalid. Correct date format: "2024-12-16 15:45:00" or "2024-12-16"')
166
- }
167
-
168
- const { date: changeDate } = change;
169
- const lastIndex = this.len - 1;
170
- const inputDate = this.input[lastIndex]?.date;
171
-
172
- //fallback if .compute(change) is triggered before .compute() alone
173
- if (this.lastComputedIndex === 0) {
174
- this.compute()
175
- }
176
-
177
-
178
- if (inputDate !== changeDate) {
179
- // Add new item
180
- this.len++
181
- this.input.push(change)
182
- parseOhlcvToVertical(this.input, this, this.len)
183
- } else {
184
- // Modify the last item
185
- this.input[lastIndex] = change
186
- parseOhlcvToVertical(this.input, this, lastIndex)
187
- }
121
+
122
+ // Mark as “in progress”
123
+ this.isComputed = false;
124
+
125
+ // Figure out whether there’s a date field in the inputs
126
+
127
+
128
+ // Only run the full loop once (or when new data appears later,
129
+ // if you extend this to reset isComputed elsewhere)
130
+ if (this.len > 0) {
131
+ mainLoop(this.input, this);
132
+ this.isComputed = true;
188
133
 
134
+ //flushing after mainLoop
135
+ this.input = []
136
+ this.instances = {}
137
+ this.firstRow = []
189
138
  }
190
-
191
-
192
-
139
+
193
140
  return this;
194
- }
141
+ }
195
142
 
196
143
 
197
144
 
198
- crossPairs(arr)
145
+ crossPairs(arr = [])
199
146
  {
147
+
148
+ const methodName = 'crossPairs'
149
+
200
150
  isAlreadyComputed(this)
201
151
 
152
+ validateArray(arr, 'arr', methodName)
153
+
202
154
  this.crossPairsList = [...this.crossPairsList, ...arr]
203
- this.inputParams.push({key: 'crossPairs', params: [this.crossPairsList]})
155
+ this.inputParams.push({key: methodName, params: [this.crossPairsList]})
204
156
 
205
157
  return this
206
158
  }
207
159
 
208
160
 
209
- lag(colKeys = ['close'], lags = 1) {
161
+ lag(colKeys = ['close'], lookback = 1) {
210
162
 
211
- isAlreadyComputed(this)
163
+ const methodName = 'lag'
212
164
 
213
- if(!Array.isArray(colKeys))
214
- {
215
- throw new Error('Param "colKeys" must be a valid array of keyNames in lag.')
216
- }
217
- if(typeof lags !== 'number' || !Number.isInteger(lags) || lags < 0)
218
- {
219
- throw new Error(`Param "lags" must be a integer greater or equal to 0: ${JSON.stringify(colKeys)}`)
220
- }
165
+ isAlreadyComputed(this)
221
166
 
167
+ validateArray(colKeys, 'colKeys', methodName)
168
+ validateNumber(lookback, {min:1, max: this.len, allowDecimals: false}, 'lookback', methodName)
222
169
 
223
- this.inputParams.push({key: 'lag', params: [colKeys, lags]})
170
+ this.inputParams.push({key: methodName, params: [colKeys, lookback]})
224
171
 
225
- for(let x = 0; x < colKeys.length; x++)
172
+ for(const key of colKeys)
226
173
  {
227
- const key = colKeys[x]
228
-
229
- if(this.priceBased.find(v => key === v))
174
+ if(this.priceBased.has(key))
230
175
  {
231
- this.priceBased.push(key)
176
+ this.priceBased.add(key);
232
177
  }
233
178
  }
234
179
 
235
180
  return this;
236
181
  }
237
182
 
238
- relativeVolume(size, options = {}) {
183
+ relativeVolume(size = 10, options = {}) {
239
184
 
240
- if(this.hasVolume === false) {
185
+ const methodName = 'relativeVolume'
186
+
187
+ if(!this.inputTypes.hasOwnProperty('volume')) {
241
188
  throw new Error('If "relativeVolume" is called the input ohlcv must contain valid volume properties.')
242
189
  }
243
190
 
244
191
  isAlreadyComputed(this)
245
192
 
246
- const {scale = null} = options
193
+ validateNumber(size, {min:1, max: this.len, allowDecimals: false}, 'size', methodName)
194
+ validateObject(options, 'options', methodName)
247
195
 
248
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
196
+ const { lag = 0} = options;
249
197
 
250
- throw new Error(`"scale" value in relativeVolume must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
251
- }
198
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
252
199
 
253
- this.inputParams.push({key: 'relativeVolume', params: [size, {scale}]})
200
+ this.inputParams.push({key: methodName, params: [size, {lag}]})
254
201
 
255
202
  return this
256
203
  }
257
204
 
258
- ema(size, options = {}) {
205
+ ema(size = 5, options = {}) {
206
+
207
+ const methodName = 'ema'
259
208
 
260
209
  isAlreadyComputed(this)
261
210
 
262
- const optionArgs = getMovingAveragesParams('ema', size, options, validMagnitudeValues)
211
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
212
+ validateObject(options, 'options', methodName)
213
+
214
+ const {target = 'close', lag = 0} = options
263
215
 
264
- this.inputParams.push({key: 'movingAverages', params: ['ema', size, optionArgs]})
216
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
217
+
218
+ this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
265
219
 
266
220
  return this
267
221
  }
268
- sma(size, options = {}) {
222
+ sma(size = 5, options = {}) {
223
+
224
+ const methodName = 'sma'
269
225
 
270
226
  isAlreadyComputed(this)
271
227
 
272
- const optionArgs = getMovingAveragesParams('sma', size, options, validMagnitudeValues)
228
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
229
+ validateObject(options, 'options', methodName)
273
230
 
274
- this.inputParams.push({key: 'movingAverages', params: ['sma', size, optionArgs]})
231
+ const {target = 'close', lag = 0} = options
232
+
233
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
234
+
235
+ this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
275
236
 
276
237
  return this
277
238
  }
@@ -279,223 +240,140 @@ export default class OHLCV_INDICATORS {
279
240
 
280
241
  macd(fast = 12, slow = 26, signal = 9, options = {}) {
281
242
 
282
- isAlreadyComputed(this)
243
+ const methodName = 'macd'
283
244
 
284
- if (typeof fast !== 'number' || fast <= 0) {
285
- throw new Error('"fast" must be a positive number in macd.');
286
- }
287
- if (typeof slow !== 'number' || slow <= fast) {
288
- throw new Error('"slow" must be a positive number greater than "fast" in macd.');
289
- }
290
- if (typeof signal !== 'number' || signal <= 0) {
291
- throw new Error('"signal" must be a positive number in macd.');
292
- }
245
+ isAlreadyComputed(this)
293
246
 
294
- if(typeof options !== 'object')
295
- {
296
- throw new Error('"options" must be an object in macd. eg: {target}');
297
- }
247
+ validateNumber(fast, {min: 1, max: this.len, allowDecimals: false}, 'fast', methodName)
248
+ validateNumber(slow, {min: 1, max: this.len, allowDecimals: false}, 'slow', methodName)
249
+ validateNumber(signal, {min: 1, max: this.len, allowDecimals: false}, 'signal', methodName)
250
+ validateObject(options, 'options', methodName)
298
251
 
299
- const {target = 'close'} = options
252
+ const {target = 'close', lag = 0} = options
253
+
254
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
300
255
 
301
- this.inputParams.push({key: 'macd', params: [fast, slow, signal, {target}]})
256
+ this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag}]})
302
257
 
303
258
  return this
304
259
 
305
260
  }
306
261
  bollingerBands(size = 20, stdDev = 2, options = {}) {
307
262
 
308
- isAlreadyComputed(this)
309
-
310
- if(!options || typeof options !== 'object')
311
- {
312
- throw new Error('"options" must be an object in bollingerBands. eg: {target, height, range}');
313
- }
263
+ const methodName = 'bollingerBands'
314
264
 
315
- const {target = 'close', height = false, range = [], scale = null, lag = 0, autoMinMax = false} = options
316
-
317
- // Validate size and times
318
- if (typeof size !== 'number' || size <= 0) {
319
- throw new Error('"size" must be a positive number in bollingerBands.');
320
- }
321
- if (typeof stdDev !== 'number' || stdDev <= 0) {
322
- throw new Error('"stdDev" must be a positive number in bollingerBands.');
323
- }
324
- if (!Array.isArray(range)) {
325
- throw new Error('If set, "range" must be a array of column names in bollingerBands.');
326
- }
327
- if (typeof lag !== 'number') {
328
-
329
- throw new Error(`"lag" value must be a number in bollingerBands.`);
330
- }
265
+ isAlreadyComputed(this)
266
+
267
+ validateNumber(size, {min:1, max: this.len, allowDecimals: false}, 'size', methodName)
268
+ validateNumber(stdDev, {min: 0.01, max: 50, allowDecimals: true}, 'stdDev', methodName)
269
+ validateObject(options, 'options', methodName)
331
270
 
332
- if (typeof autoMinMax !== 'boolean') {
271
+ const {target = 'close', height = false, range = [], lag = 0} = options
333
272
 
334
- throw new Error(`"autoMinMax" value in rsi must be a number in bollingerBands.`);
335
- }
336
-
337
- if (typeof height !== 'boolean') {
338
- throw new Error('"height" must be a boolean in bollingerBands.');
339
- }
340
- else
341
- {
342
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
343
273
 
344
- throw new Error(`"scale" value in bollingerBands must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
345
- }
346
- }
274
+ validateArray(range, 'range', methodName)
275
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
276
+ validateBoolean(height, 'height', methodName)
347
277
 
348
- this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range, lag, autoMinMax}]});
278
+ this.inputParams.push({key: methodName, params: [size, stdDev, {target, height, range, lag}]});
349
279
 
350
280
  return this;
351
281
  }
352
282
 
353
- rsi(size, options = {})
283
+ rsi(size = 14, options = {})
354
284
  {
355
- isAlreadyComputed(this)
285
+ const methodName = 'rsi'
356
286
 
357
- // Validate size and times
358
- if (typeof size !== 'number' || size <= 0) {
359
- throw new Error('"size" must be a positive number in rsi.');
360
- }
361
-
362
- const {scale = null, target = 'close', lag = 0, autoMinMax = false} = options
363
-
364
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
365
-
366
- throw new Error(`"scale" value in rsi must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
367
- }
368
-
369
- if (typeof lag !== 'number') {
370
-
371
- throw new Error(`"lag" value in rsi must be a number in rsi.`);
372
- }
287
+ isAlreadyComputed(this)
373
288
 
374
- if (typeof autoMinMax !== 'boolean') {
289
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
290
+ validateObject(options, 'options', methodName)
291
+
292
+ const {target = 'close', lag = 0} = options
375
293
 
376
- throw new Error(`"autoMinMax" value in rsi must be a number in rsi.`);
377
- }
294
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
378
295
 
379
- this.inputParams.push({key: 'rsi', params: [size, {scale, target, lag, autoMinMax}]})
296
+ this.inputParams.push({key: methodName, params: [size, {target, lag}]})
380
297
 
381
298
  return this
382
299
  }
383
300
  donchianChannels(size = 20, offset = 0, options = {}) {
384
- isAlreadyComputed(this);
385
-
386
- if (typeof size !== 'number' || size <= 0) {
387
- throw new Error('"size" must be a positive number greater than 0 in donchianChannels.');
388
- }
389
-
390
- if (typeof offset !== 'number' || offset < 0) {
391
- throw new Error('"offset" must be a number greater than or equal to 0 in donchianChannels.');
392
- }
393
-
394
- const { height = false, range = [], scale = null, lag = 0, autoMinMax = false} = options;
395
-
396
- if (!Array.isArray(range)) {
397
- throw new Error('If set, "range" must be an array of column names in donchianChannels.');
398
- }
399
301
 
400
- if (typeof lag !== 'number') {
401
- throw new Error(`"lag" value in rsi must be a number in donchianChannels.`);
402
- }
403
-
404
- if (typeof autoMinMax !== 'boolean') {
302
+ const methodName = 'donchianChannels'
405
303
 
406
- throw new Error(`"autoMinMax" value in rsi must be a number in donchianChannels.`);
407
- }
408
-
409
- if (typeof height !== 'boolean') {
410
- throw new Error('"height" must be a boolean in donchianChannels.');
411
- }
412
- else
413
- {
414
-
415
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
304
+ isAlreadyComputed(this)
416
305
 
417
- throw new Error(`"scale" value in donchianChannels must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
418
- }
419
- }
306
+
307
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
308
+ validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
420
309
 
421
- this.inputParams.push({ key: 'donchianChannels', params: [size, offset, { height, range, scale, lag, autoMinMax }] });
310
+ validateObject(options, 'options', methodName)
311
+ const { height = false, range = [], lag = 0} = options;
422
312
 
423
- return this;
424
- }
425
-
426
-
427
- candleVectors(size = 200, options = {}) {
428
- isAlreadyComputed(this);
313
+ validateArray(range, 'range', methodName)
314
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
315
+ validateBoolean(height, 'height', methodName)
429
316
 
430
- if (typeof size !== 'number' || size <= 0) {
431
- throw new Error('"size" must be a positive number greater than 0 in candleVectors.');
432
- }
317
+ this.inputParams.push({ key: methodName, params: [size, offset, { height, range, lag }] });
433
318
 
434
- const { stdDev = 1, lag = 0, scale = 0.001, patternSize = 0, autoMinMax = false } = options;
435
-
436
-
437
- if (typeof stdDev !== 'number' || stdDev <= 0) {
438
- throw new Error('"stdDev" must be a positive number greater than 0 in candleVectors.');
439
- }
440
-
441
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
442
-
443
- throw new Error(`"scale" value in candleVectors must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
444
- }
445
-
446
- if (typeof patternSize !== 'number' || !Number.isInteger(patternSize) || patternSize < 0 ) {
447
-
448
- throw new Error(`"patternSize" value in candleVectors must be a positive integer.`);
449
- }
450
- if (typeof autoMinMax !== 'boolean') {
451
-
452
- throw new Error(`"autoMinMax" value in candleVectors must be boolean`);
453
- }
454
-
455
- this.inputParams.push({ key: 'candleVectors', params: [size, {stdDev, patternSize, lag, scale, autoMinMax}] });
456
319
  return this;
457
- }
320
+ }
458
321
 
459
322
 
460
323
  volumeOscillator(fastSize = 5, slowSize = 10, options = {})
461
324
  {
325
+ const methodName = 'volumeOscillator'
462
326
 
463
- if(this.hasVolume === false) {
327
+ if(!this.inputTypes.hasOwnProperty('volume')) {
464
328
  throw new Error('If "volumeOscillator" is called the input ohlcv must contain valid volume properties.')
465
329
  }
466
330
 
467
331
  isAlreadyComputed(this)
468
332
 
469
- if (typeof fastSize !== 'number' || fastSize <= 1) {
470
- throw new Error('fastSize" must be a positive number greater than 1 in volumeOscillator.');
471
- }
333
+ validateNumber(fastSize, {min: 1, max: this.len, allowDecimals: false}, 'fastSize', methodName)
334
+ validateNumber(slowSize, {min: fastSize, max: this.len, allowDecimals: false}, 'slowSize', methodName)
335
+ validateObject(options, 'options', methodName)
472
336
 
473
- if (typeof slowSize !== 'number' || slowSize <= fastSize) {
474
- throw new Error('"slowSize" must be a positive number greater than the "fastSize" in volumeOscillator.');
475
- }
337
+ const {lag = 0} = options
476
338
 
477
- const {scale = null} = options
339
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
478
340
 
479
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
480
-
481
- throw new Error(`"scale" value in volumeOscillator must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
482
- }
483
-
484
- this.inputParams.push({key: 'volumeOscillator', params: [fastSize, slowSize, {scale}]})
341
+ this.inputParams.push({key: methodName, params: [fastSize, slowSize, {lag}]})
485
342
  return this
486
343
  }
487
- dateTime()
344
+ dateTime(options = {})
488
345
  {
346
+
347
+ const methodName = 'dateTime'
348
+
489
349
  isAlreadyComputed(this)
490
350
 
491
- this.inputParams.push({key: 'dateTime', params: []})
351
+ validateObject(options, 'options', methodName)
352
+
353
+ const {lag = 0} = options
354
+
355
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
356
+
357
+ this.inputParams.push({key: methodName, params: [{lag}]})
492
358
  return this
493
359
  }
494
- priceVariations()
360
+
361
+ Scaler(size, colKeys, options)
495
362
  {
363
+ const methodName = 'Scaler'
364
+
496
365
  isAlreadyComputed(this)
497
366
 
498
- this.inputParams.push({key: 'priceVariations', params: []})
499
- return this
367
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
368
+ validateArray(colKeys, 'colKeys', methodName)
369
+
370
+ const {group = false, range = [0, 1], lag = 0, type = 'minmax'} = options
371
+
372
+ validateBoolean(group, 'group', methodName)
373
+ validateArray(range, 'range', methodName)
374
+ validateArrayOptions(['minmax', 'zscore'], type, 'type', methodName)
375
+
376
+ this.inputParams.push({key: methodName, params: [size, colKeys, type, group, range, lag]})
377
+ return this
500
378
  }
501
379
  }
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.3.2",
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",
7
7
  "scripts": {
8
8
  "test": "node test/test.js",
9
- "study": "node test/study.js",
10
- "count": "node test/countUps.js",
11
9
  "build": "npx webpack",
12
10
  "cross": "node src/studies/findCrosses2.js"
13
11
  },