Skip to content

Commit 7cb8fc5

Browse files
authored
feat: william's percent r (willr) (#20)
1 parent bc933e3 commit 7cb8fc5

4 files changed

Lines changed: 120 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ For all configuration options, please see [the API docs](https://www.jsdocs.io/p
3535
- [Relative Strength Index (RSI)](src/momentum/RSI.ts)
3636
- [Stochastic Oscillator (STOCH)](src/momentum/Stochastic.ts)
3737
- [Stochastic RSI Oscillator (STOCHRSI)](src/momentum/StochasticRSI.ts)
38+
- [William's Percent R (WILLR)](src/momentum/WILLR.ts)
3839

3940
###### Overlap
4041

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export {
4444
type StochasticRSIOutput,
4545
type StochasticRSITick,
4646
} from '@/momentum/StochasticRSI';
47+
export { WILLR, type WILLRInput, type WILLROutput, type WILLRTick } from '@/momentum/WILLR';
4748

4849
// -- Candles -------------------------
4950
export { HA, type HAInput, type HAOutput, type HATick } from '@/candles/HA';

src/momentum/WILLR.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as assert from 'assert';
2+
3+
import { SampleBars } from '@/samples';
4+
5+
import { WILLR, WILLRInput } from './WILLR';
6+
7+
const input: WILLRInput = {
8+
high: SampleBars.high.slice(0, 30),
9+
low: SampleBars.low.slice(0, 30),
10+
close: SampleBars.close.slice(0, 30),
11+
period: 10,
12+
decimal: 6,
13+
};
14+
15+
const expectedResult = [
16+
-12.398032, -10.73518, -2.746884, -13.876842, -26.181533, -9.313818, -9.076531, -3.055431,
17+
-13.003592, -17.996414, -24.92776, -37.81178, -71.866846, -82.128221, -60.044732, -64.983234,
18+
-49.235924, -41.520695, -49.552745, -43.346998, -43.868809,
19+
];
20+
21+
describe("William's Percent R (WILLR)", function () {
22+
it('should calculate WILLR and matched the expected', function () {
23+
const result = WILLR.calculate(input);
24+
assert.deepEqual(result, expectedResult, 'Wrong Results');
25+
});
26+
});

src/momentum/WILLR.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Indicator, IndicatorInput } from '@/indicator';
2+
import { RollingWindow } from '@/utils/RollingWindow';
3+
4+
export interface WILLRInput extends IndicatorInput {
5+
high: number[];
6+
low: number[];
7+
close: number[];
8+
9+
/**
10+
* It's period.
11+
* @default 14
12+
*/
13+
period?: number;
14+
}
15+
16+
export type WILLROutput = number | undefined;
17+
18+
export interface WILLRTick {
19+
high: number;
20+
low: number;
21+
close: number;
22+
}
23+
24+
/**
25+
* ### William's Percent R (WILLR)
26+
*
27+
* William's Percent R is a momentum oscillator similar to the Stochastic Oscillator.
28+
* It attempts to identify overbought and oversold conditions.
29+
*
30+
* **Calculation**:
31+
*
32+
* ```
33+
* WILLR = 100 * ((close - LL) / (HH - LL) - 1)
34+
* ```
35+
*
36+
* Where:
37+
* - `LL` is the lowest low for the look-back period.
38+
* - `HH` is the highest high for the look-back period.
39+
*
40+
* **Sources**:
41+
*
42+
* - https://www.tradingview.com/wiki/Williams_%25R_(%25R)
43+
*/
44+
export class WILLR extends Indicator<WILLROutput, WILLRTick> {
45+
period: number;
46+
47+
protected override result: WILLROutput[] = [];
48+
protected override generator;
49+
50+
constructor(input: WILLRInput) {
51+
super(input);
52+
53+
this.period = input.period || 14;
54+
55+
this.generator = this.willrGenerator();
56+
this.generator.next();
57+
58+
input.high.forEach((high, index) => {
59+
this.nextValue({
60+
high,
61+
low: input.low[index]!,
62+
close: input.close[index]!,
63+
});
64+
});
65+
}
66+
67+
private *willrGenerator(): IterableIterator<WILLROutput, never, WILLRTick> {
68+
const highWindow = new RollingWindow(this.period);
69+
const lowWindow = new RollingWindow(this.period);
70+
71+
let tick = yield;
72+
let output;
73+
74+
while (true) {
75+
highWindow.push(tick.high);
76+
lowWindow.push(tick.low);
77+
78+
if (highWindow.filled()) {
79+
const lowestLow = lowWindow.lowest();
80+
const highestHigh = highWindow.highest();
81+
82+
output = 100 * ((tick.close - lowestLow) / (highestHigh - lowestLow) - 1);
83+
}
84+
85+
tick = yield output;
86+
}
87+
}
88+
89+
static calculate(input: WILLRInput): WILLROutput[] {
90+
return new WILLR(input).getResult();
91+
}
92+
}

0 commit comments

Comments
 (0)