RSI(Relative Strength Index、相対力指数)は、一定期間の値上がり幅と値下がり幅の比から「買われ過ぎ・売られ過ぎ」を測るオシレーター系の指標です。本記事では、定義・計算式・pandas での実装・可視化までを扱います。
目次
- RSI が示すもの
- 計算式
- サンプルデータの準備
- RSI を pandas で実装する
- 価格と RSI を 2 段で可視化する
- オーバーソールド/オーバーボートのフラグ化
- ダイバージェンス(divergence)
RSI が示すもの
RSI は 0 〜 100 の値を取り、次の解釈が広く使われています。
- 70 以上: オーバーボート(買われ過ぎ、overbought)
- 30 以下: オーバーソールド(売られ過ぎ、oversold)
- 50 付近: 中立。トレンドが定まらない / 緩やか
「70 を超えたから売り」「30 を割ったから買い」は、教科書的な解説でよく出てきますが、強いトレンドでは長期間 70 や 30 のラインに張り付くこともあります。値の絶対水準だけで判断しないのが定石です。
計算式
期間 (典型的には 14)を取り、過去 日の上昇幅平均と下落幅平均から RS を作ります。
ここで は直近 日の 上昇日の値幅平均、 は 下落日の値幅平均 です。下落幅は絶対値で扱います。
平均の取り方には次の 2 流派があり、結果の数値が少し違います。
- Wilder のスムージング(原典・TA-Lib のデフォルト): の指数平滑
- 単純移動平均(SMA): 直近 日の単純平均
本記事では、原典に近い Wilder のスムージング で実装します。
サンプルデータの準備
ランダムウォークから終値を作ります。
import numpy as npimport pandas as pd
rng = np.random.default_rng(seed=2026)n = 250returns = rng.normal(loc=0.0004, scale=0.014, size=n)close = pd.Series( 1200 * np.exp(np.cumsum(returns)), index=pd.date_range("2025-09-01", periods=n, freq="B"), name="C",)RSI を pandas で実装する
def rsi_wilder(close: pd.Series, window: int = 14) -> pd.Series: """Wilder のスムージングで RSI を計算する。""" delta = close.diff() up = delta.clip(lower=0) down = (-delta).clip(lower=0)
# 初期 N 日は単純平均で seed し、以降は α = 1/N の指数平滑 avg_up = up.ewm(alpha=1/window, adjust=False, min_periods=window).mean() avg_down = down.ewm(alpha=1/window, adjust=False, min_periods=window).mean()
rs = avg_up / avg_down.replace(0, np.nan) rsi = 100 - 100 / (1 + rs) rsi.name = f"rsi_{window}" return rsi
rsi14 = rsi_wilder(close, window=14)print(rsi14.tail())replace(0, np.nan) は、値下がりが 1 度も無い区間で avg_down がゼロになったとき、ゼロ除算を避けるための処置です。NaN になった行は実用上 RSI が「100 に近い」状態として扱います。
簡易版の SMA ベース RSI も載せておきます。
def rsi_sma(close: pd.Series, window: int = 14) -> pd.Series: delta = close.diff() up = delta.clip(lower=0) down = (-delta).clip(lower=0) rs = up.rolling(window).mean() / down.rolling(window).mean().replace(0, np.nan) return 100 - 100 / (1 + rs)両方を計算して比較すると、初期数日の挙動が少し異なります。長期間で見れば概ね近い値になります。
価格と RSI を 2 段で可視化する
価格チャートの下に RSI を並べます。
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots( nrows=2, sharex=True, figsize=(11, 6), gridspec_kw={"height_ratios": [3, 1]},)
ax1.plot(close.index, close.values, color="black", linewidth=0.8)ax1.set_title("C & RSI(14)")ax1.set_ylabel("Price")ax1.grid(alpha=0.3)
ax2.plot(rsi14.index, rsi14.values, color="tab:purple")ax2.axhline(70, color="red", linestyle="--", alpha=0.6)ax2.axhline(30, color="green", linestyle="--", alpha=0.6)ax2.fill_between(rsi14.index, 30, 70, color="gray", alpha=0.08)ax2.set_ylim(0, 100)ax2.set_ylabel("RSI")ax2.set_xlabel("Date")ax2.grid(alpha=0.3)
plt.tight_layout()plt.savefig("rsi_chart.png", dpi=120)plt.close(fig)
sharex=True で X 軸を揃え、height_ratios で価格側を広めに取るのが定番のレイアウトです。
オーバーソールド/オーバーボートのフラグ化
ライン越えのフラグを 0/1 で持たせると、後段の集計に使えます。
df = close.to_frame()df["rsi"] = rsi14df["overbought"] = df["rsi"] >= 70df["oversold"] = df["rsi"] <= 30
print(df["overbought"].sum(), "days overbought")print(df["oversold"].sum(), "days oversold")「30 を下から上抜けたとき」のような エッジ を取りたければ、diff を使います。
df["enter_oversold"] = (df["oversold"]) & (~df["oversold"].shift(1).fillna(False))df["exit_oversold"] = (~df["oversold"]) & (df["oversold"].shift(1).fillna(False))enter_oversold は「前日まで 30 超 → 当日 30 以下」の最初の 1 日だけが True になります。
ダイバージェンス(divergence)
価格と RSI が 逆方向に動く 状態をダイバージェンスと呼びます。
- ベアリッシュダイバージェンス: 価格は高値更新、RSI は前回ピークを更新できない → 上昇の勢いが鈍化
- ブリッシュダイバージェンス: 価格は安値更新、RSI は前回ボトムを更新できない → 下落の勢いが鈍化
機械的に検出する場合は、価格と RSI の 直近 2 ピーク を比較します。実装は手間がかかるため、まずは目視確認 → 仮説立てから始めるのが現実的です。
注意点
- 強トレンドでは張り付く: 強い上昇相場で RSI が 70 以上に長く滞在することは普通にある
- N の選び方: 14 は慣例。短くすると敏感、長くすると鈍くなる。最適化のしすぎは過剰適合を招く
- 値幅の単位を揃える: 株式分割があった日は調整済み終値を使う。生終値だとリターンが偽の値になる
- シグナル単独で売買しない: トレンド指標(SMA / MACD)と組み合わせる構成が多い
生成AI へのプロンプト例
ロング形式のデータに対して、Code ごとの RSI を一括計算する関数を依頼します。
入力 DataFrame:- columns: Date (datetime64), Code (str), C (float)
Code ごとに Wilder のスムージングで RSI(14) を計算し、列 rsi14 を追加した DataFrame を返す関数 add_rsi(df) を書いてください。
要件:- pandas 2.2 系- Code ごとに日付昇順に整列してから計算- 各 Code の冒頭 13 日は NaN を許容- 動作確認のサンプルコードを末尾に付ける- 関数の docstring に Wilder と SMA の差を 2 行で書くまとめ
- RSI は「直近 N 日の上昇幅 / 下落幅」の比から 0〜100 を作る指標
- Wilder のスムージングは
ewm(alpha=1/N, adjust=False)で書ける - 70 / 30 は教科書的なライン。トレンドが強いと張り付く点は常に意識する
- 価格と RSI を 2 段で並べると視覚的に把握しやすい
- 単独運用ではなく、トレンド指標と組み合わせると判断材料が増える