ohlcv-indicators 3.5.52 → 3.5.54

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,5 +1,4 @@
1
1
  import { mainLoop } from './src/core-functions/mainLoop.js'
2
- import { correlation } from './src/studies/correlation.js'
3
2
  import {
4
3
  isAlreadyComputed,
5
4
  validateArray,
@@ -14,10 +13,10 @@ import {
14
13
  import { verticalToHorizontal } from './src/utilities/verticalToHorizontal.js'
15
14
  import { pushToMain } from './src/core-functions/pushToMain.js'
16
15
  import { assignTypes } from './src/utilities/assignTypes.js'
17
- import { buildArray, getArrayType } from './src/utilities/assignTypes.js'
18
16
  import { dateOutputFormaters } from './src/utilities/dateUtilities.js'
19
17
  import { defaultMapColsCallback } from './src/studies/mapCols.js'
20
18
  import { getOrderFromArray } from './src/utilities/order.js'
19
+ import { calcPrecisionMultiplier } from './src/utilities/precisionMultiplier.js'
21
20
 
22
21
  import {
23
22
  defaultYCallback,
@@ -38,24 +37,32 @@ import { normalizeMinMax } from './src/machine-learning/ml-utilities.js'
38
37
  */
39
38
 
40
39
  export default class OHLCV_INDICATORS {
41
- constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, ML = {}}) {
40
+ constructor({input, ticker = null, inputParams = null, chunkProcess = 2000, ML = {}, precision = false}) {
42
41
 
43
42
  validateArray(input, 'input', (ticker !== null) ? `contructor ${ticker}` : 'constuctor')
44
43
  if(input.length === 0) throw Error('input OHLCV must not be empty: ' + ticker)
45
44
 
46
45
  validateNumber(chunkProcess, {min: 100, max: 50000, allowDecimals: false}, 'chunkProcess', 'constructor')
47
46
  validateObject(ML, 'ML', 'constructor')
47
+ validateBoolean(precision, 'precision', 'contructor')
48
48
 
49
49
  this.chunkProcess = chunkProcess
50
50
 
51
51
  this.input = [...input]
52
52
  this.len = this.input.length
53
53
  this.firstRow = this.input[0]
54
-
54
+ this.precision = precision
55
+
56
+ const initialPriceBasedArr = ['open', 'high', 'low', 'close']
57
+ this.initialPriceBased = new Set(initialPriceBasedArr)
58
+ this.priceBased = new Set(initialPriceBasedArr)
59
+ this.precisionMultiplier = calcPrecisionMultiplier(this)
60
+
55
61
  const {inputTypes, arrayTypes} = assignTypes(this)
56
62
 
57
63
  this.inputTypes = inputTypes
58
64
  this.arrayTypes = arrayTypes
65
+
59
66
  if(!this.firstRow.hasOwnProperty('close')) throw Error(`input OHLCV array objects require at least "close" property: ${ticker}`)
60
67
 
61
68
  this.dateType = this.inputTypes.date ? this.inputTypes.date : null;
@@ -65,10 +72,7 @@ export default class OHLCV_INDICATORS {
65
72
  this.verticalOhlcv = {}
66
73
  this.verticalOhlcvKeyNames = []
67
74
  this.verticalOhlcvTempCols = new Set()
68
-
69
- this.utilities = {
70
- correlation
71
- }
75
+ this.utilities = {}
72
76
 
73
77
  this.invalidValueIndex = -1
74
78
  this.scaledGroups = {}
@@ -105,50 +109,6 @@ export default class OHLCV_INDICATORS {
105
109
  }
106
110
 
107
111
 
108
- getDataAsCols(options = {}) {
109
- this.compute();
110
-
111
- const {skipNull = true, dateFormat = 'string'} = options
112
-
113
- validateArrayOptions(Object.keys(dateOutputFormaters), dateFormat, 'dateFormat', 'getDataAsCols')
114
- validateObject(options, 'options', 'getDataAsCols')
115
-
116
- const {
117
- len,
118
- invalidValueIndex,
119
- verticalOhlcv,
120
- verticalOhlcvTempCols,
121
- } = this
122
- const result = {}
123
-
124
- const startIndex = skipNull ? invalidValueIndex + 1 : 0
125
- const newLen = len - startIndex
126
-
127
- for (const [key, arr] of Object.entries(verticalOhlcv)) {
128
-
129
- if(verticalOhlcvTempCols.has(key)) continue
130
-
131
- const thisArrType = getArrayType(key, verticalOhlcv[key])
132
-
133
- result[key] = buildArray(thisArrType, newLen)
134
-
135
- for (let x = startIndex; x < len; x++)
136
- {
137
- if(key === 'date')
138
- {
139
- result[key][x] = dateOutputFormaters[dateFormat](arr[x])
140
- }
141
- else{
142
- result[key][x] = arr[x]
143
- }
144
- }
145
-
146
- }
147
-
148
- return result;
149
- }
150
-
151
-
152
112
  getData(options = {}) {
153
113
 
154
114
  //getData method returns the last object (row) of the new OHLCV with indicators: {open, high, low, close, rsi_14, bollinger_bands_upper}
@@ -223,9 +183,9 @@ export default class OHLCV_INDICATORS {
223
183
  validateArray(arr, 'arr', methodName)
224
184
  validateObject(options, 'options', methodName)
225
185
 
226
- const {limit = 125, oneHot = false} = options
186
+ const {limit = null, oneHot = false} = options
227
187
 
228
- validateNumber(limit, {min: 2, max: this.len}, 'options.limit', methodName)
188
+ if(limit !== null) validateNumber(limit, {min: 2, max: this.len}, 'options.limit', methodName)
229
189
  validateBoolean(oneHot, 'options.oneHot', methodName)
230
190
 
231
191
  const orderArr = []
@@ -292,6 +252,27 @@ export default class OHLCV_INDICATORS {
292
252
  return this
293
253
  }
294
254
 
255
+ volumeDelta(options = {}) {
256
+
257
+ const methodName = 'volumeDelta'
258
+
259
+ if(!this.inputTypes.hasOwnProperty('volume')) {
260
+ throw new Error('If "volumeDelta" is called the input ohlcv must contain valid volume properties.')
261
+ }
262
+
263
+ isAlreadyComputed(this)
264
+
265
+ validateObject(options, 'options', methodName)
266
+
267
+ const { lag = 0} = options;
268
+
269
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
270
+
271
+ this.inputParams.push({key: methodName, order: 0, params: [{lag}]})
272
+
273
+ return this
274
+ }
275
+
295
276
  atr(size = 14, options = {}) {
296
277
 
297
278
  const methodName = 'atr'
@@ -355,13 +336,18 @@ export default class OHLCV_INDICATORS {
355
336
  return this
356
337
  }
357
338
 
358
- heikenAshi(smoothLength = 10, afterSmoothLength = 10, options = {}) {
339
+ heikenAshi(smoothLength = null, afterSmoothLength = null, options = {}) {
359
340
  const methodName = 'heikenAshi'
360
341
 
361
342
  isAlreadyComputed(this)
362
343
 
363
- validateNumber(smoothLength, {min: 1, max: this.len, allowDecimals: false}, 'smoothLength', methodName)
364
- validateNumber(afterSmoothLength, {min: 1, max: this.len, allowDecimals: false}, 'afterSmoothLength', methodName)
344
+ const bothNull = smoothLength === null && afterSmoothLength === null;
345
+
346
+ if(bothNull === false) {
347
+ validateNumber(smoothLength, {min: 1, max: this.len, allowDecimals: false}, 'smoothLength', methodName)
348
+ validateNumber(afterSmoothLength, {min: 1, max: this.len, allowDecimals: false}, 'afterSmoothLength', methodName)
349
+ }
350
+
365
351
  validateObject(options, 'options', methodName)
366
352
 
367
353
  const {lag = 0} = options
@@ -370,7 +356,7 @@ export default class OHLCV_INDICATORS {
370
356
 
371
357
  const order = 0
372
358
 
373
- this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag}]})
359
+ this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag, bothNull}]})
374
360
 
375
361
  return this
376
362
  }
@@ -474,17 +460,16 @@ export default class OHLCV_INDICATORS {
474
460
  validateNumber(stdDev, {min: 0.01, max: 50, allowDecimals: true}, 'stdDev', methodName)
475
461
  validateObject(options, 'options', methodName)
476
462
 
477
- const {target = 'close', height = false, range = [], lag = 0, decimals = null} = options
463
+ const {target = 'close', height = false, range = [], lag = 0} = options
478
464
 
479
465
  validateString(target, 'options.target', methodName)
480
466
  validateArray(range, 'options.range', methodName)
481
467
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
482
468
  validateBoolean(height, 'options.height', methodName)
483
- if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
484
469
 
485
470
  const order = getOrderFromArray([target], methodName)
486
471
 
487
- this.inputParams.push({key: methodName, order, params: [size, stdDev, {target, height, range, lag, decimals}]});
472
+ this.inputParams.push({key: methodName, order, params: [size, stdDev, {target, height, range, lag}]});
488
473
 
489
474
  return this;
490
475
  }
@@ -532,19 +517,17 @@ export default class OHLCV_INDICATORS {
532
517
 
533
518
  isAlreadyComputed(this)
534
519
 
535
-
536
520
  validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
537
521
  validateNumber(offset, {min: 0, max: this.len, allowDecimals: false}, 'offset', methodName)
538
522
 
539
523
  validateObject(options, 'options', methodName)
540
- const { height = false, range = [], lag = 0, decimals = null} = options;
524
+ const { height = false, range = [], lag = 0} = options;
541
525
 
542
526
  validateArray(range, 'options.range', methodName)
543
527
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
544
528
  validateBoolean(height, 'options.height', methodName)
545
- if(decimals !== null) validateNumber(decimals, {min: 1, max: 15, allowDecimals: false}, 'decimals', methodName)
546
529
 
547
- this.inputParams.push({ key: methodName, order: 0, params: [size, offset, { height, range, lag, decimals}] });
530
+ this.inputParams.push({ key: methodName, order: 0, params: [size, offset, { height, range, lag}] });
548
531
 
549
532
  return this;
550
533
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.5.52",
3
+ "version": "3.5.54",
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",
@@ -10,6 +10,7 @@ import { relativeVolume } from "../moving-averages/relativeVolume.js";
10
10
  import { donchianChannels } from "../moving-averages/donchianChannel.js";
11
11
  import { bollingerBands } from "../moving-averages/bollingerBands.js";
12
12
  import { volumeOscillator } from "../oscillators/volumeOscillator.js";
13
+ import { volumeDelta } from "../oscillators/volumeDelta.js";
13
14
  import { lag } from "../studies/lag.js";
14
15
  import { crossPairs } from "../studies/findCrosses.js";
15
16
  import { dateTime } from "../studies/dateTime.js";
@@ -18,7 +19,7 @@ import { atr } from "../volatility/atr.js";
18
19
  import { mapCols } from "../studies/mapCols.js";
19
20
 
20
21
  import { buildArray } from "../utilities/assignTypes.js";
21
- import { numberFormater } from "../utilities/numberUtilities.js";
22
+ import { inputNumberFormatter } from "../utilities/numberUtilities.js";
22
23
  import { dateFormaters } from "../utilities/dateUtilities.js";
23
24
 
24
25
  import { pca } from "../machine-learning/pca.js";
@@ -36,6 +37,7 @@ const mainFunctions = {
36
37
  sma: movingAverages,
37
38
  vidya,
38
39
  macd,
40
+ volumeDelta,
39
41
  relativeVolume,
40
42
  donchianChannels,
41
43
  bollingerBands,
@@ -55,6 +57,7 @@ export const mainLoop = (input, main) => {
55
57
  inputTypes,
56
58
  chunkProcess,
57
59
  processSecondaryLoop,
60
+ precisionMultiplier
58
61
  } = main;
59
62
 
60
63
  validateInputParams(inputParams, len)
@@ -75,9 +78,11 @@ export const mainLoop = (input, main) => {
75
78
  {
76
79
  const value = curr[key]
77
80
 
78
- if(numberFormater.hasOwnProperty(formaterKey))
81
+ if(inputNumberFormatter.hasOwnProperty(formaterKey))
79
82
  {
80
- const formatedValue = numberFormater[formaterKey](value)
83
+ if(typeof value === 'undefined') console.log({index, formaterKey, key})
84
+
85
+ const formatedValue = inputNumberFormatter[formaterKey](value, precisionMultiplier)
81
86
 
82
87
  main.pushToMain({index, key, value: formatedValue})
83
88
  }
@@ -25,8 +25,7 @@ export const areKeyValuesValid = (main, index, keyNames) => {
25
25
 
26
26
  const val = verticalOhlcv[key][index]
27
27
 
28
- if(typeof val === 'undefined' || val === null || Number.isNaN(val))
29
- {
28
+ if (val == null || Number.isNaN(val)) {
30
29
  output = false
31
30
  break
32
31
  }
@@ -5,6 +5,51 @@ import { countUniqueLabels, oversampleXY } from "./balancing.js"
5
5
 
6
6
  const algo = 'classifier'
7
7
 
8
+ class Store {
9
+ constructor(args = {}) {
10
+ Object.entries(args).forEach(([k, v]) => { this[k] = v })
11
+ }
12
+
13
+ // internal helpers
14
+ _unwrap = v => Array.isArray(v) ? v[0] : v
15
+ _num = (v, def = 0) => {
16
+ const n = Number(this._unwrap(v))
17
+ return Number.isFinite(n) ? n : def
18
+ }
19
+
20
+ // set: supports value OR updater function
21
+ // if you pass a function, we feed it the current *numeric* value
22
+ set = (key, valueOrFn) => {
23
+ if (typeof valueOrFn === 'function') {
24
+ const next = valueOrFn(this._num(this[key], 0))
25
+ this[key] = next // <- change to [next] if you want to keep the array wrapper
26
+ return next
27
+ } else {
28
+ this[key] = valueOrFn // <- change to [valueOrFn] if you want to keep the array wrapper
29
+ return valueOrFn
30
+ }
31
+ }
32
+
33
+ // convenience numerics
34
+ inc = (key, delta = 1) => this.set(key, v => v + delta)
35
+ add = (key, delta = 0) => this.inc(key, delta) // alias
36
+ getNum = (key, def = 0) => this._num(this[key], def)
37
+
38
+ get = (key) => this._unwrap(this[key])
39
+ getAll = () => {
40
+
41
+ const output = {}
42
+
43
+ for(const [key, valueOrFn] of Object.entries(this)) {
44
+ if(typeof valueOrFn === 'function') continue
45
+ output[key] = valueOrFn
46
+ }
47
+
48
+
49
+ return {...output}
50
+ } // shallow clone of data-only fields
51
+ }
52
+
8
53
  export const classifier = (
9
54
  main,
10
55
  index,
@@ -39,7 +84,15 @@ export const classifier = (
39
84
  const expectedLoops = (flatY) ? predictions : 1 //
40
85
 
41
86
  instances[algo][prefix] = {
42
-
87
+ store: new Store({
88
+ strategy: null,
89
+ total: 0,
90
+ win: 0,
91
+ loss: 0,
92
+ strategyGainLoss: 0,
93
+ idx: 0,
94
+ initialBalance: verticalOhlcv.close[index]
95
+ }),
43
96
  expectedLoops,
44
97
  isTrained: new Array(expectedLoops).fill(false),
45
98
  uniqueLabels: new Array(expectedLoops).fill(0),
@@ -66,7 +119,7 @@ export const classifier = (
66
119
  {
67
120
  return
68
121
  }
69
- else if(filterCallback(verticalOhlcv, index) === false)
122
+ else if(filterCallback(verticalOhlcv, index, instances[algo][prefix].store) === false)
70
123
  {
71
124
  return
72
125
  }
@@ -138,7 +191,7 @@ export const classifier = (
138
191
  if(!trainX) return
139
192
 
140
193
  //if univariable Y (flatY) a model is created for each prediction
141
- const trainY = yCallback(index, verticalOhlcv, horizon)
194
+ const trainY = yCallback(index, verticalOhlcv, horizon, instances[algo][prefix].store)
142
195
 
143
196
  if (trainY !== null && (!Array.isArray(trainY) || trainY.length !== predictions)) {
144
197
 
@@ -1,11 +1,7 @@
1
1
  import { FasterBollingerBands } from 'trading-signals'
2
- import { roundDecimalPlaces } from '../utilities/numberUtilities.js';
3
-
4
2
  const defaultTarget = 'close'
5
3
 
6
- export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, lag, decimals }) => {
7
-
8
-
4
+ export const bollingerBands = (main, index, size, stdDev, { height, range = [], target, lag }) => {
9
5
 
10
6
  const { verticalOhlcv, instances } = main;
11
7
  const indicatorKey = `${size}_${stdDev}`;
@@ -14,7 +10,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
14
10
  // Initialization on the first call.
15
11
  if (index === 0) {
16
12
 
17
- const {inputParams, verticalOhlcv, len } = main;
13
+ const {inputParams, verticalOhlcv, len, priceBased } = main;
18
14
 
19
15
  if (!(target in verticalOhlcv)) {
20
16
  throw new Error(`bollingerBands could not find target "${target}"`);
@@ -62,6 +58,13 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
62
58
 
63
59
  }
64
60
 
61
+ if(priceBased.has(target)) {
62
+ for(const k of keyNames) {
63
+ priceBased.add(k)
64
+ }
65
+ }
66
+
67
+
65
68
  const verticalOhlcvSetup = Object.fromEntries(keyNames.map(v => [v, new Float64Array(len).fill(NaN)]))
66
69
  Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
67
70
 
@@ -106,7 +109,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
106
109
  if (!Number.isNaN(lower) && !Number.isNaN(upper)) {
107
110
  heightValue = ((upper - lower) / lower)
108
111
  }
109
- main.pushToMain({ index, key: `${prefix}_height${suffix}`, value: (decimals === null) ? heightValue : roundDecimalPlaces(heightValue, decimals) });
112
+ main.pushToMain({ index, key: `${prefix}_height${suffix}`, value: heightValue });
110
113
  }
111
114
 
112
115
  // Process each range property.
@@ -116,7 +119,7 @@ export const bollingerBands = (main, index, size, stdDev, { height, range = [],
116
119
  if (!Number.isNaN(priceValue) && !Number.isNaN(lower) && !Number.isNaN(upper)) {
117
120
  rangeValue = (priceValue - lower) / (upper - lower)
118
121
  }
119
- main.pushToMain({ index, key: `${prefix}_range_${rangeKey}${suffix}`, value: (decimals === null) ? rangeValue : roundDecimalPlaces(rangeValue, decimals) });
122
+ main.pushToMain({ index, key: `${prefix}_range_${rangeKey}${suffix}`, value: rangeValue });
120
123
  }
121
124
 
122
125
  return true;
@@ -1,12 +1,10 @@
1
1
 
2
- import { roundDecimalPlaces } from "../utilities/numberUtilities.js";
3
-
4
2
  export const donchianChannels = (main, index, size, offset, options) => {
5
3
 
6
4
 
7
5
 
8
- const {verticalOhlcv, instances, len, inputParams, lag} = main
9
- const { height: includeHeight, range: rangeKeys, decimals } = options
6
+ const {verticalOhlcv, instances, len, inputParams, lag, priceBased} = main
7
+ const { height: includeHeight, range: rangeKeys } = options
10
8
  const indicatorKey = `${size}_${offset}`
11
9
 
12
10
  // ---- INIT (only at first bar) ----
@@ -30,6 +28,10 @@ export const donchianChannels = (main, index, size, offset, options) => {
30
28
  keys.push(`${prefix}_range_${rk}`);
31
29
  }
32
30
 
31
+ for(const k of keys) {
32
+ priceBased.add(k)
33
+ }
34
+
33
35
  // bootstrap instance
34
36
  if (!instances.donchian_channel) {
35
37
  instances.donchian_channel = { numberOfIndicators, settings: {} };
@@ -96,7 +98,7 @@ export const donchianChannels = (main, index, size, offset, options) => {
96
98
  const heightVal = hasBounds && lower
97
99
  ? (upper - lower) / lower
98
100
  : NaN;
99
- main.pushToMain({ index, key: `${prefix}_height`, value: (decimals === null) ? heightVal : roundDecimalPlaces(heightVal, decimals) });
101
+ main.pushToMain({ index, key: `${prefix}_height`, value: heightVal });
100
102
  }
101
103
 
102
104
  // precompute spread once
@@ -111,7 +113,7 @@ export const donchianChannels = (main, index, size, offset, options) => {
111
113
  const val = hasBounds && price === price && spread
112
114
  ? (price - lower) / spread
113
115
  : NaN;
114
- main.pushToMain({ index, key: rangeOut[i], value: (decimals === null) ? val : roundDecimalPlaces(val, decimals) });
116
+ main.pushToMain({ index, key: rangeOut[i], value: val });
115
117
  }
116
118
 
117
119
  return true;