RSI(Relative Strength Index、相対力指数)は、一定期間の値上がり幅と値下がり幅の比から「買われ過ぎ・売られ過ぎ」を測るオシレーター系の指標です。本記事では、定義・計算式・pandas での実装・可視化までを扱います。

目次

  1. RSI が示すもの
  2. 計算式
  3. サンプルデータの準備
  4. RSI を pandas で実装する
  5. 価格と RSI を 2 段で可視化する
  6. オーバーソールド/オーバーボートのフラグ化
  7. ダイバージェンス(divergence)

RSI が示すもの

RSI は 0 〜 100 の値を取り、次の解釈が広く使われています。

  • 70 以上: オーバーボート(買われ過ぎ、overbought)
  • 30 以下: オーバーソールド(売られ過ぎ、oversold)
  • 50 付近: 中立。トレンドが定まらない / 緩やか

「70 を超えたから売り」「30 を割ったから買い」は、教科書的な解説でよく出てきますが、強いトレンドでは長期間 70 や 30 のラインに張り付くこともあります。値の絶対水準だけで判断しないのが定石です。

計算式

期間 NN(典型的には 14)を取り、過去 NN 日の上昇幅平均と下落幅平均から RS を作ります。

RSt=UtDt,RSIt=1001001+RSt\mathrm{RS}_t = \frac{\overline{U}_t}{\overline{D}_t}, \qquad \mathrm{RSI}_t = 100 - \frac{100}{1 + \mathrm{RS}_t}

ここで Ut\overline{U}_t は直近 NN 日の 上昇日の値幅平均Dt\overline{D}_t下落日の値幅平均 です。下落幅は絶対値で扱います。

平均の取り方には次の 2 流派があり、結果の数値が少し違います。

  • Wilder のスムージング(原典・TA-Lib のデフォルト): α=1/N\alpha = 1/N の指数平滑
  • 単純移動平均(SMA): 直近 NN 日の単純平均

本記事では、原典に近い Wilder のスムージング で実装します。

サンプルデータの準備

ランダムウォークから終値を作ります。

import numpy as np
import pandas as pd
rng = np.random.default_rng(seed=2026)
n = 250
returns = 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)
終値と RSI(14) を 2 段に並べた価格・オシレーター複合チャート

sharex=True で X 軸を揃え、height_ratios で価格側を広めに取るのが定番のレイアウトです。

オーバーソールド/オーバーボートのフラグ化

ライン越えのフラグを 0/1 で持たせると、後段の集計に使えます。

df = close.to_frame()
df["rsi"] = rsi14
df["overbought"] = df["rsi"] >= 70
df["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 段で並べると視覚的に把握しやすい
  • 単独運用ではなく、トレンド指標と組み合わせると判断材料が増える