Pythonで株価を機械学習

#目的

***1.　githubにコードが上がっているので、どのモデルが一番いいのか教えて欲しい***
2.　pythonを使用して、株価が次の日上がるか下がるか機械学習させて結果を表示

- 開発環境：python3
- githubのコードURL：https://github.com/maeda-naoya-fk/Machine-Learning-for-stock


#準備
- 本記事はわかりやすいようにjupyterベース(Example.ipynb)でコードを回していきます。jupyterない人も大丈夫！ config.iniをいじってコマンドで```python3 main.py```と打てばok

- ***コードの中身を詳しく説明しても長ったらしくなるので、簡単に何をしたか説明します***

```python:Example.ipynb
from datetime import datetime
from scraiping import Scraiping
from complete import Complete
from train import Tree_updown, Tree_updown_k

start = datetime(2010, 1, 1)
finish = datetime(2020, 5, 17)

target_number = '4751' #(株)サイバーエージェント
prepare_number = ['1309', #上海株式指数・上証50連動型上場投資信託
                '1325', #(NEXT FUNDS)ブラジル株式指数上場投信
                '1326', #SPDRゴールド･シェア
                '1377', #サカタのタネ
                '1465', #ダイワ上場投信-JPX日経400インバース
                '1633', #(NEXT FUNDS)不動産上場投信
                '1678', #(NEXT FUNDS)インド株式指数上場投信
                '1698', #上場インデックスファンド日本高配当
                ]

headers = {'User-Agent' : 'Please write your User-Agent'}

max_depth = 5
test_size = 0.25
k = 2
```

- 各モジュールのimport、いつからいつまでのデータをとるか、目的変数(4751)、説明変数(1309, 1325, 1326...)をセットアップしています。

- headersはこのサイトみてgetしてみてください。<'https://note.nkmk.me/python-beautiful-soup-scraping-yahoo/'> (ちなみになくても大丈夫かも。詳しくはわからないです。)

- max_depth:機械学習をする際にどのくらいの階層まで掘り下げるか
- test_size:テスト用と学習用でどのくらいの比率で分けるか
- k:株価の次の日の値が上下何%で変動するか


#スクレイピング 
使用するwebサイト：<a href="https://kabuoji3.com">株式投資メモ

- スクレイピングで株価の情報をとってきます。

```python:Example.ipynb
stock_numbers = prepare_number.copy()
stock_numbers.insert(0, target_number)
dfs = Scraiping(stock_numbers, start, finish, headers).main()
```

- scraping.pyのモジュールに引数をぶちこんでます。

```python:scraping.py
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

class Scraiping:

    def __init__(self, stock_numbers, start, finish, headers):

        self.sns = stock_numbers
        self.start = start
        self.finish = finish
        self.headers = headers

    def main(self):

        dfs = []
        for sn in self.sns:
            df = self.scra(sn)
            dfs.append(df)
            print('Done : ' + sn)

        return dfs

    def scra(self, sn):


        data = []
        for y in range(self.start.year, self.finish.year+1):

            url = 'https://kabuoji3.com/stock/{}/{}/'.format(int(sn), y)
            soup = BeautifulSoup(requests.get(url, headers = self.headers).content,'html.parser')
            tr = soup.find_all('tr')

            t = 1
            while True:

                try:
                    td_t = [t.text for t in tr[t].find_all('td')]
                    date = pd.to_datetime(td_t[0])
                    if (self.start <= date) and (date <= self.finish):
                        td_t[4] = int(td_t[4])
                        data.append(td_t)
                    elif (date > self.finish):
                        break
                except IndexError:
                    break

                t += 1

            time.sleep(1)



        df = pd.DataFrame(data, columns = ['日付', '始値', '高値', '安値', '終値', '出来高', '終値調整'])
        return df

```

#機械学習のための前準備

- 学習させるために前準備をします。とってきたデータをそのまま説明変数にしても意味がないので***前日比***を出していきます。（※とても重要）

```python:Example.ipynb
complete_df = Complete(dfs, stock_numbers).com()
```
- complete.pyのモジュールに先ほど作成したdfsを引数にぶちこんでます。
- 終値をつかって```（次の日）ー（前日）/（前日）```をして全てのデータを結合しています。

```python:complete.py
import pandas as pd
import sys

class Complete:

    def __init__(self, dfs, stock_numbers):

        self.dfs = dfs
        self.sns = stock_numbers

    def com(self):

        for i in range(len(self.dfs)):

            df = self.dfs[i][['日付', '終値']]
            num = self.sns[i]
            df = df.rename(columns = {'日付':'Date', '終値':num})

            df[num + '_Diff'] = df[num]/df[num].shift()-1

            if i == 0:
                complete_df = df

            else:
                complete_df = pd.merge(complete_df, df, on = 'Date')


        columns = self.sns.copy()
        columns.insert(0, 'Date')
        complete_isna = complete_df[columns]
        if complete_isna.isna().sum().sum() == 0:
            return complete_df

        else:
            print('Error : There are missing value > Please choose other target_number or prepare_number')
            self.isna(complete_isna)

    def isna(self, complete_isna):
        sys.exit()
```
ちなみに今回は（いろいろ試したが）欠損値が見つからなかったので、欠損値処理を無視しています。もし欠損値があったら適宜コード改善していこーと！

#機械学習（決定木）とその結果

- ここから目的の機械学習をさせて次の日の予想をしていきます。


##単にupdown

- これは単に株価の終値が前日より上がったら1、下がったら-1として目的変数にいれていきます。

```python:Example.py
tu = Tree_updown(complete_df, stock_numbers, test_size, max_depth)
X, y = tu.prepare()
tu.predict(X, y)
```

- train.pyのモジュールにcomplete_dfやその他もろもろをぶち込んでいます。
- X, y は説明変数、目的変数となっています。
- そしてpredictで予想！

###結果###
<img width="410" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655736/1d3ca973-06d5-c4c5-7d47-a6786ef8807a.png">

###感想###
次の日は下がると予想されました。
正解率はどちらも低くなっていますね...
っていうか、正解率がほぼ50%って笑笑　やった意味がないですね。
学習用正解率、テスト用正解率に差がなく、どちらも1.0に近ければいいモデルができていると思うので、みなさんも是非いろいろパラメータを変えていい結果が出たら教えてください。
説明変数がどれくらい目的変数に寄与しているかはcoefficientの列を見てもらえればわかります。

##2%updown(おまけ)

- 次に上下何%でupdownするかを考慮します(ここでは2%)
- 株価の終値が前日より2%上がったら1、下がったら-1、そうでなかったら0として目的変数にいれていきます

```python:Example.ipynb
tu_k = Tree_updown_k(complete_df, stock_numbers, test_size, max_depth, k)
X, y = tu_k.prepare_k()
tu_k.predict(X, y)
```

###結果###
<img width="330" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655736/83c47fd2-3d86-f69b-0eb1-ef1271d5b04e.png">

###感想###
正解率が上がっているのはconstantの数が多くなっているからですね。
本当に欲しい結果としては、投資する時にupかdownの情報だと思うので、あまりこの結果は意味のないような気がするが、勉強にはなりました。

- 一応train.pyも載せておきます。

```python:train.py
from sklearn.tree import DecisionTreeClassifier
import sklearn.model_selection
import pandas as pd

class Tree_updown:

    def __init__(self, complete_df, stock_numbers, test_size, max_depth):

        self.c_df = complete_df
        self.sns = stock_numbers
        self.ts = test_size
        self.m = max_depth

        self.diffs = [sn + '_Diff' for sn in self.sns]


    def prepare(self):

        y = self.c_df[self.sns[0]].shift(-1) - self.c_df[self.sns[0]]
        y.loc[y >= 0] = 1
        y.loc[y < 0] = -1
        y = y[1:-1]

        X = self.c_df[self.diffs].iloc[1:-1]

        return X, y

    def predict(self, X, y):

        X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size = self.ts, train_size = 1 - self.ts, random_state = 0)

        model = DecisionTreeClassifier(random_state = 0, max_depth = self.m)
        model.fit(X_train, y_train)

        next_day_diff = self.c_df[self.diffs].iloc[-1]
        next_day_predict = model.predict([next_day_diff])

        print('predict next day : UP') if next_day_predict == 1 else print('predict next day : CONSTANT') if next_day_predict == 0 else print('predict next day : DOWN')

        print('')
        print('テスト用正解率:{}'.format(model.score(X_test, y_test)))
        print('学習用正解率:{}'.format(model.score(X_train, y_train)))

        importance = pd.DataFrame({'feature_names':self.sns, 'coefficient':model.feature_importances_})
        print('')
        print(importance)

        print('')
        print('データ数:{}'.format(len(y)))


class Tree_updown_k(Tree_updown):

    def __init__(self, complete_df, stock_numbers, test_size, max_depth, k):
        super().__init__(complete_df, stock_numbers, test_size, max_depth)
        self.k = k

    def prepare_k(self):

        y = self.c_df[self.sns[0]].shift(-1)/self.c_df[self.sns[0]] - 1
        y.loc[y*100 >= self.k] = 1
        y.loc[y*100 <= -self.k] = -1
        y.loc[(-self.k < y*100) & (y*100 < self.k)] = 0
        y = y[1:-1]

        X = self.c_df[self.diffs].iloc[1:-1]

        return X, y

class Verify(Tree_updown_k):

    def __init__(self, complete_df, stock_numbers, verify_df, test_size, max_depth, k):
        super().__init__(complete_df, stock_numbers, test_size, max_depth, k)
        self.verify_df = verify_df

    def verify(self, X, y):

        X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size = self.ts, train_size = 1 - self.ts, random_state = 0)

        model = DecisionTreeClassifier(random_state = 0, max_depth = self.m)
        model.fit(X_train, y_train)

        y_test = self.verify_df[self.sns[0]].shift(-1)/self.verify_df[self.sns[0]] - 1
        y_test.loc[y_test*100 >= self.k] = 1
        y_test.loc[y_test*100 <= -self.k] = -1
        y_test.loc[(-self.k < y_test*100) & (y_test*100 < self.k)] = 0
        y_test = y_test[1:-1]
        X_test = self.verify_df[self.diffs][1:-1]

        next_day_predict = model.predict(X_test)
        print(next_day_predict)
        print(y_test)

        print(next_day_predict == y_test)
```

#まとめ

- 簡単にお金稼ぎなんてできないのかー
- いいモデルを皆さん探してみてください。そして教えてください。
<a href="https://github.com/maeda-naoya-fk/Machine-Learning-for-stock">株価予測のための機械学習