Cream-Kuchen

Pythonで株・為替のテクニカル分析・可視化

はじめに

株や為替のテクニカル分析手法をPythonでどのように実装するのでしょう?

また、どのように可視化したら良いのでしょう?具体的に紹介します。

目次


1, サンプルデータ取得

ここでは日経平均株価 (2020/1/1 ~ 6/30) を例に取り上げます。

サンプルコードと表データ、株価チャートを以下に示します。

# パッケージのインポート
import numpy as np
import pandas_datareader as pdr
import matplotlib.pyplot as plt
from datetime import datetime

# 適当な期間の株価指数を取得
str_date = datetime(2020, 1, 1)  # 取得日付の起点日
end_date = datetime(2020, 6, 30)  # 取得日付の終了日
df = pdr.DataReader(["NIKKEI225"], "fred", str_date, end_date)  # 日経平均株価指数をFederal Reserve Economic Dataから取得

# データ整形
df.rename(columns={"NIKKEI225":"price"}, inplace=True)  # カラム名を任意に変更
df = df.loc[~df["price"].isnull()]  # 営業日のデータだけ残し欠損値を除外


f:id:Cream-Kuchen:20200707193446p:plain

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    plt.plot(df.index, df["price"], label="price", color="blue")
    plt.legend(loc="lower right")
    plt.show();


f:id:Cream-Kuchen:20200707193643p:plain

2020年3月の暴落からV字回復していますが、年明けの水準には戻っていませんね。


2, 変化率

時系列データの前後のレコードの変化率は下記コードで算出できます。

df["chg_price"] = df["price"].pct_change()


f:id:Cream-Kuchen:20200707194436p:plain
※ 1レコード目は算出されません。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    plt.plot(df.index, df["chg_price"], label="chg_price", color="blue")
    plt.legend(loc="lower right")
    plt.show();


f:id:Cream-Kuchen:20200707194722p:plain

2020年3~4月の変動が激しいですね。VIX指数が高止まりしていた頃ですね。


3, 単純移動平均

ある期間の移動平均は下記コードで算出できます。

DAY = 5  # 算定用期間
df["mov_avg_price"] = df["price"].rolling(window=DAY, min_periods=DAY).mean()


f:id:Cream-Kuchen:20200707195534p:plain
※ 算定用期間に満たない間は算出されません。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    plt.plot(df.index, df["price"], label="price", color="blue")
    plt.plot(df.index, df["mov_avg_price"], label="mov_avg_price", color="orange")
    plt.legend(loc="lower right")
    plt.show();


f:id:Cream-Kuchen:20200707195649p:plain

株価(青)に対して移動平均(橙)が追従していますね。


4, ボリンジャーバンド

単純移動平均にそのブレを加減すると、ボリンジャーバンドになります。
www.moneypartners.co.jp

DAY = 20  # 算定用期間
df["mov_avg_price"] = df["price"].rolling(window=DAY, min_periods=DAY).mean()  # 単純移動平均
df["std_price"] = df["price"].rolling(window=DAY, min_periods=DAY).std()  # 標準偏差

# 単純移動平均から2シグマの乖離
df["bolb_upper_price"] = df["mov_avg_price"] + 2*df["std_price"]
df["bolb_lower_price"] = df["mov_avg_price"] - 2*df["std_price"]

# ボリンジャーバンドと株価の乖離率
df["diff_bolb_upper_price"] = np.log(df["bolb_upper_price"]/df["price"])
df["diff_bolb_lower_price"] = np.log(df["price"]/df["bolb_lower_price"])


f:id:Cream-Kuchen:20200707201458p:plain
※ 算定用期間に満たない間は算出されません。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    plt.plot(df.index, df["price"], label="price", color="blue")
    plt.plot(df.index, df["bolb_upper_price"], label="bolb_upper_price", color="orange")
    plt.plot(df.index, df["bolb_lower_price"], label="bolb_lower_price", color="green")

    # 算定用期間前後を示す垂直線
    plt.vlines(df.index[DAY-1], df["bolb_lower_price"].min(), df["bolb_upper_price"].max(), colors="gray")
    plt.legend(loc="lower right")
    plt.show();


f:id:Cream-Kuchen:20200707200925p:plain

単純移動平均から2シグマの乖離の下限・上限にタッチし暴落・高騰が見られますね。


5, 指数平滑移動平均

単純移動平均に対し、時系列に重みを付ける指数平滑移動平均があります。
www.moneypartners.co.jp

DAY = 5  # 算定用期間
df["exp_mov_avg_price"] = df["price"].ewm(span=DAY).mean()


f:id:Cream-Kuchen:20200707202006p:plain
移動平均算出期間に満たなくても値は出るので、注意してください。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    plt.plot(df.index, df["price"], label="price", color="blue")
    plt.plot(df.index, df["exp_mov_avg_price"], label="exp_mov_avg_price", color="orange")
    plt.legend(loc="lower right")
    plt.show();


f:id:Cream-Kuchen:20200707202205p:plain

重み付けにより単純移動平均より株価に追従して推移しています。


6, MACD(Moving Average Convergence Divergence)

短期と長期の指数平滑移動平均線により、MACDでトレンドの方向性を伺えます。
www.moneypartners.co.jp

df["exp_mov_avg_price_12"] = df["price"].ewm(span=12).mean()
df["exp_mov_avg_price_26"] = df["price"].ewm(span=26).mean()
df["MACD_price"] = df["exp_mov_avg_price_12"] - df["exp_mov_avg_price_26"]
df["signal_price"] = df["MACD_price"].ewm(span=9).mean()


f:id:Cream-Kuchen:20200707202703p:plain
移動平均算出期間に満たなくても値は出るので、注意してください。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    fig, ax1 = plt.subplots()
    ax2 = ax1.twinx()  # 右軸を用いる

    # データの描画
    ax1.plot(df.index, df["price"], label="price", color="blue")
    ax2.plot(df.index, df["MACD_price"], label="MACD_price", color="orange")
    ax2.plot(df.index, df["signal_price"], label="signal_price", color="green")

    # 垂直・水平線の描画(算定用期間前後や基準値を示す)
    ax1.vlines(df.index[25], df["price"].min(), df["price"].max(), colors="gray")
    ax2.hlines(0, df.index[0], df.index[-1], colors="gray")

    # 凡例の表示
    h1, l1 = ax1.get_legend_handles_labels()
    h2, l2 = ax2.get_legend_handles_labels()
    ax1.legend(h1+h2, l1+l2, loc='lower right')

    # ラベルを見やすく回転
    xlabels = ax1.get_xticklabels()
    plt.setp(xlabels, rotation=30)

    plt.show();


f:id:Cream-Kuchen:20200707202939p:plain

2020年3月半ばにMACD(橙)がsignal(緑)を一気に上抜け、買い戻しに反応していますね。


7, ストキャスティックス

相場の売られ過ぎ、買われ過ぎを見る方法にストキャスティックスがあります。
www.moneypartners.co.jp

KDAY = 14  # K算定用期間
MDAY = 3  # D算定用期間

# stochasticks K
df["sct_k_price"] = (
    100*
    (df["price"] - df["price"].rolling(window=KDAY, min_periods=KDAY).min())/
    (df["price"].rolling(window=KDAY, min_periods=KDAY).max() - df["price"].rolling(window=KDAY, min_periods=KDAY).min())
)

# stochasticks D
df["sct_d_price"] = (
    100*
    (df["price"] - df["price"].rolling(window=KDAY, min_periods=KDAY).min())
      .rolling(window=MDAY, min_periods=MDAY).sum()/
    (df["price"].rolling(window=KDAY, min_periods=KDAY).max() - df["price"].rolling(window=KDAY, min_periods=KDAY).min())
      .rolling(window=MDAY, min_periods=MDAY).sum()
)

# slow stochasticks
df["slow_sct_d_price"] = df["sct_d_price"].rolling(window=MDAY, min_periods=MDAY).mean()


f:id:Cream-Kuchen:20200707203732p:plain
※ 算定用期間に満たない間は算出されません。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    fig, ax1 = plt.subplots()
    ax2 = ax1.twinx()  # 右軸を用いる

    # データの描画
    ax1.plot(df.index, df["price"], label="price")
    ax2.plot(df.index, df["sct_k_price"], label="sct_k_price", color="orange")
    ax2.plot(df.index, df["sct_d_price"], label="sct_d_price", color="green")
    ax2.plot(df.index, df["slow_sct_d_price"], label="slow_sct_d_price", color="purple")

    # 垂直・水平線の描画(算定用期間前後や基準値を示す)
    ax1.vlines(df.index[KDAY-1], df["price"].min(), df["price"].max(), colors="gray")
    ax2.hlines(20, df.index[0], df.index[-1], colors="gray")
    ax2.hlines(80, df.index[0], df.index[-1], colors="gray")

    # 右軸の範囲を見やすく設定
    ax2.set_ylim(bottom=-10, top=110)

    # 凡例の表示
    h1, l1 = ax1.get_legend_handles_labels()
    h2, l2 = ax2.get_legend_handles_labels()
    ax1.legend(h1+h2, l1+l2, bbox_to_anchor=(1.1, 1.0))

    # ラベルを見やすく回転
    xlabels = ax1.get_xticklabels()
    plt.setp(xlabels, rotation=30)

    plt.show();


f:id:Cream-Kuchen:20200707203918p:plain

ストキャスティックスはレンジ相場で有効ですがトレンド相場では力を発揮しにくいため、
2020年3月の暴落時には売られ過ぎを示す下限のゼロに張り付いてしまっていますね。


8, サイコロジカルライン

投資家心理から相場の売られ・買われ過ぎを見るサイコロジカルラインがあります。
info.monex.co.jp

DAY = 12  # 算定用期間
df.loc[df["price"] >= df["price"].shift(1), "DICE_price"] = 1
df.loc[df["price"] < df["price"].shift(1), "DICE_price"] = 0
df["DICE_line_price"] = df["DICE_price"].rolling(window=DAY, min_periods=DAY).mean() * 100


f:id:Cream-Kuchen:20200707204422p:plain
※ 算定用期間に満たない間は算出されません。

with plt.rc_context({"xtick.color":"white", "ytick.color":"white", "figure.facecolor":"black"}):
    fig, ax1 = plt.subplots()
    ax2 = ax1.twinx()  # 右軸を用いる

    # データの描画
    ax1.plot(df.index, df["price"], label="price")
    ax2.plot(df.index, df["DICE_line_price"], label="DICE_line_price", color="orange")

    # 垂直・水平線の描画(算定用期間前後や基準値を示す)
    ax1.vlines(df.index[DAY-1], df["price"].min(), df["price"].max(), colors="gray")
    ax2.hlines(25, df.index[0], df.index[-1], colors="gray")
    ax2.hlines(75, df.index[0], df.index[-1], colors="gray")

    # 右軸の範囲を見やすく設定
    ax2.set_ylim(bottom=0, top=100)

    # 凡例の表示
    h1, l1 = ax1.get_legend_handles_labels()
    h2, l2 = ax2.get_legend_handles_labels()
    ax1.legend(h1+h2, l1+l2, loc="lower right")

    # ラベルを見やすく回転
    xlabels = ax1.get_xticklabels()
    plt.setp(xlabels, rotation=30)

    plt.show();


f:id:Cream-Kuchen:20200707204646p:plain

売られ・買われ過ぎの目安となる25, 75%ラインにタッチする時もありますが、
コロナ禍において売られ過ぎの目安は機能していないようですね。


おわりに

株や為替のテクニカル分析手法をPythonで実装・可視化する方法を紹介しました。

分析だけではなく、予測モデルの特徴量作成時にも参考にして頂ければ幸いです。