仮想通貨

機械学習で仮想通貨の価格予測→ムリ (Keras/GRU)

投稿日:2021年10月14日 更新日:

技術, サークル, テクノロジー, 要約, 理科, スペース, 分析, 未来的, デザイン, 青

機械学習+仮想通貨で儲けられる?

残念ながら、機械学習でのお金儲けは、かなり難しいです。

少なくとも、ローソク足だけを使って機械学習で価格予測をしようとしても、ほとんどマトモな予測はできません。

機械学習は大量のデータを読み込んで、データ間のズレを最小化するような「最も無難な予測」を立てるものです。仮想通貨等の市場は値動きが十分にランダム過ぎて、「最も無難な予測」=「現在価格の後追い」となってしまうんです。

下記で、Python+Keras+GRUで仮想通貨の価格予測を試みて、この主張の感覚を掴んでみようと思います。

機械学習での予測結果とバックテスト

事業, ピース, 仕事, 経済, セール, 進捗, 発達, 多様性, グループ, チーム, 個人, 側面

プログラムに入る前に、結果を示しておきます。下図が、機械学習の予測結果(左:青線=実測値、橙線=予測値)と、バックテストの利益推移(右)です。①予測値が実測値に先行していない(=上手く予測できていない)ことと、②利益が大きくブレつつも横ばいであることが確認できます。

なお、下記のGoogle Colabに全てのコードを置いてあります。ブログ本文中のコードは最小限なので、こちらも参照してみてください。
https://colab.research.google.com/drive/1s4NtZ5dA2h9pHOshJy1g_IxYTU7m7_jA?usp=sharing

機械学習のプログラム

一杯のコーヒー, ラップトップ, オフィス, マックブック, ワークスペース, コーヒー ブレーク

前述の予測結果を出力するプログラムを、解説していきます。なお想定読者として、私と同じ程度のプログラミング知識・経験を見込んでいます。

時系列データの取得

まずCryptowatchというサイトから、時系列データ(OHLCV/始値・高値・安値・終値・出来高)を取得します。

ここでは、bitFlyerのBTC-FX/JPYの1時間足をDataFrameで取得してみます。取得用の関数ohlcvの定義は、Colabリンクを参照してください。

df = ohlcv('bitflyer', 'btcfxjpy', 60, 6000, True)
df

特徴量の作成

今回利用するGRUはニューラルネットワークの一種で、特徴量(に相当するもの)を自身で見付け出してくれるのが利点です。とは言え、良質のヒントがあるに越したことはないということで、適当な特徴量を追加します。

今回は取りあえず、移動平均だけを加えてやります。

またNaNは機械学習の毒なので、特徴量作成に伴って出来たNaNも併せて、ここで処理を行います。

df = df.drop('Datetime', axis=1)
feat_window = [5, 20, 100]
df = add_features(df, feat_window)
df = df.dropna().reset_index(drop=True)
df

なお特徴量を作成する際には、うっかりと(ある時点から見た未来の)情報をリークさせることがないが無いよう、細心の注意を払う必要があります。

例えばSTL分解という、時系列のトレンド抽出を簡単に実施できる手法があります。これで得られたトレンドを特徴量として予測を行うと、とんでもなく良い精度が出ます。これはSTL分解でトレンドがとても上手く抽出されているからなのですが、上手く抽出できるのは、情報がリークしているからに過ぎません。STL分解は与えられた元データを俯瞰してトレンドを抽出しているため、知らず、未来の情報を利用してしまっています。

その点で移動平均線の作線は、どの時点でもその時点までの情報しか利用しないため、リークはありません。その代わり全体として動きが遅れ、特徴量としてそこまで強いものではなくなっています。

なお移動平均線のような単純なものでも、油断してDataFramediff, pct_changeshiftでマイナス値を指定すると、同じ穴にハマります。

時系列データの分割

上記の時系列データを、①訓練用(df_tr, df_tr_org)、②検証用(df_vl, df_vl_org)、③試験用(df_ts, df_ts_org)に分割していきます。xxx_orgはオリジナルのデータを保持するための変数です。後々のバックテストで使います。

trvl_rateはデータ全体の中で、①②訓練・検証用データとして使う割合。valid_rateは、①②訓練・検証用データの中で、②検証用データとして使う割合です。

trvl_rate  = 0.8
valid_rate = 0.2
df_tr, df_tr_org, df_vl, df_vl_org, df_ts, df_ts_org = split(df, trvl_rate, valid_rate)

機械学習では、①訓練用データを元にパラメータの最適化を行い、②検証用データで最適化具合の精度確認を行います。①訓練用データで最適化と精度確認の両方を実施してしまうと、①訓練用データを丸暗記したモデルが100%の精度を叩き出してしまいます(過学習)。欲しいのは(将来にも通用する)汎用的なパターンを学習したモデルなので、ここを担保するために①訓練用データと②検証用データを分けています。

とは言えこれでもなお、「①訓練用データと②検証用データの共通部分を丸暗記したモデル」が作成されている可能性が残ります。そこでダメ押しとして、③試験用データでモデルの最終確認を実施するという運びです。

(ただし実感として、「①②訓練・検証用データでは良いけど、③試験用データでは上手くいかない」ということはないです。①②訓練・検証用データで良ければ、③試験用データでも上手くいくし、逆もまた然りです。①②訓練・検証用データで良いのに、③試験用データで上手くいかない場合、プログラムのどこかにミスがあることがほとんどです)

今回は時系列データを扱うので、シャッフルは、ランダムには行いません。

時系列データの整形

ところで、時系列データを扱う際には「テーブルデータ – XGBoostやLightGBMを利用する」として扱うか、「回帰型ニューラルネットワーク(RNN) – LSTMやGRUを利用する」として扱うかの大きな二択があります。ここのイメージは、下記の通りです。

※「ブロック」は独自用語

経験的に回帰型の方が精度が良いので、今回は回帰型(GRU)を利用していきます(とは言ってもどちらも損益結果はヒドいもので、全く儲かりません)。7日前(1時間足なので24×7本)までを参照し、24時間後の終値を学習するように時系列データを整形します。

xx_xは学習データ、xx_yは教師データ、xx_sは正規化のscalerです。また、実際に運用(production)する際には答え合わせの必要がないため、pred_after=0の整形データ(pr_x, pr_y, pr_s)も用意します。

ref_before = 24*7
pred_after = 24

tr_x, tr_y, tr_s = reshape(df_tr, ref_before, pred_after)
vl_x, vl_y, vl_s = reshape(df_vl, ref_before, pred_after)
ts_x, ts_y, ts_s = reshape(df_ts, ref_before, pred_after)
pr_x, pr_y, pr_s = reshape(df_ts, ref_before, 0)

(円滑な学習のために、学習・教師データは-1~1近辺である必要があります。そのためデータを正規化するのですが、後々のステップで出力される予測値も正規化されてしまうため、予測値に対して逆正規化をかけなければなりません。そこでxx_sという形で逆正規化のための変数を保持しておきます)

reshape関数

ここのreshape関数は、回帰型ニューラルネットワークで最も分かりにくいところの一つです。先ほどは回帰型の学習データ(x)だけに触れましたが、教師データ(y)についても記載すると下図のようになります。なお、教師データには終値(Close)を利用することとします。

また学習データは、各ブロック毎に正規化(standardize)をかけます。ここの正規化はブロック毎ではなく元データ全体にかけたくなるのですが、これをやってしまうとリークです。

def standardize(df):
    return StandardScaler().fit_transform(df)

def reshape(df, ref_before, pred_after=0):
    x = []; y = []; s = []
    for i in range(len(df)-ref_before-pred_after):
        x.append(standardize(df.iloc[i:i+ref_before+1]))
        scaler = StandardScaler().fit(pd.DataFrame(df.Close.iloc[i:i+ref_before+1]))
        y.append(scaler.transform(pd.DataFrame([df.Close.iat[i+ref_before+pred_after]])))
        s.append(scaler)
    x = np.array(x).reshape(-1, ref_before+1, len(df.columns))
    y = np.array(y).reshape(-1, 1)
    return x, y, s

xyで異なるStandardScalerを使っているのですが、どちらも同じdfiloc[i:i+ref_before+1]から生成されるものであり、問題ないと考えています。xyでは列数が異なるため、別々にStandardScalerを用意しないといけないという事情もあります。

yで利用するscalerには、df.Close.iloc[i:i+ref_before+pred_after]を利用したくなるのですが、これをやってしまうとリークです。

機械学習モデルの作成と学習

Kerasを利用して、GRU学習モデルを作成、学習します。特別なことはせず、シンプルなGRU1層(=ディープではない)構成です。

model = Sequential()
model.add(layers.GRU(units, dropout=dropout, kernel_regularizer=regularizers.l1_l2(l1=l1_l2, l2=l1_l2), input_shape=tr_x[0].shape))
model.add(layers.Dense(1))

下図左側のMSEでのLossは減っているのですが、右側のMAEだとそうでもありません。今回は、このまま話を進めてしまいます。

予測とバックテスト

最後にpr_xを元に予測を行い、バックテストを実施してみます。

まず、pr_xの予測結果と実際の終値を比較してみます。pr_xのブロックidf_ts[i+ref_before+pred_after]の予測用である点と、予測値に逆正規化をかけないといけない点にだけ、注意をします。

結果が下図です。左側が本試験データ全体で、右側が全体の最後100回分を切り出したものです。青線が実際の終値で、橙線が予測値です。

今回は24時間後の値を予測しているので、橙線が青線に24時間分だけ先行していなければいけません。しかし特に価格の急変部分について、そうもなっていません。本当は、下記のような橙線が欲しいところでした。

この時点で少し暗雲が立ち込めてい入るのですが、この予測値を元に売買した際の損益グラフも作図します。予測値が実際の終値より高ければ(次の足の始値で)買い、反対であれば (次の足の始値で) 売りとした場合のバックテストです。売買ごとの手数料・スリッページを0.2%と置いています。左側が損益グラフで、右側が終値の推移です。

損益グラフは幸運にもプラスで終わっているのですが、基本的には上下を繰り返すグラフで、とても汚いものです。実際、他の銘柄で試してみると、マイナスで終わるものも多々あります。

考察

ボード, チョーク, フィードバック, レビュー, トレーニング, 学校, コミュニケーション, 大学, 会社

結果のまとめ

GRU(回帰型ニューラルネットワーク)を利用して、仮想通貨の価格予測と、予測結果を元にしたバックテストを実施しました。価格予測は特に価格急変時に上手く機能しておらず、バックテストでも利益を得られる可能性はとても低そうであることが確認できました。

想定される反論

下記で、想定される反論と、それに対する応答をしていきます。

  • OHLCVデータだけで予測ができないのは当然。ツイートなり、CryptoQuantなりのデータも学習データに含めるべき。
    →これについては、多いに賛成です。ぜひ、試してみていただければと思います。板情報や分未満の足情報等も使えるかと思います。
    →個人的なモチベーションとして「一度システムさえ作ってしまえば、泥臭い努力をしなくて済むようにしたい」というものがあり、これを狙って機械学習に手を出した経緯があります。OHLCV以外のデータに手を出し始めると、予測力のあるデータを人力で探し続けるハメになるため、私自身はここに取り組む予定はありません。
  • 特徴量の作成が甘すぎるため、今回の記事は参考にならない。
    →ここについては、少し懐疑的です。記事にはしていませんが、思いつく特徴量は一通り作成して試してみた(taライブラリのテクニカル指標を全部入れるとか)のですが、機械学習がまともな予測力を持つことはありませんでした。
    Keras本でも「ニューラルネットワークの良いところは、特徴量を作りこまなくていいところ」みたいなことが書いてあるので、特徴量に頼りすぎるのも危ないように感じています。
  • 単位根を持つ時系列データに対していくら予測をしても無駄なので、少なくとも差分を取って予測するべき。
    →今回の時系列データにDickey-Fuller検定をかけるとp値が0.639(>10%)なので、確かに単位根があります(=非定常=規則性がない)。そして対数差分を取った時系列に同検定をかけると、p値が0.000(<10%)となり、単位根が消えます。
    →とは言え、実際に対数差分を取った数列に対しても色々やってみたのですが、ダメでした。
  • 機械学習のモデルが単純すぎる。
    →ここもGRUではなくLSTMを利用してみたり、GRUをディープにしてみたり、Dropoutを弄ってみたりと色々やってみたのですが、ダメでした。実感として、今回の予測力不足はモデルのせいではありません。
  • 元の時系列を上手く変形させたら、予測可能になったりしない?
    →元の時系列を回転させたり、元の時系列をDropoutさせたりしてみたりもしたのですが、ダメでした。
  • 回帰じゃなくて、二値分類か多クラス分類で問題を簡単にしたら?
    →これも試したのですが、ダメでした。
  • bitFlyerのBTC-FXは十分に効率的になってしまっているので、他の通貨ペアで試してみては?
    →これは、上手くいく可能性があると思います。私自身は前述の通り、継続的に泥臭くなりそうなところに入っていく予定はないため、ぜひ試してみていただければと思います。

予測が上手くいかない理由

直感的に言って、予測が上手くいかないのは、市場が十分にランダムに推移しているからです。2つの十分に長いランダムな時系列を持ってくれば、ランダムであるがゆえに、それらが上手く重なりあう期間を見つけることができます(見せかけの回帰)。機械学習もこれゆえに損失関数の低下が見られるものの、実際は何も予測していないのだろうと考えています。

実際、教師データ(y)をランダムにシャッフルして学習をさせても、学習は下記のように進みます(さすがに損失自体は本来のものより大きいですが)。

そうは言っても、Twitterにはチャートだけで上手く儲けている人がいるように見えます。囲碁AIが人間以上の棋士であることを考えれば、ランダムに見える系列の中にも、AIであれば何かしらの規則を見出せそうな気がします。

Twitter上のチャートだけで儲けている(と主張している)人は、裁量トレードを行っている認識です。つまり、チャートだけ見ていると言いつつも、自然と(為替等の)他のデータ・ニュースにも触れています。

また囲碁AIが人間よりも強いのは、あくまで囲碁というゲームで限られた中であるからと考えています。囲碁も仮想通貨も、相手の出方を予測して、その裏をかくゲームと捉えることができます。大きな違いは、囲碁の相手は一人なのに、仮想通貨の相手は無数にいるということです。無数の意思が織り込まれた市場価格は効率的なものに収束するのかもしれませんが、事前にその効率的な市場価格を予測するのは至難のワザです。

感想

スマイリー, 絵文字, 怒り, 不安, 感情, 玉, 落ち着いて, キャラクター, 陽気な, 混乱している

聖杯を求めて機械学習に手を出したのですが、実際に売買をしてみても、バックテスト通り損しかしませんでした。

将来の予測(機械学習)がワークしないのならば、時間足を超短期に取ることで確実なものに手を出そうとするのがHFTだと理解しています。一方でHFTからは「継続的な泥臭さ」を感じており、二の足を踏んでいます。なかなか難しいですね。

本記事が何かの参考になりましたら、下記より投げ銭よろしくお願いします。
(MetaMaskの場合、ポップアップ画面左上の”Edit”で金額を調整できます)

Donate



-仮想通貨
-, , , , ,

執筆者:


  1. […] 残念ながら以前の記事で述べた通り、ローソク足+機械学習でマトモな価格予測を行うことはできません。前回はPython+Keras+GRUでこのことを確認したのですが、今回はPython+XGBoostで確認を […]

comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です



関連記事

no image

bitFlyer APIのTips

bitFlyer APIのTipsを列挙していきます。ご利用は自己責任で。 始めの一歩。pybitflyerを利用します。pythonコードではなく、シェル内で。 # sudo python -m p …

no image

機械学習で仮想通貨の価格予測→ムリ (XGBoost)

機械学習+仮想通貨で儲けられる? 残念ながら以前の記事で述べた通り、ローソク足+機械学習でマトモな価格予測を行うことはできません。前回はPython+Keras+GRUでこのことを確認したのですが、今 …

no image

pybitflyerで簡単自動売買

pybitflyerなるものを使えば、bitFlyerの取引を自動化できる? pybitflyerを使うことで、bitFlyerの取引を自動化することができます。 本記事ではpybitflyerを導入 …

bitFlyerのAPIでできること→ほとんど何でもできる

【2020/12/12】 pybitflyer記事へのリンクを更新しました bitFlyerを使っているのだけれど、APIなるもので何かができるらしい 確かにAPIを利用すると、bitFlyerで色々 …

プロフィール

タクマ
−−−−−−−−

東南アジア(ミャンマー&フィリピン)でNW系システムインテグレーターとして6年ほど駐在していました。本を1500冊以上読んだり、プログラミングをしたりしています。嫁さんはタイ人で、タイ在住です。
ーー
「〇〇を分かりやすく解説!」系のスカスカな記事を見ていると頭が悪くなりそうなので、対抗して、少し骨のある記事を書こうと考えています。
ーー
Twitter (@tkm_nkmr)
ーー
Twitterまとめのまとめ
YouTube Find!
SNS Trends

−−−−−−−−