ohlcv-indicators 3.5.0 → 3.5.2

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
@@ -67,6 +67,7 @@ export default class OHLCV_INDICATORS {
67
67
  this.precision = precision
68
68
  this.precisionMultiplier = calcPrecisionMultiplier(this, this.firstRow)
69
69
  this.scaledGroups = {}
70
+ this.scaledLabels = new Set()
70
71
 
71
72
  this.pushToMain = ({index, key, value}) => pushToMain({main: this, index, key, value})
72
73
 
@@ -268,12 +269,15 @@ export default class OHLCV_INDICATORS {
268
269
  validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
269
270
  validateObject(options, 'options', methodName)
270
271
 
271
- const {lag = 0, type = 'price'} = options
272
+ const {lag = 0, percentage = false, upper = null, lower = null} = options
272
273
 
273
274
  validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
274
- validateArrayOptions(['price', 'percentage'], type, 'options.type', methodName)
275
+ validateBoolean(percentage, 'options.percentage', 'atr')
275
276
 
276
- this.inputParams.push({key: methodName, params: [size, {lag, type}]})
277
+ if(upper !== null) validateNumber(upper, {min: 0.001, max: 100, allowDecimals: true}, 'options.upper', 'atr')
278
+ if(lower !== null) validateNumber(lower, {min: 0.001, max: 100, allowDecimals: true}, 'options.lower', 'atr')
279
+
280
+ this.inputParams.push({key: methodName, params: [size, {lag, percentage, upper, lower}]})
277
281
 
278
282
  return this
279
283
  }
@@ -453,9 +457,11 @@ export default class OHLCV_INDICATORS {
453
457
  validateNumber(size, {min: 1, max: this.len, allowDecimals: false}, 'size', methodName)
454
458
  validateArray(colKeys, 'colKeys', methodName)
455
459
 
456
- const {group = false, range = [0, 1], lag = 0, type = 'minmax', decimals = null} = options
460
+ const {group = false, range = [0, 1], lag = 0, type = 'zscore', decimals = null, pca = false, pcaSource = false} = options
457
461
 
458
462
  validateBoolean(group, 'options.group', methodName)
463
+ validateBoolean(pca, 'options.pca', methodName)
464
+ validateBoolean(pcaSource, 'options.pcaSource', methodName)
459
465
  validateArrayOfRanges(range, 'options.range', methodName)
460
466
  validateArrayOptions(['minmax', 'zscore'], type, 'options.type', methodName)
461
467
 
@@ -465,7 +471,7 @@ export default class OHLCV_INDICATORS {
465
471
  const groupKeyLen = colKeys.length
466
472
  const precomputed = {groupKey, groupKeyLen}
467
473
 
468
- this.inputParams.push({key: methodName, params: [size, colKeys, {type, group, range, lag, precomputed, decimals}]})
474
+ this.inputParams.push({key: methodName, params: [size, colKeys, {type, group, range, lag, precomputed, decimals, pca, pcaSource}]})
469
475
  return this
470
476
  }
471
477
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.5.0",
3
+ "version": "3.5.2",
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",
@@ -12,7 +12,9 @@
12
12
  "author": "https://jaimelias.com/",
13
13
  "license": "ISC",
14
14
  "dependencies": {
15
- "trading-signals": "^5.0.4"
15
+ "ml-pca": "^4.1.1",
16
+ "trading-signals": "^5.0.4",
17
+ "xy-scale": "^1.4.1"
16
18
  },
17
19
  "devDependencies": {
18
20
  "@babel/core": "^7.25.2",
@@ -9,13 +9,15 @@ import { volumeOscillator } from "../oscillators/volumeOscillator.js";
9
9
  import { lag } from "../studies/lag.js";
10
10
  import { crossPairs } from "../studies/findCrosses.js";
11
11
  import { dateTime } from "../studies/dateTime.js";
12
- import { scaler } from "../studies/scaler.js";
12
+ import { scaler } from "../machine-learning/scaler.js";
13
13
  import { atr } from "../volatility/atr.js";
14
14
 
15
15
  import { buildArray } from "../utilities/assignTypes.js";
16
16
  import { numberFormater } from "../utilities/numberUtilities.js";
17
17
  import { dateFormaters } from "../utilities/dateUtilities.js";
18
18
 
19
+ import { pca } from "../machine-learning/pca.js";
20
+
19
21
  // Map indicator keys to their respective functions
20
22
  const indicatorFunctions = {
21
23
  dateTime,
@@ -33,7 +35,7 @@ const indicatorFunctions = {
33
35
 
34
36
 
35
37
  export const mainLoop = (input, main) => {
36
- const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes, chunkProcess } = main;
38
+ const { len, inputParams, priceBased, precisionMultiplier, arrayTypes, verticalOhlcv, verticalOhlcvKeyNames, inputTypes, chunkProcess, scaledGroups } = main;
37
39
 
38
40
  validateInputParams(inputParams, len)
39
41
 
@@ -89,5 +91,7 @@ export const mainLoop = (input, main) => {
89
91
  }
90
92
  }
91
93
 
94
+ pca(main)
95
+
92
96
  verticalOhlcvKeyNames.push(...Object.keys(verticalOhlcv))
93
97
  }
@@ -0,0 +1,69 @@
1
+ import { PCA } from "ml-pca";
2
+
3
+ export const pca = main => {
4
+
5
+ const {inputParams, invalidValueIndex, len, verticalOhlcv} = main
6
+ const startIndex = invalidValueIndex + 1
7
+
8
+ for(const rowParams of inputParams)
9
+ {
10
+ const {key: indicatorName, params} = rowParams
11
+
12
+ if(indicatorName !== 'scaler') continue
13
+
14
+ const [size, keyNames, options] = params
15
+
16
+ const {type, group, lag, pca: pcaBoolean, pcaSource} = options
17
+
18
+ if(!group || !pcaBoolean) continue
19
+
20
+ const scaledKeyNames = keyNames.map(v => `${type}_${size}_${v}`)
21
+ const laggedKeys = []
22
+
23
+ for (const k of scaledKeyNames) {
24
+ for (let step = 1; step <= lag; step++) {
25
+ laggedKeys.push(`${k}_lag_${step}`)
26
+ }
27
+ }
28
+
29
+ scaledKeyNames.push(...laggedKeys)
30
+
31
+
32
+ const diffLen = len - startIndex
33
+ const numCols = scaledKeyNames.length
34
+ const tempArr = new Array(diffLen)
35
+
36
+ for (let i = startIndex; i < len; i++) {
37
+
38
+ const row = new Float64Array(numCols);
39
+
40
+ let j = 0
41
+
42
+ for (const key of scaledKeyNames) {
43
+ row[j++] = verticalOhlcv[key][i]
44
+ }
45
+
46
+ tempArr[i - startIndex] = row
47
+ }
48
+
49
+ if(!pcaSource)
50
+ {
51
+ for (const key of scaledKeyNames) {
52
+ delete verticalOhlcv[key]
53
+ }
54
+ }
55
+
56
+ const pca = new PCA(tempArr)
57
+ const predictions = [...pca.predict(tempArr)]
58
+
59
+ const verticalKey = `pca_${type}_${size}`
60
+ verticalOhlcv[verticalKey] = new Array(len).fill(null)
61
+
62
+ for(let x = 0; x < predictions.length; x++)
63
+ {
64
+ verticalOhlcv[verticalKey][x + startIndex + 1] = predictions[x]
65
+ }
66
+
67
+ }
68
+
69
+ }
@@ -19,7 +19,13 @@ export const scaler = (main, index, size, colKeys, {type, group, range, lag, pre
19
19
  const prefix = `${type}_${size}`;
20
20
 
21
21
  if (index === 0) {
22
- const { len } = main;
22
+ const { len, scaledLabels } = main;
23
+
24
+ if(scaledLabels.has(prefix))
25
+ {
26
+ throw new Error(`Method "scaler" cannot have both "size" and "options.type" parameters specified together.`)
27
+ }
28
+ scaledLabels.add(prefix)
23
29
 
24
30
  if(!instances.hasOwnProperty('scaler'))
25
31
  {
@@ -1,27 +1,36 @@
1
1
 
2
2
  import {FasterATR, FasterWSMA} from 'trading-signals';
3
3
 
4
- export const atr = (main, index, size, {lag, type}) => {
4
+ export const atr = (main, index, size, {lag, percentage, upper, lower}) => {
5
5
  const { verticalOhlcv, instances } = main
6
- const keyName = (type === 'percentage') ? `atr_${size}_percentage` : `atr_${size}`
6
+ const baseKeyName = `atr_${size}`
7
7
 
8
8
  if (index === 0) {
9
9
 
10
10
  const {instances, verticalOhlcv, arrayTypes, len, priceBased} = main
11
11
 
12
- instances[keyName] = new FasterATR(size, FasterWSMA)
12
+ instances[baseKeyName] = new FasterATR(size, FasterWSMA)
13
13
 
14
- if(type === 'price')
14
+ const keyNames = [baseKeyName]
15
+
16
+ if(percentage) keyNames.push(`${baseKeyName}_percentage`)
17
+ if(upper !== null) keyNames.push(`${baseKeyName}_upper`)
18
+ if(lower !== null) keyNames.push(`${baseKeyName}_lower`)
19
+
20
+ for(const k of keyNames)
15
21
  {
16
- priceBased.add(keyName)
22
+ if(!k.endsWith('_percentage'))
23
+ {
24
+ priceBased.add(k)
25
+ }
26
+
27
+ verticalOhlcv[k] = new Float64Array(len).fill(NaN)
28
+ arrayTypes[k] = 'Float64Array'
17
29
  }
18
-
19
- verticalOhlcv[keyName] = new Float64Array(len).fill(NaN)
20
- arrayTypes[keyName] = 'Float64Array'
21
-
30
+
22
31
  if(lag > 0)
23
32
  {
24
- main.lag([keyName], lag)
33
+ main.lag(keyNames, lag)
25
34
  }
26
35
  }
27
36
 
@@ -31,7 +40,7 @@ export const atr = (main, index, size, {lag, type}) => {
31
40
  low: verticalOhlcv.low[index],
32
41
  close: verticalOhlcv.close[index],
33
42
  }
34
- const instance = instances[keyName]
43
+ const instance = instances[baseKeyName]
35
44
 
36
45
  // Update the moving average instance.
37
46
  instance.update(curr);
@@ -42,13 +51,24 @@ export const atr = (main, index, size, {lag, type}) => {
42
51
 
43
52
  }
44
53
 
45
- if(type === 'percentage')
54
+ if(percentage)
55
+ {
56
+ const percentageValue = (Number.isNaN(currAtr)) ? NaN : (currAtr / curr.close)
57
+ main.pushToMain({ index, key: `${baseKeyName}_percentage`, value: percentageValue });
58
+ }
59
+ if(upper !== null)
60
+ {
61
+ const upperValue = (Number.isNaN(currAtr)) ? NaN : (curr.close + (currAtr * upper))
62
+ main.pushToMain({ index, key: `${baseKeyName}_upper`, value: upperValue });
63
+ }
64
+ if(lower !== null)
46
65
  {
47
- currAtr = (Number.isNaN(currAtr)) ? NaN : (currAtr / curr.close)
66
+ const lowerValue = (Number.isNaN(currAtr)) ? NaN : (curr.close - (currAtr * lower))
67
+ main.pushToMain({ index, key: `${baseKeyName}_lower`, value: lowerValue });
48
68
  }
49
69
 
50
70
  // Always push the MA value (even if NaN).
51
- main.pushToMain({ index, key: keyName, value: currAtr });
71
+ main.pushToMain({ index, key: baseKeyName, value: currAtr });
52
72
 
53
73
  return true;
54
74
  }
package/webpack.config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { resolve, dirname } from 'path';
2
2
  import { fileURLToPath } from 'url';
3
- import TerserPlugin from 'terser-webpack-plugin';
3
+ //import TerserPlugin from 'terser-webpack-plugin';
4
4
 
5
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
6
 
@@ -16,7 +16,7 @@ export default {
16
16
  output,
17
17
  mode: 'production',
18
18
  target: 'web',
19
- optimization: { //TerserPlugin start
19
+ /* optimization: { //TerserPlugin start
20
20
  minimize: true,
21
21
  minimizer: [
22
22
  new TerserPlugin({
@@ -28,7 +28,7 @@ export default {
28
28
  },
29
29
  }),
30
30
  ],
31
- }, // TerserPlugin end
31
+ }, // TerserPlugin end */
32
32
  module: {
33
33
  rules: [
34
34
  {