Skip to content

Commit 621e596

Browse files
committed
Added TA methods : accdist, bbw, iii, kc, kcw, nvi, pvi, pvt, range, wad, wvad
1 parent c9dc203 commit 621e596

File tree

17 files changed

+1564
-23
lines changed

17 files changed

+1564
-23
lines changed

docs/api-coverage/ta.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ All functions listed below are verified to exist in Pine Script v5.
1919
| `ta.rma()` || Rolling/Running Moving Average |
2020
| `ta.vwma()` || Volume Weighted Moving Average |
2121
| `ta.alma()` || Arnaud Legoux Moving Average |
22-
| `ta.linreg()` | ✔️ | Linear Regression |
22+
| `ta.linreg()` | | Linear Regression |
2323
| `ta.swma()` || Symmetrically Weighted Moving Average |
2424
| `ta.vwap` || Volume Weighted Average Price (variable) |
2525

@@ -48,13 +48,13 @@ All functions listed below are verified to exist in Pine Script v5.
4848
| `ta.stdev()` || Standard Deviation |
4949
| `ta.variance()` || Variance |
5050
| `ta.dev()` || Mean Absolute Deviation |
51-
| `ta.tr` | ✔️ | True Range (variable) |
52-
| `ta.tr()` | ✔️ | True Range (function) |
51+
| `ta.tr` | | True Range (variable) |
52+
| `ta.tr()` | | True Range (function) |
5353
| `ta.bb()` || Bollinger Bands |
54-
| `ta.bbw()` | | Bollinger Bands Width |
55-
| `ta.kc()` | | Keltner Channels |
56-
| `ta.kcw()` | | Keltner Channels Width |
57-
| `ta.range()` | | Range |
54+
| `ta.bbw()` | | Bollinger Bands Width |
55+
| `ta.kc()` | | Keltner Channels |
56+
| `ta.kcw()` | | Keltner Channels Width |
57+
| `ta.range()` | | Range |
5858

5959
### Trend Analysis
6060

@@ -74,13 +74,13 @@ All functions listed below are verified to exist in Pine Script v5.
7474
| Function | Status | Description |
7575
| ------------ | ------ | ------------------------------------------------------ |
7676
| `ta.obv` || On-Balance Volume (variable) |
77-
| `ta.pvt` | | Price-Volume Trend (variable) |
78-
| `ta.wad` | | Williams Accumulation/Distribution (variable) |
79-
| `ta.wvad` | | Williams Variable Accumulation/Distribution (variable) |
80-
| `ta.accdist` | | Accumulation/Distribution (variable) |
81-
| `ta.nvi` | | Negative Volume Index (variable) |
82-
| `ta.pvi` | | Positive Volume Index (variable) |
83-
| `ta.iii` | | Intraday Intensity Index (variable) |
77+
| `ta.pvt` | | Price-Volume Trend (variable) |
78+
| `ta.wad` | | Williams Accumulation/Distribution (variable) |
79+
| `ta.wvad` | | Williams Variable Accumulation/Distribution (variable) |
80+
| `ta.accdist` | | Accumulation/Distribution (variable) |
81+
| `ta.nvi` | | Negative Volume Index (variable) |
82+
| `ta.pvi` | | Positive Volume Index (variable) |
83+
| `ta.iii` | | Intraday Intensity Index (variable) |
8484

8585
### Statistical Functions
8686

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
import { Series } from '../../../Series';
4+
5+
/**
6+
* Accumulation/Distribution (AccDist)
7+
*
8+
* Formula:
9+
* AD = cum(((close - low) - (high - close)) / (high - low) * volume)
10+
*/
11+
export function accdist(context: any) {
12+
return (_callId?: string) => {
13+
if (!context.taState) context.taState = {};
14+
const stateKey = _callId || 'accdist';
15+
16+
if (!context.taState[stateKey]) {
17+
context.taState[stateKey] = {
18+
cumulativeSum: 0,
19+
};
20+
}
21+
22+
const state = context.taState[stateKey];
23+
24+
const close = context.get(context.data.close, 0);
25+
const high = context.get(context.data.high, 0);
26+
const low = context.get(context.data.low, 0);
27+
const volume = context.get(context.data.volume, 0);
28+
29+
if (isNaN(close) || isNaN(high) || isNaN(low) || isNaN(volume)) {
30+
return context.precision(state.cumulativeSum);
31+
}
32+
33+
const range = high - low;
34+
let term = 0;
35+
36+
if (range !== 0) {
37+
term = ((close - low) - (high - close)) / range * volume;
38+
}
39+
40+
state.cumulativeSum += term;
41+
42+
return context.precision(state.cumulativeSum);
43+
};
44+
}
45+

src/namespaces/ta/methods/bbw.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
import { Series } from '../../../Series';
4+
5+
/**
6+
* Bollinger Bands Width (BBW)
7+
*
8+
* Formula:
9+
* basis = ta.sma(source, length)
10+
* dev = mult * ta.stdev(source, length)
11+
* bbw = (((basis + dev) - (basis - dev)) / basis) * 100
12+
*/
13+
export function bbw(context: any) {
14+
return (source: any, _length: any, _mult: any, _callId?: string) => {
15+
const length = Series.from(_length).get(0);
16+
const mult = Series.from(_mult).get(0);
17+
18+
if (!context.taState) context.taState = {};
19+
const stateKey = _callId || `bbw_${length}_${mult}`;
20+
21+
if (!context.taState[stateKey]) {
22+
context.taState[stateKey] = {
23+
window: [],
24+
sum: 0,
25+
};
26+
}
27+
28+
const state = context.taState[stateKey];
29+
const currentValue = Series.from(source).get(0);
30+
31+
if (isNaN(currentValue)) {
32+
return NaN;
33+
}
34+
35+
state.window.unshift(currentValue);
36+
state.sum += currentValue;
37+
38+
if (state.window.length < length) {
39+
return NaN;
40+
}
41+
42+
if (state.window.length > length) {
43+
const removed = state.window.pop();
44+
state.sum -= removed;
45+
}
46+
47+
const basis = state.sum / length;
48+
49+
let sumSqDiff = 0;
50+
for (let i = 0; i < length; i++) {
51+
const diff = state.window[i] - basis;
52+
sumSqDiff += diff * diff;
53+
}
54+
const variance = sumSqDiff / length;
55+
const stdev = Math.sqrt(variance);
56+
57+
const dev = mult * stdev;
58+
59+
if (basis === 0) {
60+
return context.precision(0);
61+
}
62+
63+
const bbw = ((2 * dev) / basis) * 100;
64+
return context.precision(bbw);
65+
};
66+
}
67+

src/namespaces/ta/methods/iii.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
/**
4+
* Intraday Intensity Index (III)
5+
*
6+
* Formula:
7+
* (2 * close - high - low) / ((high - low) * volume)
8+
*/
9+
export function iii(context: any) {
10+
return (_callId?: string) => {
11+
const close = context.get(context.data.close, 0);
12+
const high = context.get(context.data.high, 0);
13+
const low = context.get(context.data.low, 0);
14+
const volume = context.get(context.data.volume, 0);
15+
16+
if (isNaN(close) || isNaN(high) || isNaN(low) || isNaN(volume)) {
17+
return NaN;
18+
}
19+
20+
const range = high - low;
21+
const denominator = range * volume;
22+
23+
if (denominator === 0) {
24+
return context.precision(0);
25+
}
26+
27+
const iii = (2 * close - high - low) / denominator;
28+
return context.precision(iii);
29+
};
30+
}
31+

src/namespaces/ta/methods/kc.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
import { Series } from '../../../Series';
4+
5+
/**
6+
* Keltner Channels (KC)
7+
*
8+
* Formula:
9+
* basis = ta.ema(src, length)
10+
* span = useTrueRange ? ta.tr : (high - low)
11+
* rangeEma = ta.ema(span, length)
12+
* upper = basis + rangeEma * mult
13+
* lower = basis - rangeEma * mult
14+
* Returns [basis, upper, lower]
15+
*/
16+
export function kc(context: any) {
17+
return (source: any, _length: any, _mult: any, _useTrueRange?: any, _callId?: string) => {
18+
const length = Series.from(_length).get(0);
19+
const mult = Series.from(_mult).get(0);
20+
let useTrueRange = true;
21+
if (typeof _useTrueRange === 'string') {
22+
//this is the _callId passed by the transpiler
23+
_callId = _useTrueRange;
24+
} else if (_useTrueRange !== undefined) {
25+
useTrueRange = Series.from(_useTrueRange).get(0);
26+
}
27+
28+
// Calculate span
29+
let span;
30+
const high = context.get(context.data.high, 0);
31+
const low = context.get(context.data.low, 0);
32+
33+
if (useTrueRange) {
34+
// Calculate TR
35+
const close1 = context.get(context.data.close, 1);
36+
if (isNaN(close1)) {
37+
span = NaN;
38+
} else {
39+
span = Math.max(high - low, Math.abs(high - close1), Math.abs(low - close1));
40+
}
41+
} else {
42+
span = high - low;
43+
}
44+
45+
const currentValue = Series.from(source).get(0);
46+
47+
if (!context.taState) context.taState = {};
48+
const stateKey = _callId || `kc_${length}_${mult}_${useTrueRange}`;
49+
50+
if (!context.taState[stateKey]) {
51+
context.taState[stateKey] = {
52+
basisState: { prevEma: null, initSum: 0, initCount: 0 },
53+
rangeState: { prevEma: null, initSum: 0, initCount: 0 },
54+
};
55+
}
56+
57+
const state = context.taState[stateKey];
58+
59+
// Helper to update EMA state
60+
const updateEma = (emaState: any, value: number, period: number) => {
61+
if (isNaN(value)) return NaN;
62+
63+
if (emaState.initCount < period) {
64+
emaState.initSum += value;
65+
emaState.initCount++;
66+
67+
if (emaState.initCount === period) {
68+
emaState.prevEma = emaState.initSum / period;
69+
return emaState.prevEma;
70+
}
71+
return NaN;
72+
}
73+
74+
const alpha = 2 / (period + 1);
75+
emaState.prevEma = value * alpha + emaState.prevEma * (1 - alpha);
76+
return emaState.prevEma;
77+
};
78+
79+
const basis = updateEma(state.basisState, currentValue, length);
80+
const rangeEma = updateEma(state.rangeState, span, length);
81+
82+
if (isNaN(basis) || isNaN(rangeEma)) {
83+
return [[NaN, NaN, NaN]];
84+
}
85+
86+
const upper = basis + rangeEma * mult;
87+
const lower = basis - rangeEma * mult;
88+
89+
return [[context.precision(basis), context.precision(upper), context.precision(lower)]];
90+
};
91+
}

src/namespaces/ta/methods/kcw.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
import { Series } from '../../../Series';
4+
5+
/**
6+
* Keltner Channels Width (KCW)
7+
*
8+
* Formula:
9+
* basis = ta.ema(src, length)
10+
* span = useTrueRange ? ta.tr : (high - low)
11+
* rangeEma = ta.ema(span, length)
12+
* kcw = ((basis + rangeEma * mult) - (basis - rangeEma * mult)) / basis
13+
* = (2 * rangeEma * mult) / basis
14+
*/
15+
export function kcw(context: any) {
16+
return (source: any, _length: any, _mult: any, _useTrueRange?: any, _callId?: string) => {
17+
const length = Series.from(_length).get(0);
18+
const mult = Series.from(_mult).get(0);
19+
20+
let useTrueRange = true;
21+
if (typeof _useTrueRange === 'string') {
22+
//this is the _callId passed by the transpiler
23+
_callId = _useTrueRange;
24+
} else if (_useTrueRange !== undefined) {
25+
useTrueRange = Series.from(_useTrueRange).get(0);
26+
}
27+
28+
if (!context.taState) context.taState = {};
29+
const stateKey = _callId || `kcw_${length}_${mult}_${useTrueRange}`;
30+
31+
if (!context.taState[stateKey]) {
32+
context.taState[stateKey] = {
33+
basisState: { prevEma: null, initSum: 0, initCount: 0 },
34+
rangeState: { prevEma: null, initSum: 0, initCount: 0 },
35+
};
36+
}
37+
38+
const state = context.taState[stateKey];
39+
40+
// Helper to update EMA state
41+
const updateEma = (emaState: any, value: number, period: number) => {
42+
if (isNaN(value)) return NaN;
43+
44+
if (emaState.initCount < period) {
45+
emaState.initSum += value;
46+
emaState.initCount++;
47+
48+
if (emaState.initCount === period) {
49+
emaState.prevEma = emaState.initSum / period;
50+
return emaState.prevEma;
51+
}
52+
return NaN;
53+
}
54+
55+
const alpha = 2 / (period + 1);
56+
emaState.prevEma = value * alpha + emaState.prevEma * (1 - alpha);
57+
return emaState.prevEma;
58+
};
59+
60+
// Calculate span
61+
let span;
62+
const high = context.get(context.data.high, 0);
63+
const low = context.get(context.data.low, 0);
64+
65+
if (useTrueRange) {
66+
// Calculate TR
67+
const close1 = context.get(context.data.close, 1);
68+
if (isNaN(close1)) {
69+
span = NaN;
70+
} else {
71+
span = Math.max(high - low, Math.abs(high - close1), Math.abs(low - close1));
72+
}
73+
} else {
74+
span = high - low;
75+
}
76+
77+
const currentValue = Series.from(source).get(0);
78+
const basis = updateEma(state.basisState, currentValue, length);
79+
const rangeEma = updateEma(state.rangeState, span, length);
80+
81+
if (isNaN(basis) || isNaN(rangeEma)) {
82+
return NaN;
83+
}
84+
85+
if (basis === 0) {
86+
return context.precision(0);
87+
}
88+
89+
const kcw = (2 * rangeEma * mult) / basis;
90+
91+
return context.precision(kcw);
92+
};
93+
}

0 commit comments

Comments
 (0)