※本記事には広告・アフィリエイトリンクが含まれます。掲載内容は筆者の調査・検証に基づき、読者の判断を助ける目的で作成しています。
動作確認環境:Python 3.11 / pandas 2.x / yfinance 0.2.x / matplotlib 3.x
結論から言うと
- RSI(相対力指数)はpandasの
rolling()とclip()を組み合わせるだけで計算できます - yfinanceで取得した株価データに対して、14行程度のコードでRSIを算出してチャートに表示できます
- 70超えで「売られ過ぎ」、30未満で「買われ過ぎ」の判定ロジックも数行で追加できます
RSIとは(計算式を含む)
RSI(Relative Strength Index:相対力指数)は、ジェイ・ウェルズ・ワイルダーが1978年に考案したモメンタム系テクニカル指標です。率直に言うと、「一定期間の値上がり幅と値下がり幅の比率」を0〜100の範囲に収めた指標という仕組みです。
計算式は以下の通りです:
RS = 平均利得 / 平均損失
RSI = 100 - (100 / (1 + RS))
一般的に14日間を計算期間として使います。RSIが70を超えると相場が「買われ過ぎ」(反落の可能性)、30を下回ると「売られ過ぎ」(反発の可能性)と解釈されます。ただし、トレンドが強い相場では70以上や30以下の水準が続くこともあるため、単一指標での判断には注意が必要です。
PythonでRSIを計算するコード
yfinanceで株価を取得し、pandasでRSIを計算します。また、マルチレベルのカラムが生成される場合は.droplevel()で1次元に変換するのがポイントです。
import yfinance as yf
import pandas as pd
# トヨタの1年分の株価を取得
df = yf.download("7203.T", period="1y", auto_adjust=True)
# マルチレベルカラム対策
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.droplevel(1)
# RSI計算(14日)
delta = df["Close"].diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(window=14).mean()
avg_loss = loss.rolling(window=14).mean()
rs = avg_gain / avg_loss
df["RSI"] = 100 - (100 / (1 + rs))
print(df[["Close", "RSI"]].tail())
RSIをチャートに表示する
sharex=Trueで横軸を共有すると、ズームが連動して確認しやすくなります。
import yfinance as yf
import matplotlib.pyplot as plt
import pandas as pd
df = yf.download("7203.T", period="1y", auto_adjust=True)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.droplevel(1)
delta = df["Close"].diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(window=14).mean()
avg_loss = loss.rolling(window=14).mean()
rs = avg_gain / avg_loss
df["RSI"] = 100 - (100 / (1 + rs))
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
ax1.plot(df.index, df["Close"], label="終値", color="#1f77b4")
ax1.set_title("トヨタ自動車(7203.T)")
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.plot(df.index, df["RSI"], label="RSI(14)", color="purple")
ax2.axhline(70, color="red", linestyle="--", label="売られ過ぎ(70)")
ax2.axhline(30, color="green", linestyle="--", label="買われ過ぎ(30)")
ax2.set_ylim(0, 100)
ax2.set_title("RSI")
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("toyota_rsi.png", dpi=150)
plt.show()
RSIの売買シグナルを判定するコード(70超え・30未満)
# シグナル判定
df["Signal"] = ""
df.loc[df["RSI"] > 70, "Signal"] = "売られ過ぎ"
df.loc[df["RSI"] < 30, "Signal"] = "買われ過ぎ"
signals = df[df["Signal"] != ""][["Close", "RSI", "Signal"]]
print(f"シグナル発生日数: {len(signals)}")
print(signals.tail(10))
# チャート上に視覚化
overbought = df[df["RSI"] > 70]
oversold = df[df["RSI"] < 30]
ax1.scatter(overbought.index, overbought["Close"], color="red", marker="v", s=60, label="売られ過ぎ", zorder=5)
ax1.scatter(oversold.index, oversold["Close"], color="green", marker="^", s=60, label="買われ過ぎ", zorder=5)
MACDとの組み合わせ
RSI単体では「トレンドが続く相場で誤シグナルが出やすい」という弱点があります。そこで実務ではMACDと組み合わせて確認することが多いです。MACDが上向きでRSIが30以下なら「強い買いシグナル候補」として使う方法が一般的という仕組みです。
# MACD計算
df["EMA12"] = df["Close"].ewm(span=12, adjust=False).mean()
df["EMA26"] = df["Close"].ewm(span=26, adjust=False).mean()
df["MACD"] = df["EMA12"] - df["EMA26"]
df["Signal_MACD"] = df["MACD"].ewm(span=9, adjust=False).mean()
# RSI+MACDの複合シグナル
df["Combined"] = (df["RSI"] < 35) & (df["MACD"] > df["Signal_MACD"])
print(f"複合シグナル発生: {df['Combined'].sum()}日")
筆者の検証メモ
トヨタ(7203.T)、ソニー(6758.T)、ソフトバンクG(9984.T)の3銘柄で過去1年分のRSIを計算して確認しました。
- トヨタ(7203.T):RSIが30を下回ったのは2024年8月の急落時と11月初旬の2回。いずれもその後1〜2週間で価格が回復する動きがありました
- ソニー(6758.T):RSIが70を超えた期間が2024年12月〜2025年1月にかけて継続。トレンド相場ではRSIが高水準のまま推移する例の典型でした
- ソフトバンクG(9984.T):ボラティリティが高い銘柄のため、RSIの振れ幅も大きく30〜75の範囲を頻繁に行き来。単体では使いにくく、MACDと組み合わせると誤シグナルが減りました
率直に言うと、RSIは「相場の過熱感を把握するための補助ツール」として使うのが現実的です。単独で売買タイミングを決めるのは難しく、複数の指標を組み合わせることで判断の精度が上がるという仕組みです。
次のステップ
RSIを計算できたら、次は取得したデータをCSVに保存してバックテストの準備に進むのが自然な流れです。また、yfinanceのデータ取得方法についてはこちらの記事も参考にしてください。
📊 関連記事
Pythonで株価データを取得する方法【yfinance vs pandas-datareader 比較】
複数のデータ取得ライブラリを比較しています。yfinanceの注意点もまとめました。
※当サイトの内容は投資判断を推奨するものではありません。掲載しているコード・分析例は学習・検証目的であり、実際の投資はご自身の責任で行ってください。

