ohlcv-indicators 3.5.79 → 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.
- package/AGENTS.md +24 -17
- package/README.md +104 -78
- package/dist/ohlcv-indicators.min.js +1 -2
- package/index.js +44 -54
- package/package.json +5 -15
- package/src/core-functions/mainLoop.js +26 -46
- package/src/core-functions/pushToMain.js +7 -24
- package/src/core-indicators/README.md +3 -22
- package/src/core-indicators/indicator.js +11 -30
- package/src/moving-averages/bollingerBands.js +29 -6
- package/src/moving-averages/donchianChannel.js +34 -9
- package/src/moving-averages/heikenAshi.js +81 -100
- package/src/moving-averages/movingAverages.js +44 -9
- package/src/moving-averages/relativeVolume.js +9 -5
- package/src/oscillators/mfi.js +56 -0
- package/src/oscillators/volumeOscillator.js +5 -11
- package/src/studies/candleFeatures.js +28 -8
- package/src/studies/dateTime.js +41 -38
- package/src/studies/findCrosses.js +2 -9
- package/src/studies/lag.js +11 -12
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +4 -37
- package/src/utilities/numberUtilities.js +3 -3
- package/src/utilities/precisionMultiplier.js +11 -6
- package/src/utilities/validators.js +1 -1
- package/src/utilities/verticalToHorizontal.js +6 -1
- package/src/volatility/atr.js +1 -1
- package/webpack.config.js +2 -42
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +0 -23
- package/src/studies/mapCols.js +0 -45
- package/test/test.js +0 -533
package/package.json
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohlcv-indicators",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.82",
|
|
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",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "node test/test.js",
|
|
9
8
|
"build": "npx webpack",
|
|
10
|
-
"
|
|
9
|
+
"test": "node test/test.js"
|
|
11
10
|
},
|
|
12
11
|
"author": "https://jaimelias.com/",
|
|
13
12
|
"license": "ISC",
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"ohlcv-indicators": "^3.5.72",
|
|
16
|
-
"trading-signals": "^5.0.4"
|
|
17
|
-
},
|
|
18
13
|
"devDependencies": {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"babel-loader": "^9.1.3",
|
|
23
|
-
"dotenv": "^16.4.5",
|
|
24
|
-
"terser-webpack-plugin": "^5.3.14",
|
|
25
|
-
"webpack": "^5.93.0",
|
|
26
|
-
"webpack-cli": "^5.1.4"
|
|
14
|
+
"dotenv": "^16.6.1",
|
|
15
|
+
"webpack": "^5.111.1",
|
|
16
|
+
"webpack-cli": "^7.2.3"
|
|
27
17
|
}
|
|
28
18
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { areKeyValuesValid } from "./pushToMain.js";
|
|
2
2
|
import { validateInputParams } from "../utilities/validators.js";
|
|
3
3
|
import { rsi } from "../oscillators/rsi.js";
|
|
4
|
+
import { mfi } from "../oscillators/mfi.js";
|
|
4
5
|
import { stochastic } from "../oscillators/stochastic.js";
|
|
5
6
|
import { movingAverages } from "../moving-averages/movingAverages.js";
|
|
6
7
|
import { heikenAshi } from "../moving-averages/heikenAshi.js";
|
|
@@ -15,12 +16,12 @@ import { crossPairs } from "../studies/findCrosses.js";
|
|
|
15
16
|
import { dateTime } from "../studies/dateTime.js";
|
|
16
17
|
import { atr } from "../volatility/atr.js";
|
|
17
18
|
import { adx } from "../volatility/adx.js";
|
|
18
|
-
import { mapCols } from "../studies/mapCols.js";
|
|
19
19
|
|
|
20
20
|
import { buildArray } from "../utilities/assignTypes.js";
|
|
21
21
|
import { inputNumberFormatter } from "../utilities/numberUtilities.js";
|
|
22
22
|
import { dateFormaters } from "../utilities/dateUtilities.js";
|
|
23
23
|
import { candleFeatures } from "../studies/candleFeatures.js";
|
|
24
|
+
import { getPrecisionDecimals } from "../utilities/precisionMultiplier.js";
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
// Map indicator keys to their respective functions
|
|
@@ -29,6 +30,7 @@ const mainFunctions = {
|
|
|
29
30
|
dateTime,
|
|
30
31
|
heikenAshi,
|
|
31
32
|
rsi,
|
|
33
|
+
mfi,
|
|
32
34
|
stochastic,
|
|
33
35
|
atr,
|
|
34
36
|
adx,
|
|
@@ -40,7 +42,6 @@ const mainFunctions = {
|
|
|
40
42
|
donchianChannels,
|
|
41
43
|
bollingerBands,
|
|
42
44
|
volumeOscillator,
|
|
43
|
-
mapCols,
|
|
44
45
|
crossPairs,
|
|
45
46
|
lag
|
|
46
47
|
};
|
|
@@ -70,16 +71,16 @@ export const mainLoop = (input, main) => {
|
|
|
70
71
|
verticalOhlcv[key] = buildArray(arrayTypes[key], len)
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
const inputColumns = Object.entries(inputTypes).map(([key, formatterKey]) => {
|
|
75
|
+
const numeric = inputNumberFormatter.hasOwnProperty(formatterKey)
|
|
76
|
+
const formatter = numeric
|
|
77
|
+
? inputNumberFormatter[formatterKey]
|
|
78
|
+
: dateFormaters.hasOwnProperty(formatterKey) ? dateFormaters[formatterKey] : null
|
|
79
|
+
return { key, formatter, numeric }
|
|
80
|
+
})
|
|
81
|
+
const precisionDecimals = typeof precisionMultiplier === 'number'
|
|
82
|
+
? getPrecisionDecimals(precisionMultiplier) : undefined
|
|
83
|
+
let validationColumns = null
|
|
83
84
|
|
|
84
85
|
// Process each row in the input
|
|
85
86
|
for (let chunkStart = 0; chunkStart < len; chunkStart += chunkProcess) {
|
|
@@ -89,37 +90,13 @@ export const mainLoop = (input, main) => {
|
|
|
89
90
|
for (let index = chunkStart; index < chunkEnd; index++) {
|
|
90
91
|
const curr = input[index]
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
main.pushToMain({ index, key, value: formattedValue })
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
for(const [key, formaterKey] of Object.entries(inputTypes))
|
|
103
|
-
{
|
|
104
|
-
let value = curr[key]
|
|
105
|
-
|
|
106
|
-
if(typeof value === 'undefined') continue
|
|
107
|
-
|
|
108
|
-
if(inputNumberFormatter.hasOwnProperty(formaterKey))
|
|
109
|
-
{
|
|
110
|
-
const formatedValue = inputNumberFormatter[formaterKey](value, precisionMultiplier)
|
|
111
|
-
|
|
112
|
-
main.pushToMain({index, key, value: formatedValue})
|
|
113
|
-
}
|
|
114
|
-
else if(dateFormaters.hasOwnProperty(formaterKey))
|
|
115
|
-
{
|
|
116
|
-
main.pushToMain({index, key, value: dateFormaters[formaterKey](value)})
|
|
117
|
-
}
|
|
118
|
-
else
|
|
119
|
-
{
|
|
120
|
-
main.pushToMain({index, key, value})
|
|
121
|
-
}
|
|
122
|
-
}
|
|
93
|
+
for (const { key, formatter, numeric } of inputColumns) {
|
|
94
|
+
const value = curr[key]
|
|
95
|
+
if (typeof value === 'undefined') continue
|
|
96
|
+
|
|
97
|
+
const formattedValue = formatter === null ? value
|
|
98
|
+
: numeric ? formatter(value, precisionMultiplier, precisionDecimals) : formatter(value)
|
|
99
|
+
main.pushToMain({ index, key, value: formattedValue })
|
|
123
100
|
}
|
|
124
101
|
|
|
125
102
|
const midPrice = (verticalOhlcv.open[index] + verticalOhlcv.close[index]) / 2
|
|
@@ -134,10 +111,13 @@ export const mainLoop = (input, main) => {
|
|
|
134
111
|
|
|
135
112
|
input[index] = null //flusing data
|
|
136
113
|
|
|
137
|
-
|
|
114
|
+
// All built-in outputs (including generated lags) exist after row zero.
|
|
115
|
+
if (validationColumns === null) {
|
|
116
|
+
validationColumns = Object.values(verticalOhlcv)
|
|
117
|
+
}
|
|
138
118
|
|
|
139
|
-
//
|
|
140
|
-
if (!areKeyValuesValid(
|
|
119
|
+
// Preserve the missing/NaN/null predicate, including Infinity behavior.
|
|
120
|
+
if (!areKeyValuesValid(validationColumns, index)) {
|
|
141
121
|
main.invalidValueIndex = index
|
|
142
122
|
}
|
|
143
123
|
}
|
|
@@ -5,29 +5,12 @@ export const pushToMain = ({main, index, key, value}) => {
|
|
|
5
5
|
verticalOhlcv[key][index] = value
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export const areKeyValuesValid = (
|
|
9
|
-
|
|
10
|
-
if(keyNames.length === 0) return false
|
|
8
|
+
export const areKeyValuesValid = (columns, index) => {
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
for(let x = 0 ; x < keyNames.length; x++)
|
|
17
|
-
{
|
|
18
|
-
const key = keyNames[x]
|
|
19
|
-
if(!verticalOhlcv.hasOwnProperty(key)) {
|
|
20
|
-
output = false
|
|
21
|
-
break
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const val = verticalOhlcv[key][index]
|
|
25
|
-
|
|
26
|
-
if (val == null || Number.isNaN(val)) {
|
|
27
|
-
output = false
|
|
28
|
-
break
|
|
29
|
-
}
|
|
10
|
+
if (columns.length === 0) return false
|
|
11
|
+
for (let x = 0; x < columns.length; x++) {
|
|
12
|
+
const val = columns[x][index]
|
|
13
|
+
if (val == null || Number.isNaN(val)) return false
|
|
30
14
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# Core indicators
|
|
2
2
|
|
|
3
|
-
Local numeric
|
|
4
|
-
`trading-signals@5.0.4`. The existing constructor arguments, `update(value,
|
|
3
|
+
Local numeric `Faster*` indicator implementations. The existing constructor arguments, `update(value,
|
|
5
4
|
replace?)`, `isStable`, and `getResult()` interface remain available. The
|
|
6
5
|
numeric series also retain result/extrema and replacement semantics. These
|
|
7
|
-
|
|
8
|
-
of compatibility with other trading-signals versions.
|
|
6
|
+
use ordinary JavaScript numbers, not arbitrary-precision decimal arithmetic.
|
|
9
7
|
|
|
10
8
|
```js
|
|
11
9
|
import { FasterEMA } from './index.js'
|
|
@@ -41,21 +39,4 @@ provided. This preserves existing JavaScript coercion for custom mapped
|
|
|
41
39
|
columns without adding object storage for normal numeric input. NaN and
|
|
42
40
|
Infinity remain numeric values and do not trigger this fallback. Candle
|
|
43
41
|
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.
|
|
42
|
+
of previously supplied objects. Private buffer layouts intentionally differ.
|
|
@@ -1,27 +1,3 @@
|
|
|
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
1
|
export class NotEnoughDataError extends Error {
|
|
26
2
|
constructor(message = 'Not enough data') {
|
|
27
3
|
super(message)
|
|
@@ -30,7 +6,9 @@ export class NotEnoughDataError extends Error {
|
|
|
30
6
|
}
|
|
31
7
|
|
|
32
8
|
export class Indicator {
|
|
33
|
-
|
|
9
|
+
constructor() {
|
|
10
|
+
this.result = undefined
|
|
11
|
+
}
|
|
34
12
|
|
|
35
13
|
get isStable() {
|
|
36
14
|
return this.result !== undefined
|
|
@@ -44,11 +22,14 @@ export class Indicator {
|
|
|
44
22
|
|
|
45
23
|
// Keep the public result/extrema and replacement semantics without price history.
|
|
46
24
|
export class NumberIndicator extends Indicator {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
25
|
+
constructor() {
|
|
26
|
+
super()
|
|
27
|
+
this.previousResult = undefined
|
|
28
|
+
this.highest = undefined
|
|
29
|
+
this.lowest = undefined
|
|
30
|
+
this.previousHighest = undefined
|
|
31
|
+
this.previousLowest = undefined
|
|
32
|
+
}
|
|
52
33
|
|
|
53
34
|
setResult(value, replace = false) {
|
|
54
35
|
if (replace) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { FasterBollingerBands } from '../core-indicators/index.js'
|
|
2
2
|
import { validateInputValues } from '../utilities/validators.js'
|
|
3
3
|
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
4
|
+
import { mathLog } from '../utilities/math.js'
|
|
4
5
|
|
|
5
|
-
export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
|
|
6
|
+
export const bollingerBands = (main, index, size, stdDev, { lag, retLogs = false } = {}) => {
|
|
6
7
|
const { verticalOhlcv, instances, useFullNames } = main
|
|
7
8
|
const indicatorKey = `${size}_${stdDev}`
|
|
8
|
-
const
|
|
9
|
+
const instanceKey = `${retLogs ? 'ret_log_' : ''}${indicatorKey}`
|
|
10
|
+
const prefix = `${retLogs ? 'ret_log_' : ''}bollinger_bands`
|
|
9
11
|
const target = 'close'
|
|
10
12
|
|
|
11
13
|
// Initialization on the first call.
|
|
@@ -31,25 +33,28 @@ export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
instances.bollinger_bands.settings[
|
|
36
|
+
instances.bollinger_bands.settings[instanceKey] = new FasterBollingerBands(size, stdDev)
|
|
35
37
|
|
|
36
38
|
const suffix = (numberOfIndicators > 1 || useFullNames) ? `_${indicatorKey}` : ''
|
|
37
39
|
|
|
38
|
-
const keyNames = [
|
|
40
|
+
const keyNames = retLogs ? [
|
|
41
|
+
`${prefix}_width${suffix}`,
|
|
42
|
+
`${prefix}_position${suffix}`,
|
|
43
|
+
] : [
|
|
39
44
|
`${prefix}_upper${suffix}`,
|
|
40
45
|
`${prefix}_middle${suffix}`,
|
|
41
46
|
`${prefix}_lower${suffix}`,
|
|
42
47
|
]
|
|
43
48
|
|
|
44
49
|
initializeColumns(main, keyNames.map(key => ({
|
|
45
|
-
key, priceBased: priceBased.has(target)
|
|
50
|
+
key, priceBased: !retLogs && priceBased.has(target)
|
|
46
51
|
})), { lag })
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
const { numberOfIndicators } = instances.bollinger_bands
|
|
50
55
|
const suffix = (numberOfIndicators > 1 || useFullNames) ? `_${indicatorKey}` : ''
|
|
51
56
|
|
|
52
|
-
const instance = instances.bollinger_bands.settings[
|
|
57
|
+
const instance = instances.bollinger_bands.settings[instanceKey]
|
|
53
58
|
const value = verticalOhlcv[target][index]
|
|
54
59
|
instance.update(value)
|
|
55
60
|
|
|
@@ -59,6 +64,24 @@ export const bollingerBands = (main, index, size, stdDev, { lag } = {}) => {
|
|
|
59
64
|
const middle = result?.middle ?? NaN
|
|
60
65
|
const lower = result?.lower ?? NaN
|
|
61
66
|
|
|
67
|
+
if (retLogs) {
|
|
68
|
+
let width = NaN
|
|
69
|
+
let position = NaN
|
|
70
|
+
const rangeRatio = upper / lower
|
|
71
|
+
if (lower > 0 && upper >= lower && Number.isFinite(upper) && Number.isFinite(rangeRatio)) {
|
|
72
|
+
const logRange = mathLog(rangeRatio, 1)
|
|
73
|
+
width = logRange / 2
|
|
74
|
+
const locationRatio = value / lower
|
|
75
|
+
if (logRange > 0 && value > 0 && locationRatio > 0 && Number.isFinite(locationRatio)) {
|
|
76
|
+
const normalized = 2 * mathLog(locationRatio, 1) / logRange - 1
|
|
77
|
+
if (Number.isFinite(normalized)) position = normalized
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
main.pushToMain({ index, key: `${prefix}_width${suffix}`, value: width })
|
|
81
|
+
main.pushToMain({ index, key: `${prefix}_position${suffix}`, value: position })
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
main.pushToMain({ index, key: `${prefix}_upper${suffix}`, value: upper })
|
|
63
86
|
main.pushToMain({ index, key: `${prefix}_middle${suffix}`, value: middle })
|
|
64
87
|
main.pushToMain({ index, key: `${prefix}_lower${suffix}`, value: lower })
|
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
import { validateInputValues } from '../utilities/validators.js'
|
|
2
2
|
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
3
|
+
import { mathLog } from '../utilities/math.js'
|
|
3
4
|
|
|
4
5
|
export const donchianChannels = (main, index, size, offset, options) => {
|
|
5
6
|
const { verticalOhlcv, instances, len, inputParams, useFullNames } = main
|
|
6
|
-
const { lag: outputLag = 0 } = options
|
|
7
|
+
const { lag: outputLag = 0, retLogs = false } = options
|
|
7
8
|
const indicatorKey = `${size}_${offset}`
|
|
9
|
+
const instanceKey = `${retLogs ? 'ret_log_' : ''}${indicatorKey}`
|
|
10
|
+
const prefix = `${retLogs ? 'ret_log_' : ''}donchian_channel`
|
|
8
11
|
|
|
9
12
|
// ---- INIT (only at first bar) ----
|
|
10
13
|
if (index === 0) {
|
|
11
14
|
validateInputValues({ high: true, low: true }, verticalOhlcv, index, 'donchianChannels')
|
|
15
|
+
if (retLogs) validateInputValues({ close: true }, verticalOhlcv, index, 'donchianChannels')
|
|
12
16
|
|
|
13
17
|
const numberOfIndicators = inputParams.filter(o => o.key === 'donchianChannels').length
|
|
14
18
|
const useIndexedKeys = numberOfIndicators > 1 || useFullNames
|
|
15
19
|
|
|
16
20
|
const getKey = name =>
|
|
17
21
|
useIndexedKeys
|
|
18
|
-
?
|
|
19
|
-
:
|
|
22
|
+
? `${prefix}_${name}_${indicatorKey}`
|
|
23
|
+
: `${prefix}_${name}`
|
|
20
24
|
|
|
21
|
-
const keys = ['upper', 'basis', 'lower'].map(getKey)
|
|
25
|
+
const keys = (retLogs ? ['width', 'position'] : ['upper', 'basis', 'lower']).map(getKey)
|
|
22
26
|
|
|
23
27
|
if (!instances.donchian_channel) {
|
|
24
28
|
instances.donchian_channel = { numberOfIndicators, settings: {} }
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
instances.donchian_channel.numberOfIndicators = numberOfIndicators
|
|
28
|
-
instances.donchian_channel.settings[
|
|
32
|
+
instances.donchian_channel.settings[instanceKey] = {
|
|
29
33
|
maxDeque: { indices: [], head: 0, length: 0 },
|
|
30
34
|
minDeque: { indices: [], head: 0, length: 0 }
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
initializeColumns(main, keys.map(key => ({ key, priceBased:
|
|
37
|
+
initializeColumns(main, keys.map(key => ({ key, priceBased: !retLogs })), { lag: outputLag })
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// ---- PER-BAR COMPUTATION ----
|
|
@@ -39,10 +43,10 @@ export const donchianChannels = (main, index, size, offset, options) => {
|
|
|
39
43
|
|
|
40
44
|
const getKey = name =>
|
|
41
45
|
useIndexedKeys
|
|
42
|
-
?
|
|
43
|
-
:
|
|
46
|
+
? `${prefix}_${name}_${indicatorKey}`
|
|
47
|
+
: `${prefix}_${name}`
|
|
44
48
|
|
|
45
|
-
const { maxDeque, minDeque } = settings[
|
|
49
|
+
const { maxDeque, minDeque } = settings[instanceKey]
|
|
46
50
|
const current = index - offset
|
|
47
51
|
const start = current - size + 1
|
|
48
52
|
|
|
@@ -74,6 +78,27 @@ export const donchianChannels = (main, index, size, offset, options) => {
|
|
|
74
78
|
const hasBounds = maxDeque.length && minDeque.length
|
|
75
79
|
const upper = hasBounds ? highs[maxDeque.indices[maxDeque.head]] : NaN
|
|
76
80
|
const lower = hasBounds ? lows[minDeque.indices[minDeque.head]] : NaN
|
|
81
|
+
|
|
82
|
+
if (retLogs) {
|
|
83
|
+
let width = NaN
|
|
84
|
+
let position = NaN
|
|
85
|
+
const rangeRatio = upper / lower
|
|
86
|
+
if (lower > 0 && upper >= lower && Number.isFinite(upper) && Number.isFinite(rangeRatio)) {
|
|
87
|
+
const logRange = mathLog(rangeRatio, 1)
|
|
88
|
+
width = logRange / 2
|
|
89
|
+
// Offset applies to the channel window, not to the current close.
|
|
90
|
+
const close = verticalOhlcv.close[index]
|
|
91
|
+
const locationRatio = close / lower
|
|
92
|
+
if (logRange > 0 && close > 0 && locationRatio > 0 && Number.isFinite(locationRatio)) {
|
|
93
|
+
const normalized = 2 * mathLog(locationRatio, 1) / logRange - 1
|
|
94
|
+
if (Number.isFinite(normalized)) position = normalized
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
main.pushToMain({ index, key: getKey('width'), value: width })
|
|
98
|
+
main.pushToMain({ index, key: getKey('position'), value: position })
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
77
102
|
const basis = hasBounds ? (upper + lower) / 2 : NaN
|
|
78
103
|
|
|
79
104
|
main.pushToMain({ index, key: getKey('upper'), value: upper })
|