Pythonで株価チャートが似ているS&P500銘柄をスクリーニングする

形が似ているグラフ(時系列データ)を見つけたいときってありますよね.

例えば,何かのきっかけで一気に暴落した銘柄の株価は大体似てたりするので,他に同じように暴落した銘柄はないか,探したかったりします.

形が似ている銘柄をスクリーニングすると,以下のようなメリットがあります.

・形が同じような銘柄は避け,分散投資に役立てる
・形が同じような銘柄を買い,ひとつの個別銘柄の不調を補う
・形が同じような銘柄の共通点を分析することで投資に役立てる

例えば,コロナショックで大暴落した銘柄の株価推移と似ている銘柄スクリーニングすることで,どんなセクターで暴落が起きたのかを分析することができます

そこで今回は,一つの銘柄を指定し,その株価チャートと形の似ている銘柄をS&P500の中から発見する方法を紹介します.

この記事では

・株価を取得する方法
・比較のために株価スケールを正規化する方法
・スクリーニングする方法

という部分部分に分けて説明しますが,ソースコードだけが欲しい方のために記事の最後にまとめコードを記載しています.説明は不要という方はまとめコードをコピペしてご使用ください.※ただし,必要なライブラリは各自でインストールしてください.

株価を取得する

軸となる銘柄の株価を取得

まず,一つの銘柄の株価チャートを取得します.今回はUAL(ユナイテッド・エアライン・ホールディングス)の株価チャートと似ている銘柄をスクリーニングするので,まずはUALの株価チャートを取得します.

import datetime
import pandas_datareader.data as web

start = datetime.date(2018,1,1)
end = datetime.date.today()

ual = web.DataReader('UAL', 'yahoo', start, end)['Adj Close']

S&P500の銘柄(ティッカーコード)を取得

次に,S&P500の中からスクリーニングしますので,S&P500の銘柄一覧を取得します.

url = "https://raw.githubusercontent.com/datasets/s-and-p-500-companies/master/data/constituents.csv"
codelist = pd.read_csv(url, encoding="SHIFT_JIS")

このコードについては,【Python】S&P500から低PERの銘柄をスクリーニングするをご覧ください.

この時点では,S&P500のティッカーコードだけをcodelistとして取得したことになります.後でスクリーニングするときに,これを使って一つ一つの株価を取得することになります.

ただし,株価を取得したらCSVなどに保存し,以降はそこから取り出すことをおすすめします.APIのサーバーの負担になるからです.

CSVへの保存,CSVからの呼び出しの方法は株価時系列データを取得してCSVファイルに保存する方法と呼び出す方法をご覧ください.

また,ナスダック銘柄を取得したい場合はこちらの記事を参考にしてください.ただし,ナスダック銘柄の数は多すぎるのでなかなか計算が終わらないと思います.

株価のスケールを合わせる(正規化)

株価というものは形が似ていてもスケールがバラバラです.

例えば,以下のUAL(ユナイテッド・エアライン・ホールディングス,青色の線)とDAL(デルタ航空,橙色の線)はコロナショックで大きく下落したので形は似ていますが,スケールが違うのでスクリーニングするときに何を基準に比較するか困ってしまいます.※比較のためにAAPL(Apple)の株価も載せています.

simlar_chart1.png

そこでまずは,スクリーニングの準備のために同じスケールに正規化して比較するコードを用意します.

正規化には,Min-Max Normalizationを使います.

$$mmn(x) = \frac{x_i-\min(x)}{\max(x)-\min(x)}$$

株価データはpandasのデータフレームなので,Min-Max Normalizationをコードで書くと,以下のようになります.

def mmn(data):
    mmn = (data - data.min()) / (data.max() - data.min())
    return mmn

この関数によって銘柄の株価は0~1に正規化されます.例として,UAL,DAL,AAPLの株価チャートを正規化してみます.

ual = mmn(ual)
dal = mmn(dal)
aapl = mmn(aapl)
simlar_chart2.png

株価が0~1に正規化され,UALとDALのチャートが似ていることが確認できます.

チャート形が似ている銘柄をスクリーニングする

これまで準備したコードを使って,S&P500からUALのチャートに似ている銘柄をスクリーニングします.

似ているかどうかは,平均絶対誤差(MAE)を用いて数値化します.

$$MAE = \frac{1}{N}\sum_{i=1}^{N}|x_{1i} – x_{2i}|$$

from sklearn.metrics import mean_absolute_error

このMAEが小さいほどチャートの形が似ているということになります.MAEが0なら,チャートは完全に一致します.

例として,UALとAAPLのMAEは以下のようになります.

MSE_ual_aapl = mean_absolute_error(ual, aapl)
print(MSE_ual_aapl)
0.5756471586956278

最後に,スクリーニングするコードを以下に示します.今回は,UALとのMAEが0.15より小さい銘柄をスクリーニングします.

similars = pd.DataFrame({'MAE(類似度)':['']}, index=['銘柄'])
error_symbols = []

for c in codelist['Symbol']:

    try:
        candidate = web.DataReader(c, 'yahoo', start, end)['Adj Close']
        candidate = mmn(candidate)
        mae = mean_absolute_error(origin, candidate)
        if mae < 0.15:
            similars.loc[c] = mae

    except:
        error_symbols.append(c)

print(similars)

print(error_symbols)

実行結果

       MAE(類似度)
銘柄             
ALK    0.117049
BA    0.0786378
BXP     0.11543
CCL    0.142564
CNP    0.119631
COP    0.129644
DAL   0.0879942
XOM    0.120234
FRT   0.0716615
HST    0.137468
L      0.138638
MTB    0.143658
NCLH  0.0890963
OMC    0.125869
OKE    0.101436
PSX    0.139819
REG   0.0967346
RCL    0.109596
SPG   0.0745186
USB     0.14598
UAL           0
VNO   0.0915754
WFC    0.140196
['BRK.B', 'BF.B', 'CARR', 'CTVA', 'DOW', 'FOXA', 'FOX', 'LUMN', 'OTIS', 'VAR']

実行が終わるまでに13分ほどかかりました(これを高速化したい場合は、こちらの記事をご覧ください).UALのチャートと似ている(MAEが0.15より小さい)銘柄は23個あることが分かりました.

※実行結果の最後のリストは,株価を読み込むときにエラーが出てしまった銘柄です.

試しに,UALとのMAEが0.14となっているWFC(ウェルズ・ファーゴ,銀行銘柄)のチャートをUALと比べてみてみましょう.

wfc = web.DataReader('WFC', 'yahoo', start, end)['Adj Close']

wfc = mmn(wfc)

import matplotlib.pyplot as plt

plt.plot(wfc,label='WFC')
plt.plot(origin,label='UAL')
plt.xlabel('date')
plt.ylabel('mmn')
plt.legend()
plt.show()

確かに,よく似た株価推移をしていますね.

当然,どれほど似ているかを判定するにはチャートの期間とMAEの閾値が関係してきます.

まとめコード


import datetime
import pandas_datareader.data as web

start = datetime.date(2018,1,1)
end = datetime.date.today()

origin = web.DataReader('UAL', 'yahoo', start, end)['Adj Close']

def mmn(data):
    mmn = (data - data.min()) / (data.max() - data.min())
    return mmn

origin = mmn(origin)

import pandas as pd

url = "https://raw.githubusercontent.com/datasets/s-and-p-500-companies/master/data/constituents.csv"
codelist = pd.read_csv(url, encoding="SHIFT_JIS")

from sklearn.metrics import mean_absolute_error

similars = pd.DataFrame({'MAE(類似度)':['']}, index=['銘柄'])
error_symbols = []

for c in codelist['Symbol']:

    try:
        candidate = web.DataReader(c, 'yahoo', start, end)['Adj Close']
        candidate = mmn(candidate)
        mae = mean_absolute_error(origin, candidate)
        if mae < 0.15:
            print(c)
            similars.loc[c] = mae

    except:
        error_symbols.append(c)

print(similars)

print(error_symbols)

参考記事

【Python】S&P500から低PERの銘柄をスクリーニングする

【Python】スケールの異なる時系列データの類似度をMAEで比較する

米国株のPERなどの指標を取得してスクリーニングする【Python, pandas datareader】

コメント

  1. taro より:

    とても勉強になる記事ありがとうございます。
    まさに私がやってみたいと思っていたことでした。
    質問なのですが、横軸(日付)についても正規化して、過去のチャートの形と似たチャートをスクリーニングするにはどうすればいいか、アドバイスいただけないでしょうか?
    よろしくお願いします。

    • adminB より:

      taro様、記事をご覧いただき、ありがとうございます。
      taro様のやりたいことと一致するか分かりませんが、
      違うスケールの横軸を拡大、縮小して一致させ、比較したうえでスクリーニングしたいのであれば、
      データの点数と期間が分かれば時系列データの間隔をどれくらい広げたり縮めることで一致させることができるかが分かるので、
      それを行えばよい気がします。
      また、もし時系列の中で部分的に動きが一致するところを見つけたいのであれば、多少複雑なアルゴリズムが必要だと思います。

      回答になってるか分かりませんが、よろしくお願いいたします。

タイトルとURLをコピーしました