Pythonで作る株価予測SlackBot

## おおまかな流れ
1. seleniumを利用して分析材料となるcsvファイルの作成
2. csvファイルを用いて機械学習で明日の株価を予測
3. 予測結果をSlackに通知

※開発環境：jupyter Notebook 5.7.4

## 1. seleniumでスクレイピングを行いcsvファイルを作る
使用するwebサイト
[株式投資メモ・株価データベース](https://kabuoji3.com)

最初にスクレイピングを利用して分析材料となる数値データを取得していきます。

```python:stockPriceDataCollection
#import vital tools
from selenium import webdriver
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline

#browser open (chrome)
browser=webdriver.Chrome()

#ready for scraping
columnNames=[]
ETFComparisonsTable=[]
for num in range(0,48):
    browser.get("https://kabuoji3.com/stock/")
    stockSearch=browser.find_element_by_class_name("form_inputs")
    stockSearchForm=stockSearch.find_element_by_class_name("form_txt")
    stockSearchForm.send_keys("ETF")
    btnClick=browser.find_element_by_class_name("btn_submit")
    btnClick.click()
    
    #choose a stock out of list
    stockClick=browser.find_elements_by_class_name("clickable")
    stockClick[num].find_element_by_tag_name("a").click()
    
    stockTable=browser.find_element_by_class_name("table_wrap")
    stockLine=stockTable.find_elements_by_tag_name("tr")
    
    #price scraping with calculation
    if len(stockLine)==302:
        ETFComparisons=[]
        for i in range(2,152):
            stockETFPriceAfter=stockLine[i-1].find_elements_by_tag_name("td")
            stockETFPriceBefore=stockLine[i].find_elements_by_tag_name("td")
            ETFComparison=float(stockETFPriceAfter[6].text)-float(stockETFPriceBefore[6].text)
            ETFComparisons.append(ETFComparison)

        stockETFPriceAfter=stockLine[151].find_elements_by_tag_name("td")
        stockETFPriceBefore=stockLine[153].find_elements_by_tag_name("td")
        ETFComparison=float(stockETFPriceAfter[6].text)-float(stockETFPriceBefore[6].text)
        ETFComparisons.append(ETFComparison)

        for i in range(154,302):
            stockETFPriceAfter=stockLine[i-1].find_elements_by_tag_name("td")
            stockETFPriceBefore=stockLine[i].find_elements_by_tag_name("td")
            ETFComparison=float(stockETFPriceAfter[6].text)-float(stockETFPriceBefore[6].text)
            ETFComparisons.append(ETFComparison)

        ETFComparisonsTable.append(ETFComparisons)
        
        #pick up title
        stockTitleBox=browser.find_element_by_class_name("base_box_ttl")
        stockTitle=stockTitleBox.find_element_by_class_name("jp").text
        columnNames.append(stockTitle)
        
#making ETF table
ETFTable=pd.DataFrame(ETFComparisonsTable)
ETFTable=ETFTable.T
ETFTable.columns=columnNames
#checking ETF table
ETF.head()
```

ここまでのコードから、分析材料となるETF(投資信託)の前日比価格が確認できます。
※ ETF = Exchange Traded Funds のこと。
![スクリーンショット 2019-02-09 13.55.25.png](https://qiita-image-store.s3.amazonaws.com/0/357086/a668afe1-f40a-2c21-3a79-1a7e7d067b10.png)

次に日付データを取得します。

```python:stockPriceDataCollection
#date scraping
browser.get("https://kabuoji3.com/stock/{}/".format(4307))
stockTable=browser.find_element_by_class_name("table_wrap")
stockLine=stockTable.find_elements_by_tag_name("tr")
dates=[]
for i in range(1,152):
    stockDate=stockLine[i].find_elements_by_tag_name("td")
    stockDate=stockDate[0].text
    dates.append(stockDate)
for i in range(153,302):
    stockDate=stockLine[i].find_elements_by_tag_name("td")
    stockDate=stockDate[0].text
    dates.append(stockDate)
df_date=pd.DataFrame()
df_date["date"]=dates
df_date["year"]=df_date["date"].apply(lambda x:int(x.split("-")[0]))
df_date["month"]=df_date["date"].apply(lambda x:int(x.split("-")[1]))
df_date["day"]=df_date["date"].apply(lambda x:int(x.split("-")[2]))
df_date.head()
```

このようなデータが取得できました。
![スクリーンショット 2019-02-09 13.57.17.png](https://qiita-image-store.s3.amazonaws.com/0/357086/d5e00556-0b80-d1e4-2493-f70340f9b38a.png)

そして、予測したい株価の前日比データを集めていきます。
※今回は(株)野村総合研究所の株価を予測します。

```python:stockPriceDataCollection
#stock scraping (comparison with yesterday)
browser.get("https://kabuoji3.com/stock/{}/".format(4307))
stockTable=browser.find_element_by_class_name("table_wrap")
stockLine=stockTable.find_elements_by_tag_name("tr")
targetStockComparisons=[]
for i in range(2,152):
    targetStockPriceAfter=stockLine[i-1].find_elements_by_tag_name("td")
    targetStockPriceBefore=stockLine[i].find_elements_by_tag_name("td")
    targetStockComparison=float(targetStockPriceAfter[6].text)-float(targetStockPriceBefore[6].text)
    targetStockComparisons.append(targetStockComparison)
targetStockPriceAfter=stockLine[151].find_elements_by_tag_name("td")
targetStockPriceBefore=stockLine[153].find_elements_by_tag_name("td")
targetStockComparison=float(targetStockPriceAfter[6].text)-float(targetStockPriceBefore[6].text)
targetStockComparisons.append(targetStockComparison)
for i in range(154,302):
    targetStockPriceAfter=stockLine[i-1].find_elements_by_tag_name("td")
    targetStockPriceBefore=stockLine[i].find_elements_by_tag_name("td")
    targetStockComparison=float(targetStockPriceAfter[6].text)-float(targetStockPriceBefore[6].text)
    targetStockComparisons.append(targetStockComparison)
df=pd.DataFrame(targetStockComparisons)
df.columns=["(株)野村総合研究所：前日比"]
df.head()
```

データが取得できました。
![スクリーンショット 2019-02-09 13.58.12.png](https://qiita-image-store.s3.amazonaws.com/0/357086/5cd32fa9-a68f-2e92-f2d9-bb2de7f145b7.png)


ここで一旦、データを結合します。

```python:stockPriceDataCollection
#add table
stockPriceTable=pd.concat([df_date,ETFTable],axis=1)
stockPriceTable=pd.concat([stockPriceTable,df],axis=1)
stockPriceTable.head()
```

![スクリーンショット 2019-02-09 13.58.52.png](https://qiita-image-store.s3.amazonaws.com/0/357086/bdb32662-eb63-3bf3-f3e5-99165c5da168.png)


最後に予測する株価の翌日比データを取得し、結合します。
翌日比データは前日比データを1日ずつ前にずらすことで取得可能です。

```python:stockPriceDataCollection
#prepare for making target values
df_next=df.copy()
df_next.columns=["(株)野村総合研究所：翌日比"]

#date scraping for target values
browser.get("https://kabuoji3.com/stock/{}/".format(4307))
stockTable=browser.find_element_by_class_name("table_wrap")
stockLine=stockTable.find_elements_by_tag_name("tr")
dates=[]
for i in range(2,152):
    stockDate=stockLine[i].find_elements_by_tag_name("td")
    stockDate=stockDate[0].text
    dates.append(stockDate)
for i in range(153,302):
    stockDate=stockLine[i].find_elements_by_tag_name("td")
    stockDate=stockDate[0].text
    dates.append(stockDate)
df_date2=pd.DataFrame()
df_date2["date"]=dates

#making target values table
df_next=pd.concat([df_date2,df_next],axis=1)
df_next.index=df_date2["date"]

#prepare for complete table
table=stockPriceTable[1:299].copy()
table.index=table["date"]

#making complete table
table["(株)野村総合研究所：翌日比"]=df_next["(株)野村総合研究所：翌日比"]
table.tail()
```

これで、すべてのデータが取得できました。
![スクリーンショット 2019-02-09 14.01.59.png](https://qiita-image-store.s3.amazonaws.com/0/357086/d5042d27-85bd-9597-cefe-0d7f1958c96c.png)


最後にcsvファイルとして出力します。

```python:stockPriceDataCollection
#making csv file
table.to_csv("stockPriceData.csv",index=False)
```

## 2. csvファイルを用いて機械学習により明日の株価を予測
次に機械学習で予測モデルを作成していきます。
※ コードの混同を避けるために、ここから別ファイルで書いていきます。

まずは必要なライブラリと、先ほど作成したcsvファイルを読み込みます。

```python:stockPriceAnalysis&SlackBotMaking
#import vital tools
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
%matplotlib inline
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn import metrics
from selenium import webdriver
import requests
import json

#reading csv file (*ETF=Exhange Traded Funds)
train=pd.read_csv("stockPriceData.csv")
train.head()
```

取得できました。
![スクリーンショット 2019-02-09 14.04.00.png](https://qiita-image-store.s3.amazonaws.com/0/357086/59041f8a-843f-234a-38e7-05f006529732.png)


そしてデータを機械学習にかけてモデルを作成していきます。
今回は[ランダムフォレスト](https://www.datacamp.com/community/tutorials/random-forests-classifier-python)を利用します。

```python:stockPriceAnalysis&SlackBotMaking
#ready for making machine learning model
features=['1329 iシェアーズ・コア 日経225ETF',
       '1364 ｉシェアーズ JPX日経400 ETF', '1369 One ETF 日経225',
       '1385 UBS ETF ユーロ圏大型株50(E･ストックス50)', '1386 UBS ETF 欧州株(MSCIヨーロッパ)',
       '1387 UBS ETF ユーロ圏株(MSCI EMU)', '1388 UBS ETF ユーロ圏小型株(MSCI EMU小型株)',
       '1389 UBS ETF 英国大型株100(FTSE 100)', '1390 UBS ETF MSCIアジア太平洋株(除く日本)',
       '1391 UBS ETF スイス株(MSCIスイス20/35)', '1392 UBS ETF 英国株(MSCI英国)',
       '1458 楽天 ETF-日経レバレッジ指数連動型', '1459 楽天 ETF-日経ダブルインバース指数連動型',
       '1473 One ETF トピックス', '1474 One ETF JPX日経400',
       '1475 iシェアーズ・コア TOPIX ETF', '1476 iシェアーズ・コア Ｊリート ETF',
       '1477 iシェアーズ MSCI 日本株最小分散 ETF', '1478 iシェアーズ MSCI ジャパン高配当利回り ETF',
       '1482 iシェアーズ・コア 米国債7-10年ETF(H有)', '1483 iシェアーズ JPX/S&P 設備･人材投資ETF',
       '1489 (NEXT FUNDS)日経平均高配当株50指数連動型ETF', '1493 One ETF JPX日経中小型',
       '1494 One ETF 高配当日本株', '1496 iシェアーズ 米ドル建て投資適格社債ETF(H有)',
       '1497 iシェアーズ 米ドル建ハイイールド社債ETF(H有)', '1552 国際のETF VIX短期先物指数',
       '1557 SPDR S&P500 ETF', '1561 国際のETF VIX中期先物指数',
       '1575 ChinaAMC CSI 300 Index ETF-JDR', '1576 南方 FTSE 中国A株 50 ETF',
       '1577 (NEXT FUNDS)野村日本株高配当70連動型ETF', '1655 iシェアーズ S&P500 米国株 ETF',
       '1656 iシェアーズ･コア　米国債7-10年 ETF', '1657 iシェアーズ･コア MSCI 先進国株(除く日本)ETF',
       '1658 iシェアーズ･コア MSCI 新興国株 ETF', '1659 iシェアーズ 米国リート ETF',
       '1674 ETFS 白金上場投信', '1683 One ETF 国内金先物', '1688 ETFS 穀物上場投資信託',
       '1689 ETFS 天然ガス上場投資信託', '1690 ETFS WTI原油上場投資信託', '1691 ETFS ガソリン上場投資信託',
       '1696 ETFS とうもろこし上場投資信託', '(株)野村総合研究所：前日比']
x=train[features]
y=train["(株)野村総合研究所：翌日比"]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.27)

#model making and prediction
model=RandomForestRegressor(n_estimators=1000)
model.fit(x_train,y_train)
y_pred=model.predict(x_test)

#make result score and get accuracy score
testUpDown=[]
for test in y_test:
    if test>0:
        testUpDown.append(1)
    else:
        testUpDown.append(-1)
predUpDown=[]
for pred in y_pred:
    if pred>0:
        predUpDown.append(1)
    else:
        predUpDown.append(-1)
print("確率："+str(metrics.accuracy_score(testUpDown,predUpDown)*100)+"%")

#feature evaluation and plots
feature_imp = pd.Series(model.feature_importances_,index=features).sort_values(ascending=False)
print(feature_imp)
sns.barplot(x=feature_imp, y=feature_imp.index)
plt.xlabel('Feature Importance Score')
plt.ylabel('Features')
plt.title("Visualizing Important Features")
plt.figure(figsize=(30,50))
plt.show()
```

今回のモデルはこのようになりました。
`確率：50.617283950617285%`

`1493 One ETF JPX日経中小型                  0.052711
1674 ETFS 白金上場投信                       0.043717
1387 UBS ETF ユーロ圏株(MSCI EMU)           0.042863
1388 UBS ETF ユーロ圏小型株(MSCI EMU小型株)      0.042087
1392 UBS ETF 英国株(MSCI英国)               0.041471
1690 ETFS WTI原油上場投資信託                  0.041453
1482 iシェアーズ・コア 米国債7-10年ETF(H有)         0.039933
1386 UBS ETF 欧州株(MSCIヨーロッパ)            0.038128
1497 iシェアーズ 米ドル建ハイイールド社債ETF(H有)        0.032815
1390 UBS ETF MSCIアジア太平洋株(除く日本)         0.031969
1483 iシェアーズ JPX/S&P 設備･人材投資ETF         0.028788
(株)野村総合研究所：前日比                         0.027987
1389 UBS ETF 英国大型株100(FTSE 100)        0.027730
1476 iシェアーズ・コア Ｊリート ETF                0.025404
1691 ETFS ガソリン上場投資信託                   0.024830
1683 One ETF 国内金先物                     0.023140
1477 iシェアーズ MSCI 日本株最小分散 ETF           0.022828
1659 iシェアーズ 米国リート ETF                  0.022787
1494 One ETF 高配当日本株                    0.022351
1391 UBS ETF スイス株(MSCIスイス20/35)        0.021710
1364 ｉシェアーズ JPX日経400 ETF               0.021315
1576 南方 FTSE 中国A株 50 ETF               0.020365
1552 国際のETF VIX短期先物指数                  0.019919
1561 国際のETF VIX中期先物指数                  0.018872
1656 iシェアーズ･コア　米国債7-10年 ETF            0.018101
1489 (NEXT FUNDS)日経平均高配当株50指数連動型ETF    0.017830
1496 iシェアーズ 米ドル建て投資適格社債ETF(H有)         0.017683
1688 ETFS 穀物上場投資信託                     0.017207
1658 iシェアーズ･コア MSCI 新興国株 ETF           0.015710
1473 One ETF トピックス                     0.015489
1655 iシェアーズ S&P500 米国株 ETF             0.014095
1575 ChinaAMC CSI 300 Index ETF-JDR    0.013956
1474 One ETF JPX日経400                  0.013780
1577 (NEXT FUNDS)野村日本株高配当70連動型ETF      0.013513
1478 iシェアーズ MSCI ジャパン高配当利回り ETF        0.012947
1459 楽天 ETF-日経ダブルインバース指数連動型            0.011617
1475 iシェアーズ・コア TOPIX ETF               0.011364
1557 SPDR S&P500 ETF                   0.010943
1385 UBS ETF ユーロ圏大型株50(E･ストックス50)      0.010926
1689 ETFS 天然ガス上場投資信託                   0.010802
1657 iシェアーズ･コア MSCI 先進国株(除く日本)ETF      0.010731
1369 One ETF 日経225                     0.009340
1329 iシェアーズ・コア 日経225ETF                0.007999
1458 楽天 ETF-日経レバレッジ指数連動型               0.006532
1696 ETFS とうもろこし上場投資信託                 0.004264
dtype: float64`
![スクリーンショット 2019-02-09 14.09.24.png](https://qiita-image-store.s3.amazonaws.com/0/357086/aa067613-5d37-885d-9038-91bdf50196fe.png)


## 3. 予測結果をSlackに通知
最後に本日のETFデータおよび株価データから明日の株価を予測して、結果をSlackに通知します。

まずは予測値を取得します。

```python:stockPriceAnalysis&SlackBotMaking
#ready for scraping
browser=webdriver.Chrome()
columnNames=[]
ETFComparisonsTable=[]
ETFfeatures=['1329 iシェアーズ・コア 日経225ETF',
       '1364 ｉシェアーズ JPX日経400 ETF', '1369 One ETF 日経225',
       '1385 UBS ETF ユーロ圏大型株50(E･ストックス50)', '1386 UBS ETF 欧州株(MSCIヨーロッパ)',
       '1387 UBS ETF ユーロ圏株(MSCI EMU)', '1388 UBS ETF ユーロ圏小型株(MSCI EMU小型株)',
       '1389 UBS ETF 英国大型株100(FTSE 100)', '1390 UBS ETF MSCIアジア太平洋株(除く日本)',
       '1391 UBS ETF スイス株(MSCIスイス20/35)', '1392 UBS ETF 英国株(MSCI英国)',
       '1458 楽天 ETF-日経レバレッジ指数連動型', '1459 楽天 ETF-日経ダブルインバース指数連動型',
       '1473 One ETF トピックス', '1474 One ETF JPX日経400',
       '1475 iシェアーズ・コア TOPIX ETF', '1476 iシェアーズ・コア Ｊリート ETF',
       '1477 iシェアーズ MSCI 日本株最小分散 ETF', '1478 iシェアーズ MSCI ジャパン高配当利回り ETF',
       '1482 iシェアーズ・コア 米国債7-10年ETF(H有)', '1483 iシェアーズ JPX/S&P 設備･人材投資ETF',
       '1489 (NEXT FUNDS)日経平均高配当株50指数連動型ETF', '1493 One ETF JPX日経中小型',
       '1494 One ETF 高配当日本株', '1496 iシェアーズ 米ドル建て投資適格社債ETF(H有)',
       '1497 iシェアーズ 米ドル建ハイイールド社債ETF(H有)', '1552 国際のETF VIX短期先物指数',
       '1557 SPDR S&P500 ETF', '1561 国際のETF VIX中期先物指数',
       '1575 ChinaAMC CSI 300 Index ETF-JDR', '1576 南方 FTSE 中国A株 50 ETF',
       '1577 (NEXT FUNDS)野村日本株高配当70連動型ETF', '1655 iシェアーズ S&P500 米国株 ETF',
       '1656 iシェアーズ･コア　米国債7-10年 ETF', '1657 iシェアーズ･コア MSCI 先進国株(除く日本)ETF',
       '1658 iシェアーズ･コア MSCI 新興国株 ETF', '1659 iシェアーズ 米国リート ETF',
       '1674 ETFS 白金上場投信', '1683 One ETF 国内金先物', '1688 ETFS 穀物上場投資信託',
       '1689 ETFS 天然ガス上場投資信託', '1690 ETFS WTI原油上場投資信託', '1691 ETFS ガソリン上場投資信託',
       '1696 ETFS とうもろこし上場投資信託']
for feature in ETFfeatures:
    feature=feature.split(" ")[0]

    browser.get("https://kabuoji3.com/stock/{}/".format(feature))
    stockTable=browser.find_element_by_class_name("table_wrap")
    stockLine=stockTable.find_elements_by_tag_name("tr")
    
    #price scraping with calculation
    if len(stockLine)==302:
        ETFComparisons=[]
        stockETFPriceAfter=stockLine[1].find_elements_by_tag_name("td")
        stockETFPriceBefore=stockLine[2].find_elements_by_tag_name("td")
        ETFComparison=float(stockETFPriceAfter[6].text)-float(stockETFPriceBefore[6].text)
        ETFComparisons.append(ETFComparison)
        ETFComparisonsTable.append(ETFComparisons)
        
        #pick up title
        stockTitleBox=browser.find_element_by_class_name("base_box_ttl")
        stockTitle=stockTitleBox.find_element_by_class_name("jp").text
        columnNames.append(stockTitle)
        
#making ETF table
ETFTable=pd.DataFrame(ETFComparisonsTable)
ETFTable=ETFTable.T
ETFTable.columns=columnNames


#date scraping and stock scraping (comparison with yesterday)
browser.get("https://kabuoji3.com/stock/{}/".format(4307))
stockTable=browser.find_element_by_class_name("table_wrap")
stockLine=stockTable.find_elements_by_tag_name("tr")

dates=[]
stockDate=stockLine[1].find_elements_by_tag_name("td")
stockDate=stockDate[0].text
dates.append(stockDate)

df_date=pd.DataFrame()
df_date["date"]=dates
df_date["year"]=df_date["date"].apply(lambda x:int(x.split("-")[0]))
df_date["month"]=df_date["date"].apply(lambda x:int(x.split("-")[1]))
df_date["day"]=df_date["date"].apply(lambda x:int(x.split("-")[2]))

targetStockComparisons=[]
targetStockPriceAfter=stockLine[1].find_elements_by_tag_name("td")
targetStockPriceBefore=stockLine[2].find_elements_by_tag_name("td")
targetStockComparison=float(targetStockPriceAfter[6].text)-float(targetStockPriceBefore[6].text)
targetStockComparisons.append(targetStockComparison)

df=pd.DataFrame(targetStockComparisons)
df.columns=["(株)野村総合研究所：前日比"]

#add table
stockPriceTable=pd.concat([df_date,ETFTable],axis=1)
stockPriceTable=pd.concat([stockPriceTable,df],axis=1)


#ready for future price prediction
valueX=stockPriceTable[features]
pred=model.predict(valueX)
print(pred)
#make result score and get accuracy score
predPriceUpDown="?"
if pred>0:
    predPriceUpDown="上昇"
else:
    predPriceUpDown="下落"

#telling result
resultNotification="「4307:(株)野村総合研究所」株価予測:\n"+stockDate+"現時点での予測値は"+str(float(targetStockPriceAfter[6].text)+float(pred))+"円。\nよって価格は"+predPriceUpDown+"見込みです。"
print(resultNotification)
browser.quit()
```

今回はこのような結果が取得できました。
`[-25.49]
「4307:(株)野村総合研究所」株価予測:
2019-02-08現時点での予測値は4154.51円。
よって価格は下落見込みです。`

そして結果をSlackに送信します。
※Slackでは[Incoming WebHocksの設定](https://vaaaaaanquish.hatenablog.com/entry/2017/09/27/154210)が必要です。

```python:stockPriceAnalysis&SlackBotMaking
#SlackBot
slackURL="{copy and paste your WebHock URL}"

def send_slack(content):
    payload={
        "text":content,
        "username":"PythonStockForecast",
        "icon_emoji":":snake:"
    }
    data=json.dumps(payload)
    requests.post(slackURL,data)
send_slack(resultNotification)
```

送信が完了しました！
![スクリーンショット 2019-02-09 14.15.46.png](https://qiita-image-store.s3.amazonaws.com/0/357086/4f5aa115-f31a-a827-478c-d630d7570028.png)


## 参考URL
- [Understanding Random Forests Classifiers in Python](https://www.datacamp.com/community/tutorials/random-forests-classifier-python)
- [Pythonで機械学習を使った株価予測のコードを書こう](http://www.stockdog.work/entry/2017/02/09/211119)
- [簡易にSlackへPostする方法とPythonスクリプト](https://vaaaaaanquish.hatenablog.com/entry/2017/09/27/154210)
- [株式投資メモ・株価データベース](https://kabuoji3.com)
- [Python Data Science Handbook](https://jakevdp.github.io/PythonDataScienceHandbook/)