ダウ平均株価をスクレイピングで自動取得しProphetで値動きを予測する

# やったこと
・毎日のダウ平均株価をWebスクレイピングによって取得するプロセスをscheduleによる定時処理を実装することで 自動化.
・初期株価データ(取得開始日前日までの5年分)に取得データを加え,Prophetで今後の株価を予測できる形にデータを加工するプロセスを自動化.
・今後1年分のダウ平均株価の値動きをProphetで予測した結果を出力.
# 開発環境
言語: Python 3.6.5
フレームワーク: Jupyter Notebook
OS: Windows 10
ブラウザ: Google Chrome
#サンプルコード
```py
import urllib.request as urlreq
from bs4 import BeautifulSoup
from datetime import datetime, timedelta, timezone
import pandas as pd
import matplotlib.pyplot as plt
from fbprophet import Prophet
import schedule
import time
import csv

def job1():
    dow_heikin = ""
    date = ""
    url = "https://fred.stlouisfed.org/series/DJIA/"
    r = urlreq.Request(url)
    with urlreq.urlopen(r) as r:
        r = r.read()
    soup = BeautifulSoup(r, "html.parser")
    spans = soup.find_all("span")
    for tag in spans:
        try:
            string_ =tag.get("class").pop(0)
            if string_ in "series-meta-observation-value":
                dow_heikin = tag.string
                break
        except:
            pass
    print(dow_heikin)
    dow_heikin_string = str(dow_heikin).replace(",", "")
    dow_heikin_float = float(dow_heikin_string)
    print(dow_heikin_float)
    CDT = timezone(timedelta(hours=-5), 'CDT')
    today = datetime.now(CDT)
    if today.strftime('%H:%M') <= '19:01':
        date = today - timedelta(days = 1)
    else:
        date = today
    date = date.strftime("%Y/%m/%d")
    print(date)
    update_data = [date, dow_heikin_float]
    print(update_data)
    with open('DJIA.csv', 'a') as f: 
        writer = csv.writer(f, lineterminator='\n') # 行末は改行
        writer.writerow(update_data)

def job2():
    data = pd.DataFrame()
    file_name = 'DJIA.csv'
    data2 = pd.read_csv(file_name, skiprows=1,header=None, names=['ds','y'])
    data3 = data2.replace('.', '')
    data4 = data3.drop(data3.index[data3.y == ''])
    data5 = data.append(data4) 
    print(data5)
    return data5

def job3(data5):
    model = Prophet(daily_seasonality=True)
    model.fit(data5)
    future_data = model.make_future_dataframe(periods=365, freq = 'd')
    forecast_data = model.predict(future_data)
    fig = model.plot(forecast_data)
    model.plot_components(forecast_data)

schedule.every().tuesday.at("09:06").do(job1)
schedule.every().wednesday.at("09:06").do(job1)
schedule.every().thursday.at("09:06").do(job1)
schedule.every().friday.at("09:06").do(job1)
schedule.every().saturday.at("09:06").do(job1)
while True:
    schedule.run_pending()
    time.sleep(1)

dow_avarage_data = job2()
job3(dow_avarage_data)
```
実装の際,**サンプルコードの間隔が空いている箇所はそれぞれJupyter Notebook上の別々のセル**にプログラムを記述していただけるとうまく動きます.

以下サンプルコードを詳しく解説
# 必要なライブラリのインポート
```py
import urllib.request as urlreq
from bs4 import BeautifulSoup
from datetime import datetime, timedelta, timezone
import pandas as pd
import matplotlib.pyplot as plt
from fbprophet import Prophet
import schedule
import time
import csv
```
・**urllib.request**・・・ダウ平均株価を取得するサイトのURLを開くために必要なライブラリ.
サイト名: FRED ECONOMIC RESEARCH
URL: https://fred.stlouisfed.org/series/DJIA#0
・**BeautifulSoup**・・・HTMLで記述されたWebサイトのソースコードからデータを習得するライブラリ.株価データが入っているHTMLタグを取得するために必要.
・**datetime**・・・日付や時間データを操作するためのライブラリ.毎日のダウ平均株価を習得する際に必要.
・**timedelta**・・・ダウ平均株価を扱う上で日本とアメリカの時差を設定するために必要なライブラリ.
・**timezone**・・・時差を扱う際にタイムゾーンの生成をするライブラリ.
・**pandas**・・・csvなど様々な形式のデータをPython上でデータフレームとして扱うために必要なライブラリ.
・**matplotlib.pyplot**・・・Pythonでデータを分析した結果をグラフで表示するためのライブラリ.
・**Prophet**・・・Pythonで時系列解析を実装できるFacebookが開発したライブラリ（pipでインストールが必要）
```$ pip install fbprophet```
これでインストールできない場合、以下リンクなどを参考にしてください．
https://github.com/facebook/prophet/issues/201
・**schedule**・・・平日その日のダウ平均株価の終値を習得するプログラムを定期実行するために必要なライブラリ.
・**time**・・・定期実行の際にtime.sleepを使うために必要なライブラリ.
・**csv**・・・取得したダウ平均株価データを初期株価データ(取得開始日前日までの5年分)が格納されているcsvファルに書き込むために必要なライブラリ.
#コードの大まかな流れ
0.(事前準備)ダウ平均株価を予測するWebサイト「FRED ECONOMIC RESEARCH」(https://fred.stlouisfed.org/series/DJIA#0/) から過去5年分の株価データをcsvファイルでダウンロードしておき,Jupyter Notebookの実行ファイルにおいておく.
![スクリーンショット (85).png](https://qiita-image-store.s3.amazonaws.com/0/296645/5f6094df-7bc3-5bdc-1792-17d0cc64c4a7.png)
↑csvファイルで株価データを5年分ダウンロード

1.ダウ平均株価をスクレイピングによって取得後,先ほどダウンロードした初期株価データ(取得開始日前日までの5年分)のcsvファイルにその日のダウ平均株価データを追加する・・・関数job1()と定義

2.job1()で最新のダウ平均株価が追加された株価のデータのcsvファイルをProphetで扱える形に加工する・・・関数job2()と定義

3.Prophetで株価予測モデルを作成し,今後1年分のダウ平均株価の値動きを予測する・・・関数job3()と定義

4.job1()→job2()→job3()の順で実行
#1.ダウ平均株価をスクレイングによって取得する関数を実装
```py
def job1():
    dow_heikin = ""
    date = ""
    #ダウ平均株価データの変数を定義
    url = "https://fred.stlouisfed.org/series/DJIA/"
    r = urlreq.Request(url)
    # 「FRED ECONOMIC RESEARCH」リンクを取得
    with urlreq.urlopen(r) as r:
        r = r.read()
    # リンクを開き読み込む
    soup = BeautifulSoup(r, "html.parser")
    # Webページのソースコードをパース
    spans = soup.find_all("span")
    # spanのついたクラスをすべて取得
    for tag in spans:
        try:
            string_ =tag.get("class").pop(0)
            if string_ in "series-meta-observation-value":
                dow_heikin = tag.string
                break
        #ダウ平均株価データが格納されているタグを見つける
        except:
            pass
        #データが見つからない場合は何もしない
    print(dow_heikin)
    #デバッグ用にコンソールに表示
    dow_heikin_string = str(dow_heikin).replace(",", "")
    dow_heikin_float = float(dow_heikin_string)
    #string型からfloat型にする
    print(dow_heikin_float)
    #デバッグ用にコンソールに表示
    CDT = timezone(timedelta(hours=-5), 'CDT')
    #アメリカ中部標準時間(CDT)を扱うために変数を定義
    today_time = datetime.now(CDT)
    #CDTの時間を取得
    if today_time.strftime('%H:%M') <= '19:01':
    #CDTの時間が19:01より前かどうか
        date = today_time - timedelta(days = 1)
        #先ほど取得した日付を1日前にしてdateと定義
    else:
        date = today_time
        #先ほど取得した日付をそのままdateと定義
    date = date.strftime("%Y/%m/%d")
    #dateを年/月/日の形にする
    print(date)
    #デバッグ用にコンソールにdateを表示
    update_data = [date, dow_heikin_float]
    #取得した株価とdateをupdate_dataと定義
    print(update_data)
    #デバッグ用にコンソールに表示
    with open('DJIA.csv', 'a') as f: 
        writer = csv.writer(f, lineterminator='\n') 
        #行末は改行
        writer.writerow(update_data)
        #初期株価データに取得した株価データを追加
```
取得する株価の日時をdateとしif文でdateの取得処理を分けている理由としては,スクレイピングするサイト(https://fred.stlouisfed.org/series/DJIA#0/)  のソースコードを見たところ,**アメリカ中部標準時間(CDT)の19:01にその日の株価の終値が更新される仕様**になっているため
中部標準時間を世界標準時から-5時間としているのは現在(10/14)がサマータイムの期間であるため.サマータイム外の期間は-6時間に設定する必要あり.
#2.スクレイピングで自動取得した株価データを分析用に加工する関数を実装
```py
def job2():
    data = pd.DataFrame()
    file_name = 'DJIA.csv'
    #DLした初期株価データのファイル名
    data2 = pd.read_csv(file_name, skiprows=1,header=None, names=['ds','y'])
    #初期株価データをpandasのデータフレームに格納
    data3 = data2.replace('.', '')
    #欠損値処理
    data4 = data3.drop(data3.index[data3.y == ''])
    #欠損値処理
    data5 = data.append(data4) 
    print(data5)
    #デバッグ用に加工後のデータをコンソールに表示
    return data5
```
![スクリーンショット (86).png](https://qiita-image-store.s3.amazonaws.com/0/296645/8aead6be-3e8c-3b72-a57a-7f510378165b.png)
↑加工後のダウ平均株価データのイメージ
#3.Prophetでモデルを作成し,今後1年分のダウ平均株価の値動きを予測する関数を実装
```py
def job3(data5):
    model = Prophet(daily_seasonality=True)
    #Prophetのモデルを作成(daily_seasonality=Trueで1日単位の値動きの傾向も見る)
    model.fit(data5)
    #モデルにデータを流し込む

    future_data = model.make_future_dataframe(periods=365, freq = 'd')
    #model.make_future_dataframe()で予測する期間を指定,periods=で予測する日数
    forecast_data = model.predict(future_data)
    #今後1年の株価の値動きを予測する関数を定義
    
    fig = model.plot(forecast_data)
    model.plot_components(forecast_data)
    #予測結果をプロット
```
#4.スクレイピングで当日の株価を取得するプログラムを平日決まった時間に定時実行させる
```py
schedule.every().tuesday.at("09:06").do(job1)
schedule.every().wednesday.at("09:06").do(job1)
schedule.every().thursday.at("09:06").do(job1)
schedule.every().friday.at("09:06").do(job1)
schedule.every().saturday.at("09:06").do(job1)
#アメリカ中部標準時で19:06に当日のダウ平均株価の終値を取得
while True:
    schedule.run_pending()
    time.sleep(1)
#定時実行処理
```
スクレイピングするサイト(https://fred.stlouisfed.org/series/DJIA#0/) ではアメリカ中部標準時で平日19:01にその日のダウ平均株価の終値が更新されるので,その5分後に株価を取得する.定時実行処理では,実行時間は時差を考慮して日本時間で翌日09:06に設定
#5.取得した株価データと初期株価データ(5年分)で今後1年の株価の値動きを予測

```py
dow_avarage_data = job2()
job3(dow_avarage_data)
```
#実際に株価の変動を予測してみた
![1.png](https://qiita-image-store.s3.amazonaws.com/0/296645/e896b5c9-62fd-604a-87e0-b274db089639.png)
・先日(10/10～10/12)の大幅な下落は例外的な動きだとモデルが解釈している.
・このままダウ平均株価は30000ドルに迫るまで伸び続けるという**非常に楽観的な予測**.
![2.png](https://qiita-image-store.s3.amazonaws.com/0/296645/8f4a9324-0f6d-867d-df1b-96736784a342.png)
・2016年から上昇トレンド
・月単位では7月に底値を迎え年末にかけて上昇するという傾向がある
・週単位では水曜が最も高くなるという傾向がある
・日単位では波打つように上がったり下がったりする
#雑感
・予測に使う株価データが約5年分というのは短いのかもしれない,予測モデルは株価が常に上がり続けるというように解釈しているが,株価(景気)はバブルみたいに上がるときもあればリーマンショックみたいに落ちるときもあるということをモデルに学習させるためにもっと長い期間の株価データが必要だと考える.
・時差のおかげでスクレイピングして株価取得日時の処理が非常にめんどくさいことになった.サマータイムの実装は力尽きてあきらめた.サマータイム実装大変なのでオリンピックの勢いで日本で導入するのはやめてほしいですね.
・時系列解析+Prophetについてもっと勉強し,予測精度上げたい.
#参考文献
・python 現在時刻取得
https://qiita.com/mykysyk@github/items/e15d7b2b1a988b8e29d4
・pandas.DataFrame, Seriesの要素の値を置換するreplace
https://note.nkmk.me/python-pandas-replace/　
・Python3のdatetimeはタイムゾーンを指定するだけで高速になる
https://qiita.com/mattsu6/items/5511c5631e7a54550f7f
・中部標準時
https://ja.wikipedia.org/wiki/%E4%B8%AD%E9%83%A8%E6%A8%99%E6%BA%96%E6%99%82
・Pythonのdatetimeで日付や時間と文字列を変換（strftime, strptime）
https://note.nkmk.me/python-datetime-usage/
・invalid character in identifier　のエラー
https://teratail.com/questions/75641
・Python で日付の計算
https://qiita.com/vkgtaro/items/da515011fe76622ada62
・scheduleライブラリを使ってPythonスクリプトを定期実行しよう
https://tadaken3.hatenablog.jp/entry/python-schedule
・schedule
https://schedule.readthedocs.io/en/stable/
・FutureWarning:Conversion of the second argument of the second argument of issubdtype from `float` to `np.floating` is deprecated
https://stackoverflow.com/questions/48340392/futurewarning-conversion-of-the-second-argument-of-issubdtype-from-float-to
・Python csvファイルに新しい行を追記するには
https://teratail.com/questions/90409