【Python】Bitcoin の価格予測を LightGBM を使って行ってみた。

# 背景
昨今、**ChatGPT** の出現から、AIやブロックチェーン技術の利活用が加速しています。

SoftBankの孫正義氏は、
「人類の叡智の総和の **10倍** のAIが、**10年以内** に達成される」と発言しています。[参考動画](https://www.youtube.com/watch?v=h3052XnZhVI)

関連ワードとして、「第四次産業革命」や「Web3.0」という単語を耳にした人が
いるかもしれません。

そんな中でにわかに盛り上がっているのが、**暗号通貨(暗号資産)** です。
**Bitcoin** なんかが代表的な暗号資産ですね。
米ドルや円じゃなくて、暗号資産の時代がくる、なんて言っている人もいます。

そんな情勢の中で、ならばAIを用いてBitcoinの **価格予測** ができないのか、
というのに挑戦したのがこの記事になります。


※2024/03/07 現在での内容です。
※この記事に含まれる内容は、私見が含まれます。

# 目次
1\. データセットの取得
2\. データセットの前処理
3\. 特徴量の作成
4\. 訓練データとテストデータの分割
5\. LightGBM モデルを適用,予測の実行
6\. モデルの評価
7\. まとめ
8\. 考察

# 開発環境
Windows 11
Google Colaboratory

# 1．データセットの取得
- Bitcoin/米ドルの価格（BTC_data）
- 日経平均株価 (NI225_data）
- NYダウ（NYdow_data）
- ドル円相場（UJ_data）
いずれも2014-09-17~2024-02-27の日ごとの**高値**のデータを使用します。
下記3つはいずれも説明変数として設定しました。

(使用データの期間に意図はないため、期間を変えることで精度の改善に繋がる可能性が考えられます。)

データセットは、Yahoo Finance US から取得することができました。

まずは、必要なライブラリをインポート。
```import.py
import pandas as pd
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import yfinance as yf
import seaborn as sns
from sklearn import metrics
import lightgbm as lgb
from sklearn.model_selection import train_test_split
```

その後データセットを取得し、**日米間のタイムゾーン** を統一します。
また、それぞれの高値のデータのみ取り出します。

```dataset.py
#BTC-USD(目的変数)、日経平均株価、NYダウ、ドル円相場（説明変数）のデータセットを読み込み、整型

btc_chart = yf.Ticker("BTC-USD")
btc_chart = btc_chart.history(start="2014-09-17", end="2024-02-27")
btc_chart.index = btc_chart.index.tz_convert('UTC')
btc_chart_h = btc_chart.iloc[:, 1].to_frame()

NI225_chart = yf.Ticker("^N225")
NI225_chart = NI225_chart.history(start="2014-09-17", end="2024-02-27")
NI225_data = NI225_chart.rename(columns={'High': 'NI225_High'})
NI225_data_h =NI225_data.iloc[:,1]
NI225_data_h.index= NI225_data_h.index.tz_convert('UTC')

NYdow_chart = yf.Ticker("^DJI")
NYdow_chart = NYdow_chart.history(start="2014-09-17", end="2024-02-27")
NYdow_data = NYdow_chart.rename(columns={'High': 'NYdow_High'})
NYdow_data_h =NYdow_data.iloc[:,1]
NYdow_data_h.index= NYdow_data_h.index.tz_convert('UTC')

UJ_chart = yf.Ticker("JPY=X")
UJ_chart = UJ_chart.history(start="2014-09-17", end="2024-02-27")
UJ_data = UJ_chart.rename(columns={'High': 'UJ_High'})
UJ_data_h = UJ_data.iloc[:,1]
UJ_data_h.index= UJ_data_h.index.tz_convert('UTC')
```


# 2．データの前処理
説明変数3つを**DataFrame型**にします。

ここで、株式市場と暗号通貨市場の違いに悩まされました。
というのも、暗号通貨市場には休みがないため全てのデータが取れるのです。
一方、株式市場は土日祝が休みなため、**データの欠損**が出てきます。
（しかも、ニューヨークと日本では祝日も異なるため、一つのデータに結合するのに苦労しました…。）
調べていくうちに、**pandas.merge.asof**メソッドを使うと、一番近い値をキーにして結合してくれるというのが見つかったので、これを使用しました。
結合した後は、欠損値の処理を行います。

```bitcoin.py
#DataFrame型に変更
NI225_data_h = NI225_data_h.to_frame()
NYdow_data_h = NYdow_data_h.to_frame()
UJ_data_h = UJ_data_h.to_frame()

#日付のリスト作成
dt_list = pd.date_range(start="2014-09-17", end="2024-02-26", freq='D')
dt_list = dt_list.to_frame(name='date')

#説明変数の結合、欠損値の処理
df_merged = pd.merge_asof(NYdow_data_h, UJ_data_h,left_index=True, right_index=True,direction='nearest')
df_merged = pd.merge_asof(df_merged, NI225_data_h, left_index=True, right_index=True, direction='nearest')
df_merged = pd.merge_asof(df_merged, btc_chart_h, left_index=True, right_index=True, direction='nearest')
df_merged.index = df_merged.index.date
df_all = pd.merge(dt_list, df_merged, left_index=True, right_index=True, how='outer').fillna(method='ffill')
df_all.index = df_all.index.date
df_all = df_all.rename(columns={'High':'BTC_High'})
```


# 3．特徴量の作成
正解データとして、今回は詳細な値動きを予測するのは難しいと考え、
翌日の高値が当日の高値を上回っていた場合は1、そうでない場合は0として変換しました。
```feature.py
#正解データとして、翌日の高値が当日の高値を上回っていた場合は1、そうでない場合は0とする。
btc_score = ((btc_chart["High"] - btc_chart["High"].shift(-1)) < 0)*1
btc_score.index= btc_score.index.tz_convert('UTC')
btc_score.index = btc_score.index.date
btc_score = btc_score.to_frame()
btc_score = btc_score.rename(columns={'High': 'BTC_score'})

df_h_all = pd.merge(df_all, btc_score, left_index=True,right_index=True)
```

# 4．訓練データとテストデータの分割
必要なデータが一つに結合されたため、説明変数と目的変数を設定し、
データを分割していきます。

```split.py
#説明変数と目的変数の設定
X_df = df_h_all.drop(['date','BTC_High',"BTC_score"], axis=1)
y_df = df_h_all['BTC_score']

#訓練データとテストデータを分割
X_train, X_test, y_train, y_test = train_test_split(X_df, y_df, test_size=0.2, shuffle=False)
```

# 5．LightGBM モデルを適用,予測の実行
ここからは、モデルの実装になります。
今回使用した **LightGBM** は、勾配ブースティングを実装するため Microsoft 社が開発した
強力な高性能機械学習ライブラリです。

機械学習の世界コンペ（Kaggleなど）でランキング上位を独占しており、信頼性が高い上に動作も軽いため人気を集めています。

```lightgbm.py
#XGBoostで学習するためのデータ形式に変換
dtrain = lgb.Dataset(X_train, y_train)
dvalid = lgb.Dataset(X_test, y_test)

#モデルパラメータの設定
callbacks=[
    lgb.early_stopping(stopping_rounds=100, verbose=True),
    lgb.log_evaluation(5),
]
params = {'objective': 'binary',
    'metric': 'auc',
    "boosting_type": "gbdt",}

model = lgb.train(params,dtrain,valid_sets=dvalid, num_boost_round=1000, callbacks=callbacks)
 #テストデータに対し予測を実施
y_pred = model.predict(X_test)
```
パラメータは、0か1のバイナリに設定します。
また、**early_stopping**も設定し、過学習を防ぎ、計算時間を短縮します。（下図）

![スクリーンショット 2024-03-07 221858.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681431/640c0229-160b-9075-4936-5dc54ba082e6.png)

# 6．モデルの評価
ここまでで、モデル構築が完了したので、精度を評価していきます。
今回は、精度の指標として、**ROC曲線**と**混同行列**を用います。

ついでに、どの説明変数が一番寄与していたかをプロットします。

```predict.py
# AUCを計算
fpr, tpr, thresholds = metrics.roc_curve(np.asarray(y_test), y_pred)
auc = metrics.auc(fpr, tpr)
print("AUC", auc)

# ROC曲線をプロット
plt.plot(fpr, tpr, label='ROC curve (area = %.2f)'%auc)
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)
plt.show()

# accuracy, precisionを計算
acc = metrics.accuracy_score(np.asarray(y_test), np.round(y_pred))
precision = metrics.precision_score(np.asarray(y_test), np.round(y_pred))
print("accuracy", acc)
print("precision", precision)

# 混同行列をプロット
y_pred = np.round(y_pred)
cm = metrics.confusion_matrix(np.asarray(y_test), np.where(y_pred < 0.5, 0, 1))
cmp = metrics.ConfusionMatrixDisplay(cm, display_labels=[0,1])
cmp.plot(cmap=plt.cm.Blues)

# 性能向上に寄与する度合いで重要度をプロット
lgb.plot_importance(model, height = 0.5, figsize = (4,8))
plt.show()
```



# 7．結果
精度の出力結果は、以下となります。

全体的な結果としては、かなり良いとまでは言えない感じです。
（人による精度がどれくらいか、どこまでの数字が基準なのかによりますが…）

| Score | Value|
|:-----------|------------:|
AUC | 0.5119451190717128
accuracy | 0.5391304347826087
precision | 0.5714285714285714


ROC曲線を作成した時に、グラフの下の部分の面積を**AUC（Area Under the Curve）** と呼びます。AUCは0から1までの値をとり、値が1に近いほど判別能が高いことを示します。

![ROC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681431/933d51c9-11b1-aba3-5dbe-cafd99347112.png)

また、混同行列を見ることで、どの予測がどれくらい正しかった（あるいは間違っていた）
のかが視覚的に理解できます。

**正解率（accuracy）** も**適合率（precision）** もここから算出できます。

![混同行列.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681431/31426888-b0d6-c514-1fc4-835b905acd24.png)



さらに、今回はどの説明変数が一番寄与していたか。グラフで表しました。
結果として、**NYダウの高値**が一番寄与していたという事になりますね。
Bitcoinの大半がアメリカにあることを考えると、妥当ではないかと思います。

![Feature_importance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681431/47b19905-f254-f0ea-c3d3-c8e203818b20.png)

# 8．考察
予測精度が芳しくなかった理由としては、
- 不適当なモデルの選択
- データの前処理の仕方
- 不適切な説明変数（3つという数、選んだ変数の妥当性）

が考えられます。

今回は時系列データですので、もっと正確に予測したいのであれば、
**時事データ**や**周期性**なども考慮すべきかもしれません。

今回は、LightGBMモデルを用いましたが、
**SARIMAXモデル**や**LASSOモデル**でも予測は可能とのことなので、
予測精度の上昇のために、モデルを正しく使い分ける必要があると痛感しました。

また、今回の分析では、欠損データ等による**データの前処理**にかなり時間を割きました。
実務面でもここに一番時間をかけるという風に聞いていたため、
実際にこの予測を行うことでその苦労を実感しました。

ただもっと経験を積んでゆけば、時間短縮できるとも思いますので、
ここは精進あるのみですね。

今回は以上です！何かの参考になれば幸いです。

最後までお読みいただき、ありがとうございました！

## 参考文献
- [Forecasting Bitcoin Prices with Time Series](https://rpubs.com/natmurad/bitcoinpred)
- [Yahoo Finance USから株価をダウンロードしてみた](https://qiita.com/innovation1005/items/5be026cf7e1d459e9562)
- [Twitter感情分析を用いて、FXのドル円予測をPythonでやってみた](https://note.com/novajun/n/nf26984e30f07)
- [Intro to Time Series Forecast](https://www.kaggle.com/code/hmoritajp718/intro-to-time-series-forecast)
- [【pandas】最も近い値でマージするmerge_asof関数](https://qiita.com/satokiyo_DS/items/5844f697cc49258b638b)
- [Python初心者による時系列解析〜SARIMAモデルの実装〜](https://note.com/fair_beetle906/n/n659b08e8830a)
- [年利30%超え！！！！！LightGBMを用いたトヨタ株自動売買シミュレーション](https://qiita.com/kt38k/items/3c0ee4251475b6407007#%E7%89%B9%E5%BE%B4%E9%87%8F%E3%81%AE%E4%BD%9C%E6%88%90)
- [時系列データを機械学習で予測(LightGBM By Python)](https://meknowledge.jpn.org/2021/04/13/lightgbm-by-python/)
- [コピペで動くLightGBM ｜Pythonで最強予測モデルの実装](https://pythonbunseki.com/python-lightgbm/)