K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

RSI

05.1.3

RSI

最終更新 2020-02-26 読了時間 6 分
まとめ
RSI(Relative Strength Index、相対力指数)は、直近 n 日の終値の「上がった日の上昇幅の平均」と「下がった日の下落幅の平均」の比から 0〜100 の値を作り、70〜80 以上を買われすぎ、20〜30 以下を売られすぎと読むテクニカル指標です。このページでは終値の diff() から平均上昇幅と平均下落幅を更新していく関数 get_rsi() を自作し、NVIDIA(NVDA)の 2021 年 5〜6 月の株価で n=7 の RSI を計算します。計算した RSI は mpf.make_addplot(panel=2) でローソク足と出来高の下に 3 段目のパネルとして並べ、80 を超えた区間を axvspan で赤く塗って「買われすぎと判定された後に株価がどう動いたか」を 1 枚で確認します。

直感 #

株価が 10 日連続で上がっていると「もう上がりすぎでは」と感じますが、感覚では売買の判断になりません。RSI はこの感覚を数値にしたもので、直近 n 日のうち上がった日の値幅の合計と下がった日の値幅の合計を比べ、上げが圧倒的なら 100 に、下げが圧倒的なら 0 に近づきます。上げ下げが半々なら 50 です。RSI が 80 を超えた日は「直近 7 日はほぼ上げ一方」という意味なので、そこから反落しやすいという経験則をチャート上で検証できます。

データを読み込む #

前ページの mplfinance で作った get_finance_data() を utils.py に保存してあり、ここから読み込みます。

1
2
3
4
import numpy as np
import pandas as pd
import mplfinance as mpf
from utils import get_finance_data

NVDA の 2021 年 5 月 1 日から 6 月 30 日までの 2 か月を取得します。n=7 の RSI を見るには短い期間で十分です。

1
2
3
4
5
6
7
8
# 銘柄名、期間、保存先ファイル
ticker_symbol = "NVDA"
start = "2021-05-01"
end = "2021-06-30"

# データを取得する
df = get_finance_data(ticker_symbol, start=start, end=end, savedir="../data")
df.head()

HighLowOpenCloseVolumeAdj Close
Date
2021-04-30153.649994149.970001151.744995150.09500120191200.0149.990234
2021-05-03152.467499147.875000151.250000148.36749320391200.0148.263931
2021-05-04146.375000140.102493146.372498143.51249740532400.0143.412323
2021-05-05148.134995143.875000147.089996144.58500729202400.0144.484070
2021-05-06145.712494142.179993144.952499145.22999619338000.0145.128616

RSI を求める #

RSI には初期値の取り方や平均の更新方法がいくつかあります[1]。ここでは Relative Strength Index (RSI) に示されている方法に従い、最初の n 日は単純平均、それ以降は「前日の平均 × (n − 1) + 今日の値」を n で割る平滑化で平均上昇幅・平均下落幅を更新します。式で書くと RS = 平均上昇幅 ÷ 平均下落幅、RSI = 100 − 100 ÷ (1 + RS) です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def get_rsi(close_prices: pd.Series, n=14):
    """RSI(相対力指数)を計算する
    RS=(n日間の終値の上昇幅の平均)÷(n日間の終値の下落幅の平均)
    RSI= 100 - (100 ÷ (RS+1))

    参考文献:
      - https://info.monex.co.jp/technical-analysis/indicators/005.html
      - https://www.investopedia.com/terms/r/rsi.asp <-- 以下のコードの記号はこのページのものを使用

    Args:
        close_price (pd.Series): 終値の系列
        days (str): n日間, optional, default is 14.

    Returns:
        rsi(pd.Series): RSI
    """
    close_prices_diff = close_prices.diff(periods=1)[1:]
    fist_n_days_diff = close_prices_diff[: n + 1]
    previous_average_gain, previous_average_loss = 0, 0
    rsi = np.zeros_like(close_prices)

    for i in range(len(close_prices)):
        if i < n:

            previous_average_gain = fist_n_days_diff[fist_n_days_diff >= 0].sum() / n
            previous_average_loss = -fist_n_days_diff[fist_n_days_diff < 0].sum() / n
            rsi[i] = 100.0 - 100.0 / (1 + previous_average_gain / previous_average_loss)
        else:
            if (cpd_i := close_prices_diff[i - 1]) > 0:
                current_gain = cpd_i
                current_loss = 0.0
            else:
                current_gain = 0.0
                current_loss = -cpd_i

            previous_average_gain = (previous_average_gain * (n - 1) + current_gain) / n
            previous_average_loss = (previous_average_loss * (n - 1) + current_loss) / n
            rsi[i] = 100.0 - 100.0 / (1 + previous_average_gain / previous_average_loss)
    return rsi

OHLC チャートと並べてプロットする #

get_rsi(df["Close"], n=7) で RSI を列として足します。続いて mpf.make_addplot で、ローソク足(パネル 0)・出来高(パネル 1)の下に RSI のパネル 2 を追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
df["rsi"] = get_rsi(df["Close"], n=7)

# RSIのグラフを追加する
apd = mpf.make_addplot(
    df["rsi"], panel=2, color="#000", ylim=(0, 100), secondary_y=True, width=0.8
)

# グラフを作成
fig, axes = mpf.plot(
    df,
    type="candle",
    style="yahoo",
    volume=True,
    mav=[5, 25, 75],
    addplot=apd,
    panel_ratios=(1, 0.6),
    datetime_format="%Y/%m/%d",
    returnfig=True,
    figscale=1.2,
)
fig.legend(
    [f"{days} days" for days in [5, 25, 75]], bbox_to_anchor=(0.0, 0.78, 0.305, 0.102)
)
<matplotlib.legend.Legend at 0x7fdeaf404130>

OHLC チャートと並べてプロットするの図

実行結果の読み方 #

最下段の黒い線が RSI で、5 月中旬の下落局面では 20 台まで落ち、5 月末から 6 月初めの連続陽線で 80 を超えています。80 を超えた直後のローソク足を見ると、数日は上げが続いたあとに陰線が混ざり始めており、「買われすぎ → すぐ反落」ではなく「買われすぎ → しばらく高止まり」になっている点がこの銘柄・この期間の特徴です。

条件を満たした範囲を塗りつぶす #

「RSI が 80 以上になった」「価格が 150 ドルを超えた」といった条件を満たす期間を背景色で示すと、シグナルと株価の関係が一目で分かります。ここでは RSI が 80 を超えた区間を赤く塗ります。

塗りつぶし範囲の区間のリストを作る #

axvspan は「開始位置と終了位置」のペアを 1 つずつ受け取るので、RSI の系列を先頭から走査して 80 を上に抜けた位置と下に抜けた位置を [開始, 終了] の形で集めます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
cond_true_spans = []
span = None
is_true_span = False

for i, rsi_i in enumerate(df["rsi"]):
    if rsi_i > 80 and not is_true_span:
        is_true_span = True
        span = [i, 0]
    elif rsi_i < 80 and is_true_span:
        is_true_span = False
        span[1] = i - 1
        cond_true_spans.append(span)
    else:
        pass

グラフの塗りつぶしをする #

前の節と同じチャートを描いたあと、集めた区間ごとに axvspan で縦の帯を引きます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apd = mpf.make_addplot(
    df["rsi"], panel=2, color="#000", ylim=(0, 100), secondary_y=True, width=0.8
)

fig, axes = mpf.plot(
    df,
    type="candle",
    style="yahoo",
    volume=True,
    mav=[5, 25, 75],
    addplot=apd,
    panel_ratios=(1, 0.6),
    datetime_format="%Y/%m/%d",
    returnfig=True,
    figscale=1.2,
)
fig.legend(
    [f"{days} days" for days in [5, 25, 75]], bbox_to_anchor=(0.0, 0.78, 0.305, 0.102)
)

for span in cond_true_spans:
    axes[0].axvspan(span[0], span[1], color="red", alpha=0.2)
    axes[-1].axvspan(span[0], span[1], color="red", alpha=0.2)

グラフの塗りつぶしをするの図

実行結果の読み方 #

赤い帯の区間で RSI は 80 を超え、同じ区間のローソク足は陽線が続いています。帯が終わった直後の数日を見ると、株価は帯の中の高値を大きく上回っておらず、RSI 80 超えの後の短期的な上値の重さと一致します。ただし帯の後に下落へ転じているとも言い切れないので、RSI 80 を「売りのサイン」と機械的に使うのではなく、「上げの勢いが一旦止まりやすい場所」として読むのが妥当です。

使いどころと落とし穴 #

get_rsi は n 日の間に下げた日が 1 日も無いと平均下落幅が 0 になり、gain / loss で 0 除算が起きます。2 か月分の NVDA では発生しませんでしたが、上昇が続く銘柄や n=3 のような短い窓では起きうるので、実運用では分母に 1e-9 を足すか np.where で loss == 0 のとき RSI を 100 にします。axvspan に渡す位置は行番号であり日付ではないため、df を絞り込んだり並べ替えた後は区間リストを作り直す必要があります。RSI の定義には単純移動平均版(Cutler 式)もあり、pandas-ta や ta-lib の RSI 関数はここで実装した指数平滑版と初期値の扱いが微妙に違うので、同じ n でも値が数ポイントずれることがあります。

まとめ #

RSI は終値の diff() を上昇幅と下落幅に分け、それぞれの平均を (前日の平均 × (n − 1) + 今日の値) / n で更新し、100 − 100 / (1 + 平均上昇幅 / 平均下落幅) で 0〜100 に写した指標です。mpf.make_addplot(panel=2, ylim=(0, 100)) を addplot に渡すとローソク足・出来高の下に RSI の段が増え、axvspan で 80 超えの行番号の区間を塗れば、買われすぎと判定された日の株価の動きが同じ図の中で確認できます。NVDA の 2021 年 5〜6 月では、80 超えの後すぐに反落するというより高止まりが続いており、閾値の意味を自分のデータで検証する大切さが分かる例になっています。