ohlcv-indicators 3.3.1 → 3.3.4

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,12 @@
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'
3
+ import { isAlreadyComputed, validateArray, validateObject, validateArrayOptions, validateBoolean, validateNumber } from './src/utilities/validators.js'
5
4
  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'
5
+ import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
6
+ import { pushToMain } from './src/core-functions/pushToMain.js'
7
+ import { assignTypes } from './src/utilities/assignTypes.js'
8
+ import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
9
+ import { selectDateFormatter, dateFormaters } from './src/utilities/dateUtilities.js'
9
10
 
10
11
  /**
11
12
  * Class OHLCV_INDICATORS
@@ -16,23 +17,31 @@ import { pushToMain } from './src/utilities/pushToMain.js'
16
17
  * OHLCV datasets.
17
18
  */
18
19
 
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
20
  export default class OHLCV_INDICATORS {
22
21
  constructor({input, ticker = null, precision = true, inputParams = null}) {
23
22
 
24
- if(!Array.isArray(input)) throw Error('input OHLCV must be an array: ' + ticker)
23
+ validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
25
24
  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
25
 
26
+ validateBoolean(precision, 'precision', 'constructor')
27
+
28
+ this.firstRow = input[0]
29
+
30
+ this.inputCols = Object.keys(this.firstRow)
31
+
32
+ const {inputTypes, arrayTypes} = assignTypes(this.firstRow)
33
+
34
+ this.inputTypes = inputTypes
35
+ this.arrayTypes = arrayTypes
36
+
37
+
38
+ if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
39
+
40
+ this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
29
41
  this.isComputed = false
30
- this.lastComputedIndex = 0
31
42
  this.input = input
32
- this.inputTypes = (this.hasVolume) ? {open: '', high: '', low: '', close: '', volume: ''} : {open: '', high: '', low: '', close: ''}
33
43
  this.priceBased = ['open', 'high', 'low', 'close']
34
44
  this.len = input.length
35
- this.lastIndexReplace = false
36
45
  this.instances = {}
37
46
  this.crossPairsList = []
38
47
  this.verticalOhlcv = {}
@@ -45,14 +54,13 @@ export default class OHLCV_INDICATORS {
45
54
  this.invalidValueIndex = -1
46
55
 
47
56
  this.precision = precision
48
- this.precisionMultiplier = (this.precision === true) ? 0 : 1
49
-
50
- this.autoMinMaxKeys = []
51
- this.minMaxRanges = {}
57
+ this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
58
+ this.ScaledGroups = {}
52
59
 
53
60
 
54
- if(Array.isArray(inputParams))
61
+ if(inputParams !== null)
55
62
  {
63
+ validateArray(inputParams, 'inputParams', 'constructor')
56
64
  this.inputParams = inputParams
57
65
  this.compute()
58
66
  }
@@ -82,7 +90,6 @@ export default class OHLCV_INDICATORS {
82
90
  const shouldSlice = invalidValueIndex >= 0 && skipNull;
83
91
  const sliceLength = len - (invalidValueIndex + 1);
84
92
 
85
-
86
93
  for (const [key, arr] of Object.entries(verticalOhlcv)) {
87
94
  // If slicing is needed, create a sliced copy, otherwise re-use the array.
88
95
  let newArr = shouldSlice ? arr.slice(-sliceLength) : arr;
@@ -125,102 +132,57 @@ export default class OHLCV_INDICATORS {
125
132
  return (precision) ? divideByMultiplier({row, precisionMultiplier, priceBased}) : row
126
133
  }
127
134
 
128
- compute(change) {
135
+ compute() {
129
136
 
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.
131
-
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
137
+ // If we've already computed, bail out immediately
138
+ if (this.isComputed) {
139
+ return this;
137
140
  }
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
- }
188
-
141
+
142
+ // Mark as “in progress”
143
+ this.isComputed = false;
144
+
145
+ // Figure out whether there’s a date field in the inputs
146
+
147
+
148
+ // Only run the full loop once (or when new data appears later,
149
+ // if you extend this to reset isComputed elsewhere)
150
+ if (this.len > 0) {
151
+ mainLoop(this.input, this);
152
+ this.isComputed = true;
189
153
  }
190
-
191
-
192
-
154
+
193
155
  return this;
194
- }
156
+ }
195
157
 
196
158
 
197
159
 
198
- crossPairs(arr)
160
+ crossPairs(arr = [])
199
161
  {
162
+
163
+ const methodName = 'crossPairs'
164
+
200
165
  isAlreadyComputed(this)
201
166
 
167
+ validateArray(arr, 'arr', methodName)
168
+
202
169
  this.crossPairsList = [...this.crossPairsList, ...arr]
203
- this.inputParams.push({key: 'crossPairs', params: [this.crossPairsList]})
170
+ this.inputParams.push({key: methodName, params: [this.crossPairsList]})
204
171
 
205
172
  return this
206
173
  }
207
174
 
208
175
 
209
- lag(colKeys = ['close'], lags = 1) {
176
+ lag(colKeys = ['close'], lookback = 1) {
210
177
 
211
- isAlreadyComputed(this)
178
+ const methodName = 'lag'
212
179
 
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
- }
180
+ isAlreadyComputed(this)
221
181
 
182
+ validateArray(colKeys, 'colKeys', methodName)
183
+ validateNumber(lookback, {min:1, max: this.len, allowDecimals: false}, 'lookback', methodName)
222
184
 
223
- this.inputParams.push({key: 'lag', params: [colKeys, lags]})
185
+ this.inputParams.push({key: methodName, params: [colKeys, lookback]})
224
186
 
225
187
  for(let x = 0; x < colKeys.length; x++)
226
188
  {
@@ -235,43 +197,59 @@ export default class OHLCV_INDICATORS {
235
197
  return this;
236
198
  }
237
199
 
238
- relativeVolume(size, options = {}) {
200
+ relativeVolume(size = 10, options = {}) {
201
+
202
+ const methodName = 'relativeVolume'
239
203
 
240
- if(this.hasVolume === false) {
204
+ if(!this.inputCols.includes('volume')) {
241
205
  throw new Error('If "relativeVolume" is called the input ohlcv must contain valid volume properties.')
242
206
  }
243
207
 
244
208
  isAlreadyComputed(this)
245
209
 
246
- const {scale = null} = options
210
+ validateNumber(size, {min:1, max: this.len, allowDecimals: false}, 'size', methodName)
211
+ validateObject(options, 'options', methodName)
247
212
 
248
- if (typeof scale === 'number' && !validMagnitudeValues.includes(scale)) {
213
+ const { lag = 0} = options;
249
214
 
250
- throw new Error(`"scale" value in relativeVolume must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
251
- }
215
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
252
216
 
253
- this.inputParams.push({key: 'relativeVolume', params: [size, {scale}]})
217
+ this.inputParams.push({key: methodName, params: [size, {lag}]})
254
218
 
255
219
  return this
256
220
  }
257
221
 
258
- ema(size, options = {}) {
222
+ ema(size = 5, options = {}) {
223
+
224
+ const methodName = 'ema'
259
225
 
260
226
  isAlreadyComputed(this)
261
227
 
262
- const optionArgs = getMovingAveragesParams('ema', size, options, validMagnitudeValues)
228
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
229
+ validateObject(options, 'options', methodName)
230
+
231
+ const {target = 'close', lag = 0} = options
263
232
 
264
- this.inputParams.push({key: 'movingAverages', params: ['ema', size, optionArgs]})
233
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
234
+
235
+ this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
265
236
 
266
237
  return this
267
238
  }
268
- sma(size, options = {}) {
239
+ sma(size = 5, options = {}) {
240
+
241
+ const methodName = 'sma'
269
242
 
270
243
  isAlreadyComputed(this)
271
244
 
272
- const optionArgs = getMovingAveragesParams('sma', size, options, validMagnitudeValues)
245
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
246
+ validateObject(options, 'options', methodName)
247
+
248
+ const {target = 'close', lag = 0} = options
249
+
250
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
273
251
 
274
- this.inputParams.push({key: 'movingAverages', params: ['sma', size, optionArgs]})
252
+ this.inputParams.push({key: methodName, params: [methodName, size, {target, lag}]})
275
253
 
276
254
  return this
277
255
  }
@@ -279,223 +257,140 @@ export default class OHLCV_INDICATORS {
279
257
 
280
258
  macd(fast = 12, slow = 26, signal = 9, options = {}) {
281
259
 
260
+ const methodName = 'macd'
261
+
282
262
  isAlreadyComputed(this)
283
263
 
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
- }
264
+ validateNumber(fast, {min: 1, max: this.len, allowDecimals: false}, 'fast', methodName)
265
+ validateNumber(slow, {min: 1, max: this.len, allowDecimals: false}, 'slow', methodName)
266
+ validateNumber(signal, {min: 1, max: this.len, allowDecimals: false}, 'signal', methodName)
267
+ validateObject(options, 'options', methodName)
293
268
 
294
- if(typeof options !== 'object')
295
- {
296
- throw new Error('"options" must be an object in macd. eg: {target}');
297
- }
298
-
299
- const {target = 'close'} = options
269
+ const {target = 'close', lag = 0} = options
270
+
271
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
300
272
 
301
- this.inputParams.push({key: 'macd', params: [fast, slow, signal, {target}]})
273
+ this.inputParams.push({key: methodName, params: [fast, slow, signal, {target, lag}]})
302
274
 
303
275
  return this
304
276
 
305
277
  }
306
278
  bollingerBands(size = 20, stdDev = 2, options = {}) {
307
279
 
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
- }
280
+ const methodName = 'bollingerBands'
314
281
 
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
- }
282
+ isAlreadyComputed(this)
283
+
284
+ validateNumber(size, {min:1, max: this.len, allowDecimals: false}, 'size', methodName)
285
+ validateNumber(stdDev, {min: 0.01, max: 50, allowDecimals: true}, 'stdDev', methodName)
286
+ validateObject(options, 'options', methodName)
331
287
 
332
- if (typeof autoMinMax !== 'boolean') {
288
+ const {target = 'close', height = false, range = [], lag = 0} = options
333
289
 
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
290
 
344
- throw new Error(`"scale" value in bollingerBands must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
345
- }
346
- }
291
+ validateArray(range, 'range', methodName)
292
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
293
+ validateBoolean(height, 'height', methodName)
347
294
 
348
- this.inputParams.push({key: 'bollingerBands', params: [size, stdDev, {target, height, scale, range, lag, autoMinMax}]});
295
+ this.inputParams.push({key: methodName, params: [size, stdDev, {target, height, range, lag}]});
349
296
 
350
297
  return this;
351
298
  }
352
299
 
353
- rsi(size, options = {})
300
+ rsi(size = 14, options = {})
354
301
  {
355
- isAlreadyComputed(this)
356
-
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
- }
302
+ const methodName = 'rsi'
368
303
 
369
- if (typeof lag !== 'number') {
370
-
371
- throw new Error(`"lag" value in rsi must be a number in rsi.`);
372
- }
304
+ isAlreadyComputed(this)
373
305
 
374
- if (typeof autoMinMax !== 'boolean') {
306
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
307
+ validateObject(options, 'options', methodName)
308
+
309
+ const {target = 'close', lag = 0} = options
375
310
 
376
- throw new Error(`"autoMinMax" value in rsi must be a number in rsi.`);
377
- }
311
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
378
312
 
379
- this.inputParams.push({key: 'rsi', params: [size, {scale, target, lag, autoMinMax}]})
313
+ this.inputParams.push({key: methodName, params: [size, {target, lag}]})
380
314
 
381
315
  return this
382
316
  }
383
317
  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
-
400
- if (typeof lag !== 'number') {
401
- throw new Error(`"lag" value in rsi must be a number in donchianChannels.`);
402
- }
403
318
 
404
- if (typeof autoMinMax !== 'boolean') {
319
+ const methodName = 'donchianChannels'
405
320
 
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)) {
321
+ isAlreadyComputed(this)
416
322
 
417
- throw new Error(`"scale" value in donchianChannels must be any of the following numbers: ${validMagnitudeValues.join(', ')}`);
418
- }
419
- }
420
-
421
- this.inputParams.push({ key: 'donchianChannels', params: [size, offset, { height, range, scale, lag, autoMinMax }] });
323
+
324
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
325
+ validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
422
326
 
423
- return this;
424
- }
327
+ validateObject(options, 'options', methodName)
328
+ const { height = false, range = [], lag = 0} = options;
425
329
 
426
-
427
- candleVectors(size = 200, options = {}) {
428
- isAlreadyComputed(this);
330
+ validateArray(range, 'range', methodName)
331
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
332
+ validateBoolean(height, 'height', methodName)
429
333
 
430
- if (typeof size !== 'number' || size <= 0) {
431
- throw new Error('"size" must be a positive number greater than 0 in candleVectors.');
432
- }
433
-
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
- }
334
+ this.inputParams.push({ key: methodName, params: [size, offset, { height, range, lag }] });
454
335
 
455
- this.inputParams.push({ key: 'candleVectors', params: [size, {stdDev, patternSize, lag, scale, autoMinMax}] });
456
336
  return this;
457
- }
337
+ }
458
338
 
459
339
 
460
340
  volumeOscillator(fastSize = 5, slowSize = 10, options = {})
461
341
  {
342
+ const methodName = 'volumeOscillator'
462
343
 
463
- if(this.hasVolume === false) {
344
+ if(!this.inputCols.includes('volume')) {
464
345
  throw new Error('If "volumeOscillator" is called the input ohlcv must contain valid volume properties.')
465
346
  }
466
347
 
467
348
  isAlreadyComputed(this)
468
349
 
469
- if (typeof fastSize !== 'number' || fastSize <= 1) {
470
- throw new Error('fastSize" must be a positive number greater than 1 in volumeOscillator.');
471
- }
350
+ validateNumber(fastSize, {min: 1, max: this.len, allowDecimals: false}, 'fastSize', methodName)
351
+ validateNumber(slowSize, {min: fastSize, max: this.len, allowDecimals: false}, 'slowSize', methodName)
352
+ validateObject(options, 'options', methodName)
472
353
 
473
- if (typeof slowSize !== 'number' || slowSize <= fastSize) {
474
- throw new Error('"slowSize" must be a positive number greater than the "fastSize" in volumeOscillator.');
475
- }
354
+ const {lag = 0} = options
476
355
 
477
- const {scale = null} = options
356
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
478
357
 
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}]})
358
+ this.inputParams.push({key: methodName, params: [fastSize, slowSize, {lag}]})
485
359
  return this
486
360
  }
487
- dateTime()
361
+ dateTime(options = {})
488
362
  {
363
+
364
+ const methodName = 'dateTime'
365
+
489
366
  isAlreadyComputed(this)
490
367
 
491
- this.inputParams.push({key: 'dateTime', params: []})
368
+ validateObject(options, 'options', methodName)
369
+
370
+ const {lag = 0} = options
371
+
372
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'lag', methodName)
373
+
374
+ this.inputParams.push({key: methodName, params: [{lag}]})
492
375
  return this
493
376
  }
494
- priceVariations()
377
+
378
+ Scaler(size, colKeys, options)
495
379
  {
380
+ const methodName = 'Scaler'
381
+
496
382
  isAlreadyComputed(this)
497
383
 
498
- this.inputParams.push({key: 'priceVariations', params: []})
499
- return this
384
+ validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
385
+ validateArray(colKeys, 'colKeys', methodName)
386
+
387
+ const {group = false, range = [0, 1], lag = 0, type = 'minmax'} = options
388
+
389
+ validateBoolean(group, 'group', methodName)
390
+ validateArray(range, 'range', methodName)
391
+ validateArrayOptions(['minmax', 'zscore'], type, 'type', methodName)
392
+
393
+ this.inputParams.push({key: methodName, params: [size, colKeys, type, group, range, lag]})
394
+ return this
500
395
  }
501
396
  }
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.3.1",
3
+ "version": "3.3.4",
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
  },