Skip to content

Commit 18161e1

Browse files
committed
Add williams percent range indicator
1 parent 66b7349 commit 18161e1

File tree

7 files changed

+119
-6
lines changed

7 files changed

+119
-6
lines changed

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pip install pyindicators
3333
* [Momentum indicators](#momentum-indicators)
3434
* [Relative Strength Index (RSI)](#relative-strength-index-rsi)
3535
* [Relative Strength Index Wilders method (Wilders RSI)](#wilders-relative-strength-index-wilders-rsi)
36+
* [Williams %R](#williams-r)
3637
* [Indicator helpers](#indicator-helpers)
3738
* [Crossover](#crossover)
3839
* [Is Crossover](#is-crossover)
@@ -213,7 +214,48 @@ pd_df = wilders_rsi(pd_df, source_column="Close", period=14, result_column="RSI_
213214
pd_df.tail(10)
214215
```
215216

216-
![RSI](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/wilders_rsi.png)
217+
![wilders_RSI](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/wilers_rsi.png)
218+
219+
#### Williams %R
220+
221+
Williams %R (Williams Percent Range) is a momentum indicator used in technical analysis to measure overbought and oversold conditions in a market. It moves between 0 and -100 and helps traders identify potential reversal points.
222+
223+
224+
```python
225+
def willr(
226+
data: Union[pd.DataFrame, pl.DataFrame],
227+
period: int = 14,
228+
result_column: str = None,
229+
high_column: str = "High",
230+
low_column: str = "Low",
231+
close_column: str = "Close"
232+
) -> Union[pd.DataFrame, pl.DataFrame]:
233+
```
234+
235+
Example
236+
237+
```python
238+
from investing_algorithm_framework import CSVOHLCVMarketDataSource
239+
240+
from pyindicators import willr
241+
242+
# For this example the investing algorithm framework is used for dataframe creation,
243+
csv_path = "./tests/test_data/OHLCV_BTC-EUR_BINANCE_15m_2023-12-01:00:00_2023-12-25:00:00.csv"
244+
data_source = CSVOHLCVMarketDataSource(csv_file_path=csv_path)
245+
246+
pl_df = data_source.get_data()
247+
pd_df = data_source.get_data(pandas=True)
248+
249+
# Calculate Williams%R for Polars DataFrame
250+
pl_df = willr(pl_df, result_column="WILLR")
251+
pl_df.show(10)
252+
253+
# Calculate Williams%R for Pandas DataFrame
254+
pd_df = willr(pd_df, result_column="WILLR")
255+
pd_df.tail(10)
256+
```
257+
258+
![williams %R](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/willr.png)
217259

218260
### Indicator helpers
219261

pyindicators/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .indicators import sma, rsi, is_crossover, crossunder, ema, wilders_rsi, \
2-
crossover, is_crossover, wma, macd
2+
crossover, is_crossover, wma, macd, willr
33

44
__all__ = [
55
'sma',
@@ -11,5 +11,6 @@
1111
'ema',
1212
'rsi',
1313
"wilders_rsi",
14-
'macd'
14+
'macd',
15+
'willr'
1516
]

pyindicators/indicators/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .exponential_moving_average import ema
66
from .rsi import rsi, wilders_rsi
77
from .macd import macd
8+
from .williams_percent_range import willr
89

910
__all__ = [
1011
'sma',
@@ -15,5 +16,6 @@
1516
'ema',
1617
'rsi',
1718
'wilders_rsi',
18-
'macd'
19+
'macd',
20+
'willr'
1921
]

pyindicators/indicators/rsi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def rsi(
77
data: Union[pd.DataFrame, pl.DataFrame],
88
source_column: str,
9-
period: int,
9+
period: int = 14,
1010
result_column: str = None,
1111
) -> Union[pd.DataFrame, pl.DataFrame]:
1212
"""
@@ -81,7 +81,7 @@ def rsi(
8181
def wilders_rsi(
8282
data: Union[pd.DataFrame, pl.DataFrame],
8383
source_column: str,
84-
period: int,
84+
period: int = 14,
8585
result_column: str = None,
8686
) -> Union[pd.DataFrame, pl.DataFrame]:
8787
"""
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from typing import Union
2+
import pandas as pd
3+
import polars as pl
4+
from pyindicators.exceptions import PyIndicatorException
5+
6+
7+
def willr(
8+
data: Union[pd.DataFrame, pl.DataFrame],
9+
period: int = 14,
10+
result_column: str = None,
11+
high_column: str = "High",
12+
low_column: str = "Low",
13+
close_column: str = "Close"
14+
) -> Union[pd.DataFrame, pl.DataFrame]:
15+
"""
16+
Function to calculate the Williams %R indicator of a series.
17+
18+
Args:
19+
data (Union[pd.DataFrame, pl.DataFrame]): The input data.
20+
source_column (str): The name of the series.
21+
period (int): The period for the Williams %R calculation.
22+
result_column (str, optional): The name of the column to store
23+
the Williams %R values. Defaults to None, which means it will
24+
be named "WilliamsR_{period}".
25+
26+
Returns:
27+
Union[pd.DataFrame, pl.DataFrame]: The DataFrame with
28+
the Williams %R column added.
29+
"""
30+
31+
# Check if the high and low columns are present
32+
if high_column not in data.columns:
33+
raise PyIndicatorException(
34+
f"Column '{high_column}' not found in DataFrame"
35+
)
36+
37+
if low_column not in data.columns:
38+
raise PyIndicatorException(
39+
f"Column '{low_column}' not found in DataFrame"
40+
)
41+
42+
if isinstance(data, pd.DataFrame):
43+
data["high_n"] = data[high_column]\
44+
.rolling(window=period, min_periods=1).max()
45+
data["low_n"] = data[low_column]\
46+
.rolling(window=period, min_periods=1).min()
47+
data[result_column] = ((data["high_n"] - data[close_column]) /
48+
(data["high_n"] - data["low_n"])) * -100
49+
return data.drop(columns=["high_n", "low_n"])
50+
51+
elif isinstance(data, pl.DataFrame):
52+
high_n = data.select(pl.col(high_column).rolling_max(period).alias("high_n"))
53+
low_n = data.select(pl.col(low_column).rolling_min(period).alias("low_n"))
54+
55+
data = data.with_columns([
56+
high_n["high_n"],
57+
low_n["low_n"]
58+
])
59+
data = data.with_columns(
60+
((pl.col("high_n") - pl.col(close_column)) /
61+
(pl.col("high_n") - pl.col("low_n")) * -100).alias(result_column)
62+
)
63+
return data.drop(["high_n", "low_n"])
64+
65+
else:
66+
raise PyIndicatorException(
67+
"Unsupported data type. Must be pandas or polars DataFrame."
68+
)

static/images/indicators/macd.png

-22.9 KB
Loading

static/images/indicators/willr.png

147 KB
Loading

0 commit comments

Comments
 (0)