ohlcv-indicators 3.5.48 → 3.5.49

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
@@ -355,6 +355,26 @@ export default class OHLCV_INDICATORS {
355
355
  return this
356
356
  }
357
357
 
358
+ heikenAshi(smoothLength = 10, afterSmoothLength = 10, options = {}) {
359
+ const methodName = 'heikenAshi'
360
+
361
+ isAlreadyComputed(this)
362
+
363
+ validateNumber(smoothLength, {min: 1, max: this.len, allowDecimals: false}, 'smoothLength', methodName)
364
+ validateNumber(afterSmoothLength, {min: 1, max: this.len, allowDecimals: false}, 'afterSmoothLength', methodName)
365
+ validateObject(options, 'options', methodName)
366
+
367
+ const {lag = 0} = options
368
+
369
+ validateNumber(lag, {min: 0, max: this.len, allowDecimals: false}, 'options.lag', methodName)
370
+
371
+ const order = 0
372
+
373
+ this.inputParams.push({key: methodName, order, params: [smoothLength, afterSmoothLength, {lag}]})
374
+
375
+ return this
376
+ }
377
+
358
378
  vidya(size = 14, momentum = 20, options = {}) {
359
379
 
360
380
  const methodName = 'vidya'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohlcv-indicators",
3
- "version": "3.5.48",
3
+ "version": "3.5.49",
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",
@@ -3,6 +3,7 @@ import { validateInputParams } from "../utilities/validators.js";
3
3
  import { rsi } from "../oscillators/rsi.js";
4
4
  import { stochastic } from "../oscillators/stochastic.js";
5
5
  import { movingAverages } from "../moving-averages/movingAverages.js";
6
+ import { heikenAshi } from "../moving-averages/heikenAshi.js";
6
7
  import { vidya } from "../moving-averages/vidya.js";
7
8
  import { macd } from "../moving-averages/macd.js";
8
9
  import { relativeVolume } from "../moving-averages/relativeVolume.js";
@@ -27,6 +28,7 @@ import { classifier } from "../machine-learning/classifier.js";
27
28
  // Map indicator keys to their respective functions
28
29
  const mainFunctions = {
29
30
  dateTime,
31
+ heikenAshi,
30
32
  rsi,
31
33
  stochastic,
32
34
  atr,
@@ -0,0 +1,113 @@
1
+ import { FasterEMA } from 'trading-signals';
2
+
3
+ export const heikenAshi = (main, index, smoothLength, afterSmoothLength, {lag}) => {
4
+ const { verticalOhlcv, instances, len } = main
5
+ const prefix = `heiken_ashi_${smoothLength}_${afterSmoothLength}`
6
+ const keys = ['open', 'high', 'low', 'close']
7
+
8
+ // ---- INIT ----
9
+ if (index === 0) {
10
+
11
+ // Initialize pre-smoothing EMA instances and buffers
12
+ instances[prefix] = {
13
+ emaPre: Object.fromEntries(keys.map(k => [k, new FasterEMA(smoothLength)])),
14
+ emaPost: Object.fromEntries(keys.map(k => [k, new FasterEMA(afterSmoothLength)])),
15
+ prevHaOpen: NaN,
16
+ prevHaClose: NaN,
17
+ isTrendUp: false,
18
+ };
19
+
20
+ const keyNames = keys.map(k => `${prefix}_${k}`)
21
+
22
+ const verticalOhlcvSetup = Object.fromEntries([...keyNames, `${prefix}_cross`].map(v => [v, new Float64Array(len).fill(NaN)]))
23
+
24
+ Object.assign(verticalOhlcv, {...verticalOhlcvSetup})
25
+
26
+ if(lag > 0)
27
+ {
28
+ main.lag(keyNames, lag)
29
+ }
30
+ }
31
+
32
+ // ---- FETCH RAW INPUTS ----
33
+ const open = verticalOhlcv.open[index]
34
+ const high = verticalOhlcv.high[index]
35
+ const low = verticalOhlcv.low[index]
36
+ const close = verticalOhlcv.close[index]
37
+ const inst = instances[prefix]
38
+
39
+ // ---- PRE-SMOOTHING (EMA) ----
40
+ inst.emaPre.open.update(open)
41
+ inst.emaPre.high.update(high)
42
+ inst.emaPre.low.update(low)
43
+ inst.emaPre.close.update(close)
44
+
45
+ let sOpen, sHigh, sLow, sClose
46
+
47
+ try {
48
+ sOpen = inst.emaPre.open.getResult()
49
+ sHigh = inst.emaPre.high.getResult()
50
+ sLow = inst.emaPre.low.getResult()
51
+ sClose = inst.emaPre.close.getResult()
52
+ } catch {
53
+ return true; // skip if not enough data
54
+ }
55
+
56
+ // ---- HEIKEN ASHI CORE ----
57
+ const haClose = (sOpen + sHigh + sLow + sClose) / 4
58
+ const haOpen = (Number.isNaN(inst.prevHaOpen) || Number.isNaN(inst.prevHaClose)) ? (sOpen + sClose) / 2 : (inst.prevHaOpen + inst.prevHaClose) / 2
59
+ const haHigh = Math.max(sHigh, haOpen, haClose)
60
+ const haLow = Math.min(sLow, haOpen, haClose)
61
+
62
+ // Store for next round
63
+ inst.prevHaOpen = haOpen
64
+ inst.prevHaClose = haClose
65
+
66
+ // ---- POST-SMOOTHING (EMA) ----
67
+ inst.emaPost.open.update(haOpen)
68
+ inst.emaPost.high.update(haHigh)
69
+ inst.emaPost.low.update(haLow)
70
+ inst.emaPost.close.update(haClose)
71
+
72
+ let smOpen, smHigh, smLow, smClose
73
+
74
+ try {
75
+ smOpen = inst.emaPost.open.getResult()
76
+ smHigh = inst.emaPost.high.getResult()
77
+ smLow = inst.emaPost.low.getResult()
78
+ smClose = inst.emaPost.close.getResult()
79
+ } catch {
80
+ return true
81
+ }
82
+
83
+ // Cumulative cross counter: positive when uptrend, negative when downtrend
84
+ const prevCross = index > 0 ? verticalOhlcv[`${prefix}_cross`][index - 1] : 0
85
+ const prevHaOpen = index > 0 ? verticalOhlcv[`${prefix}_open`][index - 1] : NaN
86
+ const prevHaClose = index > 0 ? verticalOhlcv[`${prefix}_close`][index - 1] : NaN
87
+ const crossUp = !Number.isNaN(prevHaClose) && prevHaClose <= prevHaOpen && smClose > smOpen
88
+ const crossDown = !Number.isNaN(prevHaClose) && prevHaClose >= prevHaOpen && smClose < smOpen
89
+
90
+
91
+ if (crossUp) inst.isTrendUp = true;
92
+ if (crossDown) inst.isTrendUp = false;
93
+
94
+ let cross = 0
95
+
96
+ if (index > 0) {
97
+ if (inst.isTrendUp) {
98
+ cross = prevCross > 0 ? prevCross + 1 : 1
99
+ } else {
100
+ cross = prevCross < 0 ? prevCross - 1 : -1
101
+ }
102
+ }
103
+
104
+ // ---- PUSH OUTPUTS ----
105
+ main.pushToMain({ index, key: `${prefix}_open`, value: smOpen })
106
+ main.pushToMain({ index, key: `${prefix}_high`, value: smHigh })
107
+ main.pushToMain({ index, key: `${prefix}_low`, value: smLow })
108
+ main.pushToMain({ index, key: `${prefix}_close`, value: smClose })
109
+ main.pushToMain({ index, key: `${prefix}_cross`, value: cross })
110
+
111
+
112
+ return true
113
+ }