ohlcv-indicators 2.0.4 → 2.0.6

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.
@@ -188,7 +188,7 @@ export const crossPairs = (main, index) => {
188
188
  main.instances[crossName].update({fast: fastValue, slow: slowValue})
189
189
  }
190
190
 
191
- main.verticalOhlcv[crossName][index] = main.instances[crossName].getResult()
191
+ main.pushToMain({index, key: crossName, value: main.instances[crossName].getResult()})
192
192
  }
193
193
 
194
194
  }
@@ -8,6 +8,19 @@ export const lag = (main, index) => {
8
8
 
9
9
  const params = findParams.map(o => o.params)
10
10
 
11
+ for (const [colKeys, lags] of params)
12
+ {
13
+ for (const colKey of colKeys)
14
+ {
15
+ if(main.priceBased.includes(colKey))
16
+ {
17
+ for (let lag = 1; lag <= lags; lag++) {
18
+ main.priceBased.push(`${colKey}_lag_${lag}`)
19
+ }
20
+ }
21
+ }
22
+ }
23
+
11
24
  main.instances.lag = {
12
25
  lagParams: params
13
26
  }
@@ -33,7 +46,7 @@ export const lag = (main, index) => {
33
46
 
34
47
  if(laggedIndex <= 0) break
35
48
 
36
- main.verticalOhlcv[key][index] = currentColumn[laggedIndex]
49
+ main.pushToMain({index, key, value: currentColumn[laggedIndex]})
37
50
  }
38
51
  }
39
52
  }
@@ -9,14 +9,15 @@ export const priceVariations = (main, index) => {
9
9
  mid_price_open_close: [...main.nullArray],
10
10
  mid_price_high_low: [...main.nullArray]
11
11
  })
12
+
13
+
12
14
  }
13
15
 
14
16
  const open = main.verticalOhlcv.open[index]
15
17
  const high = main.verticalOhlcv.high[index]
16
18
  const low = main.verticalOhlcv.low[index]
17
19
  const close = main.verticalOhlcv.close[index]
18
-
19
- main.verticalOhlcv.mid_price_open_close[index] = (open + close) / 2
20
- main.verticalOhlcv.mid_price_high_low[index] = (high + low) / 2
21
-
20
+
21
+ main.pushToMain({index, key: 'mid_price_open_close', value: (open + close) / 2})
22
+ main.pushToMain({index, key: 'mid_price_high_low', value: (high + low) / 2})
22
23
  }
@@ -1,110 +1,174 @@
1
- import { validateInputParams } from "./validators.js"
2
- import { rsi } from "../oscillators/rsi.js"
3
- import { sma } from "../moving-averages/sma.js"
4
- import { ema } from "../moving-averages/ema.js"
5
- import { macd } from "../moving-averages/macd.js"
6
- import { relativeVolume } from "../moving-averages/relativeVolume.js"
7
- import { donchianChannels } from "../moving-averages/donchianChannel.js"
8
- import { bollingerBands } from "../moving-averages/bollingerBands.js"
9
- import { volumeOscillator } from "../oscillators/volumeOscillator.js"
10
- import { candleStudies } from "../studies/candleStudies.js"
11
- import { lag } from "../studies/lag.js"
12
- import {crossPairs} from "../studies/findCrosses.js"
13
- import { dateTime } from "../studies/dateTime.js"
14
- import { priceVariations } from "../studies/priceVariations.js"
15
-
16
-
1
+ import { validateInputParams } from "./validators.js";
2
+ import { rsi } from "../oscillators/rsi.js";
3
+ import { sma } from "../moving-averages/sma.js";
4
+ import { ema } from "../moving-averages/ema.js";
5
+ import { macd } from "../moving-averages/macd.js";
6
+ import { relativeVolume } from "../moving-averages/relativeVolume.js";
7
+ import { donchianChannels } from "../moving-averages/donchianChannel.js";
8
+ import { bollingerBands } from "../moving-averages/bollingerBands.js";
9
+ import { volumeOscillator } from "../oscillators/volumeOscillator.js";
10
+ import { candleStudies } from "../studies/candleStudies.js";
11
+ import { lag } from "../studies/lag.js";
12
+ import { crossPairs } from "../studies/findCrosses.js";
13
+ import { dateTime } from "../studies/dateTime.js";
14
+ import { priceVariations } from "../studies/priceVariations.js";
15
+
16
+ // Helper to clean non-numeric characters (except "-" at the start and decimal point)
17
+ const cleanNumStr = str => str.replace(/(?!^-)[^0-9.]/g, '');
18
+
19
+ // Map indicator keys to their respective functions
17
20
  const indicatorFunctions = {
18
- dateTime,
19
- priceVariations,
20
- rsi,
21
- sma,
22
- ema,
23
- macd,
24
- relativeVolume,
25
- donchianChannels,
26
- bollingerBands,
27
- volumeOscillator,
28
- candleStudies
29
- }
30
-
31
-
32
- export const parseOhlcvToVertical = (input, main, startIndex = 0) => {
33
- const { len, inputParams } = main
34
-
35
- main.nullArray = new Array(len).fill(null)
36
-
37
- if (startIndex === 0) {
38
-
39
- validateInputParams(main)
40
-
41
- const baseKeys = ['open', 'high', 'low', 'close', 'volume'];
42
- const baseKeysSet = new Set(baseKeys);
21
+ dateTime,
22
+ priceVariations,
23
+ rsi,
24
+ sma,
25
+ ema,
26
+ macd,
27
+ relativeVolume,
28
+ donchianChannels,
29
+ bollingerBands,
30
+ volumeOscillator,
31
+ candleStudies
32
+ };
43
33
 
44
- // Fresh initialization
45
- for (let i = 0; i < baseKeys.length; i++) {
46
- main.verticalOhlcv[baseKeys[i]] = [...main.nullArray]
47
- }
34
+ // Processes the inputParams into a list of indicator calls
35
+ const processIndicatorCalls = inputParams =>
36
+ Array.isArray(inputParams)
37
+ ? inputParams.map(({ key, params }) => ({
38
+ key,
39
+ fn: indicatorFunctions[key],
40
+ args: params
41
+ }))
42
+ : [];
43
+
44
+ // Sets the input types for each key based on the first row of data
45
+ const setInputTypes = (row0, main) => {
46
+ const isValidNumberString = str => /^(\d+(\.\d*)?|\.\d+)$/.test(str);
47
+
48
+ for (const key of Object.keys(main.inputTypes)) {
49
+ if (!(key in row0)) {
50
+ throw new Error(`Property "${key}" not found in ohlcv array. ${main.ticker}`);
51
+ }
48
52
 
49
- const inputKeys = Object.keys(input[0]);
50
- const otherKeys = [];
51
- for (let i = 0; i < inputKeys.length; i++) {
52
- const key = inputKeys[i]
53
- if (!baseKeysSet.has(key)) {
54
- main.verticalOhlcv[key] = [...main.nullArray]
55
- otherKeys.push(key)
56
- }
57
- }
58
- main.otherKeys = otherKeys;
53
+ let value = row0[key];
59
54
 
55
+
60
56
 
57
+ if (typeof value === 'number' && value > 0) {
58
+ main.inputTypes[key] = 'number';
59
+ } else if (typeof value === 'string' && isValidNumberString(value)) {
60
+ main.inputTypes[key] = 'string';
61
+ value = cleanNumStr(value);
61
62
  } else {
62
- if (!main.otherKeys) {
63
- throw new Error("otherKeys not found in main. Ensure you run with startIndex=0 first to initialize.");
64
- }
63
+ throw new Error(`Invalid property "${key}" in ohlcv array. ${main.ticker}`);
65
64
  }
66
65
 
67
- const indicatorCalls = processIndicatorCalls(inputParams);
68
-
69
-
70
- for (let x = startIndex; x < len; x++) {
71
- const current = input[x];
72
-
73
- main.verticalOhlcv.open[x] = current.open;
74
- main.verticalOhlcv.high[x] = current.high;
75
- main.verticalOhlcv.low[x] = current.low;
76
- main.verticalOhlcv.close[x] = current.close;
77
- main.verticalOhlcv.volume[x] = current.volume;
78
-
79
- for (let i = 0; i < main.otherKeys.length; i++) {
80
- const k = main.otherKeys[i];
81
- main.verticalOhlcv[k][x] = current[k];
82
- }
83
-
84
- for (let i = 0; i < indicatorCalls.length; i++) {
85
- const { fn, args, key } = indicatorCalls[i];
86
- if (key === 'crossPairs' || key === 'lag') continue;
87
-
88
- fn(main, x, ...args)
89
- }
90
-
91
- lag(main, x);
92
- crossPairs(main, x);
93
- main.lastComputedIndex++;
66
+ // Determine precisionMultiplier if not already set
67
+ if (main.precisionMultiplier === 0) {
68
+ const [, decimals = ''] = String(value).split('.');
69
+ const decimalPrecision = Math.max(4, decimals.length);
70
+ main.precisionMultiplier = decimalPrecision > 1 ? Math.pow(10, decimalPrecision - 1) : 1;
94
71
  }
72
+ }
95
73
  };
96
74
 
75
+ // Parses a number from either a numeric or string input, applying a multiplier to fix precision issues
76
+ const parseNumber = (num, type, precisionMultiplier) => {
77
+ const addMultiplier = cNum =>
78
+ precisionMultiplier > 1 ? cNum * precisionMultiplier : cNum;
79
+
80
+ if (type === 'number') {
81
+ return addMultiplier(num);
82
+ } else if (type === 'string') {
83
+ const cleanedStr = cleanNumStr(num);
84
+ return addMultiplier(Number(cleanedStr));
85
+ } else
86
+ {
87
+ return 0 // Optionally: throw an error if type is not recognized
88
+ }
89
+
90
+ };
91
+
92
+ const parseVolume = (volume, type) => {
97
93
 
98
- const processIndicatorCalls = inputParams => {
99
- if (!inputParams || typeof inputParams !== 'object') return []
94
+ if(type === 'number') return volume
95
+ else if(type === 'string'){
96
+ return Number(cleanNumStr(volume))
97
+ }
98
+ else
99
+ {
100
+ return 0 // Optionally: throw an error if type is not recognized
101
+ }
102
+ }
100
103
 
101
- const indicatorCalls = []
104
+ export const parseOhlcvToVertical = (input, main, startIndex = 0) => {
105
+ const { len, inputParams } = main;
106
+ main.nullArray = new Array(len).fill(null);
107
+
108
+ const BASE_KEYS = ['open', 'high', 'low', 'close', 'volume'];
109
+ const baseKeysSet = new Set(BASE_KEYS);
110
+
111
+ if (startIndex === 0) {
112
+ // Validate parameters and initialize base keys in verticalOhlcv
113
+ validateInputParams(main);
114
+ BASE_KEYS.forEach(key => {
115
+ main.verticalOhlcv[key] = [...main.nullArray];
116
+ });
117
+
118
+ // Identify any additional keys from the first input row and initialize them
119
+ const inputKeys = Object.keys(input[0]);
120
+ const otherKeys = inputKeys.filter(key => !baseKeysSet.has(key));
121
+ otherKeys.forEach(key => {
122
+ main.verticalOhlcv[key] = [...main.nullArray];
123
+ });
124
+ main.otherKeys = otherKeys;
125
+
126
+ // Set the input types once using the first row
127
+ setInputTypes(input[0], main);
128
+ } else if (!main.otherKeys) {
129
+ throw new Error(
130
+ "otherKeys not found in main. Ensure you run with startIndex=0 first to initialize."
131
+ );
132
+ }
133
+
134
+ // Prepare the list of indicator function calls
135
+ const indicatorCalls = processIndicatorCalls(inputParams);
136
+
137
+ // Process each row in the input
138
+ for (let x = startIndex; x < len; x++) {
139
+ const current = input[x];
140
+ // Destructure the base keys and use the rest for other properties
141
+ const { open, high, low, close, volume, ...rest } = current
142
+ const {precisionMultiplier} = main
143
+
144
+ const parsedOpen = parseNumber(open, main.inputTypes.open, precisionMultiplier)
145
+ const parsedHigh = parseNumber(open, main.inputTypes.high, precisionMultiplier)
146
+ const parsedLow = parseNumber(open, main.inputTypes.low, precisionMultiplier)
147
+ const parsedClose = parseNumber(open, main.inputTypes.close, precisionMultiplier)
148
+ const parsedVolume = parseVolume (volume, main.inputTypes.volume)
149
+
150
+ main.pushToMain({index: x, key: 'open', value: parsedOpen})
151
+ main.pushToMain({index: x, key: 'high', value: parsedHigh})
152
+ main.pushToMain({index: x, key: 'low', value: parsedLow})
153
+ main.pushToMain({index: x, key: 'close', value: parsedClose})
154
+ main.pushToMain({index: x, key: 'volume', value: parsedVolume})
155
+
156
+
157
+ // Populate any extra keys identified during initialization
158
+ for (const key of main.otherKeys) {
159
+ main.pushToMain({index: x, key, value: rest[key]})
160
+ }
102
161
 
103
- for(let x = 0; x < inputParams.length; x++)
104
- {
105
- const {key, params} = inputParams[x]
106
- indicatorCalls.push({key, fn: indicatorFunctions[key], args: params})
162
+ // Run all indicator functions except for the ones processed later
163
+ for (const { key, fn, args } of indicatorCalls) {
164
+ if (['crossPairs', 'lag'].includes(key)) continue;
165
+ fn(main, x, ...args);
107
166
  }
108
167
 
109
- return indicatorCalls;
168
+ // Process these indicators separately (ensuring their execution order)
169
+ lag(main, x);
170
+ crossPairs(main, x);
171
+
172
+ main.lastComputedIndex++;
173
+ }
110
174
  };