Prophetを用いた時系列解析によるビットコイン価格予測

この記事は、[Life is Tech ! Advent Calendar 2017](https://qiita.com/advent-calendar/2017/lit-mentor) 23日目の記事です。
#はじめに
こんにちは、メディアアート"っぽい"ことをやっているシオンです。
いつもは高専にて回路やプログラミングを学んでいます。
何を書くか色々迷ったのですがなにかと最近話題なビットコインで一攫千金を狙いたいと思います。

#本記事について
ビットコインってそもそも何？という方はこちらの記事がわかりやすいです。
[仮想通貨「Bitcoin」とは一体何か、どういう仕組みかが一発で分かるまとめ](https://gigazine.net/news/20131007-what-is-bitcoin/)

いわゆる仮想(デジタル)な通貨ということですがあくまで通貨なので現実のように為替があります。
つまり、安いときに買って高いときに売ればその差が利益になるわけです。
試しにある日のチャートを見てみましょう。
<img width="451" alt="スクリーンショット 2017-12-23 9.15.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/35859/b367ee43-d86f-32c2-ce37-09585cc17ea3.png">
一番安いときで2041293円、高いときで2406356円です。
一番安いときに買って、高いときに売れば**40万円**の利益になります！
どうです、一攫千金狙いたくなってきませんか。

しかし、利益を得るには**今の時点から未来のある時点の価格を予測する必要**があります。
そこで統計学における時系列解析が必要となるわけですが
今回は、Facebook社が作った<code>Prophet</code>というライブラリを用いて解析を行なっていきます。

**本記事では数式などは一切扱わずとりあえず時系列解析をやってみたい方にオススメです:v:**

#Prophetとは
![prophet.png](https://qiita-image-store.s3.amazonaws.com/0/35859/488dafcc-ab95-fd64-5da2-9970c28413ff.png)

[Prophet](https://facebook.github.io/prophet)はFacebook社が開発した時系列解析ライブラリです。
最大の特徴として統計学の知識を必要とせず簡単に解析・予測を行うことができます。
通常の時系列解析となるとARIMAや状態空間ベクトルといった専門的な知識を必要としますが全く知らなくてもある程度は大丈夫です！
(Prophet自体はSARIMAのようなモデルです。)

また、Prophetにはトレンドといった傾向を予測に含むことができます(詳しくは後ほど)。
他にも便利な機能が山盛りなのでProphetを使っていきます。
現在は、RとPythonにて扱うことができます。

それでは早速一攫千金の一歩を踏み出しましょう:moneybag:

#Prophetを用いた時系列解析によるビットコイン価格予測
##準備
今回は、Python(3.5)にて実装をしていきます。
<code>pip3 install fbprophet</code>
にてProphetをインストールしてください。

##過去の価格データについて
統計学においてもっとも重要なのが統計データです。
今回の場合だとビットコインの過去の価格データです。
[Cryptowatch](https://cryptowat.ch/)というサイトからデータを取得します。
![Cryptowatch   live Bitcoin price charts (1).png](https://qiita-image-store.s3.amazonaws.com/0/35859/d0c6d826-4ca7-b0b8-347b-caeac1fb3fd5.png)
Cryptowatchでは各取引所の各通貨データを収集し、可視化をしています。

Cryptowatch APIでは次のURLにリクエストを送ることでJSON形式でデータが返ってきます。
<code>https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohlc</code>
また、下記のパラメータを指定することができます。
※全ての時間はUnix Timestampで指定します
**・periods**
取得する間隔を指定できます。
例: 1時間ごと => 3600、1分ごと => 600
**・after**
指定した日時より後のデータを取得します。
例: 2017-12-01 00:00:00 => 1512054000
**・before**
指定した日時より前のデータを取得します。
例: 2017-12-23 10:00:00 => 1513990800

例えば、12月1日の0時から７日の24時までの取引データを1時間ごとに取得しようとすると
<code>https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=3600&after=1512054000&before=1512655200</code>となります。
ここで、返ってきたJSONの要素に一つを見てみると下のようになっています。
<code>[1512050400,1188000,1189246,1076640,1113070,18874.441,21350033000]</code>
これは、
<code>[ CloseTime, OpenPrice, HighPrice, LowPrice, ClosePrice, Volume, 意味不明]</code>
という順番になっています。最後の要素は意味がわかりません:open_hands_tone2:

###**過去の価格データの取得・保存**
それでは、先ほどのAPIを用いて過去の価格データを取得・保存してみます。

```python3
import requests
import json
import datetime as dt
import pandas as pd
import numpy as np

startDate = dt.datetime.strptime(startDate, '%Y-%m-%d %H:%M:%S')
endDate = dt.datetime.strptime(endDate, '%Y-%m-%d %H:%M:%S')

startTimestamp = startDate.timestamp()
endTimestamp = endDate.timestamp()

query = {"periods": "3600", "after": str(int(startTimestamp)), "before": str(int(endTimestamp))}
res = json.loads(requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc", params=query).text)["result"]["3600"]
res = np.array(res)

time_stamp = res[:, 0].reshape(len(res), 1)
time_stamp = convertUnix2Date(time_stamp)
close_price = res[:, 4].reshape(len(res), 1)

tmp_data = np.hstack((time_stamp, close_price))
data = pd.DataFrame(tmp_data, columns={"y", "ds"})
data.to_csv("../data/bitflyer-" + str(int(startTimestamp)) + "-" + str(int(endTimestamp)) + ".csv", index=False)
```

startDateとendDateにて期間を指定し、１時間足で取得しています。
また、今回は時間と各時間における終値のみを使うためNumpyにて0番目と4番目の要素を抽出しています。
試しに12月1日0時から12月14日24時までの2週間を取得しCSVファイルに保存しました。

| ds | y |
|:-:|:-:|
|  2017-12-01 00:00:00  |  1157356.0  |
|  2017-12-01 01:00:00  |  1101090.0  |
|  2017-12-01 02:00:00  |  1108239.0  |
|  ・・・  |  ・・・  |
|  2017-12-07 23:00:00  |  1881000.0  |

###**過去の価格データのグラフ化**
先ほど保存したCSVを読み込みグラフに描画してみます。

```python3
import matplotlib.pyplot as plt
import pandas as pd

data = pd.read_csv("../data/bitflyer-startDate-endDate.csv")
data.plot()
plt.show()
```
Prophetでは統計データをデータ分析用ライブラリPandasにて扱う必要があります。
そこでCSVをPandasを用いて読み込んでいます。
![data.png](https://qiita-image-store.s3.amazonaws.com/0/35859/c3adf64f-726e-53a9-6447-091cc9d6e391.png)
**わずか２週間で120万が倍の240万円に、、、**
恐ろしいですね、先が不安になってきました。

まあ、そんなことは無視して過去のデータ(2週間分)を１時間ごとに取得・保存、そして描画することができました！
次はいよいよ解析・予測をしていきます。

##Prophetによる予測
###**予測モデルを作成**
特に何も設定などを行わずこのまま予測モデルを作成します。
予測モデルとは簡単に言ってしまえば予測するためのパターン(ルール)のようなものです。

```python3
from fbprophet import Prophet

model = Prophet()
````
###**予測モデルに学習させる**
作成した予測モデルに過去データを用いて学習させます。

```python3

model.fit(data)
```
###**予測する**
学習が終わったら実際に予測します。
予測したい期間を指定し、空のDateFrameを作ります。

```python3
future_data = model.make_future_dataframe(periods=24, freq='H')
```
make_future_dataframeでは以下のパラメーターを指定できます。
**・periods**
予測する期間です。単位はfreqに基づきます。
例: 1時間ごと => 3600、1分ごと => 600
**・freq**
予測する期間の単位です。
例: 1時間 => H、1月 -> M

作成した空のDateFrameを元に予測します。また<code>model.plot(data)</code>により簡単に予測結果をグラフ化できます。今回は1日分を1時間ごとに予測しています。

```python3
forecast_data = model.predict(future_data) #予測

model.plot(forecast_data)
plt.show()
```
![predict.png](https://qiita-image-store.s3.amazonaws.com/0/35859/aeefd7d8-ef98-dcc5-3c53-46d1f245d325.png)
黒い点が元のデータ、青い線がモデルです。
さすがに9日の突発的な上昇はずれていますがいい感じにフィットしています。
**では、問題の予測結果はどうだったのでしょうか、、、**

###**予測結果**
オレンジの線が実際の価格、青い線が予測した価格です。
![predict_answer.png](https://qiita-image-store.s3.amazonaws.com/0/35859/26c58e07-8f3f-b5f1-0ba5-e5e4262749a8.png)
予測した部分のみ拡大してみます。
![predict_answer.png](https://qiita-image-store.s3.amazonaws.com/0/35859/0c62951a-231f-a600-7525-5f7163024870.png)
急激な価格上昇があったようですがその上昇を予測できていません。
一攫千金を狙う身としてはまさにこの上昇を捉えて欲しいところです:frowning2:

##精度向上をはかる
Prophetには**トレンド**というものがあります。
これは日本でも使われているトレンド(流行など)と同義です。
与えた時系列データのトレンドを考慮して予測してくれます。

###**トレンド選択**
以下の2つのトレンドを選択できます。
**・線形トレンド(liner)**
**・非線形トレンド(logistic)**
デフォルトでは線形トレンドとなっています。
詳しい説明は省略しますが、例えば非線形としたい場合は以下のように指定します。

```python3
Prophet(growth="logistic")
```

###**変化点の指定**
ある日付によりトレンドが変化することがあります。
ビットコインだと難易度調整などがその一種です。
変化点を指定するには以下のようにします。

```python3
Prophet(changepoints="2017-12-01")
```
また、<code>n_changepoints</code>を指定することにより変化点を指定した数だけ等間隔に配置することができます。

```python3
Prophet(n_changepoints="10")
```

###**周期性**
以下の2つの周期を選択できます。
**・年周期(yearly_seasonality)**
対象のデータは季節によって左右される。
**・週周期(weekly_seasonality)**
対象のデータは曜日によって左右される。
デフォルトはオートになっています。ビットコインの場合、季節の左右よりも曜日による影響が大きいように見えます。
例えばその場合は以下のように指定します。

```python3
Prophet(yearly_seasonality=False, weekly_seasonality=True)
```

###**イベント効果**
不定期に発生するイベントを考慮することができます。
例えば、国民の祝日は取引量が増えるがクリスマスは逆に取引量が減るとします。
ます、そう言ったイベント日を指定するためDataFrameを作成します。

| holiday | ds | lower_window | upper_window |
|:-:|:-:|:-:|:-:|
| 勤労感謝の日 | 2017-11-23 | 0 | 1 |
| 天皇誕生日 | 2017-12-23 | 0 | 1 |
| クリスマス  | 2017-12-15 | -1 | 0 |

作成したDataFrameを指定します。

```python3
Prophet(holidays=event_dataframe)
```

<br>
主に、トレンド、変化、周期、イベントの4つのパラメータを用いてモデルを調整します。
それぞれの名前からしても非常に直感的に扱うことができ、これらのパラメータを調整することによりさらなる精度向上をはかることができます:ok_hand:

##予測精度の評価
ProphetにはMAPEやSHFといった代表的な予測精度を評価するものが**実装されていません。**
そのため、現在は自分で実装し評価する必要があります。
そのことについては後日書く"かも"しれません。


#2018年1月大予想
先ほど精度が悪かったのは、
**・学習期間及び予測期間の指定**
**・トレンドなどのパラメータを指定していなかった**
**・そもそもProphetがいまいち**
の3つが原因として考えられます。
最後のを除いて諸々調整したもので2017年を学習し、2018年1月を大予想してみます。
![predict_answer_2018_1.png](https://qiita-image-store.s3.amazonaws.com/0/35859/485728bd-b8d1-0917-3626-9768434e2da5.png)
はい、当てになりません:frowning2:
上昇傾向ではあるらしいですが最近大暴落が多いのでどうなるのか、、


#まとめ
>####世の中には3種類の嘘がある。 嘘、大嘘、そして統計だ。
>*ベンジャミン・ディズレーリ*

経済における統計学ではその信頼性に長年議論があります。
少なくともProphetを用いたビットコイン価格予測は厳しそうです:bow_tone1:
RNNを用いたディープラーニングの方が精度が高そうなので次回あたりやってみます。


ちなみにこの記事を参考に予測したら大当たりした方いたらコメントください。
僕は最近PS4が欲しいです。