ohlcv-indicators 3.5.54 → 3.5.56
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/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +8 -2
- package/package.json +1 -1
- package/src/machine-learning/classifier.js +1 -1
- package/src/moving-averages/movingAverages.js +0 -2
- package/src/moving-averages/relativeVolume.js +0 -2
- package/src/moving-averages/vidya.js +53 -53
- package/src/studies/mapCols.js +10 -10
- package/src/utilities/precisionMultiplier.js +72 -61
- package/src/volatility/atr.js +2 -1
package/index.js
CHANGED
|
@@ -924,13 +924,19 @@ export default class OHLCV_INDICATORS {
|
|
|
924
924
|
|
|
925
925
|
validateObject(options, 'options', methodName)
|
|
926
926
|
|
|
927
|
-
const {lag = 0} = options
|
|
927
|
+
const {lag = 0, isPriceBased = false} = options
|
|
928
928
|
|
|
929
929
|
validateArray(newCols, 'newCols', methodName)
|
|
930
930
|
validateNumber(lag, {min: 0, allowDecimals: false}, 'options.lag', methodName)
|
|
931
|
+
validateBoolean(isPriceBased, 'isPriceBased', 'mapCols')
|
|
932
|
+
|
|
933
|
+
if(this.precision === false && isPriceBased) {
|
|
934
|
+
throw new Error(`Invalid param: If "mapCols.options.isPriceBased" is true, the "constructor.precision" param must be also true.`)
|
|
935
|
+
}
|
|
936
|
+
|
|
931
937
|
const order = getOrderFromArray(newCols, methodName)
|
|
932
938
|
|
|
933
|
-
this.inputParams.push({key: methodName, order, params: [newCols, callback, {lag}]})
|
|
939
|
+
this.inputParams.push({key: methodName, order, params: [newCols, callback, {lag, isPriceBased}]})
|
|
934
940
|
|
|
935
941
|
return this
|
|
936
942
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.56",
|
|
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",
|
|
@@ -291,7 +291,7 @@ export const classifier = (
|
|
|
291
291
|
{
|
|
292
292
|
uniqueLabels[loopIdx] = countUniqueLabels(yRows)
|
|
293
293
|
|
|
294
|
-
if(uniqueLabels[loopIdx] <
|
|
294
|
+
if(uniqueLabels[loopIdx] < 1)
|
|
295
295
|
{
|
|
296
296
|
throw new Error(`Invalid number or labels in ${prefix}. Check the logic of your "yCallback" function.`)
|
|
297
297
|
}
|
|
@@ -8,8 +8,6 @@ const indicatorClasses = {
|
|
|
8
8
|
|
|
9
9
|
export const movingAverages = (main, index, methodName, size, { target, lag }) => {
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
11
|
const { verticalOhlcv, instances, priceBased } = main
|
|
14
12
|
const suffix = (target !== 'close') ? `_${target}` : ''
|
|
15
13
|
const keyName = `${methodName}_${size}${suffix}`
|
|
@@ -9,11 +9,11 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
9
9
|
// Initialization on the first bar
|
|
10
10
|
if (index === 0) {
|
|
11
11
|
if (!(target in verticalOhlcv)) {
|
|
12
|
-
throw new Error(`VIDYA could not find target "${target}"`)
|
|
12
|
+
throw new Error(`VIDYA could not find target "${target}"`)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const prefix = `vidya_${indicatorKey}
|
|
16
|
-
const keyBase = target === 'close' ? prefix : `${prefix}_${target}
|
|
15
|
+
const prefix = `vidya_${indicatorKey}`
|
|
16
|
+
const keyBase = target === 'close' ? prefix : `${prefix}_${target}`
|
|
17
17
|
const keys = {
|
|
18
18
|
vidya: keyBase,
|
|
19
19
|
upper: `${prefix}_upper`,
|
|
@@ -21,7 +21,7 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
21
21
|
delta_vol: `${prefix}_delta_vol`,
|
|
22
22
|
cross: `${prefix}_cross`,
|
|
23
23
|
liquidity: `${prefix}_liquidity`,
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
const priceBasedVidyaKeys = [keyBase, keys.upper, keys.lower, keys.liquidity]
|
|
27
27
|
|
|
@@ -63,13 +63,13 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
// Series inputs
|
|
66
|
-
const price = verticalOhlcv[target][index]
|
|
67
|
-
const high = verticalOhlcv.high[index]
|
|
68
|
-
const low = verticalOhlcv.low[index]
|
|
69
|
-
const close = verticalOhlcv.close[index]
|
|
70
|
-
const open = verticalOhlcv.open[index]
|
|
71
|
-
const vol = verticalOhlcv.volume[index]
|
|
72
|
-
const prevCross = index > 0 ? verticalOhlcv[keys.cross][index - 1] : 0
|
|
66
|
+
const price = verticalOhlcv[target][index]
|
|
67
|
+
const high = verticalOhlcv.high[index]
|
|
68
|
+
const low = verticalOhlcv.low[index]
|
|
69
|
+
const close = verticalOhlcv.close[index]
|
|
70
|
+
const open = verticalOhlcv.open[index]
|
|
71
|
+
const vol = verticalOhlcv.volume[index]
|
|
72
|
+
const prevCross = index > 0 ? verticalOhlcv[keys.cross][index - 1] : 0
|
|
73
73
|
|
|
74
74
|
const highestHigh = (inst.liquidity.high.length === liquidityLookback) ? Math.max(...inst.liquidity.high) : NaN
|
|
75
75
|
const lowestLow = (inst.liquidity.low.length === liquidityLookback) ? Math.min(...inst.liquidity.low) : NaN
|
|
@@ -81,77 +81,77 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
81
81
|
high - low,
|
|
82
82
|
Math.abs(high - prevClose),
|
|
83
83
|
Math.abs(low - prevClose)
|
|
84
|
-
)
|
|
85
|
-
inst.atrBuffer.push(tr)
|
|
86
|
-
if (inst.atrBuffer.length > atrLength) inst.atrBuffer.shift()
|
|
84
|
+
)
|
|
85
|
+
inst.atrBuffer.push(tr)
|
|
86
|
+
if (inst.atrBuffer.length > atrLength) inst.atrBuffer.shift()
|
|
87
87
|
}
|
|
88
88
|
const atr = inst.atrBuffer.length === atrLength
|
|
89
89
|
? inst.atrBuffer.reduce((a, b) => a + b, 0) / atrLength
|
|
90
|
-
: NaN
|
|
90
|
+
: NaN
|
|
91
91
|
|
|
92
92
|
// Compute raw VIDYA
|
|
93
|
-
let rawVidya = NaN
|
|
94
|
-
let mom = NaN
|
|
95
|
-
if (index > 0) mom = price - verticalOhlcv[target][index - 1]
|
|
96
|
-
inst.momentumBuffer.push(mom)
|
|
97
|
-
if (inst.momentumBuffer.length > momentum) inst.momentumBuffer.shift()
|
|
93
|
+
let rawVidya = NaN
|
|
94
|
+
let mom = NaN
|
|
95
|
+
if (index > 0) mom = price - verticalOhlcv[target][index - 1]
|
|
96
|
+
inst.momentumBuffer.push(mom)
|
|
97
|
+
if (inst.momentumBuffer.length > momentum) inst.momentumBuffer.shift()
|
|
98
98
|
if (inst.momentumBuffer.length === momentum) {
|
|
99
|
-
let sumPos = 0, sumNeg = 0
|
|
99
|
+
let sumPos = 0, sumNeg = 0
|
|
100
100
|
inst.momentumBuffer.forEach(m => m >= 0 ? sumPos += m : sumNeg += -m);
|
|
101
|
-
const denom = sumPos + sumNeg
|
|
101
|
+
const denom = sumPos + sumNeg
|
|
102
102
|
const cmo = denom === 0 ? 0 : Math.abs((sumPos - sumNeg) / denom) * 100;
|
|
103
|
-
const alpha = 2 / (size + 1)
|
|
104
|
-
const a = alpha * (cmo / 100)
|
|
103
|
+
const alpha = 2 / (size + 1)
|
|
104
|
+
const a = alpha * (cmo / 100)
|
|
105
105
|
rawVidya = isNaN(inst.prevVidya) ? price : (a * price + (1 - a) * inst.prevVidya);
|
|
106
|
-
inst.prevVidya = rawVidya
|
|
106
|
+
inst.prevVidya = rawVidya
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Smooth VIDYA using fixed 15-bar SMA
|
|
110
|
-
inst.sma.update(rawVidya)
|
|
111
|
-
let smoothedVidya = NaN
|
|
112
|
-
try { smoothedVidya = inst.sma.getResult()
|
|
110
|
+
inst.sma.update(rawVidya)
|
|
111
|
+
let smoothedVidya = NaN
|
|
112
|
+
try { smoothedVidya = inst.sma.getResult() } catch {}
|
|
113
113
|
|
|
114
114
|
// Bands
|
|
115
115
|
const upper = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
116
116
|
? smoothedVidya + atr * bandDistance
|
|
117
|
-
: NaN
|
|
117
|
+
: NaN
|
|
118
118
|
const lower = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
119
119
|
? smoothedVidya - atr * bandDistance
|
|
120
|
-
: NaN
|
|
120
|
+
: NaN
|
|
121
121
|
|
|
122
122
|
// Detect crosses
|
|
123
|
-
const prevPrice = index > 0 ? verticalOhlcv[target][index - 1] : NaN
|
|
124
|
-
const prevUpper = index > 0 ? verticalOhlcv[keys.upper][index - 1] : NaN
|
|
125
|
-
const prevLower = index > 0 ? verticalOhlcv[keys.lower][index - 1] : NaN
|
|
126
|
-
const crossUp = index > 0 && prevPrice <= prevUpper && price > upper
|
|
127
|
-
const crossDown = index > 0 && prevPrice >= prevLower && price < lower
|
|
123
|
+
const prevPrice = index > 0 ? verticalOhlcv[target][index - 1] : NaN
|
|
124
|
+
const prevUpper = index > 0 ? verticalOhlcv[keys.upper][index - 1] : NaN
|
|
125
|
+
const prevLower = index > 0 ? verticalOhlcv[keys.lower][index - 1] : NaN
|
|
126
|
+
const crossUp = index > 0 && prevPrice <= prevUpper && price > upper
|
|
127
|
+
const crossDown = index > 0 && prevPrice >= prevLower && price < lower
|
|
128
128
|
|
|
129
129
|
// Update trend state
|
|
130
|
-
if (crossUp) inst.isTrendUp = true
|
|
131
|
-
if (crossDown) inst.isTrendUp = false
|
|
130
|
+
if (crossUp) inst.isTrendUp = true
|
|
131
|
+
if (crossDown) inst.isTrendUp = false
|
|
132
132
|
|
|
133
133
|
// Cumulative cross counter: positive when uptrend, negative when downtrend
|
|
134
134
|
let cross = 0;
|
|
135
135
|
if (index > 0) {
|
|
136
136
|
if (inst.isTrendUp) {
|
|
137
|
-
cross = prevCross > 0 ? prevCross + 1 : 1
|
|
137
|
+
cross = prevCross > 0 ? prevCross + 1 : 1
|
|
138
138
|
} else {
|
|
139
|
-
cross = prevCross < 0 ? prevCross - 1 : -1
|
|
139
|
+
cross = prevCross < 0 ? prevCross - 1 : -1
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// Volume accumulation
|
|
144
144
|
if (crossUp || crossDown) {
|
|
145
|
-
inst.upVol = inst.downVol = 0
|
|
145
|
+
inst.upVol = inst.downVol = 0
|
|
146
146
|
} else {
|
|
147
|
-
inst.upVol += (close > open ? vol : 0)
|
|
148
|
-
inst.downVol += (close < open ? vol : 0)
|
|
147
|
+
inst.upVol += (close > open ? vol : 0)
|
|
148
|
+
inst.downVol += (close < open ? vol : 0)
|
|
149
149
|
}
|
|
150
|
-
const avgVol = (inst.upVol + inst.downVol) / 2 || 1
|
|
151
|
-
const deltaPct = (inst.upVol - inst.downVol) / avgVol
|
|
150
|
+
const avgVol = (inst.upVol + inst.downVol) / 2 || 1
|
|
151
|
+
const deltaPct = (inst.upVol - inst.downVol) / avgVol
|
|
152
152
|
|
|
153
153
|
// Trend-based boundary (reset on crossover)
|
|
154
|
-
const trendValue = (inst.isTrendUp) ? lower : upper
|
|
154
|
+
const trendValue = (inst.isTrendUp) ? lower : upper
|
|
155
155
|
let liquidity = NaN
|
|
156
156
|
|
|
157
157
|
if(cross > 0)
|
|
@@ -164,12 +164,12 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// Push outputs
|
|
167
|
-
main.pushToMain({ index, key: keys.vidya, value: trendValue })
|
|
168
|
-
main.pushToMain({ index, key: keys.upper, value: upper })
|
|
169
|
-
main.pushToMain({ index, key: keys.lower, value: lower })
|
|
170
|
-
main.pushToMain({ index, key: keys.delta_vol, value: deltaPct })
|
|
171
|
-
main.pushToMain({ index, key: keys.cross, value: cross })
|
|
172
|
-
main.pushToMain({ index, key: keys.liquidity, value: liquidity })
|
|
167
|
+
main.pushToMain({ index, key: keys.vidya, value: trendValue })
|
|
168
|
+
main.pushToMain({ index, key: keys.upper, value: upper })
|
|
169
|
+
main.pushToMain({ index, key: keys.lower, value: lower })
|
|
170
|
+
main.pushToMain({ index, key: keys.delta_vol, value: deltaPct })
|
|
171
|
+
main.pushToMain({ index, key: keys.cross, value: cross })
|
|
172
|
+
main.pushToMain({ index, key: keys.liquidity, value: liquidity })
|
|
173
173
|
|
|
174
174
|
inst.liquidity.high.push(high)
|
|
175
175
|
inst.liquidity.low.push(low)
|
|
@@ -177,5 +177,5 @@ export const vidya = (main, index, size, momentum, options = {}) => {
|
|
|
177
177
|
if(inst.liquidity.high.length > liquidityLookback) inst.liquidity.high.shift()
|
|
178
178
|
if(inst.liquidity.low.length > liquidityLookback) inst.liquidity.low.shift()
|
|
179
179
|
|
|
180
|
-
return true
|
|
181
|
-
}
|
|
180
|
+
return true
|
|
181
|
+
}
|
package/src/studies/mapCols.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
export const mapCols = (main, index, newCols, callback, {lag}) => {
|
|
1
|
+
export const mapCols = (main, index, newCols, callback, {lag, isPriceBased}) => {
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const {verticalOhlcv, len} = main
|
|
3
|
+
const {verticalOhlcv, len, priceBased, precision} = main
|
|
6
4
|
|
|
7
5
|
if(index === 0)
|
|
8
6
|
{
|
|
9
|
-
|
|
10
7
|
for(const key of newCols)
|
|
11
8
|
{
|
|
12
9
|
if(verticalOhlcv.hasOwnProperty(key)) {
|
|
@@ -14,6 +11,11 @@ export const mapCols = (main, index, newCols, callback, {lag}) => {
|
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
verticalOhlcv[key] = new Array(len).fill(0)
|
|
14
|
+
|
|
15
|
+
if(precision && isPriceBased)
|
|
16
|
+
{
|
|
17
|
+
priceBased.add(key)
|
|
18
|
+
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
if(lag > 0)
|
|
@@ -35,12 +37,10 @@ export const mapCols = (main, index, newCols, callback, {lag}) => {
|
|
|
35
37
|
export const defaultMapColsCallback = ({main, index}) => {
|
|
36
38
|
const {verticalOhlcv} = main
|
|
37
39
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
+
const close = verticalOhlcv.close[index]
|
|
41
|
+
const open = verticalOhlcv.open[index-1]
|
|
40
42
|
|
|
41
|
-
if(typeof prevClose === 'undefined') return null
|
|
42
|
-
|
|
43
43
|
return {
|
|
44
|
-
change: (
|
|
44
|
+
change: ((close - open) / open) * 100
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,98 +1,109 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
|
|
2
|
+
//calcPrecisionMultiplier is calculated once in the class constructor, using the open, high, low and close values of the first row
|
|
3
|
+
//the multiplier (min 10000) is store in constructor
|
|
4
|
+
|
|
4
5
|
export const calcPrecisionMultiplier = main => {
|
|
5
|
-
const { precision, firstRow, initialPriceBased } = main
|
|
6
|
+
const { precision, firstRow, initialPriceBased } = main
|
|
6
7
|
if (precision === false) return 1
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const err = 'If "precision" is set to true all the "open", "high", "low" and "close" values must be numeric strings like "123.45" with no commas or currency symbols.'
|
|
9
|
+
let maxMultiplier = 1
|
|
11
10
|
|
|
12
11
|
for (const [key, strNum] of Object.entries(firstRow)) {
|
|
12
|
+
if (!initialPriceBased.has(key)) continue
|
|
13
|
+
|
|
14
|
+
if (typeof strNum !== 'string' || !/^\d+(\.\d+)?$/.test(strNum)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`If "precision" is set to true all the "open", "high", "low" and "close" values must be numeric strings like "123.45" with no commas or currency symbols. Property "${key}" had the value "${strNum}" type ${typeof strNum}.`
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [integerStr, decimals = ''] = strNum.split('.')
|
|
21
|
+
const integer = Number(integerStr)
|
|
22
|
+
|
|
23
|
+
let decimalPrecision = 4
|
|
24
|
+
|
|
25
|
+
// Treat any non-zero integer (positive or negative) as "normal"
|
|
26
|
+
if (Math.abs(integer) === 0) {
|
|
27
|
+
const decimalsLen = decimals.length
|
|
28
|
+
const countDecimals = Math.max(4, decimalsLen)
|
|
29
|
+
|
|
30
|
+
// index of first non-zero decimal digit, -1 if none
|
|
31
|
+
const firstNonZeroIndex = decimals.search(/[1-9]/)
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
33
|
+
if (firstNonZeroIndex === -1) {
|
|
34
|
+
// 0, 0.0, 0.0000... -> nothing special to preserve
|
|
35
|
+
decimalPrecision = countDecimals
|
|
36
|
+
} else {
|
|
37
|
+
// Give extra headroom after the first non-zero digit
|
|
38
|
+
const extra = 5 // tweak as you like
|
|
39
|
+
const needed = firstNonZeroIndex + 1 + extra
|
|
40
|
+
decimalPrecision = Math.max(countDecimals, needed)
|
|
41
|
+
}
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
|
|
44
|
+
// Optional: cap exponent to keep numbers sane
|
|
45
|
+
const multiplier = 10 ** decimalPrecision
|
|
22
46
|
|
|
23
|
-
|
|
24
|
-
const decimalPrecision = Math.max(4, decimals.length)
|
|
25
|
-
multipliers.push(Math.pow(10, decimalPrecision)) // returns Number
|
|
47
|
+
if (multiplier > maxMultiplier) maxMultiplier = multiplier
|
|
26
48
|
}
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
return Math.max(...multipliers)
|
|
50
|
+
return maxMultiplier
|
|
30
51
|
}
|
|
31
52
|
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
export const addPrecisionExact = (strNum, mul) => {
|
|
53
|
+
|
|
54
|
+
//this function converts the strNum into a integer * multiplier
|
|
55
|
+
export const addPrecisionAsNumber = (strNum, multiplier) => {
|
|
36
56
|
if (typeof strNum !== 'string') throw new Error('addPrecisionExact expects a string when precision=true')
|
|
37
57
|
|
|
38
58
|
const negative = strNum.charAt(0) === '-'
|
|
39
59
|
const s = negative ? strNum.slice(1) : strNum
|
|
40
60
|
const [intPart, decPart = ''] = s.split('.')
|
|
41
61
|
|
|
42
|
-
// Compute decimals from
|
|
43
|
-
const mulStr = String(
|
|
44
|
-
const decimals = (
|
|
62
|
+
// Compute decimals from multiplier (multiplier expected as 10^decimals)
|
|
63
|
+
const mulStr = String(multiplier);
|
|
64
|
+
const decimals = (multiplier === 1) ? 0 : (mulStr.length - 1)
|
|
45
65
|
|
|
46
66
|
const decPadded = decPart.padEnd(decimals, '0').slice(0, decimals); // pad or trim
|
|
47
67
|
const integerString = intPart + decPadded // e.g. "12" + "3400" => "123400"
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
let result = parseInt(integerString || '0', 10)
|
|
70
|
+
|
|
51
71
|
if (negative) result = -result
|
|
72
|
+
|
|
52
73
|
return result
|
|
53
74
|
}
|
|
54
75
|
|
|
55
|
-
export const revertPrecisionExact = (num, mul) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Convert input to BigInt without using literal suffixes
|
|
59
|
-
const big = (typeof num === 'bigint') ? num : BigInt(parseInt(num))
|
|
60
|
-
const neg = big < BigInt(0)
|
|
61
|
-
const abs = neg ? -big : big
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
//this function converts the outputs the number generated in addPrecisionAsNumber / multiplier
|
|
78
|
+
export const revertPrecisionAsNumber = (num, multiplier) => {
|
|
79
|
+
if (typeof num !== 'number' || !Number.isFinite(num)) {
|
|
80
|
+
throw new Error('revertPrecisionAsNumber expects a finite number')
|
|
67
81
|
}
|
|
68
82
|
|
|
69
|
-
const
|
|
70
|
-
|
|
83
|
+
const negative = num < 0
|
|
84
|
+
let abs = Math.abs(num)
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
// Make sure we're working with an integer, mirroring addPrecisionAsNumber
|
|
87
|
+
abs = Math.trunc(abs)
|
|
74
88
|
|
|
75
|
-
|
|
76
|
-
const trimmedFrac = fracStr.replace(/0+$/, '')
|
|
77
|
-
const result = trimmedFrac.length === 0
|
|
78
|
-
? intPart.toString()
|
|
79
|
-
: `${intPart.toString()}.${trimmedFrac}`
|
|
89
|
+
const decimals = (multiplier === 1) ? 0 : (String(multiplier).length - 1)
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
if (decimals === 0) {
|
|
92
|
+
// No fractional part; just restore sign
|
|
93
|
+
return negative ? -abs : abs
|
|
94
|
+
}
|
|
83
95
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
export const addPrecisionAsNumber = (strNum, mul) => {
|
|
96
|
+
// Integer division and remainder
|
|
97
|
+
const intPart = Math.trunc(abs / multiplier)
|
|
98
|
+
const fracPart = abs % multiplier
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
100
|
+
// Build fractional string with leading zeros, then trim trailing zeros
|
|
101
|
+
let fracStr = String(fracPart).padStart(decimals, '0')
|
|
102
|
+
fracStr = fracStr.replace(/0+$/, '')
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
const resultStr = fracStr.length === 0
|
|
105
|
+
? String(intPart)
|
|
106
|
+
: `${intPart}.${fracStr}`
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
108
|
+
return Number(negative ? `-${resultStr}` : resultStr)
|
|
109
|
+
}
|
package/src/volatility/atr.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {FasterATR, FasterWSMA} from 'trading-signals';
|
|
3
3
|
|
|
4
4
|
export const atr = (main, index, size, {lag, percentage, upper, lower}) => {
|
|
5
|
-
const { verticalOhlcv, instances } = main
|
|
5
|
+
const { verticalOhlcv, instances, priceBased } = main
|
|
6
6
|
const baseKeyName = `atr_${size}`
|
|
7
7
|
|
|
8
8
|
if (index === 0) {
|
|
@@ -20,6 +20,7 @@ export const atr = (main, index, size, {lag, percentage, upper, lower}) => {
|
|
|
20
20
|
for(const k of keyNames)
|
|
21
21
|
{
|
|
22
22
|
verticalOhlcv[k] = new Float64Array(len).fill(NaN)
|
|
23
|
+
priceBased.add(k)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
if(lag > 0)
|