RSIをプロット

RSI(相対力指数)とはテクニカルチャートの一種であり買われすぎか、売られすぎかを判断するための指標です。 ここではpythonでRSIを計算し、過去に買われすぎと判断された状況で株価がどうなったかをグラフで見てみようと思います。
import numpy as np
import pandas as pd
import mplfinance as mpf
from utils import get_finance_data
# 銘柄名、期間、保存先ファイル
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)で示されている方法で計算してみます。

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 チャートと並べてプロットする

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>

png

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

「RSIが●×以上になった」「価格が●×を超えた」など、条件が満たされた期間を塗りつぶしてわかりやすくしたいです。 ここでは、80を超えた区間を塗りつぶしてみます。

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

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

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

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)

png