【Python】pandas-datareaderで取得した株価データをデータベースにキャッシュして高速化

Pythonを用いて株価を取得した後、同じデータを使って繰り返し分析するときに再度株価を取得するようにしてしまうと時間がかかってしまい、非効率です。

そこで今回は、取得した株価データをデータベースにキャッシュ(cache)して、それを使いまわすことで作業を効率化する方法を紹介します。

実行環境:Windows10

キャッシュせずに普通に株価を取得する

まずは、普通にPythonで株価を取得して、計算するとどれくらい時間がかかるかを確認します。

株価は、pandas-datareaderというモジュールを使用して取得します。

・pandas-datareaderのインストール

pip install pandas_datareader

pandas-datareaderは、株価などのデータをAPIから取得できるモジュールです。使い方に関して詳しくはPythonのpandas datareaderをインストールして株価データを取得する方法【日本株・米国株】をご覧ください。

スクリーニングするソースコード

今回は、チャートの形が似ている銘柄をスクリーニングするプログラムを対象とします。このプログラムは、約500銘柄の株価を取得してある銘柄Aと似ているかどうかを判定するものです。

必要なモジュールをインポートします。

import datetime
import pandas_datareader.data as web

比較元の銘柄Aの株価を取得します。

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

# 比較元の株価チャート
origin = web.DataReader('AAPL', 'yahoo', start, end)['Adj Close']

以下、形が似ているチャートをスクリーニングするソースコードです。このコード自体は今回の趣旨とは関係ありませんので理解する必要はありませんが、気になる方はPythonで株価チャートが似ているS&P500銘柄をスクリーニングするをご覧ください。

# 似ているチャートをスクリーニングする vvvvvvvvvvvvv

# 規格化する関数
def mms(ticker):
    mms = (ticker - ticker.min()) / (ticker.max() - ticker.min())
    return mms

origin = mms(origin)

from sklearn.metrics import mean_absolute_error
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")

similars = pd.DataFrame({'MAE(類似度)':['']}, index=['銘柄'])
error_symbols = []
i = 0
for c in codelist['Symbol']:
    i += 1
    print(i)
    try:
        candidate = web.DataReader(c, 'yahoo', start, end)['Adj Close']
        candidate = mms(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)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

簡単にだけ説明すると、forループの中で一つ一つの銘柄の株価チャートを取得し、比較元の銘柄A(origin)のチャートと比較し、それが似ているものなら配列similarsに挿入しています。このソースコードを実行することで、比較元と似ているチャートをもつ銘柄が出力されます。

出力例

      MAE(類似度)
銘柄
ABT   0.134396
ACN   0.106962
ADBE   0.08864
AMD   0.059262
...        ...
VRTX  0.130791
WMT   0.126826
WST   0.056574
ZBRA    0.1188
ZTS   0.127086

これを実行すると、(環境にもよりますが)20分ほどかかります。

再度これを実行するとき(たとえば、比較元の銘柄AAPLを別の銘柄に変えて実行するとき)、そのまま実行すると再度20分ほどかかってしまいます。

そこで、この500銘柄の株価チャートをデータベースにキャッシュして使いまわすことで、実行を高速化します。

株価を取得してキャッシュする

ここからが今回のメインです。

株価をキャッシュするには、requests_cacheというモジュールを使います。

pip install requests_cache

以下は、キャッシュのための準備です。

from datetime import timedelta 
from requests_cache import CachedSession

expire_after = timedelta(days=3) # 3日間キャッシュする
session = CachedSession(cache_name='cache', backend='sqlite', 
                        expire_after=expire_after) 
session.headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0',
    'Accept': 'application/json;charset=utf-8'}

expire_afterは、キャッする期間です。

sessionのbackend=’sqlite’が、SQLite(データベース)にキャッシュすることを意味します。

session.headersにはUbuntuやLinux、Firefoxという文字が見えますが、OSがWindowsでも、ブラウザがchromeでも動きます。私の環境はWindowsですが、session.headersが無いとエラーが起きました(参考)。

pandas-datareaderで取得する株価データをキャッシュするには、以下のように「session」オプションを付けます。

株価データをキャッシュするには

sessionオプションを付ける

web.DataReader('AAPL’, 'yahoo’, start, end, session=session)

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

# 比較元の株価チャート
origin = web.DataReader('AAPL', 'yahoo', start, end, session=session)['Adj Close']

これで、AAPLの株価チャートのデータはキャッシュされます。

ここまでを実行すると、cache.sqliteというファイルが実行ディレクトリに作成されると思います。

では、改めて他の銘柄もキャッシュしてみましょう。先ほどのスクリーニングするソースコードの株価取得部分を

candidate = web.DataReader(c, 'yahoo', start, end, session=session)['Adj Close']

と変更します。

# 似ているチャートをスクリーニングする vvvvvvvvvvvvv

# 規格化する関数
def mms(ticker):
    mms = (ticker - ticker.min()) / (ticker.max() - ticker.min())
    return mms

origin = mms(origin)

from sklearn.metrics import mean_absolute_error
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")

similars = pd.DataFrame({'MAE(類似度)':['']}, index=['銘柄'])
error_symbols = []
i = 0
for c in codelist['Symbol']:
    i += 1
    print(i)
    try:
        candidate = web.DataReader(c, 'yahoo', start, end, session=session)['Adj Close']
        candidate = mms(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)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

キャッシュ(cache)を貯めた状態にしてから、これを実行すると20秒くらいで計算が終わると思います。

キャッシュを貯めてない状態(一回目の実行)では20分かかっていましたから、20分→20秒となり、かなり効率化できました。

とはいえ、最初の1回目は20分かかりますが…。

株価データをデータベースにキャッシュして高速化できました!

参考

pandas-datareaderを使ってみよう

How to fix new unable to read URL error in python for yahoo finance