ohlcv-indicators 3.5.78 → 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.
- package/AGENTS.md +105 -0
- package/README.md +415 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
- package/index.js +93 -55
- package/package.json +1 -1
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +47 -22
- package/src/core-functions/pushToMain.js +1 -3
- package/src/core-indicators/README.md +61 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +82 -0
- package/src/core-indicators/movingAverages.js +88 -0
- package/src/core-indicators/rsi.js +41 -0
- package/src/core-indicators/volatility.js +107 -0
- package/src/moving-averages/bollingerBands.js +11 -26
- package/src/moving-averages/donchianChannel.js +30 -29
- package/src/moving-averages/heikenAshi.js +23 -33
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +7 -23
- package/src/moving-averages/relativeVolume.js +23 -21
- package/src/oscillators/rsi.js +7 -26
- package/src/oscillators/stochastic.js +6 -19
- package/src/oscillators/volumeDelta.js +36 -28
- package/src/oscillators/volumeOscillator.js +29 -31
- package/src/studies/candleFeatures.js +12 -20
- package/src/studies/dateTime.js +22 -27
- package/src/studies/findCrosses.js +16 -7
- package/src/studies/lag.js +26 -13
- package/src/studies/mapCols.js +14 -15
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +64 -0
- package/src/utilities/numberUtilities.js +2 -0
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +21 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +533 -0
- package/src/moving-averages/vidya.js +0 -181
package/src/studies/dateTime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {oneHotEncode} from '../machine-learning/ml-utilities.js'
|
|
2
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
2
3
|
|
|
3
4
|
export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
4
5
|
|
|
@@ -9,11 +10,16 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
9
10
|
|
|
10
11
|
if(index === 0)
|
|
11
12
|
{
|
|
12
|
-
const {
|
|
13
|
+
const {dateType} = main
|
|
13
14
|
if(!dateType) throw Error('dateTime method found and invalid "date" in input ohlcv')
|
|
14
15
|
|
|
15
16
|
Object.assign(instances, {
|
|
16
17
|
dateTime: {
|
|
18
|
+
formatter: new Intl.DateTimeFormat('en-US-u-ca-gregory-nu-latn', {
|
|
19
|
+
timeZone: main.timeZone,
|
|
20
|
+
year: 'numeric', month: 'numeric', day: 'numeric', weekday: 'short',
|
|
21
|
+
hour: 'numeric', minute: 'numeric', hourCycle: 'h23'
|
|
22
|
+
}),
|
|
17
23
|
colKeys: [...precompute.colKeys, `${prefix}year`].filter(v => v !== 'one_hot_year'),
|
|
18
24
|
colKeySizes: {
|
|
19
25
|
...precompute.colKeySizes
|
|
@@ -23,28 +29,18 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
23
29
|
|
|
24
30
|
const { colKeys } = instances.dateTime
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (const key of colKeys) {
|
|
32
|
-
|
|
33
|
-
verticalOhlcv[key] = new ctor(len).fill(fillVal)
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// finally, apply lag once
|
|
38
|
-
if (lag > 0) {
|
|
39
|
-
main.lag(colKeys, lag)
|
|
40
|
-
}
|
|
32
|
+
initializeColumns(main, colKeys.map(key => ({
|
|
33
|
+
key,
|
|
34
|
+
type: oneHot ? 'Array' : 'Int16Array',
|
|
35
|
+
fill: oneHot ? null : 0
|
|
36
|
+
})), { lag })
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
const {colKeySizes} = instances.dateTime
|
|
39
|
+
const {colKeySizes, formatter} = instances.dateTime
|
|
44
40
|
|
|
45
41
|
const currDate = verticalOhlcv.date[index]
|
|
46
42
|
|
|
47
|
-
const dateInfo = getDateInfo(currDate, oneHot, colKeySizes)
|
|
43
|
+
const dateInfo = getDateInfo(currDate, oneHot, colKeySizes, formatter)
|
|
48
44
|
|
|
49
45
|
for(const [key, value] of Object.entries(dateInfo))
|
|
50
46
|
{
|
|
@@ -54,17 +50,16 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
54
50
|
|
|
55
51
|
|
|
56
52
|
|
|
57
|
-
const getDateInfo = (date, oneHot, colKeySizes) => {
|
|
58
|
-
|
|
53
|
+
const getDateInfo = (date, oneHot, colKeySizes, formatter) => {
|
|
59
54
|
|
|
60
|
-
const year = date.getFullYear();
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
56
|
+
const parts = Object.fromEntries(formatter.formatToParts(date).map(({ type, value }) => [type, value]));
|
|
57
|
+
const year = Number(parts.year);
|
|
58
|
+
const month = Number(parts.month) - 1;
|
|
59
|
+
const hour = Number(parts.hour);
|
|
60
|
+
const minute = Number(parts.minute);
|
|
61
|
+
const dayOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].indexOf(parts.weekday);
|
|
62
|
+
const dayOfMonth = Number(parts.day) - 1;
|
|
68
63
|
|
|
69
64
|
if (!oneHot) {
|
|
70
65
|
return {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { oneHotEncode } from "../machine-learning/ml-utilities.js"
|
|
2
2
|
import { buildArray } from "../utilities/assignTypes.js"
|
|
3
3
|
import { addPrecisionAsNumber } from "../utilities/precisionMultiplier.js"
|
|
4
|
+
import { validateInputValues } from "../utilities/validators.js"
|
|
5
|
+
import { initializeColumns } from "../core-functions/initializeColumns.js"
|
|
4
6
|
const eq = (fast, slow) => fast === slow
|
|
5
7
|
const gt = (fast, slow) => fast > slow
|
|
6
8
|
const lt = (fast, slow) => fast < slow
|
|
@@ -143,6 +145,16 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
|
143
145
|
|
|
144
146
|
if(index === 0)
|
|
145
147
|
{
|
|
148
|
+
validateInputValues(
|
|
149
|
+
{
|
|
150
|
+
...(fast === 'price' ? { high: true, low: true, close: true } : { [fast]: true }),
|
|
151
|
+
...(typeof slow === 'number' ? {} : { [slow]: true })
|
|
152
|
+
},
|
|
153
|
+
verticalOhlcv,
|
|
154
|
+
index,
|
|
155
|
+
'crossPairs'
|
|
156
|
+
)
|
|
157
|
+
|
|
146
158
|
// allow numeric 'slow' as a constant column
|
|
147
159
|
if (typeof slow === 'number') {
|
|
148
160
|
|
|
@@ -173,12 +185,10 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
|
173
185
|
|
|
174
186
|
const crossArrType = (limit !== null && limit <= 125) ? 'Int8Array' : 'Float64Array'
|
|
175
187
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
verticalOhlcv[`one_hot_${crossName}`] = buildArray('Array', len, null)
|
|
181
|
-
}
|
|
188
|
+
initializeColumns(main, [
|
|
189
|
+
{ key: crossName, type: crossArrType, fill: 0 },
|
|
190
|
+
{ key: `one_hot_${crossName}`, type: 'Array', enabled: oneHot }
|
|
191
|
+
])
|
|
182
192
|
|
|
183
193
|
} else if(index + 1 === len) {
|
|
184
194
|
// sanity checks
|
|
@@ -228,5 +238,4 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
|
228
238
|
|
|
229
239
|
}
|
|
230
240
|
|
|
231
|
-
return true
|
|
232
241
|
}
|
package/src/studies/lag.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { initializeColumns } from "../core-functions/initializeColumns.js"
|
|
2
|
+
|
|
3
|
+
const lagColumnsKey = Symbol('lagColumns')
|
|
2
4
|
|
|
3
5
|
export const lag = (main, index, colKeys, lookback) => {
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { verticalOhlcv, len, priceBased } = main
|
|
7
|
+
const { verticalOhlcv, priceBased, instances } = main
|
|
8
8
|
|
|
9
9
|
if(index === 0)
|
|
10
10
|
{
|
|
11
|
+
const lagColumns = new Map()
|
|
12
|
+
|
|
11
13
|
for (const targetKey of colKeys) {
|
|
12
14
|
if (!verticalOhlcv.hasOwnProperty(targetKey)) {
|
|
13
15
|
throw new Error(
|
|
@@ -16,29 +18,41 @@ export const lag = (main, index, colKeys, lookback) => {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const addToPriceBased = priceBased.has(targetKey)
|
|
21
|
+
const type = Array.isArray(verticalOhlcv[targetKey]) ? 'Array' : 'Float64Array'
|
|
22
|
+
const columns = []
|
|
23
|
+
const keyNames = []
|
|
19
24
|
|
|
20
25
|
for (let step = 1; step <= lookback; step++) {
|
|
21
26
|
const key = `${targetKey}_lag_${step}`
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const thisArrType = getArrayType(targetKey, verticalOhlcv[targetKey])
|
|
26
|
-
|
|
27
|
-
verticalOhlcv[key] = buildArray(thisArrType, len)
|
|
28
|
+
columns.push({ key, type, priceBased: addToPriceBased, includeInLag: false })
|
|
29
|
+
keyNames.push(key)
|
|
28
30
|
}
|
|
31
|
+
|
|
32
|
+
initializeColumns(main, columns)
|
|
33
|
+
lagColumns.set(targetKey, keyNames)
|
|
29
34
|
}
|
|
35
|
+
|
|
36
|
+
// colKeys belongs to the runtime queue; keep cached names out of exported params.
|
|
37
|
+
if (!instances[lagColumnsKey]) instances[lagColumnsKey] = new WeakMap()
|
|
38
|
+
instances[lagColumnsKey].set(colKeys, lagColumns)
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
const lagColumns = instances[lagColumnsKey]?.get(colKeys)
|
|
42
|
+
|
|
32
43
|
for (const targetKey of colKeys) {
|
|
44
|
+
const keyNames = lagColumns?.get(targetKey)
|
|
33
45
|
const currentColumn = verticalOhlcv[targetKey]
|
|
46
|
+
const missingValue = Array.isArray(currentColumn) ? null : NaN
|
|
34
47
|
|
|
35
48
|
// Populate the lagged values each tick:
|
|
36
49
|
for (let step = 1; step <= lookback; step++) {
|
|
37
|
-
|
|
50
|
+
// Runtime callbacks may change the target list or lookback after initialization.
|
|
51
|
+
const key = keyNames?.[step - 1] ?? `${targetKey}_lag_${step}`
|
|
38
52
|
const laggedIndex = index - step
|
|
39
53
|
const value =
|
|
40
|
-
laggedIndex < 0 || currentColumn[laggedIndex]
|
|
41
|
-
?
|
|
54
|
+
laggedIndex < 0 || currentColumn[laggedIndex] == null
|
|
55
|
+
? missingValue
|
|
42
56
|
: currentColumn[laggedIndex]
|
|
43
57
|
|
|
44
58
|
main.pushToMain({ index, key, value })
|
|
@@ -46,5 +60,4 @@ export const lag = (main, index, colKeys, lookback) => {
|
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
return true
|
|
50
63
|
}
|
package/src/studies/mapCols.js
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { validateInputValues } from '../utilities/validators.js'
|
|
2
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
export const mapCols = (main, index, newCols, callback, {lag, isPriceBased, callbackParams = {}}) => {
|
|
5
|
+
|
|
6
|
+
const {verticalOhlcv, precision} = main
|
|
4
7
|
|
|
5
8
|
if(index === 0)
|
|
6
9
|
{
|
|
10
|
+
if (callback === defaultMapColsCallback) {
|
|
11
|
+
validateInputValues({ open: true, close: true }, verticalOhlcv, index, 'mapCols')
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
for(const key of newCols)
|
|
8
15
|
{
|
|
9
16
|
if(verticalOhlcv.hasOwnProperty(key)) {
|
|
10
17
|
throw new Error(`New property "${key}" already exist in "verticalOhlcv" and can not be modified using mapCols.`)
|
|
11
18
|
}
|
|
12
|
-
|
|
13
|
-
verticalOhlcv[key] = new Array(len).fill(0)
|
|
14
|
-
|
|
15
|
-
if(precision && isPriceBased)
|
|
16
|
-
{
|
|
17
|
-
priceBased.add(key)
|
|
18
|
-
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
21
|
+
initializeColumns(main, newCols.map(key => ({
|
|
22
|
+
key, type: 'Array', priceBased: precision && isPriceBased
|
|
23
|
+
})), { lag })
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
const cols = callback({index, main})
|
|
26
|
+
const cols = callback({index, main, params: callbackParams})
|
|
28
27
|
|
|
29
|
-
if(cols == null) return
|
|
28
|
+
if(cols == null) return;
|
|
30
29
|
|
|
31
30
|
for(const [key, value] of Object.entries(cols))
|
|
32
31
|
{
|
|
@@ -29,7 +29,7 @@ export const buildArray = (arrayType, len, fallbackValue) => {
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
// Ctor.from({ length }, mapFn) works for Array *and* all TypedArrays
|
|
32
|
-
return Ctor
|
|
32
|
+
return new Ctor(len).fill(fillValue)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export const assignTypes = main => {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { defaultMapColsCallback } from '../studies/mapCols.js';
|
|
2
|
+
|
|
3
|
+
export const CONFIG_VERSION = 1;
|
|
4
|
+
const mapCallbacks = new Map([['default', defaultMapColsCallback]]);
|
|
5
|
+
|
|
6
|
+
// Copy configuration without JSON's silent conversion of functions/NaN to null.
|
|
7
|
+
export const copyConfig = (value, allowFunctions = false, ancestors = new Set()) => {
|
|
8
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
9
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
10
|
+
if (typeof value === 'function' && allowFunctions) return value;
|
|
11
|
+
if (typeof value !== 'object' || value === null) {
|
|
12
|
+
throw new TypeError('Configuration values must be JSON-safe: no undefined, non-finite numbers, or unregistered functions.');
|
|
13
|
+
}
|
|
14
|
+
if (ancestors.has(value)) throw new TypeError('Configuration must not contain circular references.');
|
|
15
|
+
const prototype = Object.getPrototypeOf(value);
|
|
16
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
|
|
17
|
+
throw new TypeError('Configuration must contain only plain objects and arrays.');
|
|
18
|
+
}
|
|
19
|
+
if (Object.getOwnPropertySymbols(value).length) throw new TypeError('Configuration must not contain symbol keys.');
|
|
20
|
+
|
|
21
|
+
ancestors.add(value);
|
|
22
|
+
const result = Array.isArray(value)
|
|
23
|
+
? Array.from(value, item => copyConfig(item, allowFunctions, ancestors))
|
|
24
|
+
: Object.fromEntries(Object.entries(value).map(([key, item]) => [key, copyConfig(item, allowFunctions, ancestors)]));
|
|
25
|
+
ancestors.delete(value);
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const freezeConfig = value => {
|
|
30
|
+
if (value !== null && typeof value === 'object') {
|
|
31
|
+
for (const item of Object.values(value)) freezeConfig(item);
|
|
32
|
+
Object.freeze(value);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const registerMapCallback = (name, callback) => {
|
|
38
|
+
if (typeof name !== 'string' || !name || typeof callback !== 'function') {
|
|
39
|
+
throw new TypeError('registerMapCallback requires a nonempty name and a function.');
|
|
40
|
+
}
|
|
41
|
+
if (mapCallbacks.has(name) && mapCallbacks.get(name) !== callback) {
|
|
42
|
+
throw new Error(`mapCols callback "${name}" is already registered. Use a new versioned name.`);
|
|
43
|
+
}
|
|
44
|
+
mapCallbacks.set(name, callback);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const resolveMapCallback = callback => {
|
|
48
|
+
if (typeof callback === 'function') return callback;
|
|
49
|
+
if (typeof callback === 'string' && mapCallbacks.has(callback)) return mapCallbacks.get(callback);
|
|
50
|
+
throw new Error(`Unknown mapCols callback "${callback}". Register it before loading the configuration.`);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const exportInputParams = inputParams => {
|
|
54
|
+
const result = copyConfig(inputParams, true);
|
|
55
|
+
for (const job of result) {
|
|
56
|
+
if (job.key !== 'mapCols' || typeof job.params[1] !== 'function') continue;
|
|
57
|
+
const entry = [...mapCallbacks].find(([, callback]) => callback === job.params[1]);
|
|
58
|
+
if (!entry) {
|
|
59
|
+
throw new Error('Cannot export an unregistered mapCols callback. Register it with OHLCV_INDICATORS.registerMapCallback(name, callback).');
|
|
60
|
+
}
|
|
61
|
+
job.params[1] = entry[0];
|
|
62
|
+
}
|
|
63
|
+
return copyConfig(result);
|
|
64
|
+
};
|
|
@@ -5,7 +5,14 @@ export const validateInputParams = (inputParams, len) => {
|
|
|
5
5
|
throw new Error('Property "inputParams" must be an "Array" or "Set".')
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
for (const
|
|
8
|
+
for (const job of inputParams) {
|
|
9
|
+
if (!job || typeof job.key !== 'string' || !Array.isArray(job.params)) {
|
|
10
|
+
throw new TypeError('Each inputParams entry must have an indicator key and a params array.')
|
|
11
|
+
}
|
|
12
|
+
if (job.order !== undefined && !Number.isFinite(job.order)) {
|
|
13
|
+
throw new TypeError('Indicator order must be a finite number when provided.')
|
|
14
|
+
}
|
|
15
|
+
const { params } = job
|
|
9
16
|
for (const v of params) {
|
|
10
17
|
if (typeof v === 'number' && v > len) {
|
|
11
18
|
console.log(v, len);
|
|
@@ -16,6 +23,7 @@ export const validateInputParams = (inputParams, len) => {
|
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
export const isAlreadyComputed = main => {
|
|
26
|
+
if(main.isComputing === true) throw Error('You cannot add indicators while computation is in progress.')
|
|
19
27
|
if(main.isComputed === true) throw Error('ohlcv is already computed, you can not add new indicators after "compute", "getLastValues" or "getData" methods are called.')
|
|
20
28
|
}
|
|
21
29
|
|
|
@@ -117,3 +125,34 @@ export const validateString = (txt, paramName, callerName) => {
|
|
|
117
125
|
|
|
118
126
|
return true
|
|
119
127
|
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
export const validateInputValues = (
|
|
131
|
+
requiredParams,
|
|
132
|
+
verticalOhlcv,
|
|
133
|
+
index,
|
|
134
|
+
callbackName
|
|
135
|
+
) => {
|
|
136
|
+
if (index !== 0) return true;
|
|
137
|
+
|
|
138
|
+
const invalidParams = [];
|
|
139
|
+
|
|
140
|
+
for (const key of Object.keys(requiredParams)) {
|
|
141
|
+
const value = verticalOhlcv[key]?.[index];
|
|
142
|
+
|
|
143
|
+
const isValid = key === "volume"
|
|
144
|
+
? Number.isInteger(value) && value >= 0
|
|
145
|
+
: Number.isFinite(value) && value > 0;
|
|
146
|
+
|
|
147
|
+
if (!isValid) invalidParams.push(key);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (invalidParams.length > 0) {
|
|
151
|
+
throw new TypeError(
|
|
152
|
+
`Invalid or missing input value(s) [${invalidParams.join(", ")}] ` +
|
|
153
|
+
`at index ${index} in "${callbackName}".`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
@@ -13,29 +13,34 @@ export const verticalToHorizontal = ({main, skipNull = false, startIndex = 0, da
|
|
|
13
13
|
const maxStartIndex = Math.max(skipNullIndex, startIndex)
|
|
14
14
|
const diffLen = len - maxStartIndex
|
|
15
15
|
const result = Array.from({ length: diffLen }, () => ({}))
|
|
16
|
-
const formatter = {}
|
|
17
|
-
|
|
18
|
-
for(const key of verticalOhlcvKeyNames) {
|
|
19
|
-
if(precision && priceBased.has(key)) {
|
|
20
|
-
formatter[key] = (num, mul) => outputNumberFormatter.precisionNumberCleanString(num, mul)
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
formatter[key] = v => v
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
16
|
|
|
27
17
|
for(const [key, arr] of Object.entries(verticalOhlcv)){
|
|
28
18
|
if(verticalOhlcvTempCols.has(key)) continue
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
if(key === 'date')
|
|
31
21
|
{
|
|
32
|
-
|
|
22
|
+
const formatter = dateOutputFormaters[dateFormat]
|
|
23
|
+
for (let i = maxStartIndex; i < len; i++)
|
|
33
24
|
{
|
|
34
|
-
result[i - maxStartIndex][key] =
|
|
35
|
-
}
|
|
36
|
-
|
|
25
|
+
result[i - maxStartIndex][key] = formatter(arr[i])
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if(precision && priceBased.has(key))
|
|
29
|
+
{
|
|
30
|
+
const formatter = outputNumberFormatter.precisionNumberCleanString
|
|
31
|
+
for (let i = maxStartIndex; i < len; i++)
|
|
32
|
+
{
|
|
33
|
+
const value = arr[i]
|
|
34
|
+
result[i - maxStartIndex][key] = value == null || Number.isNaN(value)
|
|
35
|
+
? value
|
|
36
|
+
: formatter(value, precisionMultiplier)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
{
|
|
41
|
+
for (let i = maxStartIndex; i < len; i++)
|
|
37
42
|
{
|
|
38
|
-
result[i - maxStartIndex][key] =
|
|
43
|
+
result[i - maxStartIndex][key] = arr[i]
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
}
|
package/src/volatility/adx.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { FasterADX } from '
|
|
1
|
+
import { FasterADX } from '../core-indicators/index.js';
|
|
2
2
|
import { mathLog } from '../utilities/math.js';
|
|
3
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
4
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
3
5
|
|
|
4
6
|
export const adx = (main, index, size, { lag, retLogs }) => {
|
|
5
7
|
const { verticalOhlcv, instances, useFullNames } = main;
|
|
@@ -10,7 +12,7 @@ export const adx = (main, index, size, { lag, retLogs }) => {
|
|
|
10
12
|
|
|
11
13
|
// Initialization on the first index.
|
|
12
14
|
if (index === 0) {
|
|
13
|
-
|
|
15
|
+
validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'adx');
|
|
14
16
|
|
|
15
17
|
if (!verticalOhlcv.hasOwnProperty('high')) {
|
|
16
18
|
throw new Error('Property high not found in verticalOhlcv for adx.');
|
|
@@ -28,32 +30,17 @@ export const adx = (main, index, size, { lag, retLogs }) => {
|
|
|
28
30
|
[instanceKey]: new FasterADX(size)
|
|
29
31
|
});
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
[adxKey]: new Float64Array(len).fill(NaN)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (lag > 0) {
|
|
37
|
-
main.lag([adxKey], lag);
|
|
38
|
-
}
|
|
33
|
+
initializeColumns(main, [{ key: adxKey }], { lag });
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
const high = verticalOhlcv.high[index];
|
|
42
37
|
const low = verticalOhlcv.low[index];
|
|
43
38
|
const close = verticalOhlcv.close[index];
|
|
44
39
|
|
|
45
|
-
let currentAdx = NaN;
|
|
46
|
-
|
|
47
40
|
// Update the ADX indicator.
|
|
48
41
|
instances[instanceKey].update({ high, low, close });
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
currentAdx = instances[instanceKey].getResult();
|
|
52
|
-
|
|
53
|
-
instances[instanceKey].get
|
|
54
|
-
} catch (err) {
|
|
55
|
-
currentAdx = NaN;
|
|
56
|
-
}
|
|
43
|
+
const currentAdx = instances[instanceKey].isStable ? instances[instanceKey].getResult() : NaN;
|
|
57
44
|
|
|
58
45
|
|
|
59
46
|
const adxVal = Number.isNaN(currentAdx)
|
|
@@ -62,4 +49,4 @@ export const adx = (main, index, size, { lag, retLogs }) => {
|
|
|
62
49
|
|
|
63
50
|
|
|
64
51
|
main.pushToMain({ index, key: adxKey, value: adxVal });
|
|
65
|
-
};
|
|
52
|
+
};
|
package/src/volatility/atr.js
CHANGED
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
|
|
2
|
-
import {FasterATR, FasterWSMA} from '
|
|
2
|
+
import {FasterATR, FasterWSMA} from '../core-indicators/index.js';
|
|
3
3
|
import { mathLog } from '../utilities/math.js';
|
|
4
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
5
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
4
6
|
|
|
5
7
|
export const atr = (main, index, size, {lag, retLogs}) => {
|
|
6
|
-
const { verticalOhlcv, instances
|
|
8
|
+
const { verticalOhlcv, instances } = main
|
|
7
9
|
const baseKeyName = retLogs ? `ret_log_atr_${size}` : `atr_${size}`
|
|
8
10
|
|
|
9
11
|
if (index === 0) {
|
|
10
12
|
|
|
11
|
-
const {instances, verticalOhlcv
|
|
13
|
+
const {instances, verticalOhlcv} = main
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const keyNames = [baseKeyName]
|
|
15
|
+
validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'atr')
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
verticalOhlcv[k] = new Float64Array(len).fill(NaN)
|
|
20
|
-
priceBased.add(k)
|
|
21
|
-
}
|
|
17
|
+
instances[baseKeyName] = new FasterATR(size, FasterWSMA)
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
main.lag(keyNames, lag)
|
|
26
|
-
}
|
|
19
|
+
initializeColumns(main, [{ key: baseKeyName, priceBased: true }], { lag })
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
// Retrieve the current price value.
|
|
@@ -36,15 +29,9 @@ export const atr = (main, index, size, {lag, retLogs}) => {
|
|
|
36
29
|
|
|
37
30
|
// Update the moving average instance.
|
|
38
31
|
instance.update(curr);
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
currAtr = instance.getResult();
|
|
42
|
-
} catch (err) {
|
|
43
|
-
|
|
44
|
-
}
|
|
32
|
+
const currAtr = instance.isStable ? instance.getResult() : NaN;
|
|
45
33
|
|
|
46
34
|
// Always push the MA value (even if NaN).
|
|
47
35
|
main.pushToMain({ index, key: baseKeyName, value: Number.isNaN(currAtr) ? NaN : (retLogs ? mathLog(currAtr, curr.close) : currAtr) });
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
}
|
|
37
|
+
}
|