QoreSDKを使ってBTCの価格上昇・下降を予測してみる

だいぶ投稿が遅くなってしまいましたがこちらの記事は[深層学習以外の機械学習と応用技術 by QuantumCore Advent Calendar 2019](https://qiita.com/advent-calendar/2019/quantumcore_reservoir)の7日目の記事です。

# はじめに
[Qiita - QoreSDKの紹介とQoreで不整脈検出](https://qiita.com/sk1010k/items/d8ca3332e6a1f1f939a1)

まずはじめにこちらのQoreSDKの紹介記事読んで、Qoreの強みが時系列データに強く、学習が高速でデータが少なくても精度が出せるという点であるということを理解しました。
そこで通貨取引の価格上昇下降のタイミングが素早く検出できれば取引の利益を大きく損失を小さくすることができるのではないかと考えました。
次に扱う題材ですがBTC(ビットコン)に決めました。また、BTCの取引履歴はオープンデータとして公開されているため開発者が扱いやすいという利点も選択理由の一つです。

というわけでKaggleに公開されていますこちらのデータを[KaggleDataset - bitcoin-historical-data](https://www.kaggle.com/mczielinski/bitcoin-historical-data)を今回は使います。

これ以降の紹介で使っておりますソースコードは下記GooglaColabratoryで動作確認したコードになります。
[GoogleColabratory - btc.ipynb](https://colab.research.google.com/drive/14OSP85rn0rY0WN2ykaQhy12auVZCEzEf)


# データの読み込み
まずKaggleDatasetからデータをダウンロードしたことを前提に話を進めます。GoogleColabratory上でKaggleDatasetからデータをダウンロードする方法については下記記事を参考にしました。

[Qiita - Google Colab上でKaggleのデータをロード、モデル訓練、提出の全てを行う](https://qiita.com/katsu1110/items/a8d508a1b6f07bd3a243)

まずはデータを読み込む部分です、元々加工済みのCSVファイルということもありpandasでそのまま読み込めます。

```python:load_data.py
import numpy as np
import pandas as pd

btc = pd.read_csv("/content/bitstampUSD_1-min_data_2012-01-01_to_2019-08-12.csv")
btc.head()

"""
	Timestamp	Open	High	Low	Close	Volume_(BTC)	Volume_(Currency)	Weighted_Price
0	1325317920	4.39	4.39	4.39	4.39	0.455581	2.0	4.39
1	1325317980	NaN	NaN	NaN	NaN	NaN	NaN	NaN
2	1325318040	NaN	NaN	NaN	NaN	NaN	NaN	NaN
3	1325318100	NaN	NaN	NaN	NaN	NaN	NaN	NaN
4	1325318160	NaN	NaN	NaN	NaN	NaN	NaN	NaN
"""
```

# データの前処理
データを見てみると欠損値が存在するため、直前の値で欠損値を埋める[ffill](https://deepage.net/features/pandas-manipulate-na.html)を使って欠損値を埋めます。

また今回の予測対象である価格上昇・下降の定義ですが、終値(カラムではClose)が始値(カラムではOpen)よりも値が上回っていた場合は価格が上昇したと定義します。

最後にTimestampカラムをto_datetimeを使って扱いやすいように型変換しておきます。

```python:preprocess.py
btc[btc.columns.values] = btc[btc.columns.values].ffill()

btc['Delta'] = btc['Close'] - btc['Open']

def digitize(n):
    if n > 0:
        return 1
    return 0
    
btc['label'] = btc['Delta'].apply(lambda d: digitize(d))

# https://stackoverflow.com/questions/19231871/convert-unix-time-to-readable-date-in-pandas-dataframe
btc["Timestamp"] = pd.to_datetime(btc["Timestamp"], unit='s')

btc.head()
"""
Timestamp	Open	High	Low	Close	Volume_(BTC)	Volume_(Currency)	Weighted_Price	Delta	label
0	2011-12-31 07:52:00	4.39	4.39	4.39	4.39	0.455581	2.0	4.39	0.0	0
1	2011-12-31 07:53:00	4.39	4.39	4.39	4.39	0.455581	2.0	4.39	0.0	0
2	2011-12-31 07:54:00	4.39	4.39	4.39	4.39	0.455581	2.0	4.39	0.0	0
3	2011-12-31 07:55:00	4.39	4.39	4.39	4.39	0.455581	2.0	4.39	0.0	0
4	2011-12-31 07:56:00	4.39	4.39	4.39	4.39	0.455581	2.0	4.39	0.0	0
"""

btc.tail()
"""
Timestamp	Open	High	Low	Close	Volume_(BTC)	Volume_(Currency)	Weighted_Price	Delta	label
3997692	2019-08-11 23:56:00	11555.57	11555.57	11540.37	11540.58	0.036868	425.909106	11552.336234	-14.99	0
3997693	2019-08-11 23:57:00	11553.49	11556.22	11553.49	11556.22	0.623462	7204.428272	11555.520505	2.73	1
3997694	2019-08-11 23:58:00	11559.73	11561.22	11546.77	11561.22	0.159070	1838.731403	11559.252199	1.49	1
3997695	2019-08-11 23:59:00	11559.73	11589.73	11528.73	11528.73	16.198210	187504.635170	11575.638889	-31.00	0
3997696	2019-08-12 00:00:00	11527.44	11551.57	11520.00	11520.00	23.805939	274731.256920	11540.450291	-7.44	0
"""
```

モデルの学習に使う訓練データと評価に使うテストデータを作っていきます。

データを作る際の注意点としてQoreSDKの使い方について事前にメールにて以下のように注意書きがあったためそれに従うため、訓練・テストデータ共に時間長を約12時間(1サンプル1分間隔)に抑えています。

> なお、一度にAPIに投げられるデータサイズに制限がありまして、N*T*V < 150,000 && N*T < 10,000の大きさまでのデータとして頂きますようお願いいたします。

またAPIのドキュメントを読むと訓練、予測時共に型が`numpy.ndarray`でありかつ学習・推論用データの次元が(データ数, 時間, 実データ)、正解ラベルの次元が(データ数, 1)であったのでそのように変換しておきます。

- [qcore-info - qore_sdk.client.WebQoreClient.classifier_train](https://qcore-info.github.io/advent-calendar-2019/#qore_sdk.client.WebQoreClient.classifier_train)
> X (numpy.ndarray) – 学習データ X.shape = (データ数, 時間, 実データ)
> Y (numpy.ndarray) – 正解ラベル Y.shape = (データ数, 1)

- [qcore-info - qore_sdk.client.WebQoreClient.classifier_predict](https://qcore-info.github.io/advent-calendar-2019/#qore_sdk.client.WebQoreClient.classifier_predict)
> X (numpy.ndarray) – 推論用データ X.shape = (データ数, 時間, 実データ)
> Y (numpy.ndarray) – 正解ラベル Y.shape = (データ数, 1)


```python:preprocess2.py
data = btc[('2019-01-01' < btc['Timestamp']) & (btc['Timestamp'] < '2019-01-01 12:00')]

data
"""
Timestamp	Open	High	Low	Close	Volume_(BTC)	Volume_(Currency)	Weighted_Price	Delta	label
3676577	2019-01-01 00:01:00	3694.72	3694.72	3690.65	3690.65	9.500151	35080.265871	3692.600865	-4.07	0
3676578	2019-01-01 00:02:00	3689.73	3689.73	3686.62	3686.62	0.965966	3562.371230	3687.884698	-3.11	0
3676579	2019-01-01 00:03:00	3692.85	3692.85	3688.32	3692.35	0.296662	1095.220713	3691.813285	-0.50	0
3676580	2019-01-01 00:04:00	3692.35	3692.35	3690.34	3690.34	0.111622	412.065433	3691.614849	-2.01	0
3676581	2019-01-01 00:05:00	3690.40	3690.85	3690.40	3690.85	2.247676	8295.406915	3690.659723	0.45	1
...	...	...	...	...	...	...	...	...	...	...
3677291	2019-01-01 11:55:00	3706.01	3706.01	3706.01	3706.01	0.412970	1530.471061	3706.010000	0.00	0
3677292	2019-01-01 11:56:00	3706.01	3706.01	3706.01	3706.01	0.412970	1530.471061	3706.010000	0.00	0
3677293	2019-01-01 11:57:00	3706.01	3706.01	3706.01	3706.01	0.412970	1530.471061	3706.010000	0.00	0
3677294	2019-01-01 11:58:00	3706.01	3706.01	3706.01	3706.01	0.071164	263.734496	3706.010000	0.00	0
3677295	2019-01-01 11:59:00	3706.01	3706.01	3700.00	3700.00	14.862874	55009.746820	3701.151476	-6.01	0
"""

labels = data["label"]
data.drop(columns=["Timestamp", "label", "Delta"], inplace=True)
data = np.array(data)
labels = np.array(labels)

data = data.reshape((data.shape[0], data.shape[1], 1))
labels = labels.reshape((labels.shape[0], 1))

test_data = btc[('2019-01-01 12:00' < btc['Timestamp']) & (btc['Timestamp'] < '2019-01-01 23:59')]

test_data
"""
Timestamp	Open	High	Low	Close	Volume_(BTC)	Volume_(Currency)	Weighted_Price	Delta	label
3677297	2019-01-01 12:01:00	3694.63	3694.63	3694.63	3694.63	2.572921	9505.991188	3694.630000	0.00	0
3677298	2019-01-01 12:02:00	3694.63	3694.63	3694.63	3694.63	2.572921	9505.991188	3694.630000	0.00	0
3677299	2019-01-01 12:03:00	3694.63	3694.63	3694.63	3694.63	2.572921	9505.991188	3694.630000	0.00	0
3677300	2019-01-01 12:04:00	3701.03	3704.20	3701.03	3704.20	2.176152	8060.808805	3704.156299	3.17	1
3677301	2019-01-01 12:05:00	3704.20	3704.20	3704.20	3704.20	0.045025	166.779901	3704.200000	0.00	0
...	...	...	...	...	...	...	...	...	...	...
3678010	2019-01-01 23:54:00	3812.78	3821.38	3812.78	3821.38	21.387205	81679.855061	3819.099098	8.60	1
3678011	2019-01-01 23:55:00	3819.70	3819.70	3816.41	3816.41	1.699109	6488.410322	3818.713657	-3.29	0
3678012	2019-01-01 23:56:00	3816.41	3816.99	3813.81	3816.99	3.007816	11480.373641	3816.847055	0.58	1
3678013	2019-01-01 23:57:00	3816.41	3816.99	3813.81	3816.99	3.007816	11480.373641	3816.847055	0.58	1
3678014	2019-01-01 23:58:00	3816.41	3816.99	3813.81	3816.99	3.007816	11480.373641	3816.847055	0.58	1
"""

test_labels = test_data["label"]
test_data.drop(columns=["Timestamp", "label", "Delta"], inplace=True)
test_data = np.array(test_data)
test_labels = np.array(test_labels)

test_data = test_data.reshape((test_data.shape[0], test_data.shape[1], 1))
test_labels = test_labels.reshape((test_labels.shape[0], 1))
```

# モデルの学習

準備した訓練データを使ってモデルの学習をしてゆきます。今回は分類問題なので評価結果を[sklearn.metrics.classification_report](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html)を使って求めます。
今回予測したかった価格の上昇がうまく学習できていないのかまったく予測できていません。

```python:train.py
from qore_sdk.client import WebQoreClient

# api_keyの読み取り
with open(api_key_path, "r") as f:
    api_key = json.load(f)

client = WebQoreClient(**api_key)
client.classifier_train(data, labels)
# {'res': 'ok', 'train_time': 0.8283402919769287}

response = client.classifier_predict(data)

from sklearn.metrics import classification_report

report = classification_report(labels, response['Y'])
print(report)
"""
precision    recall  f1-score   support

           0       0.66      1.00      0.79       473
           1       0.00      0.00      0.00       246

    accuracy                           0.66       719
   macro avg       0.33      0.50      0.40       719
weighted avg       0.43      0.66      0.52       719
"""
```

# モデルのテスト
最後に訓練データにはない未知データを使って予測してゆきます。
不思議なことに学習では一切予測が当たらなかった価格の上昇をそれなりに当てています。

```python:predict.py
response = client.classifier_predict(test_data)
report = classification_report(test_labels, response['Y'])
print(report)
"""
precision    recall  f1-score   support

           0       0.11      0.02      0.03       441
           1       0.34      0.79      0.47       277

    accuracy                           0.32       718
   macro avg       0.22      0.41      0.25       718
weighted avg       0.20      0.32      0.20       718
"""
```

# 参考
- [KaggleDataset - bitcoin-historical-data](https://www.kaggle.com/mczielinski/bitcoin-historical-data)
- [Qiita - QoreSDKの紹介とQoreで不整脈検出](https://qiita.com/sk1010k/items/d8ca3332e6a1f1f939a1)
- [Qiita - リザーバコンピューティングの世界 ~Qoreとともに~](https://qiita.com/ryoppippi/items/f607c8828238094eade0)
- [Github - qcore-info/advent-calendar-2019](https://github.com/qcore-info/advent-calendar-2019)