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.
Files changed (42) hide show
  1. package/AGENTS.md +105 -0
  2. package/README.md +415 -0
  3. package/dist/ohlcv-indicators.min.js +2 -1
  4. package/dist/ohlcv-indicators.min.js.LICENSE.txt +23 -0
  5. package/index.js +96 -127
  6. package/package.json +1 -1
  7. package/src/core-functions/initializeColumns.js +40 -0
  8. package/src/core-functions/mainLoop.js +49 -26
  9. package/src/core-functions/pushToMain.js +1 -3
  10. package/src/core-indicators/README.md +61 -0
  11. package/src/core-indicators/composites.js +136 -0
  12. package/src/core-indicators/index.js +5 -0
  13. package/src/core-indicators/indicator.js +82 -0
  14. package/src/core-indicators/movingAverages.js +88 -0
  15. package/src/core-indicators/rsi.js +41 -0
  16. package/src/core-indicators/volatility.js +107 -0
  17. package/src/machine-learning/ml-utilities.js +0 -9
  18. package/src/moving-averages/bollingerBands.js +11 -26
  19. package/src/moving-averages/donchianChannel.js +30 -29
  20. package/src/moving-averages/heikenAshi.js +23 -33
  21. package/src/moving-averages/macd.js +10 -26
  22. package/src/moving-averages/movingAverages.js +7 -23
  23. package/src/moving-averages/relativeVolume.js +23 -21
  24. package/src/oscillators/rsi.js +7 -26
  25. package/src/oscillators/stochastic.js +6 -19
  26. package/src/oscillators/volumeDelta.js +36 -28
  27. package/src/oscillators/volumeOscillator.js +29 -31
  28. package/src/studies/{priceFeatures.js → candleFeatures.js} +15 -23
  29. package/src/studies/dateTime.js +22 -27
  30. package/src/studies/findCrosses.js +16 -7
  31. package/src/studies/lag.js +26 -13
  32. package/src/studies/mapCols.js +14 -15
  33. package/src/utilities/assignTypes.js +1 -1
  34. package/src/utilities/config.js +64 -0
  35. package/src/utilities/numberUtilities.js +2 -0
  36. package/src/utilities/validators.js +37 -21
  37. package/src/utilities/verticalToHorizontal.js +21 -16
  38. package/src/volatility/adx.js +7 -20
  39. package/src/volatility/atr.js +10 -23
  40. package/test/test.js +533 -0
  41. package/src/machine-learning/scaler.js +0 -165
  42. 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
- }