ohlcv-indicators 3.5.80 → 3.5.82

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.
@@ -1,59 +1,53 @@
1
1
  import { FasterEMA } from '../core-indicators/index.js';
2
2
  import { validateInputValues } from '../utilities/validators.js';
3
3
  import { initializeColumns } from '../core-functions/initializeColumns.js';
4
-
4
+ import { mathLog } from '../utilities/math.js';
5
5
  const isBadNumber = v => v == null || !Number.isFinite(v);
6
+ const arithmeticReturn = (next, prev) => (next - prev) / prev;
7
+
6
8
 
7
9
  export const heikenAshi = (
8
10
  main,
9
11
  index,
10
12
  smoothLength,
11
13
  afterSmoothLength,
12
- { lag = 0, bothNull = false, retLogs = true } = {}
14
+ { lag = 0, noSmoothing = false, retLogs = false } = {}
13
15
  ) => {
14
16
  const { verticalOhlcv, instances, scaledGroups } = main;
15
- const indicatorKey = `${smoothLength}_${afterSmoothLength}`;
16
-
17
- const getRet = (next, prev) => retLogs
18
- ? Math.log(next / prev)
19
- : (next - prev) / prev;
20
-
21
- const prefix = retLogs ? 'ret_log_' : 'ret_';
22
-
23
- const paramsKey = bothNull ? '' : `_${indicatorKey}`;
24
-
25
- const getKey = key => `${prefix}heiken_ashi_${key}${paramsKey}`;
26
-
27
- const crossKey = bothNull
28
- ? 'heiken_ashi_cross'
29
- : `heiken_ashi_cross_${indicatorKey}`;
30
-
31
- const instanceKey = bothNull
17
+ const instanceKey = noSmoothing
32
18
  ? 'heiken_ashi'
33
- : `heiken_ashi_${indicatorKey}`;
19
+ : `heiken_ashi_${smoothLength}_${afterSmoothLength}`;
34
20
 
35
21
  // ---- INIT ----
36
22
  if (index === 0) {
37
23
  validateInputValues({ open: true, high: true, low: true, close: true }, verticalOhlcv, index, 'heikenAshi');
38
24
  const ohlcKeys = ['open', 'high', 'low', 'close'];
39
25
  const featureKeys = ['body', 'upper_wick', 'lower_wick', 'range'];
26
+ const paramsKey = noSmoothing ? '' : `_${smoothLength}_${afterSmoothLength}`;
27
+ const prefix = retLogs ? 'ret_log_' : 'ret_';
28
+ const outputKeys = Object.fromEntries(
29
+ featureKeys.map(key => [key, `${prefix}heiken_ashi_${key}${paramsKey}`])
30
+ );
31
+ const crossKey = `heiken_ashi_cross${paramsKey}`;
32
+ const keyNames = Object.values(outputKeys);
40
33
 
41
34
  instances[instanceKey] = {
42
- emaPre: !bothNull
35
+ emaPre: !noSmoothing
43
36
  ? Object.fromEntries(ohlcKeys.map(k => [k, new FasterEMA(smoothLength)]))
44
37
  : null,
45
- emaPost: !bothNull
38
+ emaPost: !noSmoothing
46
39
  ? Object.fromEntries(ohlcKeys.map(k => [k, new FasterEMA(afterSmoothLength)]))
47
40
  : null,
48
41
  prevHaOpen: NaN,
49
42
  prevHaClose: NaN,
50
43
  prevSmOpen: NaN,
51
44
  prevSmClose: NaN,
52
- isTrendUp: false,
45
+ trendDirection: 0,
46
+ getRet: retLogs ? mathLog : arithmeticReturn,
47
+ outputKeys,
48
+ crossKey
53
49
  };
54
50
 
55
- const keyNames = featureKeys.map(getKey);
56
-
57
51
  initializeColumns(main, [...keyNames, crossKey].map(key => ({ key })), { lag });
58
52
 
59
53
  if (scaledGroups) {
@@ -67,6 +61,7 @@ export const heikenAshi = (
67
61
  const low = verticalOhlcv.low[index];
68
62
  const close = verticalOhlcv.close[index];
69
63
  const inst = instances[instanceKey];
64
+ const { crossKey, emaPre, emaPost, getRet, outputKeys } = inst;
70
65
 
71
66
  if (
72
67
  isBadNumber(open) ||
@@ -79,19 +74,19 @@ export const heikenAshi = (
79
74
 
80
75
  let sOpen, sHigh, sLow, sClose;
81
76
 
82
- if (!bothNull) {
77
+ if (emaPre !== null) {
83
78
  // ---- PRE-SMOOTHING EMA ----
84
- inst.emaPre.open.update(open);
85
- inst.emaPre.high.update(high);
86
- inst.emaPre.low.update(low);
87
- inst.emaPre.close.update(close);
88
-
89
- if (!inst.emaPre.open.isStable || !inst.emaPre.high.isStable ||
90
- !inst.emaPre.low.isStable || !inst.emaPre.close.isStable) return;
91
- sOpen = inst.emaPre.open.getResult();
92
- sHigh = inst.emaPre.high.getResult();
93
- sLow = inst.emaPre.low.getResult();
94
- sClose = inst.emaPre.close.getResult();
79
+ emaPre.open.update(open);
80
+ emaPre.high.update(high);
81
+ emaPre.low.update(low);
82
+ emaPre.close.update(close);
83
+
84
+ if (!emaPre.open.isStable || !emaPre.high.isStable ||
85
+ !emaPre.low.isStable || !emaPre.close.isStable) return;
86
+ sOpen = emaPre.open.getResult();
87
+ sHigh = emaPre.high.getResult();
88
+ sLow = emaPre.low.getResult();
89
+ sClose = emaPre.close.getResult();
95
90
  } else {
96
91
  sOpen = open;
97
92
  sHigh = high;
@@ -126,19 +121,19 @@ export const heikenAshi = (
126
121
 
127
122
  let smOpen, smHigh, smLow, smClose;
128
123
 
129
- if (!bothNull) {
124
+ if (emaPost !== null) {
130
125
  // ---- POST-SMOOTHING EMA ----
131
- inst.emaPost.open.update(haOpen);
132
- inst.emaPost.high.update(haHigh);
133
- inst.emaPost.low.update(haLow);
134
- inst.emaPost.close.update(haClose);
135
-
136
- if (!inst.emaPost.open.isStable || !inst.emaPost.high.isStable ||
137
- !inst.emaPost.low.isStable || !inst.emaPost.close.isStable) return;
138
- smOpen = inst.emaPost.open.getResult();
139
- smHigh = inst.emaPost.high.getResult();
140
- smLow = inst.emaPost.low.getResult();
141
- smClose = inst.emaPost.close.getResult();
126
+ emaPost.open.update(haOpen);
127
+ emaPost.high.update(haHigh);
128
+ emaPost.low.update(haLow);
129
+ emaPost.close.update(haClose);
130
+
131
+ if (!emaPost.open.isStable || !emaPost.high.isStable ||
132
+ !emaPost.low.isStable || !emaPost.close.isStable) return;
133
+ smOpen = emaPost.open.getResult();
134
+ smHigh = emaPost.high.getResult();
135
+ smLow = emaPost.low.getResult();
136
+ smClose = emaPost.close.getResult();
142
137
  } else {
143
138
  smOpen = haOpen;
144
139
  smHigh = haHigh;
@@ -161,40 +156,35 @@ export const heikenAshi = (
161
156
 
162
157
  // ---- TREND/CROSS LOGIC ----
163
158
  // Cross uses raw smoothed HA values, not the returned log/return features.
164
- const prevCross = index > 0 ? verticalOhlcv[crossKey][index - 1] : 0;
165
- const prevSmOpen = inst.prevSmOpen;
166
- const prevSmClose = inst.prevSmClose;
167
-
168
- const crossUp = (
169
- Number.isFinite(prevSmOpen) &&
170
- Number.isFinite(prevSmClose) &&
171
- prevSmClose <= prevSmOpen &&
172
- smClose > smOpen
173
- );
174
-
175
- const crossDown = (
176
- Number.isFinite(prevSmOpen) &&
177
- Number.isFinite(prevSmClose) &&
178
- prevSmClose >= prevSmOpen &&
179
- smClose < smOpen
180
- );
181
-
182
- if (crossUp) inst.isTrendUp = true;
183
- if (crossDown) inst.isTrendUp = false;
184
-
185
- let cross = 0;
186
-
187
- if (index > 0) {
188
- if (inst.isTrendUp) {
189
- cross = prevCross > 0 ? prevCross + 1 : 1;
190
- } else {
191
- cross = prevCross < 0 ? prevCross - 1 : -1;
192
- }
159
+ const hasPrevious = (
160
+ Number.isFinite(inst.prevSmOpen) &&
161
+ Number.isFinite(inst.prevSmClose)
162
+ )
163
+
164
+ const prevCross = index > 0
165
+ ? verticalOhlcv[crossKey][index - 1]
166
+ : NaN
167
+
168
+ // A doji preserves the established direction.
169
+ // Before any direction exists, it remains neutral.
170
+ const direction = smClose > smOpen
171
+ ? 1
172
+ : smClose < smOpen
173
+ ? -1
174
+ : inst.trendDirection
175
+
176
+ let cross = 0
177
+
178
+ if (hasPrevious && direction !== 0) {
179
+ cross = Number.isFinite(prevCross) &&
180
+ Math.sign(prevCross) === direction
181
+ ? prevCross + direction
182
+ : direction
193
183
  }
194
184
 
195
- // Store previous raw smoothed HA values after cross calculation.
196
- inst.prevSmOpen = smOpen;
197
- inst.prevSmClose = smClose;
185
+ inst.trendDirection = direction
186
+ inst.prevSmOpen = smOpen
187
+ inst.prevSmClose = smClose
198
188
 
199
189
  // ---- HEIKEN ASHI STRUCTURE RETURNS ----
200
190
  const bodyTop = Math.max(smOpen, smClose);
@@ -205,10 +195,10 @@ export const heikenAshi = (
205
195
  const lowerWick = getRet(bodyBottom, smLow);
206
196
  const range = getRet(smHigh, smLow);
207
197
 
208
- if (!isBadNumber(body)) main.pushToMain({ index, key: getKey('body'), value: body });
209
- if (!isBadNumber(upperWick)) main.pushToMain({ index, key: getKey('upper_wick'), value: upperWick });
210
- if (!isBadNumber(lowerWick)) main.pushToMain({ index, key: getKey('lower_wick'), value: lowerWick });
211
- if (!isBadNumber(range)) main.pushToMain({ index, key: getKey('range'), value: range });
198
+ if (!isBadNumber(body)) main.pushToMain({ index, key: outputKeys.body, value: body });
199
+ if (!isBadNumber(upperWick)) main.pushToMain({ index, key: outputKeys.upper_wick, value: upperWick });
200
+ if (!isBadNumber(lowerWick)) main.pushToMain({ index, key: outputKeys.lower_wick, value: lowerWick });
201
+ if (!isBadNumber(range)) main.pushToMain({ index, key: outputKeys.range, value: range });
212
202
 
213
203
  // Do not add return logs to cross.
214
204
  main.pushToMain({ index, key: crossKey, value: cross });
@@ -4,6 +4,9 @@ import { validateInputValues } from '../utilities/validators.js';
4
4
  import { initializeColumns } from '../core-functions/initializeColumns.js';
5
5
  import { mathLog } from '../utilities/math.js';
6
6
 
7
+ const isBadNumber = v => v == null || !Number.isFinite(v);
8
+
9
+
7
10
  const indicatorClasses = {
8
11
  ema: FasterEMA,
9
12
  sma: FasterSMA
@@ -27,27 +30,49 @@ export const movingAverages = (main, index, methodName, size, { target, lag, ret
27
30
  // Create the main moving average instance.
28
31
  instances[keyName] = new indicatorClasses[methodName](size)
29
32
 
30
- initializeColumns(main, [{ key: keyName, priceBased: !retLogs && priceBased.has(target) }], { lag })
33
+ if(retLogs) {
34
+ initializeColumns(main, [
35
+ { key: `${keyName}_distance`, priceBased: false },
36
+ { key: `${keyName}_change`, priceBased: false }
37
+ ], { lag })
38
+ } else {
39
+ initializeColumns(main, [{ key: keyName, priceBased: priceBased.has(target) }], { lag })
40
+ }
41
+
31
42
  }
32
43
 
33
44
  // Retrieve the current price value
34
- const value = verticalOhlcv[target][index]
35
- const instance = instances[keyName]
45
+ const currVal = verticalOhlcv[target][index];
46
+
47
+
48
+ if(isBadNumber(currVal)) return;
49
+
50
+ const instance = instances[keyName];
51
+
52
+ //The easiest way to compute prevMa change is to retrieve the previous result before updating the indicator.
53
+ const prevMa = instance.isStable ? instance.getResult() : NaN;
36
54
 
37
55
  // Update the moving average instance.
38
- instance.update(value)
56
+ instance.update(currVal)
39
57
 
40
- const currMa = instance.isStable ? instance.getResult() : NaN
58
+ const currMa = instance.isStable ? instance.getResult() : NaN;
59
+
60
+ if(isBadNumber(currMa)) return;
41
61
 
42
- let output = currMa
43
62
  if (retLogs) {
44
- const ratio = value / currMa
45
- // Log-relative distance is defined only for finite, positive ratios and operands.
46
- output = value > 0 && currMa > 0 && ratio > 0 && Number.isFinite(ratio)
47
- ? mathLog(ratio, 1)
48
- : NaN
63
+
64
+ const ratio = currVal / currMa;
65
+ const distance = mathLog(currVal, currMa);
66
+
67
+ main.pushToMain({ index, key: `${keyName}_distance`, value: distance })
68
+
69
+ if(isBadNumber(prevMa)) return;
70
+
71
+ const change = mathLog(currMa, prevMa);
72
+
73
+ main.pushToMain({ index, key: `${keyName}_change`, value: change })
74
+ } else {
75
+ main.pushToMain({ index, key: keyName, value: currMa })
49
76
  }
50
77
 
51
- // Always push the selected value (even if NaN).
52
- main.pushToMain({ index, key: keyName, value: output })
53
78
  }
@@ -47,7 +47,7 @@ export const assignTypes = main => {
47
47
  }
48
48
  else if (colName === 'volume') {
49
49
  inputTypes[colName] = classifyNum(cellValue, true)
50
- arrayTypes[colName] = 'Int32Array'
50
+ arrayTypes[colName] = 'Float64Array'
51
51
  }
52
52
  else if (initialPriceBased.has(colName)) {
53
53
  inputTypes[colName] = classifyNum(cellValue, true, precision)
@@ -141,7 +141,7 @@ export const validateInputValues = (
141
141
  const value = verticalOhlcv[key]?.[index];
142
142
 
143
143
  const isValid = key === "volume"
144
- ? Number.isInteger(value) && value >= 0
144
+ ? Number.isFinite(value) && value >= 0
145
145
  : Number.isFinite(value) && value > 0;
146
146
 
147
147
  if (!isValid) invalidParams.push(key);
package/webpack.config.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { resolve, dirname } from 'path';
2
2
  import { fileURLToPath } from 'url';
3
- //import TerserPlugin from 'terser-webpack-plugin';
4
3
 
5
4
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
5
 
@@ -8,40 +7,12 @@ const entry = './index.js';
8
7
  const output = {
9
8
  filename: 'ohlcv-indicators.min.js',
10
9
  path: resolve(__dirname, './dist'),
11
- library: 'OHLCV_INDICATORS'
10
+ library: {name: 'OHLCV_INDICATORS', type: 'var', export: 'default'}
12
11
  };
13
12
 
14
13
  export default {
15
14
  entry,
16
15
  output,
17
16
  mode: 'production',
18
- target: 'web',
19
- /* optimization: { //TerserPlugin start
20
- minimize: true,
21
- minimizer: [
22
- new TerserPlugin({
23
- terserOptions: {
24
- output: {
25
- beautify: true, // Beautify the output
26
- comments: true, // Preserve comments
27
- },
28
- },
29
- }),
30
- ],
31
- }, // TerserPlugin end */
32
- module: {
33
- rules: [
34
- {
35
- test: /\.js$/,
36
- exclude: /node_modules/,
37
- use: {
38
- loader: 'babel-loader',
39
- options: {
40
- presets: ['@babel/preset-env'],
41
- plugins: ['@babel/plugin-proposal-class-properties']
42
- }
43
- }
44
- }
45
- ]
46
- }
17
+ target: 'web'
47
18
  };
@@ -1,22 +0,0 @@
1
- /*!
2
- * MIT License
3
- * Copyright (c) 2020 Benny Neugebauer
4
- *
5
- * Permission is hereby granted, free of charge, to any person obtaining a copy
6
- * of this software and associated documentation files (the "Software"), to deal
7
- * in the Software without restriction, including without limitation the rights
8
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- * copies of the Software, and to permit persons to whom the Software is
10
- * furnished to do so, subject to the following conditions:
11
- *
12
- * The above copyright notice and this permission notice shall be included in all
13
- * copies or substantial portions of the Software.
14
- *
15
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- * SOFTWARE.
22
- */