[Pythonによる株式分析]モダンなライブラリで移動平均線とゴールデンクロスを検出する
Table of Contents
概要
PolarsやPlotly、marimo、kandなどPythonのモダンなライブラリを使って、移動平均線とゴールデンクロスを検出します。
モチベーション
私は給料の一部をRSUの形式で受け取っています。RSUはその銘柄が好調な時は収入源として魅力的ですが卵を1つのカゴに盛って運搬しているのと同じで、その会社の調子が悪くなると損失が大きくなります。なので、卵を複数のカゴに分散することが大事です。現金だけ持つ、1つの銘柄だけでRSUを持つのも望ましくなくリスクに備えて適切に分散をすることが大事です。インデックスファンド(の投資信託)や純金商品などに手早く分散させるのが楽ではありますが、積極的にリスクをとりに行ったり配当金や株主優待を楽しむ上では個別銘柄も楽しいと思います(そもそもRSUも個別銘柄のようなものです)。なので、自身が所有するRSUに対する理解も深めることも念頭に置きつつ、個別銘柄の分析をしていきます。
PythonにはPandasやTa-Lib-Py、Matplotlib、yfinance、Jupyterなど成熟した豊富なライブラリがありそれぞれの連携のしやすさでも非常に便利です。ですが、2020年代に入ってモダンなライブラリが数々登場して、痒いところに手が届いたり速度のメリットがあったりなど従来のライブラリとは違う良さもあります。古いライブラリは成熟しており情報も豊富ですので、新しいライブラリを同時に学びながら分析を楽しんでいきたいと思っています。なお、システムトレードにも利用できる手法ですが、私は人間が分かりやすく管理と把握でき、ファンダメンタルズの問題がないことも確認したいので売買の候補とタイミングを知るためにテクニカル分析を試みます。
使用するライブラリ
ライブラリは以下の通りです。
| ライブラリ | 関連ライブラリ | 説明 |
|---|---|---|
| polars | pandas | データフレームを高速に扱えるライブラリ |
| plotly | matplotlib | 可視化ライブラリでユーザーがインタラクティブにグラフを操作できる |
| marimo | jupyter | py形式で動くノートブックでインタラクティブにコードを動かすことができる |
| kand | ta-lib-python | Rust製のテクニカル分析の計算ライブラリで、PYO3やWasmを通じてPythonやWebでも活用できる |
| yfinance-pl | yfinance | yfinance-rsをラップして作ったPython向けのライブラリでPolarsでYahoo Financeの情報を扱える |
ノートとコード
marimoのノートのHTML版はこちらにあります。GitHub上のPython形式のコードにもリンクします
解説
株価情報の取得
株価情報はyfinance-plを用いて取得します。 証券コードに.Tを付与することでyfinance-plからpolarsのDataFrame形式で取得できます。トヨタなら7203.T、ソフトバンクなら9984.Tなどです。私は配当や株主優待を狙って地方銀行ながら経営が安定していると思われる山陰合同銀行(ごうぎん)8381.Tを所持しているので8381.Tを例にします。
import yfinance_pl as yf
ticker = yf.Ticker("8381.T")
hist = ticker.history(period="1y")periodは1yや1mなど期間を入力します。yfinance_plではLiteralを指定しているのでVisual Studio CodeやPyCharmなどを利用していればコード補完で入力できます。histをTSV形式で表示すると下記の通りです(最新の5件です)。
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:00移動平均線の計算
移動平均線は当日を含むN日分のデータを日付ごとに移動させながら計算します。短期間の平均であれば5日移動平均線、1ヶ月のトレンドであれば25日移動平均線で計算します。日々、株価が動きますが平均にすることによって日々のばらつきを抑えつつトレンドを把握できます。この計算にはTa-Lib-PythonではなくRust製のkandを使いますが、差異はほとんどなく簡単に計算できます。
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がSimple Moving Average(単純移動平均線)の略であり、periodの数値を変えることで90日、200日などより長期の投資のためのトレンドを算出できます。
hist_with_maの先頭30行をTSV形式で表示すると以下の通りです。
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.365日移動平均線は当該日を含む過去5日分のデータがないと計算できませんので先頭4行はNaNです。25日移動平均線も同様に過去のデータが必要となるため24行がNaNです。
チャートの可視化
PlotlyのCandlestick(ローソク足)を利用すると簡単にチャートに表示できます。ローソク足は四角形(実体)と線(ヒゲ)で表現され、上ヒゲ・下ヒゲ・始値・終値で表示され、始値と終値の上下関係によって赤と緑などで表現され増減やその日の価格の揺れ幅がわかります。この4つの情報と色、横軸の日付データを渡すと簡単に表示できます。
# 略
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}の株価",
),
# 中略
]
ma_fig = go.Figure(data=ma_data, layout=go.Layout(ma_layout))
# 中略
ma_fig # ノート上で図表が表示されるゴールデンクロス・デッドクロス
長期と短期の移動平均線が交わることでトレンドの転換(もしくは売買のシグナル)を示す1つの指標となります。長期の移動平均線を短期の移動平均線が上回った時に「ゴールデンクロス」、長期の移動平均線を短期の移動平均線が下回った時に「デッドクロス」と呼びます。
これらは短期の移動平均線と長期の移動平均線の差を算出し、前日と当日とでその差の値の正負が変化するかで検出できます。
(1) 前日は長期の移動平均より短期の移動平均の価格が安く、当日は短期の移動平均が長期の移動平均を上回ったらゴールデンクロス (2) 前日は長期の移動平均より短期の移動平均の価格が高く、当日は短期の移動平均線が長期の移動平均線を下回ったらデッドクロス
# 前日と当日のSMA5とSMA25の差分を計算
diff = SMA5 - SMA25
prev_diff = diff.shift(1)
# ゴールデンクロス: 前日は SMA5 < SMA25、当日は SMA5 > SMA25
golden_cross = (prev_diff < 0) & (diff > 0)
# デッドクロス: 前日は SMA5 > SMA25、当日は SMA5 < SMA25
dead_cross = (prev_diff > 0) & (diff < 0)golden_crossがTrueの時にゴールデンクロス、dead_crossがTrueの時にデッドクロスと表示できます。
Plotlyにもゴールデンクロスとデッドクロスを表示できます。
# SMA5と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(
# ゴールデンクロス: 前日は負(SMA5 < SMA25)、当日は正(SMA5 > SMA25)
golden_cross=(pl.col("prev_diff") < 0) & (pl.col("diff") > 0),
# デッドクロス: 前日は正(SMA5 > SMA25)、当日は負(SMA5 < SMA25)
dead_cross=(pl.col("prev_diff") > 0) & (pl.col("diff") < 0),
)
)
# ゴールデンクロスの日付を抽出
golden_crosses = df_cross.filter(pl.col("golden_cross")).select(
pl.col("date"),
pl.col("close.amount").alias("price"),
pl.lit("ゴールデンクロス").alias("signal"),
)
# デッドクロスの日付を抽出
dead_crosses = df_cross.filter(pl.col("dead_cross")).select(
pl.col("date"),
pl.col("close.amount").alias("price"),
pl.lit("デッドクロス").alias("signal"),
)
# ゴールデンクロスとデッドクロスを結合してソート
signals = pl.concat([golden_crosses, dead_crosses]).sort("date")
# ゴールデンクロスとデッドクロスのデータを分離
_golden = signals.filter(pl.col("signal") == "ゴールデンクロス")
_dead = signals.filter(pl.col("signal") == "デッドクロス")
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}の株価",
),
# 中略
# ゴールデンクロスのマーカー
go.Scatter(
yaxis="y1",
x=_golden["date"].to_list(),
y=_golden["price"].cast(pl.Float64).to_list(),
mode="markers",
name="ゴールデンクロス",
marker={
"symbol": "triangle-up",
"size": 15,
"color": "lime",
"line": {"color": "darkgreen", "width": 2},
},
),
# デッドクロスのマーカー
go.Scatter(
yaxis="y1",
x=_dead["date"].to_list(),
y=_dead["price"].cast(pl.Float64).to_list(),
mode="markers",
name="デッドクロス",
marker={
"symbol": "triangle-down",
"size": 15,
"color": "red",
"line": {"color": "darkred", "width": 2},
},
),
]まとめ
ここまで株価の取得および、ローソク足での表示、移動平均の計算、ゴールデンクロスとデッドクロスの検出までをご紹介しました。Pandasを利用した方が便利な状況が続いていましたが、コードをご覧の通りにPolarsだけでも株価の取得から可視化までPandasを介さずにできます。ゴールデンクロス・デッドクロスだけでは弱いですが基本的で分かりやすい指標をまず作れるようになり、分析の一歩を歩み出すことができたかと思います。今後も、他の指標やより高度な分析をモダンなライブラリを通じて行っていきます。
![[Pythonによる株式分析]週足・月足の表示とボリンジャーバンドで値動きの範囲を把握する](https://b.rmc-8.com/img/2025/12/01/0218c7e535f81400a91a9ad80357a034.png)
![[Pythonによる株式分析]Polarsで一目均衡表の計算をし表示する](https://b.rmc-8.com/img/2025/12/02/00541cf97f979f588ad2cad0ac6d6d7f.png)




コメントを読み込み中...