[Stock Analysis with Python] Detecting Moving Averages and Golden Cross Using Modern Libraries
Table of Contents
Overview
Using modern Python libraries like Polars, Plotly, marimo, and kand, we’ll detect moving averages and golden cross patterns.
Motivation
I receive part of my salary in the form of RSU (Restricted Stock Units). RSUs are attractive as an income source when the stock is performing well, but it’s like carrying all your eggs in one basket—if the company’s performance declines, losses can be significant. Therefore, it’s important to diversify your eggs into multiple baskets. Holding only cash or only one stock as RSU is not ideal; proper diversification is essential for risk management. While it’s easy to quickly diversify into index funds (investment trusts) or gold products, individual stocks can be enjoyable for actively taking risks or enjoying dividends and shareholder benefits (after all, RSUs are essentially individual stocks). So, while keeping in mind the need to deepen my understanding of the RSUs I own, I’ll be analyzing individual stocks.
Python has mature and abundant libraries like Pandas, Ta-Lib-Python, Matplotlib, yfinance, and Jupyter, which are very convenient due to their ease of integration. However, since the 2020s, many modern libraries have emerged that offer additional benefits like addressing pain points and speed advantages that traditional libraries don’t have. While older libraries are mature with abundant information, I want to enjoy analysis while learning new libraries simultaneously. Although these methods can also be used for systematic trading, I prefer technical analysis to identify trading candidates and timing while ensuring humans can easily manage and understand the situation, and also verifying there are no fundamental issues.
Libraries Used
The libraries are as follows:
| Library | Related Library | Description |
|---|---|---|
| polars | pandas | A library for high-speed data frame operations |
| plotly | matplotlib | A visualization library that allows users to interact with graphs |
| marimo | jupyter | A notebook that runs in .py format and allows interactive code execution |
| kand | ta-lib-python | A Rust-based technical analysis library that can be used in Python and Web through PYO3 and Wasm |
| yfinance-pl | yfinance | A Python library wrapping yfinance-rs that allows handling Yahoo Finance data with Polars |
Notebook and Code
The HTML version of the marimo notebook is available here. Here’s also a link to the Python code on GitHub.
Explanation
Fetching Stock Price Data
Stock price data is fetched using yfinance-pl. By appending .T to the stock code, you can retrieve data in Polars DataFrame format from yfinance-pl. For example, Toyota is 7203.T, SoftBank is 9984.T, etc. I hold shares of San-in Godo Bank (Gogin) 8381.T, a regional bank that I believe has stable management, for dividends and shareholder benefits, so I’ll use 8381.T as an example.
import yfinance_pl as yf
ticker = yf.Ticker("8381.T")
hist = ticker.history(period="1y")For the period parameter, enter a duration like 1y or 1m. Since yfinance_pl specifies Literals, you can use code completion in Visual Studio Code or PyCharm. Here’s the hist data in TSV format (showing the latest 5 entries):
open.amount open.currency high.amount high.currency low.amount low.currency close.amount close.currency close_unadj.amount close_unadj.currency volume date
Decimal('1374.0000000000') JPY Decimal('1404.0000000000') JPY Decimal('1372.0000000000') JPY Decimal('1400.0000000000') JPY Decimal('1400.0000000000') JPY 400000 2025-11-21 00:00:00
Decimal('1405.0000000000') JPY Decimal('1413.0000000000') JPY Decimal('1384.0000000000') JPY Decimal('1393.0000000000') JPY Decimal('1393.0000000000') JPY 317800 2025-11-25 00:00:00
Decimal('1409.0000000000') JPY Decimal('1412.0000000000') JPY Decimal('1399.0000000000') JPY Decimal('1407.0000000000') JPY Decimal('1407.0000000000') JPY 387300 2025-11-26 00:00:00
Decimal('1420.0000000000') JPY Decimal('1454.0000000000') JPY Decimal('1420.0000000000') JPY Decimal('1442.0000000000') JPY Decimal('1442.0000000000') JPY 547300 2025-11-27 00:00:00
Decimal('1450.0000000000') JPY Decimal('1469.0000000000') JPY Decimal('1449.0000000000') JPY Decimal('1453.0000000000') JPY Decimal('1453.0000000000') JPY 428800 2025-11-28 00:00:00Calculating Moving Averages
Moving averages are calculated by sliding N days of data including the current day for each date. For short-term averages, use the 5-day moving average, and for monthly trends, use the 25-day moving average. While stock prices fluctuate daily, averaging helps understand trends while reducing daily variance. For this calculation, I use the Rust-based kand instead of Ta-Lib-Python, but there’s almost no difference and it’s easy to calculate.
import kand as ka
import polars as pl
import yfinance_pl as yf
ticker = yf.Ticker("8381.T")
hist = ticker.history(period="1y")
close = hist["close.amount"].to_numpy().astype("float64")
hist_with_ma = hist.with_columns(
ma5=pl.Series(ka.sma(close, period=5)),
ma25=pl.Series(ka.sma(close, period=25)),
)SMA stands for Simple Moving Average, and by changing the period value, you can calculate trends for longer-term investments such as 90 days or 200 days.
Here are the first 30 rows of hist_with_ma in TSV format:
open.amount open.currency high.amount high.currency low.amount low.currency close.amount close.currency close_unadj.amount close_unadj.currency volume date ma5 ma25
Decimal('1147.0000000000') JPY Decimal('1155.0000000000') JPY Decimal('1142.0000000000') JPY Decimal('1151.0000000000') JPY Decimal('1195.0000000000') JPY 212000 2024-11-28 00:00:00 NaN NaN
Decimal('1151.0000000000') JPY Decimal('1169.0000000000') JPY Decimal('1145.0000000000') JPY Decimal('1163.0000000000') JPY Decimal('1207.0000000000') JPY 222500 2024-11-29 00:00:00 NaN NaN
Decimal('1173.0000000000') JPY Decimal('1213.0000000000') JPY Decimal('1169.0000000000') JPY Decimal('1211.0000000000') JPY Decimal('1257.0000000000') JPY 493800 2024-12-02 00:00:00 NaN NaN
Decimal('1214.0000000000') JPY Decimal('1235.0000000000') JPY Decimal('1208.0000000000') JPY Decimal('1226.0000000000') JPY Decimal('1272.0000000000') JPY 440100 2024-12-03 00:00:00 NaN NaN
Decimal('1220.0000000000') JPY Decimal('1226.0000000000') JPY Decimal('1190.0000000000') JPY Decimal('1190.0000000000') JPY Decimal('1235.0000000000') JPY 279400 2024-12-04 00:00:00 1188.2 NaN
Decimal('1200.0000000000') JPY Decimal('1213.0000000000') JPY Decimal('1198.0000000000') JPY Decimal('1209.0000000000') JPY Decimal('1255.0000000000') JPY 241000 2024-12-05 00:00:00 1199.8 NaN
Decimal('1210.0000000000') JPY Decimal('1216.0000000000') JPY Decimal('1198.0000000000') JPY Decimal('1202.0000000000') JPY Decimal('1248.0000000000') JPY 130400 2024-12-06 00:00:00 1207.6 NaN
Decimal('1207.0000000000') JPY Decimal('1225.0000000000') JPY Decimal('1196.0000000000') JPY Decimal('1216.0000000000') JPY Decimal('1262.0000000000') JPY 309500 2024-12-09 00:00:00 1208.6 NaN
Decimal('1232.0000000000') JPY Decimal('1232.0000000000') JPY Decimal('1209.0000000000') JPY Decimal('1215.0000000000') JPY Decimal('1261.0000000000') JPY 300100 2024-12-10 00:00:00 1206.4 NaN
Decimal('1216.0000000000') JPY Decimal('1226.0000000000') JPY Decimal('1204.0000000000') JPY Decimal('1226.0000000000') JPY Decimal('1272.0000000000') JPY 256900 2024-12-11 00:00:00 1213.6 NaN
Decimal('1238.0000000000') JPY Decimal('1242.0000000000') JPY Decimal('1230.0000000000') JPY Decimal('1233.0000000000') JPY Decimal('1280.0000000000') JPY 307600 2024-12-12 00:00:00 1218.4 NaN
Decimal('1223.0000000000') JPY Decimal('1232.0000000000') JPY Decimal('1210.0000000000') JPY Decimal('1216.0000000000') JPY Decimal('1262.0000000000') JPY 348500 2024-12-13 00:00:00 1221.2 NaN
Decimal('1218.0000000000') JPY Decimal('1221.0000000000') JPY Decimal('1194.0000000000') JPY Decimal('1201.0000000000') JPY Decimal('1247.0000000000') JPY 252900 2024-12-16 00:00:00 1218.2 NaN
Decimal('1205.0000000000') JPY Decimal('1215.0000000000') JPY Decimal('1193.0000000000') JPY Decimal('1195.0000000000') JPY Decimal('1240.0000000000') JPY 215500 2024-12-17 00:00:00 1214.2 NaN
Decimal('1193.0000000000') JPY Decimal('1195.0000000000') JPY Decimal('1182.0000000000') JPY Decimal('1193.0000000000') JPY Decimal('1238.0000000000') JPY 189600 2024-12-18 00:00:00 1207.6 NaN
Decimal('1175.0000000000') JPY Decimal('1195.0000000000') JPY Decimal('1169.0000000000') JPY Decimal('1190.0000000000') JPY Decimal('1235.0000000000') JPY 363000 2024-12-19 00:00:00 1199 NaN
Decimal('1197.0000000000') JPY Decimal('1197.0000000000') JPY Decimal('1168.0000000000') JPY Decimal('1168.0000000000') JPY Decimal('1212.0000000000') JPY 511500 2024-12-20 00:00:00 1189.4 NaN
Decimal('1171.0000000000') JPY Decimal('1204.0000000000') JPY Decimal('1171.0000000000') JPY Decimal('1204.0000000000') JPY Decimal('1250.0000000000') JPY 403500 2024-12-23 00:00:00 1190 NaN
Decimal('1216.0000000000') JPY Decimal('1226.0000000000') JPY Decimal('1209.0000000000') JPY Decimal('1222.0000000000') JPY Decimal('1268.0000000000') JPY 320300 2024-12-24 00:00:00 1195.4 NaN
Decimal('1217.0000000000') JPY Decimal('1217.0000000000') JPY Decimal('1194.0000000000') JPY Decimal('1205.0000000000') JPY Decimal('1251.0000000000') JPY 207300 2024-12-25 00:00:00 1197.8 NaN
Decimal('1204.0000000000') JPY Decimal('1208.0000000000') JPY Decimal('1193.0000000000') JPY Decimal('1208.0000000000') JPY Decimal('1254.0000000000') JPY 230600 2024-12-26 00:00:00 1201.4 NaN
Decimal('1209.0000000000') JPY Decimal('1220.0000000000') JPY Decimal('1205.0000000000') JPY Decimal('1220.0000000000') JPY Decimal('1266.0000000000') JPY 252400 2024-12-27 00:00:00 1211.8 NaN
Decimal('1224.0000000000') JPY Decimal('1230.0000000000') JPY Decimal('1214.0000000000') JPY Decimal('1219.0000000000') JPY Decimal('1265.0000000000') JPY 159400 2024-12-30 00:00:00 1214.8 NaN
Decimal('1228.0000000000') JPY Decimal('1228.0000000000') JPY Decimal('1211.0000000000') JPY Decimal('1225.0000000000') JPY Decimal('1271.0000000000') JPY 443500 2025-01-06 00:00:00 1215.4 NaN
Decimal('1231.0000000000') JPY Decimal('1232.0000000000') JPY Decimal('1217.0000000000') JPY Decimal('1222.0000000000') JPY Decimal('1268.0000000000') JPY 403500 2025-01-07 00:00:00 1218.8 1205.2
Decimal('1216.0000000000') JPY Decimal('1244.0000000000') JPY Decimal('1215.0000000000') JPY Decimal('1237.0000000000') JPY Decimal('1284.0000000000') JPY 469100 2025-01-08 00:00:00 1224.6 1208.64
Decimal('1232.0000000000') JPY Decimal('1232.0000000000') JPY Decimal('1207.0000000000') JPY Decimal('1207.0000000000') JPY Decimal('1253.0000000000') JPY 421100 2025-01-09 00:00:00 1222 1210.4
Decimal('1204.0000000000') JPY Decimal('1207.0000000000') JPY Decimal('1190.0000000000') JPY Decimal('1196.0000000000') JPY Decimal('1241.0000000000') JPY 328400 2025-01-10 00:00:00 1217.4 1209.8
Decimal('1190.0000000000') JPY Decimal('1194.0000000000') JPY Decimal('1164.0000000000') JPY Decimal('1173.0000000000') JPY Decimal('1217.0000000000') JPY 394900 2025-01-14 00:00:00 1207 1207.68
Decimal('1182.0000000000') JPY Decimal('1207.0000000000') JPY Decimal('1178.0000000000') JPY Decimal('1207.0000000000') JPY Decimal('1253.0000000000') JPY 362200 2025-01-15 00:00:00 1204 1208.36The 5-day moving average cannot be calculated without 5 days of data including the current day, so the first 4 rows are NaN. Similarly, the 25-day moving average requires past data, so 24 rows are NaN.
Chart Visualization
Using Plotly’s Candlestick, you can easily display charts. Candlesticks are represented by rectangles (body) and lines (wicks), showing upper wick, lower wick, open, and close prices. They are colored in red and green based on the relationship between open and close prices, showing price increases/decreases and daily price volatility. You can easily display the chart by passing these 4 pieces of information along with the color and date data on the horizontal axis.
# omitted
df_plot = hist_with_ma.with_columns(
[
pl.col("open.amount").cast(pl.Float64),
pl.col("high.amount").cast(pl.Float64),
pl.col("low.amount").cast(pl.Float64),
pl.col("close.amount").cast(pl.Float64),
]
)
dates = df_plot["date"].to_list()
ma_data = [
go.Candlestick(
yaxis="y1",
x=dates,
open=df_plot["open.amount"],
high=df_plot["high.amount"],
low=df_plot["low.amount"],
close=df_plot["close.amount"],
increasing_line_color="red",
decreasing_line_color="green",
name=f"{company_name} Stock Price",
),
# omitted
]
ma_fig = go.Figure(data=ma_data, layout=go.Layout(ma_layout))
# omitted
ma_fig # displays the chart in the notebookGolden Cross and Dead Cross
When long-term and short-term moving averages intersect, it serves as an indicator of trend reversal (or trading signal). When the short-term moving average crosses above the long-term moving average, it’s called a “Golden Cross”; when it crosses below, it’s called a “Dead Cross”.
These can be detected by calculating the difference between short-term and long-term moving averages and checking whether the sign of that difference changes between the previous day and the current day.
(1) If the short-term moving average was below the long-term moving average yesterday, and today the short-term moving average crosses above the long-term moving average, it’s a Golden Cross (2) If the short-term moving average was above the long-term moving average yesterday, and today the short-term moving average crosses below the long-term moving average, it’s a Dead Cross
# Calculate the difference between SMA5 and SMA25 for previous and current day
diff = SMA5 - SMA25
prev_diff = diff.shift(1)
# Golden Cross: previous day SMA5 < SMA25, current day SMA5 > SMA25
golden_cross = (prev_diff < 0) & (diff > 0)
# Dead Cross: previous day SMA5 > SMA25, current day SMA5 < SMA25
dead_cross = (prev_diff > 0) & (diff < 0)When golden_cross is True, it indicates a Golden Cross; when dead_cross is True, it indicates a Dead Cross.
You can also display Golden Cross and Dead Cross on Plotly.
# Calculate the difference between SMA5 and SMA25
df_cross = (
hist_with_ma.with_columns(
diff=(pl.col("ma5") - pl.col("ma25")),
)
.with_columns(
prev_diff=pl.col("diff").shift(1),
)
.with_columns(
# Golden Cross: previous day is negative (SMA5 < SMA25), current day is positive (SMA5 > SMA25)
golden_cross=(pl.col("prev_diff") < 0) & (pl.col("diff") > 0),
# Dead Cross: previous day is positive (SMA5 > SMA25), current day is negative (SMA5 < SMA25)
dead_cross=(pl.col("prev_diff") > 0) & (pl.col("diff") < 0),
)
)
# Extract Golden Cross dates
golden_crosses = df_cross.filter(pl.col("golden_cross")).select(
pl.col("date"),
pl.col("close.amount").alias("price"),
pl.lit("Golden Cross").alias("signal"),
)
# Extract Dead Cross dates
dead_crosses = df_cross.filter(pl.col("dead_cross")).select(
pl.col("date"),
pl.col("close.amount").alias("price"),
pl.lit("Dead Cross").alias("signal"),
)
# Combine and sort Golden Cross and Dead Cross
signals = pl.concat([golden_crosses, dead_crosses]).sort("date")
# Separate Golden Cross and Dead Cross data
_golden = signals.filter(pl.col("signal") == "Golden Cross")
_dead = signals.filter(pl.col("signal") == "Dead Cross")
signal_data = [
go.Candlestick(
yaxis="y1",
x=_dates,
open=_df_plot["open.amount"],
high=_df_plot["high.amount"],
low=_df_plot["low.amount"],
close=_df_plot["close.amount"],
increasing_line_color="red",
decreasing_line_color="green",
name=f"{_company_name} Stock Price",
),
# omitted
# Golden Cross markers
go.Scatter(
yaxis="y1",
x=_golden["date"].to_list(),
y=_golden["price"].cast(pl.Float64).to_list(),
mode="markers",
name="Golden Cross",
marker={
"symbol": "triangle-up",
"size": 15,
"color": "lime",
"line": {"color": "darkgreen", "width": 2},
},
),
# Dead Cross markers
go.Scatter(
yaxis="y1",
x=_dead["date"].to_list(),
y=_dead["price"].cast(pl.Float64).to_list(),
mode="markers",
name="Dead Cross",
marker={
"symbol": "triangle-down",
"size": 15,
"color": "red",
"line": {"color": "darkred", "width": 2},
},
),
]Summary
In this article, I’ve introduced how to fetch stock prices, display them in candlestick charts, calculate moving averages, and detect Golden Cross and Dead Cross patterns. While using Pandas has been convenient for a long time, as you can see from the code, you can perform everything from fetching stock prices to visualization using only Polars without going through Pandas. While Golden Cross and Dead Cross alone may not be sufficient, we’ve managed to create basic and understandable indicators, taking the first step in analysis. I’ll continue to perform other indicators and more advanced analysis through modern libraries.
Reference book: Practical Stock Investment Analysis with Python (Hikaru Katafuchi (Author), Yoshihiro Yamada (Supervisor))
![[Stock Analysis with Python] Weekly and Monthly Charts with Bollinger Bands to Understand Price Volatility](https://b.rmc-8.com/img/2025/12/01/0218c7e535f81400a91a9ad80357a034.png)
![[Python Stock Analysis] Calculating and Visualizing Ichimoku Cloud with Polars](https://b.rmc-8.com/img/2025/12/02/00541cf97f979f588ad2cad0ac6d6d7f.png)


![[Python] Automatically Record Sleep Time to Toggl](https://pub-21c8df4785a6478092d6eb23a55a5c42.r2.dev/img/eyecatch/garmin_toggl.webp)
![[Python] Summarizing and Translating Foreign Tech Articles Using Generative AI](https://pub-21c8df4785a6478092d6eb23a55a5c42.r2.dev/img/eyecatch/langchain_eyecatch.webp)
Loading comments...