ohlcv-indicators 3.5.77 → 3.5.79

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.
Files changed (42) hide show
  1. package/AGENTS.md +105 -0
  2. package/README.md +415 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
  5. package/index.js +96 -127
  6. package/package.json +1 -1
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +49 -26
  9. package/src/core-functions/pushToMain.js +1 -3
  10. package/src/core-indicators/README.md +61 -0
  11. package/src/core-indicators/composites.js +136 -0
  12. package/src/core-indicators/index.js +5 -0
  13. package/src/core-indicators/indicator.js +82 -0
  14. package/src/core-indicators/movingAverages.js +88 -0
  15. package/src/core-indicators/rsi.js +41 -0
  16. package/src/core-indicators/volatility.js +107 -0
  17. package/src/machine-learning/ml-utilities.js +0 -9
  18. package/src/moving-averages/bollingerBands.js +11 -26
  19. package/src/moving-averages/donchianChannel.js +30 -29
  20. package/src/moving-averages/heikenAshi.js +23 -33
  21. package/src/moving-averages/macd.js +10 -26
  22. package/src/moving-averages/movingAverages.js +7 -23
  23. package/src/moving-averages/relativeVolume.js +23 -21
  24. package/src/oscillators/rsi.js +7 -26
  25. package/src/oscillators/stochastic.js +6 -19
  26. package/src/oscillators/volumeDelta.js +36 -28
  27. package/src/oscillators/volumeOscillator.js +29 -31
  28. package/src/studies/{priceFeatures.js → candleFeatures.js} +15 -23
  29. package/src/studies/dateTime.js +22 -27
  30. package/src/studies/findCrosses.js +16 -7
  31. package/src/studies/lag.js +26 -13
  32. package/src/studies/mapCols.js +14 -15
  33. package/src/utilities/assignTypes.js +1 -1
  34. package/src/utilities/config.js +64 -0
  35. package/src/utilities/numberUtilities.js +2 -0
  36. package/src/utilities/validators.js +37 -21
  37. package/src/utilities/verticalToHorizontal.js +21 -16
  38. package/src/volatility/adx.js +7 -20
  39. package/src/volatility/atr.js +10 -23
  40. package/test/test.js +533 -0
  41. package/src/machine-learning/scaler.js +0 -165
  42. package/src/moving-averages/vidya.js +0 -181
@@ -0,0 +1,61 @@
1
+ # Core indicators
2
+
3
+ Local numeric replacements for the `Faster*` implementations used from
4
+ `trading-signals@5.0.4`. The existing constructor arguments, `update(value,
5
+ replace?)`, `isStable`, and `getResult()` interface remain available. The
6
+ numeric series also retain result/extrema and replacement semantics. These
7
+ are not replacements for the arbitrary-precision `Big` classes or a promise
8
+ of compatibility with other trading-signals versions.
9
+
10
+ ```js
11
+ import { FasterEMA } from './index.js'
12
+
13
+ const ema = new FasterEMA(14)
14
+ ema.update(price)
15
+ const value = ema.isStable ? ema.getResult() : NaN
16
+ ```
17
+
18
+ `getResult()` still throws `NotEnoughDataError` during warm-up for API
19
+ compatibility. All readiness checks are non-throwing; project handlers check
20
+ readiness before reading. EMA still returns its provisional result from
21
+ `update()` before it becomes stable, so do not substitute that return value
22
+ for a readiness-checked read.
23
+
24
+ ## Storage and calculation choices
25
+
26
+ - EMA uses scalar state and a readiness counter.
27
+ - SMA uses a bounded circular `Float64Array`. It sums oldest to newest to
28
+ preserve floating-point rounding and recovery after NaN/Infinity. It remains
29
+ O(period) per update rather than adopting a different running-sum formula.
30
+ - WSMA releases its seed SMA after initialization, then updates in O(1).
31
+ - RSI retains two prices instead of the entire input history.
32
+ - MACD uses a capped counter instead of retaining an unused price window.
33
+ - Bollinger uses a bounded numeric ring and two ordered passes, without
34
+ intermediate deviation arrays. Its size + 1 warm-up is intentional.
35
+ - Stochastic uses a bounded candle-reference ring, scans extrema without
36
+ temporary arrays, and preserves the original zero/NaN smoothing behavior.
37
+ - TR/ATR/DX/ADX retain the existing arithmetic and replacement behavior.
38
+
39
+ Scalar rings switch once to a bounded ordinary Array if a non-number is
40
+ provided. This preserves existing JavaScript coercion for custom mapped
41
+ columns without adding object storage for normal numeric input. NaN and
42
+ Infinity remain numeric values and do not trigger this fallback. Candle
43
+ references are retained where the reference implementation observes mutations
44
+ of previously supplied objects. Private buffer layouts intentionally differ.
45
+
46
+ ## Verification
47
+
48
+ `npm test` compares the core classes directly with the installed
49
+ trading-signals reference, checks bounded retained storage, and exercises all
50
+ public indicators, precision/log modes, lags, callbacks, and config replay.
51
+ The dependency is retained as a regression oracle; production source imports
52
+ only these local classes. Preserve the reference version in the lockfile.
53
+
54
+ `node test/test.js --baseline /path/to/original/index.js --benchmark` also
55
+ compares the full pipeline with a pre-change checkout. Benchmarks report
56
+ local timings and retained array slots/buffer bytes, not total heap usage or
57
+ performance guarantees. Results must remain exact; changing summation order,
58
+ warm-up, or quirks is a separate behavior change.
59
+
60
+ Adapted portions retain the upstream MIT notice in `indicator.js`, also
61
+ preserved in the generated distribution's license notice.
@@ -0,0 +1,136 @@
1
+ import { Indicator } from './indicator.js'
2
+ import { FasterSMA } from './movingAverages.js'
3
+
4
+ export class FasterBollingerBands extends Indicator {
5
+ constructor(interval, deviationMultiplier = 2) {
6
+ super()
7
+ this.interval = interval
8
+ this.deviationMultiplier = deviationMultiplier
9
+ this.prices = new Float64Array(interval)
10
+ this.count = 0
11
+ this.head = 0
12
+ }
13
+
14
+ update(price) {
15
+ // Custom Array-backed columns may contain raw values whose addition must not be coerced.
16
+ if (typeof price !== 'number' && !Array.isArray(this.prices)) {
17
+ this.prices = Array.from(this.prices)
18
+ }
19
+
20
+ if (this.count < this.interval) {
21
+ this.prices[this.count++] = price
22
+ return
23
+ }
24
+
25
+ // The first full window is deliberately skipped, matching the original warm-up.
26
+ this.prices[this.head] = price
27
+ this.head = (this.head + 1) % this.interval
28
+
29
+ let sum = 0
30
+ let slot = this.head
31
+ for (let i = 0; i < this.count; i++) {
32
+ sum += this.prices[slot]
33
+ if (++slot === this.interval) slot = 0
34
+ }
35
+ const middle = this.count ? sum / this.count : 0
36
+ let deviationMiddle = middle
37
+ if (!deviationMiddle && Array.isArray(this.prices)) {
38
+ // Match the original average || fallback, including repeated raw-value coercion.
39
+ sum = 0
40
+ slot = this.head
41
+ for (let i = 0; i < this.count; i++) {
42
+ sum += this.prices[slot]
43
+ if (++slot === this.interval) slot = 0
44
+ }
45
+ deviationMiddle = this.count ? sum / this.count : 0
46
+ }
47
+
48
+ // Re-sum in chronological order; a rolling variance changes floating-point results.
49
+ let squaredSum = 0
50
+ slot = this.head
51
+ for (let i = 0; i < this.count; i++) {
52
+ const difference = this.prices[slot] - deviationMiddle
53
+ squaredSum += difference * difference
54
+ if (++slot === this.interval) slot = 0
55
+ }
56
+ const standardDeviation = Math.sqrt(this.count ? squaredSum / this.count : 0)
57
+
58
+ return (this.result = {
59
+ lower: middle - standardDeviation * this.deviationMultiplier,
60
+ middle,
61
+ upper: middle + standardDeviation * this.deviationMultiplier
62
+ })
63
+ }
64
+ }
65
+
66
+ export class FasterMACD extends Indicator {
67
+ constructor(short, long, signal) {
68
+ super()
69
+ this.short = short
70
+ this.long = long
71
+ this.signal = signal
72
+ this.pricesCounter = 0
73
+ }
74
+
75
+ update(price, replace = false) {
76
+ if (!replace || this.pricesCounter === 0) this.pricesCounter++
77
+
78
+ const short = this.short.update(price, replace)
79
+ const long = this.long.update(price, replace)
80
+
81
+ if (this.pricesCounter > this.long.interval) this.pricesCounter--
82
+ if (this.pricesCounter === this.long.interval) {
83
+ const macd = short - long
84
+ const signal = this.signal.update(macd, replace)
85
+ return (this.result = { histogram: macd - signal, macd, signal })
86
+ }
87
+ }
88
+ }
89
+
90
+ export class FasterStochasticOscillator extends Indicator {
91
+ constructor(n, m, p) {
92
+ super()
93
+ this.n = n
94
+ this.m = m
95
+ this.p = p
96
+ this.periodM = new FasterSMA(m)
97
+ this.periodP = new FasterSMA(p)
98
+ // Keep candle references: callers may reuse or mutate objects retained in the window.
99
+ this.candles = new Array(n)
100
+ this.count = 0
101
+ this.head = 0
102
+ }
103
+
104
+ update(candle) {
105
+ if (this.count < this.n) {
106
+ this.candles[this.count++] = candle
107
+ } else {
108
+ this.candles[this.head] = candle
109
+ this.head = (this.head + 1) % this.n
110
+ }
111
+
112
+ if (this.count === this.n) {
113
+ let highest = -Infinity
114
+ let lowest = Infinity
115
+ let slot = this.head
116
+ for (let i = 0; i < this.count; i++) {
117
+ highest = Math.max(highest, this.candles[slot].high)
118
+ if (++slot === this.n) slot = 0
119
+ }
120
+ slot = this.head
121
+ for (let i = 0; i < this.count; i++) {
122
+ lowest = Math.min(lowest, this.candles[slot].low)
123
+ if (++slot === this.n) slot = 0
124
+ }
125
+
126
+ const divisor = highest - lowest
127
+ const fastK = ((candle.close - lowest) * 100) / (divisor === 0 ? 1 : divisor)
128
+ const stochK = this.periodM.update(fastK)
129
+ // Zero and NaN deliberately skip periodP, while still producing a result.
130
+ const stochD = stochK && this.periodP.update(stochK)
131
+ if (stochK !== undefined && stochD !== undefined) {
132
+ return (this.result = { stochD, stochK })
133
+ }
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,5 @@
1
+ export { NotEnoughDataError } from './indicator.js'
2
+ export { FasterEMA, FasterSMA, FasterWSMA } from './movingAverages.js'
3
+ export { FasterRSI } from './rsi.js'
4
+ export { FasterTR, FasterATR, FasterDX, FasterADX } from './volatility.js'
5
+ export { FasterBollingerBands, FasterMACD, FasterStochasticOscillator } from './composites.js'
@@ -0,0 +1,82 @@
1
+ /*!
2
+ * Portions adapted from trading-signals 5.0.4.
3
+ * MIT License
4
+ * Copyright (c) 2020 Benny Neugebauer
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ export class NotEnoughDataError extends Error {
26
+ constructor(message = 'Not enough data') {
27
+ super(message)
28
+ this.name = 'NotEnoughDataError'
29
+ }
30
+ }
31
+
32
+ export class Indicator {
33
+ result
34
+
35
+ get isStable() {
36
+ return this.result !== undefined
37
+ }
38
+
39
+ getResult() {
40
+ if (!this.isStable) throw new NotEnoughDataError()
41
+ return this.result
42
+ }
43
+ }
44
+
45
+ // Keep the public result/extrema and replacement semantics without price history.
46
+ export class NumberIndicator extends Indicator {
47
+ previousResult
48
+ highest
49
+ lowest
50
+ previousHighest
51
+ previousLowest
52
+
53
+ setResult(value, replace = false) {
54
+ if (replace) {
55
+ this.highest = this.previousHighest
56
+ this.lowest = this.previousLowest
57
+ this.result = this.previousResult
58
+ }
59
+ if (this.highest === undefined) this.highest = value
60
+ else {
61
+ this.previousHighest = this.highest
62
+ if (value > this.highest) this.highest = value
63
+ }
64
+ if (this.lowest === undefined) this.lowest = value
65
+ else {
66
+ this.previousLowest = this.lowest
67
+ if (value < this.lowest) this.lowest = value
68
+ }
69
+ this.previousResult = this.result
70
+ this.result = value
71
+ return value
72
+ }
73
+
74
+ replace(input) {
75
+ return this.update(input, true)
76
+ }
77
+
78
+ updates(prices) {
79
+ prices.forEach(price => this.update(price))
80
+ return this.result
81
+ }
82
+ }
@@ -0,0 +1,88 @@
1
+ import { NumberIndicator } from './indicator.js'
2
+
3
+ export class FasterEMA extends NumberIndicator {
4
+ constructor(interval) {
5
+ super()
6
+ this.interval = interval
7
+ this.pricesCounter = 0
8
+ this.weightFactor = 2 / (interval + 1)
9
+ }
10
+
11
+ update(price, replace = false) {
12
+ if (!replace || this.pricesCounter === 0) this.pricesCounter++
13
+
14
+ const previous = replace && this.previousResult !== undefined
15
+ ? this.previousResult
16
+ : this.result !== undefined ? this.result : price
17
+
18
+ return this.setResult(price * this.weightFactor + previous * (1 - this.weightFactor), replace)
19
+ }
20
+
21
+ get isStable() {
22
+ return this.pricesCounter >= this.interval
23
+ }
24
+ }
25
+
26
+ export class FasterSMA extends NumberIndicator {
27
+ constructor(interval) {
28
+ super()
29
+ this.interval = interval
30
+ this.prices = new Float64Array(interval)
31
+ this.count = 0
32
+ this.head = 0
33
+ }
34
+
35
+ update(price, replace = false) {
36
+ // Custom columns may contain raw values after their validated first row.
37
+ // Retain JavaScript addition/coercion semantics without slowing numeric windows.
38
+ if (typeof price !== 'number' && !Array.isArray(this.prices)) {
39
+ this.prices = Array.from(this.prices)
40
+ }
41
+ const { interval, prices } = this
42
+
43
+ if (replace && this.count) {
44
+ let last = this.head + this.count - 1
45
+ if (last >= interval) last -= interval
46
+ prices[last] = price
47
+ } else if (this.count < interval) {
48
+ prices[this.count++] = price
49
+ } else {
50
+ prices[this.head] = price
51
+ if (++this.head === interval) this.head = 0
52
+ }
53
+
54
+ if (this.count === interval) {
55
+ // Preserve chronological addition order, including NaN/Infinity recovery.
56
+ let sum = 0
57
+ for (let i = this.head; i < interval; i++) sum += prices[i]
58
+ for (let i = 0; i < this.head; i++) sum += prices[i]
59
+ return this.setResult(sum / interval, replace)
60
+ }
61
+ }
62
+ }
63
+
64
+ export class FasterWSMA extends NumberIndicator {
65
+ constructor(interval) {
66
+ super()
67
+ this.interval = interval
68
+ this.indicator = new FasterSMA(interval)
69
+ this.smoothingFactor = 1 / interval
70
+ }
71
+
72
+ update(price, replace = false) {
73
+ if (this.result === undefined) {
74
+ // The existing FasterWSMA appends seed samples even for replacement updates.
75
+ const sma = this.indicator.update(price)
76
+ if (sma !== undefined) {
77
+ this.indicator = undefined
78
+ return this.setResult(sma, replace)
79
+ }
80
+ } else if (replace && this.previousResult !== undefined) {
81
+ const smoothed = (price - this.previousResult) * this.smoothingFactor
82
+ return this.setResult(smoothed + this.previousResult, replace)
83
+ } else if (!replace) {
84
+ const smoothed = (price - this.result) * this.smoothingFactor
85
+ return this.setResult(smoothed + this.result, replace)
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,41 @@
1
+ import { NumberIndicator } from './indicator.js';
2
+ import { FasterWSMA } from './movingAverages.js';
3
+
4
+ export class FasterRSI extends NumberIndicator {
5
+ constructor(interval, SmoothingIndicator = FasterWSMA) {
6
+ super();
7
+ this.interval = interval;
8
+ this.avgGain = new SmoothingIndicator(interval);
9
+ this.avgLoss = new SmoothingIndicator(interval);
10
+ this.maxValue = 100;
11
+ this.priceCount = 0;
12
+ this.previousPrice = undefined;
13
+ this.currentPrice = undefined;
14
+ }
15
+
16
+ update(price, replace = false) {
17
+ // Only the latest two prices are needed, including repeated replacements.
18
+ if (!replace || this.priceCount === 0) {
19
+ this.previousPrice = this.currentPrice;
20
+ if (this.priceCount < 2) this.priceCount++;
21
+ }
22
+ this.currentPrice = price;
23
+ if (this.priceCount < 2) return;
24
+
25
+ const previousPrice = this.previousPrice;
26
+ if (price > previousPrice) {
27
+ this.avgLoss.update(0, replace);
28
+ this.avgGain.update(price - previousPrice, replace);
29
+ } else {
30
+ this.avgLoss.update(previousPrice - price, replace);
31
+ this.avgGain.update(0, replace);
32
+ }
33
+
34
+ if (this.avgGain.isStable) {
35
+ const avgLoss = this.avgLoss.getResult();
36
+ if (avgLoss === 0) return this.setResult(100, replace);
37
+ const relativeStrength = this.avgGain.getResult() / avgLoss;
38
+ return this.setResult(this.maxValue - this.maxValue / (relativeStrength + 1), replace);
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,107 @@
1
+ import { NumberIndicator } from './indicator.js';
2
+ import { FasterWSMA } from './movingAverages.js';
3
+
4
+ export class FasterTR extends NumberIndicator {
5
+ constructor() {
6
+ super();
7
+ this.previousCandle = undefined;
8
+ this.twoPreviousCandle = undefined;
9
+ }
10
+
11
+ update(candle, replace = false) {
12
+ const { high, low } = candle;
13
+ const highLow = high - low;
14
+ if (this.previousCandle && replace) this.previousCandle = this.twoPreviousCandle;
15
+ if (this.previousCandle) {
16
+ const highClose = Math.abs(high - this.previousCandle.close);
17
+ const lowClose = Math.abs(low - this.previousCandle.close);
18
+ this.twoPreviousCandle = this.previousCandle;
19
+ this.previousCandle = candle;
20
+ return this.setResult(Math.max(highLow, highClose, lowClose), replace);
21
+ }
22
+ this.twoPreviousCandle = this.previousCandle;
23
+ this.previousCandle = candle;
24
+ return this.setResult(highLow, replace);
25
+ }
26
+ }
27
+
28
+ export class FasterATR extends NumberIndicator {
29
+ constructor(interval, SmoothingIndicator = FasterWSMA) {
30
+ super();
31
+ this.interval = interval;
32
+ this.tr = new FasterTR();
33
+ this.smoothing = new SmoothingIndicator(interval);
34
+ }
35
+
36
+ update(candle, replace = false) {
37
+ const trueRange = this.tr.update(candle, replace);
38
+ this.smoothing.update(trueRange, replace);
39
+ if (this.smoothing.isStable) return this.setResult(this.smoothing.getResult(), replace);
40
+ }
41
+ }
42
+
43
+ export class FasterDX extends NumberIndicator {
44
+ constructor(interval, SmoothingIndicator = FasterWSMA) {
45
+ super();
46
+ this.interval = interval;
47
+ this.atr = new FasterATR(interval, SmoothingIndicator);
48
+ this.movesDown = new SmoothingIndicator(interval);
49
+ this.movesUp = new SmoothingIndicator(interval);
50
+ this.previousCandle = undefined;
51
+ this.mdi = undefined;
52
+ this.pdi = undefined;
53
+ }
54
+
55
+ updateState(candle, pdm = 0, mdm = 0) {
56
+ this.atr.update(candle);
57
+ this.movesUp.update(pdm);
58
+ this.movesDown.update(mdm);
59
+ this.previousCandle = candle;
60
+ }
61
+
62
+ update(candle) {
63
+ if (!this.previousCandle) {
64
+ this.updateState(candle);
65
+ return;
66
+ }
67
+ const higherHigh = candle.high - this.previousCandle.high;
68
+ const lowerLow = this.previousCandle.low - candle.low;
69
+ const pdm = higherHigh < 0 || higherHigh < lowerLow ? 0 : higherHigh;
70
+ const mdm = lowerLow < 0 || lowerLow < higherHigh ? 0 : lowerLow;
71
+ this.updateState(candle, pdm, mdm);
72
+
73
+ if (this.movesUp.isStable) {
74
+ const atr = this.atr.getResult();
75
+ this.pdi = this.movesUp.getResult() / atr;
76
+ this.mdi = this.movesDown.getResult() / atr;
77
+ const dmDiff = Math.abs(this.pdi - this.mdi);
78
+ const dmSum = this.pdi + this.mdi;
79
+ if (dmSum === 0) return this.setResult(0, false);
80
+ return this.setResult((dmDiff / dmSum) * 100, false);
81
+ }
82
+ }
83
+ }
84
+
85
+ export class FasterADX extends NumberIndicator {
86
+ constructor(interval, SmoothingIndicator = FasterWSMA) {
87
+ super();
88
+ this.interval = interval;
89
+ this.adx = new SmoothingIndicator(interval);
90
+ this.dx = new FasterDX(interval, SmoothingIndicator);
91
+ }
92
+
93
+ get mdi() {
94
+ return this.dx.mdi;
95
+ }
96
+
97
+ get pdi() {
98
+ return this.dx.pdi;
99
+ }
100
+
101
+ update(candle, replace = false) {
102
+ // DX/smoothing intentionally advance on replace, matching the existing API.
103
+ const result = this.dx.update(candle);
104
+ if (result !== undefined) this.adx.update(result);
105
+ if (this.adx.isStable) return this.setResult(this.adx.getResult(), replace);
106
+ }
107
+ }
@@ -3,12 +3,3 @@ export const oneHotEncode = (idx, size) => {
3
3
  vec[idx] = 1
4
4
  return vec
5
5
  }
6
-
7
- export const normalizeMinMax = (value, min, max, [validMin, validMax]) => {
8
- const clamped = Math.min(Math.max(value, min), max)
9
- return ((clamped - min) / (max - min)) * (validMax - validMin) + validMin
10
- }
11
-
12
- export const normalizeZScore = (value, mean, std) => {
13
- return std === 0 ? 0 : (value - mean) / std
14
- }
@@ -1,4 +1,6 @@
1
- import { FasterBollingerBands } from 'trading-signals'
1
+ import { FasterBollingerBands } from '../core-indicators/index.js'
2
+ import { validateInputValues } from '../utilities/validators.js'
3
+ import { initializeColumns } from '../core-functions/initializeColumns.js'
2
4
 
3
5
  export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
4
6
  const { verticalOhlcv, instances, useFullNames } = main
@@ -8,7 +10,9 @@ export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
8
10
 
9
11
  // Initialization on the first call.
10
12
  if (index === 0) {
11
- const { inputParams, verticalOhlcv, len, priceBased } = main
13
+ const { inputParams, verticalOhlcv, priceBased } = main
14
+
15
+ validateInputValues({ [target]: true }, verticalOhlcv, index, 'bollingerBands')
12
16
 
13
17
  if (!(target in verticalOhlcv)) {
14
18
  throw new Error(`bollingerBands could not find target "${target}"`)
@@ -37,21 +41,9 @@ export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
37
41
  `${prefix}_lower${suffix}`,
38
42
  ]
39
43
 
40
- if (priceBased.has(target)) {
41
- for (const k of keyNames) {
42
- priceBased.add(k)
43
- }
44
- }
45
-
46
- const verticalOhlcvSetup = Object.fromEntries(
47
- keyNames.map(v => [v, new Float64Array(len).fill(NaN)])
48
- )
49
-
50
- Object.assign(verticalOhlcv, { ...verticalOhlcvSetup })
51
-
52
- if (lag > 0) {
53
- main.lag(keyNames, lag)
54
- }
44
+ initializeColumns(main, keyNames.map(key => ({
45
+ key, priceBased: priceBased.has(target)
46
+ })), { lag })
55
47
  }
56
48
 
57
49
  const { numberOfIndicators } = instances.bollinger_bands
@@ -61,12 +53,7 @@ export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
61
53
  const value = verticalOhlcv[target][index]
62
54
  instance.update(value)
63
55
 
64
- let result = {}
65
- try {
66
- result = instance.getResult()
67
- } catch (err) {
68
- // If not available, result stays {}.
69
- }
56
+ const result = instance.isStable ? instance.getResult() : null
70
57
 
71
58
  const upper = result?.upper ?? NaN
72
59
  const middle = result?.middle ?? NaN
@@ -75,6 +62,4 @@ export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
75
62
  main.pushToMain({ index, key: `${prefix}_upper${suffix}`, value: upper })
76
63
  main.pushToMain({ index, key: `${prefix}_middle${suffix}`, value: middle })
77
64
  main.pushToMain({ index, key: `${prefix}_lower${suffix}`, value: lower })
78
-
79
- return true
80
- }
65
+ }