ohlcv-indicators 3.5.78 → 3.5.80
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 +113 -0
- package/README.md +456 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
- package/index.js +112 -84
- package/package.json +2 -5
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +35 -30
- package/src/core-functions/pushToMain.js +8 -27
- package/src/core-indicators/README.md +60 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +81 -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 +39 -31
- package/src/moving-averages/donchianChannel.js +62 -36
- package/src/moving-averages/heikenAshi.js +33 -52
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +19 -25
- package/src/moving-averages/relativeVolume.js +30 -24
- package/src/oscillators/mfi.js +56 -0
- 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 +24 -32
- package/src/studies/candleFeatures.js +40 -28
- package/src/studies/dateTime.js +54 -56
- package/src/studies/findCrosses.js +18 -16
- package/src/studies/lag.js +32 -20
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +31 -0
- package/src/utilities/numberUtilities.js +5 -3
- package/src/utilities/precisionMultiplier.js +11 -6
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +26 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +1230 -0
- package/webpack.config.js +0 -11
- package/src/moving-averages/vidya.js +0 -181
- package/src/studies/mapCols.js +0 -46
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { FasterStochasticOscillator } from '
|
|
1
|
+
import { FasterStochasticOscillator } 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 stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag, retLogs}) => {
|
|
5
7
|
|
|
@@ -13,39 +15,24 @@ export const stochastic = (main, index, kPeriod, kSlowingPeriod, dPeriod, {lag,
|
|
|
13
15
|
|
|
14
16
|
// Initialization on the first index.
|
|
15
17
|
if (index === 0) {
|
|
16
|
-
|
|
17
|
-
const {len } = main;
|
|
18
|
+
validateInputValues({ high: true, low: true, close: true }, verticalOhlcv, index, 'stochastic');
|
|
18
19
|
|
|
19
20
|
Object.assign(instances, {
|
|
20
21
|
[instanceKey]: new FasterStochasticOscillator(kPeriod, kSlowingPeriod, dPeriod)
|
|
21
22
|
})
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
[stochD]: new Float64Array(len).fill(NaN),
|
|
25
|
-
[stochK]: new Float64Array(len).fill(NaN),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const baseKeys = [stochD, stochK]
|
|
29
|
-
|
|
30
|
-
if (lag > 0) {
|
|
31
|
-
main.lag(baseKeys, lag);
|
|
32
|
-
}
|
|
24
|
+
initializeColumns(main, [{ key: stochD }, { key: stochK }], { lag });
|
|
33
25
|
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
const close = verticalOhlcv['close'][index]
|
|
37
29
|
const low = verticalOhlcv['low'][index]
|
|
38
30
|
const high = verticalOhlcv['high'][index]
|
|
39
|
-
let stockObj = null
|
|
40
31
|
|
|
41
32
|
// Update the stochastic indicator.
|
|
42
33
|
instances[instanceKey].update({close, low, high})
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
stockObj = instances[instanceKey].getResult()
|
|
46
|
-
} catch (err) {
|
|
47
|
-
stockObj = null
|
|
48
|
-
}
|
|
35
|
+
const stockObj = instances[instanceKey].isStable ? instances[instanceKey].getResult() : null
|
|
49
36
|
|
|
50
37
|
const kVal = stockObj ? (retLogs ? mathLog(stockObj.stochK, 50) : stockObj.stochK) : NaN
|
|
51
38
|
const dVal = stockObj ? (retLogs ? mathLog(stockObj.stochD, 50) : stockObj.stochD) : NaN
|
|
@@ -1,29 +1,41 @@
|
|
|
1
|
+
import { isPositiveInteger } from '../utilities/numberUtilities.js';
|
|
2
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
3
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
export const volumeDelta = (main, index, { lag = 0 }) => {
|
|
2
|
-
const { verticalOhlcv, instances
|
|
3
|
-
const
|
|
7
|
+
const { verticalOhlcv, instances } = main;
|
|
8
|
+
const key = 'volume_delta';
|
|
4
9
|
|
|
5
10
|
if (index === 0) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const keys = [
|
|
9
|
-
`${groupName}_open`,
|
|
10
|
-
`${groupName}_high`,
|
|
11
|
-
`${groupName}_low`,
|
|
12
|
-
`${groupName}_close`,
|
|
13
|
-
`${groupName}_cross`,
|
|
14
|
-
];
|
|
11
|
+
validateInputValues({ open: true, close: true, volume: true }, verticalOhlcv, index, 'volumeDelta');
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
if (!instances[key]) {
|
|
14
|
+
instances[key] = { isBuyVolume: true, cross: 0};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const keyNames = [
|
|
18
|
+
`${key}_high`,
|
|
19
|
+
`${key}_low`,
|
|
20
|
+
`${key}_close`,
|
|
21
|
+
`${key}_cross`,
|
|
22
|
+
];
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
if (lag > 0) main.lag(keys, lag);
|
|
24
|
+
initializeColumns(main, keyNames.map(key => ({ key })), { lag });
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
const inst = instances[
|
|
27
|
+
const inst = instances[key];
|
|
28
|
+
|
|
29
|
+
const {hasInvalidInputValue} = inst
|
|
24
30
|
|
|
25
31
|
// inputs
|
|
26
|
-
const
|
|
32
|
+
const volume = verticalOhlcv.volume[index];
|
|
33
|
+
|
|
34
|
+
if(volume === 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
const open = verticalOhlcv.open[index];
|
|
28
40
|
const close = verticalOhlcv.close[index];
|
|
29
41
|
const prevClose = index > 0 ? verticalOhlcv.close[index - 1] : NaN;
|
|
@@ -55,20 +67,16 @@ export const volumeDelta = (main, index, { lag = 0 }) => {
|
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
const delta = Number.isFinite(vol) ? (isBuy ? vol : -vol) : NaN;
|
|
70
|
+
const delta = isBuy ? volume : -volume;
|
|
60
71
|
|
|
61
72
|
// delta "candle"
|
|
62
|
-
const openVal = Number.isNaN(delta) ? NaN : 0;
|
|
63
73
|
const closeVal = delta;
|
|
64
|
-
const highVal =
|
|
65
|
-
const lowVal =
|
|
74
|
+
const highVal = Math.max(delta, 0);
|
|
75
|
+
const lowVal = Math.min(delta, 0);
|
|
66
76
|
|
|
67
|
-
main.pushToMain({ index, key: `${
|
|
68
|
-
main.pushToMain({ index, key: `${
|
|
69
|
-
main.pushToMain({ index, key: `${
|
|
70
|
-
main.pushToMain({ index, key: `${
|
|
71
|
-
main.pushToMain({ index, key: `${groupName}_cross`, value: inst.cross });
|
|
77
|
+
main.pushToMain({ index, key: `${key}_high`, value: highVal });
|
|
78
|
+
main.pushToMain({ index, key: `${key}_low`, value: lowVal });
|
|
79
|
+
main.pushToMain({ index, key: `${key}_close`, value: closeVal });
|
|
80
|
+
main.pushToMain({ index, key: `${key}_cross`, value: inst.cross });
|
|
72
81
|
|
|
73
|
-
return true;
|
|
74
82
|
};
|
|
@@ -1,59 +1,51 @@
|
|
|
1
|
-
import { FasterEMA } from '
|
|
1
|
+
import { FasterEMA } from '../core-indicators/index.js';
|
|
2
|
+
import { mathLog } from '../utilities/math.js';
|
|
3
|
+
import { isPositiveInteger } from '../utilities/numberUtilities.js';
|
|
4
|
+
import { validateInputValues } from '../utilities/validators.js';
|
|
5
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js';
|
|
2
6
|
|
|
3
|
-
export const volumeOscillator = (main, index, fast, slow, {lag}) => {
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
export const volumeOscillator = (main, index, fast, slow, {lag, retLogs}) => {
|
|
6
8
|
|
|
7
9
|
const {verticalOhlcv, instances} = main
|
|
8
|
-
const
|
|
9
|
-
const key = `volume_oscillator_${fast}_${slow}`
|
|
10
|
+
const key = retLogs ? `ret_log_volume_oscillator_${fast}_${slow}` : `volume_oscillator_${fast}_${slow}`
|
|
10
11
|
|
|
11
12
|
if (index === 0) {
|
|
13
|
+
validateInputValues({ volume: true }, verticalOhlcv, index, 'volumeOscillator');
|
|
12
14
|
|
|
13
|
-
const {len} = main
|
|
14
15
|
Object.assign(instances, {
|
|
15
16
|
[key]: {
|
|
16
17
|
fastEMA: new FasterEMA(fast),
|
|
17
|
-
slowEMA: new FasterEMA(slow)
|
|
18
|
+
slowEMA: new FasterEMA(slow),
|
|
18
19
|
}
|
|
19
20
|
})
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if(lag > 0)
|
|
24
|
-
{
|
|
25
|
-
main.lag([key], lag)
|
|
26
|
-
}
|
|
22
|
+
initializeColumns(main, [{ key }], { lag });
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
const { fastEMA, slowEMA } = instances[key];
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
slowEMA.update(value);
|
|
33
|
-
|
|
34
|
-
let fastValue = NaN;
|
|
35
|
-
let slowValue = NaN;
|
|
27
|
+
const volume = verticalOhlcv.volume[index];
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} catch (err) {
|
|
40
|
-
|
|
29
|
+
if(volume === 0) {
|
|
30
|
+
return;
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} catch (err) {
|
|
33
|
+
fastEMA.update(volume);
|
|
34
|
+
slowEMA.update(volume);
|
|
46
35
|
|
|
47
|
-
|
|
36
|
+
const fastValue = fastEMA.isStable ? fastEMA.getResult() : NaN;
|
|
37
|
+
const slowValue = slowEMA.isStable ? slowEMA.getResult() : NaN;
|
|
48
38
|
|
|
49
39
|
let volumeOscValue = NaN
|
|
40
|
+
let volumeOscRetLog = NaN
|
|
50
41
|
|
|
51
|
-
if(
|
|
52
|
-
|
|
53
|
-
volumeOscValue = 100 * (fastValue - slowValue) / slowValue
|
|
42
|
+
if(Number.isNaN(fastValue) || Number.isNaN(slowValue)) {
|
|
43
|
+
return;
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
|
|
46
|
+
volumeOscValue = retLogs
|
|
47
|
+
? mathLog(fastValue, slowValue)
|
|
48
|
+
: 100 * (fastValue - slowValue) / slowValue
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
main.pushToMain({index, key, value: volumeOscValue})
|
|
59
51
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { validateInputValues } from '../utilities/validators.js'
|
|
2
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
2
3
|
|
|
3
4
|
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
5
|
|
|
@@ -9,19 +10,16 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
|
|
|
9
10
|
const prefix = (retLogs) ? 'ret_log_' : 'ret_'
|
|
10
11
|
|
|
11
12
|
if(index === 0) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
[`${prefix}lower_wick`]: new Float64Array(len).fill(NaN),
|
|
19
|
-
[`${prefix}gap`]: new Float64Array(len).fill(NaN),
|
|
20
|
-
[`${prefix}body`]: new Float64Array(len).fill(NaN),
|
|
21
|
-
[`${prefix}range`]: new Float64Array(len).fill(NaN)
|
|
22
|
-
}
|
|
13
|
+
validateInputValues({ open: true, high: true, low: true, close: true }, verticalOhlcv, index, 'candleFeatures')
|
|
14
|
+
|
|
15
|
+
const columns = [
|
|
16
|
+
'change', 'mid_price_change', 'upper_wick', 'lower_wick',
|
|
17
|
+
'gap', 'body', 'range'
|
|
18
|
+
].map(name => ({ key: `${prefix}${name}` }))
|
|
23
19
|
|
|
24
20
|
for(const target of colKeys) {
|
|
21
|
+
validateInputValues({ [target]: true }, verticalOhlcv, index, 'candleFeatures')
|
|
22
|
+
|
|
25
23
|
if(!verticalOhlcv.hasOwnProperty(target)) {
|
|
26
24
|
throw new Error(
|
|
27
25
|
`Target property in "options.colKey" array "${target}" not found in verticalOhlcv for "candleFeatures".`
|
|
@@ -34,16 +32,10 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
|
|
|
34
32
|
);
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
columns.push({ key: `${prefix}${target}` })
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
scaledGroups.candleFeatures = Object.keys(newCols)
|
|
43
|
-
|
|
44
|
-
if(lag) {
|
|
45
|
-
main.lag(Object.keys(newCols), lag)
|
|
46
|
-
}
|
|
38
|
+
scaledGroups.candleFeatures = initializeColumns(main, columns, { lag })
|
|
47
39
|
}
|
|
48
40
|
|
|
49
41
|
const prevClose = verticalOhlcv.close[index - 1]
|
|
@@ -64,14 +56,34 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
|
|
|
64
56
|
const upperWickTop = Math.max(currOpen, currClose)
|
|
65
57
|
const lowerWickTop = Math.min(currOpen, currClose)
|
|
66
58
|
|
|
59
|
+
const change = getRet(currClose, prevClose)
|
|
60
|
+
const midPriceChange = getRet(currMidPrice, prevMidPrice)
|
|
61
|
+
const upperWick = getRet(currHigh, upperWickTop)
|
|
62
|
+
const lowerWick = getRet(lowerWickTop, currLow)
|
|
63
|
+
const gap = getRet(currOpen, prevClose)
|
|
64
|
+
const body = getRet(currClose, currOpen)
|
|
65
|
+
const range = getRet(currHigh, currLow)
|
|
66
|
+
|
|
67
|
+
if (colKeys.length === 0) {
|
|
68
|
+
main.pushToMain({ index, key: `${prefix}change`, value: change })
|
|
69
|
+
main.pushToMain({ index, key: `${prefix}mid_price_change`, value: midPriceChange })
|
|
70
|
+
main.pushToMain({ index, key: `${prefix}upper_wick`, value: upperWick })
|
|
71
|
+
main.pushToMain({ index, key: `${prefix}lower_wick`, value: lowerWick })
|
|
72
|
+
main.pushToMain({ index, key: `${prefix}gap`, value: gap })
|
|
73
|
+
main.pushToMain({ index, key: `${prefix}body`, value: body })
|
|
74
|
+
main.pushToMain({ index, key: `${prefix}range`, value: range })
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Keep optional-target overwrite/order semantics and all-or-nothing validation.
|
|
67
79
|
const row = {
|
|
68
|
-
[`${prefix}change`]:
|
|
69
|
-
[`${prefix}mid_price_change`]:
|
|
70
|
-
[`${prefix}upper_wick`]:
|
|
71
|
-
[`${prefix}lower_wick`]:
|
|
72
|
-
[`${prefix}gap`]:
|
|
73
|
-
[`${prefix}body`]:
|
|
74
|
-
[`${prefix}range`]:
|
|
80
|
+
[`${prefix}change`]: change,
|
|
81
|
+
[`${prefix}mid_price_change`]: midPriceChange,
|
|
82
|
+
[`${prefix}upper_wick`]: upperWick,
|
|
83
|
+
[`${prefix}lower_wick`]: lowerWick,
|
|
84
|
+
[`${prefix}gap`]: gap,
|
|
85
|
+
[`${prefix}body`]: body,
|
|
86
|
+
[`${prefix}range`]: range
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
let hasInvalidTarget = false
|
|
@@ -94,4 +106,4 @@ export const candleFeatures = (main, index, {lag, colKeys, retLogs}) => {
|
|
|
94
106
|
main.pushToMain({index, key, value})
|
|
95
107
|
}
|
|
96
108
|
|
|
97
|
-
}
|
|
109
|
+
}
|
package/src/studies/dateTime.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import {oneHotEncode} from '../machine-learning/ml-utilities.js'
|
|
2
|
+
import { initializeColumns } from '../core-functions/initializeColumns.js'
|
|
3
|
+
|
|
4
|
+
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
2
5
|
|
|
3
6
|
export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
4
7
|
|
|
@@ -9,11 +12,16 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
9
12
|
|
|
10
13
|
if(index === 0)
|
|
11
14
|
{
|
|
12
|
-
const {
|
|
15
|
+
const {dateType} = main
|
|
13
16
|
if(!dateType) throw Error('dateTime method found and invalid "date" in input ohlcv')
|
|
14
17
|
|
|
15
18
|
Object.assign(instances, {
|
|
16
19
|
dateTime: {
|
|
20
|
+
formatter: new Intl.DateTimeFormat('en-US-u-ca-gregory-nu-latn', {
|
|
21
|
+
timeZone: main.timeZone,
|
|
22
|
+
year: 'numeric', month: 'numeric', day: 'numeric', weekday: 'short',
|
|
23
|
+
hour: 'numeric', minute: 'numeric', hourCycle: 'h23'
|
|
24
|
+
}),
|
|
17
25
|
colKeys: [...precompute.colKeys, `${prefix}year`].filter(v => v !== 'one_hot_year'),
|
|
18
26
|
colKeySizes: {
|
|
19
27
|
...precompute.colKeySizes
|
|
@@ -23,65 +31,55 @@ export const dateTime = (main, index, {lag, oneHot, precompute}) => {
|
|
|
23
31
|
|
|
24
32
|
const { colKeys } = instances.dateTime
|
|
25
33
|
|
|
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
|
-
}
|
|
34
|
+
initializeColumns(main, colKeys.map(key => ({
|
|
35
|
+
key,
|
|
36
|
+
type: oneHot ? 'Array' : 'Int16Array',
|
|
37
|
+
fill: oneHot ? null : 0
|
|
38
|
+
})), { lag })
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const {colKeySizes} = instances.dateTime
|
|
41
|
+
const {colKeySizes, formatter} = instances.dateTime
|
|
44
42
|
|
|
45
43
|
const currDate = verticalOhlcv.date[index]
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
let yearPart, monthPart, hourPart, minutePart, weekdayPart, dayPart
|
|
46
|
+
for (const part of formatter.formatToParts(currDate)) {
|
|
47
|
+
switch (part.type) {
|
|
48
|
+
case 'year': yearPart = part.value; break
|
|
49
|
+
case 'month': monthPart = part.value; break
|
|
50
|
+
case 'hour': hourPart = part.value; break
|
|
51
|
+
case 'minute': minutePart = part.value; break
|
|
52
|
+
case 'weekday': weekdayPart = part.value; break
|
|
53
|
+
case 'day': dayPart = part.value; break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const year = Number(yearPart)
|
|
57
|
+
const month = Number(monthPart) - 1
|
|
58
|
+
const hour = Number(hourPart)
|
|
59
|
+
const minute = Number(minutePart)
|
|
60
|
+
const dayOfWeek = weekdays.indexOf(weekdayPart)
|
|
61
|
+
const dayOfMonth = Number(dayPart) - 1
|
|
62
|
+
|
|
63
|
+
if (!oneHot) {
|
|
64
|
+
main.pushToMain({ index, key: 'year', value: year })
|
|
65
|
+
main.pushToMain({ index, key: 'month', value: month + 1 })
|
|
66
|
+
main.pushToMain({ index, key: 'hour', value: hour })
|
|
67
|
+
main.pushToMain({ index, key: 'minute', value: minute })
|
|
68
|
+
main.pushToMain({ index, key: 'day_of_the_week', value: ((dayOfWeek + 6) % 7) + 1 })
|
|
69
|
+
main.pushToMain({ index, key: 'day_of_the_month', value: dayOfMonth + 1 })
|
|
70
|
+
return
|
|
52
71
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const getDateInfo = (date, oneHot, colKeySizes) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const year = date.getFullYear();
|
|
61
|
-
|
|
62
|
-
//start with 0 values
|
|
63
|
-
const month = date.getMonth();
|
|
64
|
-
const hour = date.getHours();
|
|
65
|
-
const minute = date.getMinutes();
|
|
66
|
-
const dayOfWeek = date.getDay();
|
|
67
|
-
const dayOfMonth = date.getDate() - 1; // keep same offset
|
|
68
|
-
|
|
69
|
-
if (!oneHot) {
|
|
70
|
-
return {
|
|
71
|
-
year: year,
|
|
72
|
-
month: month + 1, //iso month
|
|
73
|
-
hour: hour,
|
|
74
|
-
minute: minute,
|
|
75
|
-
day_of_the_week: ((dayOfWeek + 6) % 7) + 1, //iso date of the week
|
|
76
|
-
day_of_the_month: dayOfMonth + 1, //iso date of the month
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
73
|
+
// Build a fresh vector for every field and row before writing.
|
|
74
|
+
const oneHotMonth = oneHotEncode(month, colKeySizes.one_hot_month)
|
|
75
|
+
const oneHotHour = oneHotEncode(hour, colKeySizes.one_hot_hour)
|
|
76
|
+
const oneHotMinute = oneHotEncode(minute, colKeySizes.one_hot_minute)
|
|
77
|
+
const oneHotWeekday = oneHotEncode(dayOfWeek, colKeySizes.one_hot_day_of_the_week)
|
|
78
|
+
const oneHotDay = oneHotEncode(dayOfMonth, colKeySizes.one_hot_day_of_the_month)
|
|
79
|
+
|
|
80
|
+
main.pushToMain({ index, key: 'one_hot_month', value: oneHotMonth })
|
|
81
|
+
main.pushToMain({ index, key: 'one_hot_hour', value: oneHotHour })
|
|
82
|
+
main.pushToMain({ index, key: 'one_hot_minute', value: oneHotMinute })
|
|
83
|
+
main.pushToMain({ index, key: 'one_hot_day_of_the_week', value: oneHotWeekday })
|
|
84
|
+
main.pushToMain({ index, key: 'one_hot_day_of_the_month', value: oneHotDay })
|
|
85
|
+
}
|
|
@@ -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
|
|
@@ -22,11 +24,7 @@ class CrossInstance {
|
|
|
22
24
|
prevSlow: NaN,
|
|
23
25
|
prevHigh: NaN,
|
|
24
26
|
prevLow: NaN,
|
|
25
|
-
areHighAndLowUndefined: false
|
|
26
|
-
crossIndexes: {
|
|
27
|
-
up: [],
|
|
28
|
-
down: []
|
|
29
|
-
}
|
|
27
|
+
areHighAndLowUndefined: false
|
|
30
28
|
})
|
|
31
29
|
|
|
32
30
|
}
|
|
@@ -113,9 +111,6 @@ class CrossInstance {
|
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
|
|
116
|
-
if(this.interval === 1) this.crossIndexes.up.push(index)
|
|
117
|
-
if(this.interval === -1) this.crossIndexes.down.push(index)
|
|
118
|
-
|
|
119
114
|
//save prev state
|
|
120
115
|
Object.assign(this, {
|
|
121
116
|
prevState: currState,
|
|
@@ -143,6 +138,16 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
|
143
138
|
|
|
144
139
|
if(index === 0)
|
|
145
140
|
{
|
|
141
|
+
validateInputValues(
|
|
142
|
+
{
|
|
143
|
+
...(fast === 'price' ? { high: true, low: true, close: true } : { [fast]: true }),
|
|
144
|
+
...(typeof slow === 'number' ? {} : { [slow]: true })
|
|
145
|
+
},
|
|
146
|
+
verticalOhlcv,
|
|
147
|
+
index,
|
|
148
|
+
'crossPairs'
|
|
149
|
+
)
|
|
150
|
+
|
|
146
151
|
// allow numeric 'slow' as a constant column
|
|
147
152
|
if (typeof slow === 'number') {
|
|
148
153
|
|
|
@@ -173,12 +178,10 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
|
173
178
|
|
|
174
179
|
const crossArrType = (limit !== null && limit <= 125) ? 'Int8Array' : 'Float64Array'
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
verticalOhlcv[`one_hot_${crossName}`] = buildArray('Array', len, null)
|
|
181
|
-
}
|
|
181
|
+
initializeColumns(main, [
|
|
182
|
+
{ key: crossName, type: crossArrType, fill: 0 },
|
|
183
|
+
{ key: `one_hot_${crossName}`, type: 'Array', enabled: oneHot }
|
|
184
|
+
])
|
|
182
185
|
|
|
183
186
|
} else if(index + 1 === len) {
|
|
184
187
|
// sanity checks
|
|
@@ -228,5 +231,4 @@ export const crossPairs = (main, index, crossPairsList, {oneHot, limit}) => {
|
|
|
228
231
|
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
|
|
232
|
-
}
|
|
234
|
+
}
|
package/src/studies/lag.js
CHANGED
|
@@ -1,44 +1,57 @@
|
|
|
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
|
-
|
|
12
|
-
|
|
11
|
+
const lagColumns = new Map()
|
|
12
|
+
|
|
13
|
+
for (const target of colKeys) {
|
|
14
|
+
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
13
15
|
throw new Error(
|
|
14
|
-
`Lag processing invoked by col "${
|
|
16
|
+
`Lag processing invoked by col "${target}" was not found in "verticalOhlcv".`
|
|
15
17
|
)
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
const addToPriceBased = priceBased.has(
|
|
20
|
+
const addToPriceBased = priceBased.has(target)
|
|
21
|
+
const type = Array.isArray(verticalOhlcv[target]) ? 'Array' : 'Float64Array'
|
|
22
|
+
const columns = []
|
|
23
|
+
const keyNames = []
|
|
19
24
|
|
|
20
25
|
for (let step = 1; step <= lookback; step++) {
|
|
21
|
-
const key = `${
|
|
22
|
-
|
|
23
|
-
if(addToPriceBased) priceBased.add(key)
|
|
24
|
-
|
|
25
|
-
const thisArrType = getArrayType(targetKey, verticalOhlcv[targetKey])
|
|
26
|
+
const key = `${target}_lag_${step}`
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
columns.push({ key, type, priceBased: addToPriceBased, includeInLag: false })
|
|
29
|
+
keyNames.push(key)
|
|
28
30
|
}
|
|
31
|
+
|
|
32
|
+
initializeColumns(main, columns)
|
|
33
|
+
lagColumns.set(target, 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
|
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
const lagColumns = instances[lagColumnsKey]?.get(colKeys)
|
|
42
|
+
|
|
43
|
+
for (const target of colKeys) {
|
|
44
|
+
const keyNames = lagColumns?.get(target)
|
|
45
|
+
const currentColumn = verticalOhlcv[target]
|
|
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
|
-
const key = `${
|
|
50
|
+
const key = keyNames?.[step - 1] ?? `${target}_lag_${step}`
|
|
38
51
|
const laggedIndex = index - step
|
|
39
52
|
const value =
|
|
40
|
-
laggedIndex < 0 || currentColumn[laggedIndex]
|
|
41
|
-
?
|
|
53
|
+
laggedIndex < 0 || currentColumn[laggedIndex] == null
|
|
54
|
+
? missingValue
|
|
42
55
|
: currentColumn[laggedIndex]
|
|
43
56
|
|
|
44
57
|
main.pushToMain({ index, key, value })
|
|
@@ -46,5 +59,4 @@ export const lag = (main, index, colKeys, lookback) => {
|
|
|
46
59
|
}
|
|
47
60
|
}
|
|
48
61
|
|
|
49
|
-
return true
|
|
50
62
|
}
|
|
@@ -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,31 @@
|
|
|
1
|
+
export const CONFIG_VERSION = 1;
|
|
2
|
+
|
|
3
|
+
// Copy configuration without JSON's silent conversion of functions/NaN to null.
|
|
4
|
+
export const copyConfig = (value, ancestors = new Set()) => {
|
|
5
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
6
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
7
|
+
if (typeof value !== 'object' || value === null) {
|
|
8
|
+
throw new TypeError('Configuration values must be JSON-safe: no undefined, non-finite numbers, or functions.');
|
|
9
|
+
}
|
|
10
|
+
if (ancestors.has(value)) throw new TypeError('Configuration must not contain circular references.');
|
|
11
|
+
const prototype = Object.getPrototypeOf(value);
|
|
12
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
|
|
13
|
+
throw new TypeError('Configuration must contain only plain objects and arrays.');
|
|
14
|
+
}
|
|
15
|
+
if (Object.getOwnPropertySymbols(value).length) throw new TypeError('Configuration must not contain symbol keys.');
|
|
16
|
+
|
|
17
|
+
ancestors.add(value);
|
|
18
|
+
const result = Array.isArray(value)
|
|
19
|
+
? Array.from(value, item => copyConfig(item, ancestors))
|
|
20
|
+
: Object.fromEntries(Object.entries(value).map(([key, item]) => [key, copyConfig(item, ancestors)]));
|
|
21
|
+
ancestors.delete(value);
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const freezeConfig = value => {
|
|
26
|
+
if (value !== null && typeof value === 'object') {
|
|
27
|
+
for (const item of Object.values(value)) freezeConfig(item);
|
|
28
|
+
Object.freeze(value);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
};
|