Skip to content

Commit 8bb99d0

Browse files
committed
Add stochastic oscillator implementation
1 parent bddcb19 commit 8bb99d0

File tree

5 files changed

+143
-1
lines changed

5 files changed

+143
-1
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pip install pyindicators
2929
* [Weighted Moving Average (WMA)](#weighted-moving-average-wma)
3030
* [Simple Moving Average (SMA)](#simple-moving-average-sma)
3131
* [Exponential Moving Average (EMA)](#exponential-moving-average-ema)
32+
* [Stochastic Oscillator (STO)](#stochastic-oscillator-sto)
3233
* [Momentum indicators](#momentum-indicators)
3334
* [Moving Average Convergence Divergence (MACD)](#moving-average-convergence-divergence-macd)
3435
* [Relative Strength Index (RSI)](#relative-strength-index-rsi)
@@ -207,6 +208,56 @@ pd_df.tail(10)
207208

208209
![EMA](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/ema.png)
209210

211+
#### Stochastic Oscillator (STO)
212+
The Stochastic Oscillator (STO) is a momentum indicator that compares a particular closing price of an asset to a range of its prices over a certain period. It is used to identify overbought or oversold conditions in a market. The STO consists of two lines: %K and %D, where %K is the main line and %D is the signal line.
213+
214+
```python
215+
def stochastic_oscillator(
216+
data: Union[pd.DataFrame, pl.DataFrame],
217+
high_column: str = "High",
218+
low_column: str = "Low",
219+
close_column: str = "Close",
220+
k_period: int = 14,
221+
k_slowing: int = 3,
222+
d_period: int = 3,
223+
result_column: Optional[str] = None
224+
) -> Union[pd.DataFrame, pl.DataFrame]:
225+
```
226+
227+
Example
228+
229+
```python
230+
from investing_algorithm_framework import download
231+
from pyindicators import stochastic_oscillator
232+
pl_df = download(
233+
symbol="btc/eur",
234+
market="binance",
235+
time_frame="1d",
236+
start_date="2023-12-01",
237+
end_date="2023-12-25",
238+
save=True,
239+
storage_path="./data"
240+
)
241+
pd_df = download(
242+
symbol="btc/eur",
243+
market="binance",
244+
time_frame="1d",
245+
start_date="2023-12-01",
246+
end_date="2023-12-25",
247+
pandas=True,
248+
save=True,
249+
storage_path="./data"
250+
)
251+
# Calculate Stochastic Oscillator for Polars DataFrame
252+
pl_df = stochastic_oscillator(pl_df, high_column="High", low_column="Low", close_column="Close", k_period=14, k_slowing=3, d_period=3, result_column="STO")
253+
pl_df.show(10)
254+
# Calculate Stochastic Oscillator for Pandas DataFrame
255+
pd_df = stochastic_oscillator(pd_df, high_column="High", low_column="Low", close_column="Close", k_period=14, k_slowing=3, d_period=3, result_column="STO")
256+
pd_df.tail(10)
257+
```
258+
259+
![STO](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/sto.png)
260+
210261
### Momentum Indicators
211262

212263
Indicators that measure the strength and speed of price movements rather than the direction.

pyindicators/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
has_slope_above_threshold, has_any_lower_then_threshold, \
66
has_values_above_threshold, has_values_below_threshold, is_down_trend, \
77
is_up_trend, up_and_downtrends, detect_peaks, \
8-
bearish_divergence, bullish_divergence
8+
bearish_divergence, bullish_divergence, stochastic_oscillator
99
from .exceptions import PyIndicatorException
1010
from .date_range import DateRange
1111

@@ -41,4 +41,5 @@
4141
'bearish_divergence',
4242
'bullish_divergence',
4343
'is_divergence',
44+
'stochastic_oscillator',
4445
]

pyindicators/indicators/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .up_and_down_trends import up_and_downtrends
1818
from .divergence import detect_peaks, bearish_divergence, \
1919
bullish_divergence
20+
from .stochastic_oscillator import stochastic_oscillator
2021

2122
__all__ = [
2223
'sma',
@@ -48,4 +49,5 @@
4849
'bearish_divergence',
4950
'bullish_divergence',
5051
'is_divergence',
52+
'stochastic_oscillator',
5153
]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from typing import Union, Optional
2+
import pandas as pd
3+
import polars as pl
4+
5+
6+
def stochastic_oscillator(
7+
data: Union[pd.DataFrame, pl.DataFrame],
8+
high_column: str = "High",
9+
low_column: str = "Low",
10+
close_column: str = "Close",
11+
k_period: int = 14,
12+
k_slowing: int = 3,
13+
d_period: int = 3,
14+
result_column: Optional[str] = None
15+
) -> Union[pd.DataFrame, pl.DataFrame]:
16+
"""
17+
Calculate the Stochastic Oscillator (%K and %D) for a given DataFrame.
18+
The Stochastic Oscillator is a momentum indicator comparing
19+
a particular closing price of a security to a range of its
20+
prices over a certain period.
21+
22+
Args:
23+
data (Union[pd.DataFrame, pl.DataFrame]): Input DataFrame
24+
containing the high, low, and close prices.
25+
high_column (str): Name of the column containing high prices.
26+
low_column (str): Name of the column containing low prices.
27+
close_column (str): Name of the column containing close prices.
28+
k_period (int): The period for %K calculation.
29+
d_period (int): The period for %D calculation.
30+
k_slowing (int): The period for smoothing the %K line.
31+
result_column (Optional[str]): Optional prefix for result
32+
columns. If None, defaults to "%K" and "%D".
33+
34+
Returns:
35+
Union[pd.DataFrame, pl.DataFrame]: DataFrame
36+
with %K and %D columns.
37+
"""
38+
39+
k_col = f"{result_column}_%K" if result_column else "%K"
40+
d_col = f"{result_column}_%D" if result_column else "%D"
41+
42+
if isinstance(data, pd.DataFrame):
43+
# Fast %K
44+
low_min = data[low_column].rolling(
45+
window=k_period, min_periods=k_period
46+
).min()
47+
high_max = data[high_column].rolling(
48+
window=k_period, min_periods=k_period
49+
).max()
50+
51+
fast_k = 100 * (data[close_column] - low_min) / (high_max - low_min)
52+
53+
# Slow %K (smoothed Fast %K)
54+
slow_k = fast_k.rolling(window=k_slowing, min_periods=k_slowing).mean()
55+
56+
# %D (smoothed Slow %K)
57+
d = slow_k.rolling(window=d_period, min_periods=d_period).mean()
58+
59+
data[k_col] = slow_k
60+
data[d_col] = d
61+
62+
return data
63+
64+
elif isinstance(data, pl.DataFrame):
65+
# Compute Fast %K
66+
low_min = pl.col(low_column).rolling_min(k_period)
67+
high_max = pl.col(high_column).rolling_max(k_period)
68+
69+
fast_k_expr = ((pl.col(close_column) - low_min) /
70+
(high_max - low_min)) * 100
71+
72+
# Compute Slow %K and %D in steps
73+
df = data.with_columns([
74+
fast_k_expr.alias("_fast_k")
75+
])
76+
77+
df = df.with_columns([
78+
pl.col("_fast_k").rolling_mean(k_slowing).alias(k_col)
79+
])
80+
81+
df = df.with_columns([
82+
pl.col(k_col).rolling_mean(d_period).alias(d_col)
83+
])
84+
85+
return df.drop("_fast_k")
86+
87+
else:
88+
raise TypeError("Input data must be a pandas or polars DataFrame.")

static/images/indicators/sto.png

119 KB
Loading

0 commit comments

Comments
 (0)