Signals·Technical Analysis·Beginner
Technical Analysis with pandas-ta
Use the pandas-ta library for fast, vectorised indicator computation — covers setup, strategy objects, and bulk indicator runs.
pandas-taindicatorsvectorized
Technical Indicators Framework — pandas-ta
This notebook defines a standardized protocol for computing technical
indicators using the pandas-ta library on OHLCV data. It covers
trend, momentum, volatility, and volume indicators on a representative synthetic dataset.
1. Dependency Installation
[8]
import sys
!{sys.executable} -m pip install pandas-taRequirement already satisfied: pandas-ta in /usr/local/lib/python3.12/dist-packages (0.4.71b0) Requirement already satisfied: numba==0.61.2 in /usr/local/lib/python3.12/dist-packages (from pandas-ta) (0.61.2) Requirement already satisfied: numpy>=2.2.6 in /usr/local/lib/python3.12/dist-packages (from pandas-ta) (2.2.6) Requirement already satisfied: pandas>=2.3.2 in /usr/local/lib/python3.12/dist-packages (from pandas-ta) (3.0.2) Requirement already satisfied: tqdm>=4.67.1 in /usr/local/lib/python3.12/dist-packages (from pandas-ta) (4.67.3) Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /usr/local/lib/python3.12/dist-packages (from numba==0.61.2->pandas-ta) (0.44.0) Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas>=2.3.2->pandas-ta) (2.9.0.post0) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas>=2.3.2->pandas-ta) (1.17.0)
2. Library Imports
[9]
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import pandas_ta as ta
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots3. What Is pandas-ta?
pandas-ta is a Python library that adds over 130 technical indicators
directly to a pandas DataFrame. Instead of writing indicator formulas
manually, a single method call computes and appends the result as a
new column. It is built on top of pandas and numpy, requires no
compiled dependencies, and works on any OHLCV DataFrame.
[10]
def generate_larger_data(periods: int) -> pd.DataFrame:
"""Generates a larger synthetic OHLCV dataset with more realistic price fluctuations."""
start_date = pd.to_datetime("2024-01-01 00:00:00+00:00")
datetime_index = pd.date_range(start_date, periods=periods, freq="1min", tz="UTC")
price_data = []
last_close = 42000 # Starting price
volatility_scale = 0.005 # Controls the general magnitude of price changes
wick_deviation_scale = 0.002 # Controls how much wicks extend beyond body
for i in range(periods):
# Open price drifts slightly from the previous close
open_price = last_close + np.random.normal(0, last_close * volatility_scale * 0.1)
# Simulate a price change to determine the closing price
price_change = np.random.normal(0, last_close * volatility_scale)
close_price = open_price + price_change
# Determine the high and low of the candle body
body_high = max(open_price, close_price)
body_low = min(open_price, close_price)
# Simulate wicks extending beyond the body
# High wick should be above the body_high
high_wick_extension = np.abs(np.random.normal(0, last_close * wick_deviation_scale))
high_price = body_high + high_wick_extension
# Low wick should be below the body_low
low_wick_extension = np.abs(np.random.normal(0, last_close * wick_deviation_scale))
low_price = body_low - low_wick_extension
# Ensure OHLC integrity: High must be the absolute highest, Low the absolute lowest
high_price = max(high_price, open_price, close_price)
low_price = min(low_price, open_price, close_price)
# Ensure High is never less than Low
if high_price < low_price:
high_price, low_price = low_price, high_price # Swap if somehow invalid
# Ensure all values are positive integers
open_price = max(1, int(open_price))
high_price = max(1, int(high_price))
low_price = max(1, int(low_price))
close_price = max(1, int(close_price))
price_data.append({
"open": open_price,
"high": high_price,
"low": low_price,
"close": close_price
})
last_close = close_price # Update last_close for the next iteration
df_large = pd.DataFrame(price_data, index=datetime_index)
df_large.index.name = "datetime"
# Simulate volume with some fluctuation
df_large["volume"] = np.random.uniform(100.0, 500.0, periods)
return df_large4. Initial Sample Dataset
[11]
df = generate_larger_data(periods=30)
print("--- Raw OHLCV Data ---")
display(df.head())--- Raw OHLCV Data ---
| open | high | low | close | volume | |
|---|---|---|---|---|---|
| datetime | |||||
| 2024-01-01 00:00:00+00:00 | 42000 | 42003 | 41856 | 41960 | 166.144497 |
| 2024-01-01 00:01:00+00:00 | 41955 | 41966 | 41795 | 41865 | 336.282519 |
| 2024-01-01 00:02:00+00:00 | 41882 | 41971 | 41739 | 41814 | 314.321868 |
| 2024-01-01 00:03:00+00:00 | 41817 | 41988 | 41754 | 41958 | 127.443400 |
| 2024-01-01 00:04:00+00:00 | 41924 | 41970 | 41566 | 41593 | 186.226600 |
[12]
def compute_indicators_pandas_ta(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df.ta.sma(length=10, append=True) # Simple Moving Average
df.ta.ema(length=10, append=True) # Exponential Moving Average
df.ta.rsi(length=14, append=True) # Relative Strength Index
df.ta.macd(append=True) # MACD, Signal, Histogram
df.ta.bbands(length=10, append=True) # Bollinger Bands
df.ta.atr(length=10, append=True) # Average True Range
df.ta.obv(append=True) # On-Balance Volume
df.ta.stoch(append=True) # Stochastic Oscillator
return df
df_indicators = compute_indicators_pandas_ta(df)
print("--- Indicators Output ---")
display(df_indicators.tail())
df_indicators.info()--- Indicators Output ---
| open | high | low | close | volume | SMA_10 | EMA_10 | RSI_14 | BBL_10_2.0_2.0 | BBM_10_2.0_2.0 | BBU_10_2.0_2.0 | BBB_10_2.0_2.0 | BBP_10_2.0_2.0 | ATRr_10 | OBV | STOCHk_14_3_3 | STOCHd_14_3_3 | STOCHh_14_3_3 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| datetime | ||||||||||||||||||
| 2024-01-01 00:25:00+00:00 | 40415 | 40645 | 40367 | 40610 | 162.829932 | 41124.3 | 40994.630431 | 29.533279 | 40249.671419 | 41124.3 | 41998.928581 | 4.253585 | 0.205989 | 290.722800 | -2204.311787 | 6.327272 | 12.496478 | -6.169206 |
| 2024-01-01 00:26:00+00:00 | 40623 | 40668 | 40157 | 40276 | 312.512870 | 41008.1 | 40863.970352 | 25.663124 | 40017.617536 | 41008.1 | 41998.582464 | 4.830667 | 0.130433 | 312.750520 | -2516.824658 | 7.817989 | 7.463132 | 0.354857 |
| 2024-01-01 00:27:00+00:00 | 40279 | 40486 | 40231 | 40453 | 115.295272 | 40902.6 | 40789.248470 | 30.835737 | 39924.095835 | 40902.6 | 41881.104165 | 4.784557 | 0.270262 | 306.975468 | -2401.529386 | 13.539840 | 9.228367 | 4.311473 |
| 2024-01-01 00:28:00+00:00 | 40474 | 40636 | 40433 | 40494 | 165.690232 | 40812.4 | 40735.566930 | 32.015810 | 39870.426091 | 40812.4 | 41754.373909 | 4.616116 | 0.330993 | 296.577921 | -2235.839154 | 14.710485 | 12.022771 | 2.687714 |
| 2024-01-01 00:29:00+00:00 | 40517 | 40525 | 40229 | 40338 | 120.323779 | 40712.9 | 40663.282034 | 29.923775 | 39805.770976 | 40712.9 | 41620.029024 | 4.456224 | 0.293359 | 296.520129 | -2356.162933 | 15.923318 | 14.724548 | 1.198770 |
<class 'pandas.DataFrame'> DatetimeIndex: 30 entries, 2024-01-01 00:00:00+00:00 to 2024-01-01 00:29:00+00:00 Freq: min Data columns (total 18 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 open 30 non-null int64 1 high 30 non-null int64 2 low 30 non-null int64 3 close 30 non-null int64 4 volume 30 non-null float64 5 SMA_10 21 non-null float64 6 EMA_10 21 non-null float64 7 RSI_14 29 non-null float64 8 BBL_10_2.0_2.0 21 non-null float64 9 BBM_10_2.0_2.0 21 non-null float64 10 BBU_10_2.0_2.0 21 non-null float64 11 BBB_10_2.0_2.0 21 non-null float64 12 BBP_10_2.0_2.0 21 non-null float64 13 ATRr_10 21 non-null float64 14 OBV 29 non-null float64 15 STOCHk_14_3_3 15 non-null float64 16 STOCHd_14_3_3 13 non-null float64 17 STOCHh_14_3_3 13 non-null float64 dtypes: float64(14), int64(4) memory usage: 5.5 KB
Code Logic
df.ta.{indicator}(append=True): Each call computes the indicator and appends its output column(s) directly to the DataFrame. No manual column assignment is required.sma/ema: Trend-following averages. SMA weights all periods equally; EMA weights recent periods more heavily — it reacts faster to new price information.rsi: Measures how overbought or oversold a market is on a 0–100 scale. Above 70 typically signals overbought; below 30 signals oversold.macd: Computes the difference between a fast and slow EMA, a signal line (EMA of MACD), and a histogram (MACD minus signal). Used to identify momentum direction and potential reversals.bbands: Bollinger Bands — upper, middle, and lower bands based on rolling mean and standard deviation.atr: Average True Range — a measure of how much the price typically moves per candle.obv: On-Balance Volume — a running total of volume that adds volume on up days and subtracts on down days. Used to confirm price trends with volume.stoch: Stochastic Oscillator — measures where the close sits relative to the high-low range over a lookback window, expressed as a 0–100 value.
5.1 Larger Dataset Generation and Indicator Recomputation
[13]
# Generate a larger dataset (e.g., 200 data points)
df_large = generate_larger_data(200)
print("--- Larger Raw OHLCV Data ---")
display(df_large.head())
display(df_large.tail())
df_large.info()
# Recompute indicators on the larger dataset
df_indicators_large = compute_indicators_pandas_ta(df_large)
print("--- Indicators Output for Larger Data ---")
display(df_indicators_large.tail())
df_indicators_large.info()--- Larger Raw OHLCV Data ---
| open | high | low | close | volume | |
|---|---|---|---|---|---|
| datetime | |||||
| 2024-01-01 00:00:00+00:00 | 41964 | 42166 | 41937 | 42125 | 371.943585 |
| 2024-01-01 00:01:00+00:00 | 42090 | 42112 | 41902 | 41919 | 468.555769 |
| 2024-01-01 00:02:00+00:00 | 41908 | 42043 | 41817 | 41969 | 460.370758 |
| 2024-01-01 00:03:00+00:00 | 42019 | 42250 | 41977 | 42183 | 451.094123 |
| 2024-01-01 00:04:00+00:00 | 42172 | 42568 | 42151 | 42381 | 272.489297 |
| open | high | low | close | volume | |
|---|---|---|---|---|---|
| datetime | |||||
| 2024-01-01 03:15:00+00:00 | 42053 | 42172 | 41997 | 42156 | 348.269917 |
| 2024-01-01 03:16:00+00:00 | 42160 | 42272 | 42104 | 42248 | 385.621538 |
| 2024-01-01 03:17:00+00:00 | 42214 | 42249 | 42181 | 42244 | 491.590707 |
| 2024-01-01 03:18:00+00:00 | 42231 | 42363 | 42175 | 42270 | 153.290961 |
| 2024-01-01 03:19:00+00:00 | 42313 | 42609 | 42180 | 42289 | 238.091390 |
<class 'pandas.DataFrame'> DatetimeIndex: 200 entries, 2024-01-01 00:00:00+00:00 to 2024-01-01 03:19:00+00:00 Freq: min Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 open 200 non-null int64 1 high 200 non-null int64 2 low 200 non-null int64 3 close 200 non-null int64 4 volume 200 non-null float64 dtypes: float64(1), int64(4) memory usage: 9.4 KB --- Indicators Output for Larger Data ---
| open | high | low | close | volume | SMA_10 | EMA_10 | RSI_14 | MACD_12_26_9 | MACDh_12_26_9 | ... | BBL_10_2.0_2.0 | BBM_10_2.0_2.0 | BBU_10_2.0_2.0 | BBB_10_2.0_2.0 | BBP_10_2.0_2.0 | ATRr_10 | OBV | STOCHk_14_3_3 | STOCHd_14_3_3 | STOCHh_14_3_3 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| datetime | |||||||||||||||||||||
| 2024-01-01 03:15:00+00:00 | 42053 | 42172 | 41997 | 42156 | 348.269917 | 41744.9 | 41776.105629 | 77.660185 | 379.484120 | 55.008683 | ... | 41230.181997 | 41744.9 | 42259.618003 | 2.466016 | 0.899345 | 260.241942 | 6465.365068 | 95.411247 | 90.537876 | 4.873371 |
| 2024-01-01 03:16:00+00:00 | 42160 | 42272 | 42104 | 42248 | 385.621538 | 41831.7 | 41861.904606 | 78.922298 | 394.607246 | 56.105447 | ... | 41298.067542 | 41831.7 | 42365.332458 | 2.551330 | 0.890062 | 251.017748 | 6850.986606 | 97.147110 | 94.116714 | 3.030397 |
| 2024-01-01 03:17:00+00:00 | 42214 | 42249 | 42181 | 42244 | 491.590707 | 41903.8 | 41931.376496 | 78.714075 | 401.639812 | 50.510410 | ... | 41360.792081 | 41903.8 | 42446.807919 | 2.591688 | 0.813255 | 232.715973 | 6359.395899 | 97.827522 | 96.795293 | 1.032229 |
| 2024-01-01 03:18:00+00:00 | 42231 | 42363 | 42175 | 42270 | 153.290961 | 41975.6 | 41992.944406 | 79.100061 | 404.646639 | 42.813790 | ... | 41449.728702 | 41975.6 | 42501.471298 | 2.505605 | 0.779916 | 228.244376 | 6512.686860 | 95.548652 | 96.841095 | -1.292443 |
| 2024-01-01 03:19:00+00:00 | 42313 | 42609 | 42180 | 42289 | 238.091390 | 42049.1 | 42046.772696 | 79.394120 | 403.906727 | 33.659103 | ... | 41583.068575 | 42049.1 | 42515.131425 | 2.216606 | 0.757386 | 248.319938 | 6750.778250 | 88.103241 | 93.826472 | -5.723231 |
5 rows × 21 columns
<class 'pandas.DataFrame'> DatetimeIndex: 200 entries, 2024-01-01 00:00:00+00:00 to 2024-01-01 03:19:00+00:00 Freq: min Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 open 200 non-null int64 1 high 200 non-null int64 2 low 200 non-null int64 3 close 200 non-null int64 4 volume 200 non-null float64 5 SMA_10 191 non-null float64 6 EMA_10 191 non-null float64 7 RSI_14 199 non-null float64 8 MACD_12_26_9 175 non-null float64 9 MACDh_12_26_9 167 non-null float64 10 MACDs_12_26_9 167 non-null float64 11 BBL_10_2.0_2.0 191 non-null float64 12 BBM_10_2.0_2.0 191 non-null float64 13 BBU_10_2.0_2.0 191 non-null float64 14 BBB_10_2.0_2.0 191 non-null float64 15 BBP_10_2.0_2.0 191 non-null float64 16 ATRr_10 191 non-null float64 17 OBV 199 non-null float64 18 STOCHk_14_3_3 185 non-null float64 19 STOCHd_14_3_3 183 non-null float64 20 STOCHh_14_3_3 183 non-null float64 dtypes: float64(17), int64(4) memory usage: 42.5 KB
6. Data Visualization with Plotly
6.1 Candlestick Chart with Moving Averages (SMA, EMA)
[14]
fig = go.FigureWidget(data=[
go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
)
])
# Add SMA_10
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['SMA_10'],
mode='lines',
name='SMA 10',
line=dict(color='blue', width=1)
))
# Add EMA_10
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['EMA_10'],
mode='lines',
name='EMA 10',
line=dict(color='orange', width=1)
))
fig.update_layout(
title_text='Candlestick Chart with SMA and EMA',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=600,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles
)
fig.show()6.2 Candlestick Chart with Bollinger Bands
[15]
fig = go.FigureWidget(data=[
go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
)
])
# Add Bollinger Bands
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['BBL_10_2.0_2.0'],
mode='lines',
name='BB Lower',
line=dict(color='red', width=1)
))
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['BBM_10_2.0_2.0'],
mode='lines',
name='BB Middle',
line=dict(color='green', width=1, dash='dot')
))
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['BBU_10_2.0_2.0'],
mode='lines',
name='BB Upper',
line=dict(color='red', width=1)
))
fig.update_layout(
title_text='Candlestick Chart with Bollinger Bands',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=600,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles
)
fig.show()6.3 Relative Strength Index (RSI)
[16]
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3]
)
# Candlestick chart
fig.add_trace(go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
), row=1, col=1)
# RSI chart
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['RSI_14'],
mode='lines',
name='RSI 14',
line=dict(color='purple', width=1)
), row=2, col=1)
# Add RSI overbought/oversold levels
fig.add_hline(y=70, line_dash="dot", line_color="red", row=2, col=1)
fig.add_hline(y=30, line_dash="dot", line_color="green", row=2, col=1)
fig.update_layout(
title_text='Candlestick Chart with RSI',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=800,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)
fig.update_yaxes(title_text="RSI", row=2, col=1)
fig.show()6.4 Moving Average Convergence Divergence (MACD)
[17]
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3]
)
# Candlestick chart
fig.add_trace(go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
), row=1, col=1)
# MACD traces
# Note: pandas-ta names MACD as MACD_12_26_9, Signal as MACDs_12_26_9, Histogram as MACDh_12_26_9
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['MACD_12_26_9'],
mode='lines',
name='MACD',
line=dict(color='blue', width=1)
), row=2, col=1)
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['MACDs_12_26_9'],
mode='lines',
name='Signal',
line=dict(color='red', width=1)
), row=2, col=1)
fig.add_trace(go.Bar(
x=df_indicators_large.index,
y=df_indicators_large['MACDh_12_26_9'],
name='Histogram',
marker_color='green' # Green for positive, could add logic for red for negative
), row=2, col=1)
fig.update_layout(
title_text='Candlestick Chart with MACD',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=800,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)
fig.update_yaxes(title_text="MACD", row=2, col=1)
fig.show()6.5 Average True Range (ATR)
[18]
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3]
)
# Candlestick chart
fig.add_trace(go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
), row=1, col=1)
# ATR chart
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['ATRr_10'],
mode='lines',
name='ATR 10',
line=dict(color='darkorange', width=1)
), row=2, col=1)
fig.update_layout(
title_text='Candlestick Chart with ATR',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=800,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)
fig.update_yaxes(title_text="ATR", row=2, col=1)
fig.show()6.6 On-Balance Volume (OBV)
[19]
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3]
)
# Candlestick chart
fig.add_trace(go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
), row=1, col=1)
# OBV chart
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['OBV'],
mode='lines',
name='OBV',
line=dict(color='brown', width=1)
), row=2, col=1)
fig.update_layout(
title_text='Candlestick Chart with OBV',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=800,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)
fig.update_yaxes(title_text="OBV", row=2, col=1)
fig.show()6.7 Stochastic Oscillator
[20]
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3]
)
# Candlestick chart
fig.add_trace(go.Candlestick(
x=df_indicators_large.index,
open=df_indicators_large['open'],
high=df_indicators_large['high'],
low=df_indicators_large['low'],
close=df_indicators_large['close'],
name='Price'
), row=1, col=1)
# Stochastic Oscillator chart
# Note: pandas-ta names Stochastic %K as STOCHk_14_3_3 and %D as STOCHd_14_3_3
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['STOCHk_14_3_3'],
mode='lines',
name='%K',
line=dict(color='blue', width=1)
), row=2, col=1)
fig.add_trace(go.Scatter(
x=df_indicators_large.index,
y=df_indicators_large['STOCHd_14_3_3'],
mode='lines',
name='%D',
line=dict(color='red', width=1)
), row=2, col=1)
# Add overbought/oversold levels for Stochastic
fig.add_hline(y=80, line_dash="dot", line_color="red", row=2, col=1)
fig.add_hline(y=20, line_dash="dot", line_color="green", row=2, col=1)
fig.update_layout(
title_text='Candlestick Chart with Stochastic Oscillator',
xaxis_rangeslider_visible=False,
xaxis_title='Date',
yaxis_title='Price',
height=800,
yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)
fig.update_yaxes(title_text="Stochastic", row=2, col=1)
fig.show()[20]