ohlcv-indicators 3.5.77 → 3.5.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +96 -127
- package/package.json +1 -1
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +49 -26
- 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/machine-learning/ml-utilities.js +0 -9
- 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/{priceFeatures.js → candleFeatures.js} +15 -23
- 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 +37 -21
- 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/machine-learning/scaler.js +0 -165
- package/src/moving-averages/vidya.js +0 -181
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { normalizeMinMax, normalizeZScore } from "./ml-utilities.js";
|
|
2
|
-
|
|
3
|
-
const isBadNumber = (v) => v == null || !Number.isFinite(v)
|
|
4
|
-
|
|
5
|
-
//rowWiseScaler
|
|
6
|
-
|
|
7
|
-
export const scaler = (main, index, size, colKeys, {type, minMaxRange, lookback, weights, euclideanWeights, byFeatureRange, offset}) => {
|
|
8
|
-
|
|
9
|
-
//this function performs scaling row by row "row wise scaling"
|
|
10
|
-
|
|
11
|
-
const { verticalOhlcv, instances } = main
|
|
12
|
-
const prefix = `${type}_${size}`
|
|
13
|
-
|
|
14
|
-
if(index === 0) {
|
|
15
|
-
const { len, isAlreadyComputed } = main
|
|
16
|
-
|
|
17
|
-
if(isAlreadyComputed.has(prefix))
|
|
18
|
-
{
|
|
19
|
-
throw new Error(`In scaler method can not repeat params "size" and "options.type" together between indicators.`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if(!instances.hasOwnProperty('scaler')) {
|
|
23
|
-
instances.scaler = {}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if(!instances.scaler.hasOwnProperty(prefix))
|
|
27
|
-
{
|
|
28
|
-
instances.scaler[prefix] = {
|
|
29
|
-
extectedFeatures: (lookback === 0) ? colKeys.length : colKeys.length + (lookback * colKeys.length)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const features = []
|
|
34
|
-
|
|
35
|
-
for (const target of colKeys) {
|
|
36
|
-
if (!verticalOhlcv.hasOwnProperty(target)) {
|
|
37
|
-
throw new Error(`Target property "${target}" not found in verticalOhlcv`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const key = `${prefix}_${target}`;
|
|
41
|
-
verticalOhlcv[key] = new Float64Array(len).fill(NaN)
|
|
42
|
-
|
|
43
|
-
if (!main.scaledGroups[prefix]) main.scaledGroups[prefix] = [];
|
|
44
|
-
|
|
45
|
-
main.scaledGroups[prefix].push(key);
|
|
46
|
-
|
|
47
|
-
if (lookback > 0) {
|
|
48
|
-
const laggedKeys = Array.from({ length: lookback }).map((_, i) => `${key}_lag_${i + 1}`)
|
|
49
|
-
|
|
50
|
-
for(const lKey of laggedKeys) {
|
|
51
|
-
verticalOhlcv[lKey] = new Float64Array(len).fill(NaN)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
main.scaledGroups[prefix].push(...laggedKeys)
|
|
55
|
-
features.push(key)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for(const weKey of Object.keys(weights)) {
|
|
60
|
-
if (!verticalOhlcv.hasOwnProperty(weKey)) {
|
|
61
|
-
throw new Error(`Target property "${weKey}" not found in verticalOhlcv`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const { extectedFeatures } = instances.scaler[prefix]
|
|
68
|
-
|
|
69
|
-
let hasInvalidVal = false
|
|
70
|
-
const row = {}
|
|
71
|
-
const rowValues = []
|
|
72
|
-
let countFeatures = 0
|
|
73
|
-
|
|
74
|
-
const sourceIndex = index - offset
|
|
75
|
-
|
|
76
|
-
if(sourceIndex < 0) return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for (let x = 0; x < colKeys.length; x++) {
|
|
81
|
-
const target = colKeys[x]
|
|
82
|
-
const col = verticalOhlcv[target]
|
|
83
|
-
const currWeight = weights?.[target]?.[0] ?? 1
|
|
84
|
-
const currVal = col[sourceIndex]
|
|
85
|
-
|
|
86
|
-
if(isBadNumber(currVal))
|
|
87
|
-
{
|
|
88
|
-
hasInvalidVal = true
|
|
89
|
-
break
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
row[target] = {
|
|
93
|
-
val: currVal,
|
|
94
|
-
weight: (euclideanWeights) ? Math.sqrt(currWeight) : currWeight
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
rowValues.push(currVal)
|
|
98
|
-
countFeatures++
|
|
99
|
-
|
|
100
|
-
if(lookback > 0) {
|
|
101
|
-
for (let step = 1; step <= lookback; step++) {
|
|
102
|
-
|
|
103
|
-
const laggedWeight = weights?.[target]?.[step] ?? 1
|
|
104
|
-
const laggedVal = col[sourceIndex - step]
|
|
105
|
-
|
|
106
|
-
if(isBadNumber(laggedVal))
|
|
107
|
-
{
|
|
108
|
-
hasInvalidVal = true
|
|
109
|
-
break
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
row[`${target}_lag_${step}`] = {
|
|
113
|
-
val: laggedVal,
|
|
114
|
-
weight: (euclideanWeights) ? Math.sqrt(laggedWeight) : laggedWeight
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
rowValues.push(laggedVal)
|
|
118
|
-
countFeatures++
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if(hasInvalidVal) break
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if(hasInvalidVal || countFeatures !== extectedFeatures) return
|
|
126
|
-
|
|
127
|
-
if (type === "minmax") {
|
|
128
|
-
const mn = Math.min(...rowValues);
|
|
129
|
-
const mx = Math.max(...rowValues);
|
|
130
|
-
|
|
131
|
-
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
132
|
-
const scaled = normalizeMinMax(val, mn, mx, minMaxRange)
|
|
133
|
-
const weigthed = scaled === 0 ? 0 : scaled * weight
|
|
134
|
-
main.pushToMain({ index, key: `${prefix}_${key}`, value: weigthed });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (type === "zscore") {
|
|
139
|
-
const n = rowValues.length;
|
|
140
|
-
const mean = rowValues.reduce((s, x) => s + x, 0) / n;
|
|
141
|
-
const variance = rowValues.reduce((s, x) => s + (x - mean) ** 2, 0) / n;
|
|
142
|
-
const std = Math.sqrt(variance);
|
|
143
|
-
|
|
144
|
-
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
145
|
-
const scaled = normalizeZScore(val, mean, std)
|
|
146
|
-
const weigthed = scaled === 0 ? 0 : scaled * weight
|
|
147
|
-
main.pushToMain({ index, key: `${prefix}_${key}`, value: weigthed });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if(type === "byfeature") {
|
|
152
|
-
|
|
153
|
-
const [mn, mx] = byFeatureRange
|
|
154
|
-
|
|
155
|
-
for (const [key, { val, weight }] of Object.entries(row)) {
|
|
156
|
-
|
|
157
|
-
const scaled = normalizeMinMax(val, mn, mx, minMaxRange)
|
|
158
|
-
const weigthed = scaled === 0 ? 0 : scaled * weight
|
|
159
|
-
|
|
160
|
-
main.pushToMain({ index, key: `${prefix}_${key}`, value: weigthed });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { FasterSMA } from 'trading-signals';
|
|
2
|
-
// Variable Index Dynamic Average (VIDYA) indicator with volume stats
|
|
3
|
-
|
|
4
|
-
export const vidya = (main, index, size, momentum, options = {}) => {
|
|
5
|
-
const { verticalOhlcv, instances, len, priceBased } = main;
|
|
6
|
-
const { target, lag, atrLength = 200, bandDistance = 2, liquidityLookback = 5 } = options
|
|
7
|
-
const indicatorKey = `${size}_${momentum}`;
|
|
8
|
-
|
|
9
|
-
// Initialization on the first bar
|
|
10
|
-
if (index === 0) {
|
|
11
|
-
if (!(target in verticalOhlcv)) {
|
|
12
|
-
throw new Error(`VIDYA could not find target "${target}"`)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const prefix = `vidya_${indicatorKey}`
|
|
16
|
-
const keyBase = target === 'close' ? prefix : `${prefix}_${target}`
|
|
17
|
-
const keys = {
|
|
18
|
-
vidya: keyBase,
|
|
19
|
-
upper: `${prefix}_upper`,
|
|
20
|
-
lower: `${prefix}_lower`,
|
|
21
|
-
delta_vol: `${prefix}_delta_vol`,
|
|
22
|
-
cross: `${prefix}_cross`,
|
|
23
|
-
liquidity: `${prefix}_liquidity`,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const priceBasedVidyaKeys = [keyBase, keys.upper, keys.lower, keys.liquidity]
|
|
27
|
-
|
|
28
|
-
for(const k of priceBasedVidyaKeys) {
|
|
29
|
-
priceBased.add(k)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Prepare instances container
|
|
33
|
-
if (!instances.vidya) instances.vidya = { settings: {} };
|
|
34
|
-
instances.vidya.settings[indicatorKey] = {
|
|
35
|
-
momentumBuffer: [], prevVidya: NaN,
|
|
36
|
-
sma: new FasterSMA(15), atrBuffer: [],
|
|
37
|
-
isTrendUp: false, upVol: 0, downVol: 0,
|
|
38
|
-
liquidity: {
|
|
39
|
-
high: [],
|
|
40
|
-
low: []
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Allocate output arrays
|
|
45
|
-
Object.values(keys).forEach(key => {
|
|
46
|
-
verticalOhlcv[key] = new Float64Array(len).fill(NaN)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
if (lag > 0) main.lag(Object.values(keys), lag);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Per-bar computation
|
|
53
|
-
const inst = instances.vidya.settings[indicatorKey];
|
|
54
|
-
const prefix = `vidya_${indicatorKey}`;
|
|
55
|
-
const keyBase = target === 'close' ? prefix : `${prefix}_${target}`;
|
|
56
|
-
const keys = {
|
|
57
|
-
vidya: keyBase,
|
|
58
|
-
upper: `${prefix}_upper`,
|
|
59
|
-
lower: `${prefix}_lower`,
|
|
60
|
-
delta_vol: `${prefix}_delta_vol`,
|
|
61
|
-
cross: `${prefix}_cross`,
|
|
62
|
-
liquidity: `${prefix}_liquidity`,
|
|
63
|
-
};
|
|
64
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
const highestHigh = (inst.liquidity.high.length === liquidityLookback) ? Math.max(...inst.liquidity.high) : NaN
|
|
75
|
-
const lowestLow = (inst.liquidity.low.length === liquidityLookback) ? Math.min(...inst.liquidity.low) : NaN
|
|
76
|
-
|
|
77
|
-
// Compute ATR (Wilder's RMA)
|
|
78
|
-
if (index > 0) {
|
|
79
|
-
const prevClose = verticalOhlcv.close[index - 1];
|
|
80
|
-
const tr = Math.max(
|
|
81
|
-
high - low,
|
|
82
|
-
Math.abs(high - prevClose),
|
|
83
|
-
Math.abs(low - prevClose)
|
|
84
|
-
)
|
|
85
|
-
inst.atrBuffer.push(tr)
|
|
86
|
-
if (inst.atrBuffer.length > atrLength) inst.atrBuffer.shift()
|
|
87
|
-
}
|
|
88
|
-
const atr = inst.atrBuffer.length === atrLength
|
|
89
|
-
? inst.atrBuffer.reduce((a, b) => a + b, 0) / atrLength
|
|
90
|
-
: NaN
|
|
91
|
-
|
|
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()
|
|
98
|
-
if (inst.momentumBuffer.length === momentum) {
|
|
99
|
-
let sumPos = 0, sumNeg = 0
|
|
100
|
-
inst.momentumBuffer.forEach(m => m >= 0 ? sumPos += m : sumNeg += -m);
|
|
101
|
-
const denom = sumPos + sumNeg
|
|
102
|
-
const cmo = denom === 0 ? 0 : Math.abs((sumPos - sumNeg) / denom) * 100;
|
|
103
|
-
const alpha = 2 / (size + 1)
|
|
104
|
-
const a = alpha * (cmo / 100)
|
|
105
|
-
rawVidya = isNaN(inst.prevVidya) ? price : (a * price + (1 - a) * inst.prevVidya);
|
|
106
|
-
inst.prevVidya = rawVidya
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Smooth VIDYA using fixed 15-bar SMA
|
|
110
|
-
inst.sma.update(rawVidya)
|
|
111
|
-
let smoothedVidya = NaN
|
|
112
|
-
try { smoothedVidya = inst.sma.getResult() } catch {}
|
|
113
|
-
|
|
114
|
-
// Bands
|
|
115
|
-
const upper = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
116
|
-
? smoothedVidya + atr * bandDistance
|
|
117
|
-
: NaN
|
|
118
|
-
const lower = (!isNaN(atr) && !isNaN(smoothedVidya))
|
|
119
|
-
? smoothedVidya - atr * bandDistance
|
|
120
|
-
: NaN
|
|
121
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
// Update trend state
|
|
130
|
-
if (crossUp) inst.isTrendUp = true
|
|
131
|
-
if (crossDown) inst.isTrendUp = false
|
|
132
|
-
|
|
133
|
-
// Cumulative cross counter: positive when uptrend, negative when downtrend
|
|
134
|
-
let cross = 0;
|
|
135
|
-
if (index > 0) {
|
|
136
|
-
if (inst.isTrendUp) {
|
|
137
|
-
cross = prevCross > 0 ? prevCross + 1 : 1
|
|
138
|
-
} else {
|
|
139
|
-
cross = prevCross < 0 ? prevCross - 1 : -1
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Volume accumulation
|
|
144
|
-
if (crossUp || crossDown) {
|
|
145
|
-
inst.upVol = inst.downVol = 0
|
|
146
|
-
} else {
|
|
147
|
-
inst.upVol += (close > open ? vol : 0)
|
|
148
|
-
inst.downVol += (close < open ? vol : 0)
|
|
149
|
-
}
|
|
150
|
-
const avgVol = (inst.upVol + inst.downVol) / 2 || 1
|
|
151
|
-
const deltaPct = (inst.upVol - inst.downVol) / avgVol
|
|
152
|
-
|
|
153
|
-
// Trend-based boundary (reset on crossover)
|
|
154
|
-
const trendValue = (inst.isTrendUp) ? lower : upper
|
|
155
|
-
let liquidity = NaN
|
|
156
|
-
|
|
157
|
-
if(cross > 0)
|
|
158
|
-
{
|
|
159
|
-
liquidity = lowestLow
|
|
160
|
-
}
|
|
161
|
-
else if(cross < 0)
|
|
162
|
-
{
|
|
163
|
-
liquidity = highestHigh
|
|
164
|
-
}
|
|
165
|
-
|
|
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 })
|
|
173
|
-
|
|
174
|
-
inst.liquidity.high.push(high)
|
|
175
|
-
inst.liquidity.low.push(low)
|
|
176
|
-
|
|
177
|
-
if(inst.liquidity.high.length > liquidityLookback) inst.liquidity.high.shift()
|
|
178
|
-
if(inst.liquidity.low.length > liquidityLookback) inst.liquidity.low.shift()
|
|
179
|
-
|
|
180
|
-
return true
|
|
181
|
-
}
|