今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

日経平均が日中どのくらい変動するかをTensorFlowで予測する (今までのまとめ)

この記事について

少し時間が空いてしましましたが、日経平均予測シリーズの続編です。
ニューラルネットワークを用いて、ある日の日経平均の終値が当日の始値と比べて「上がる」か「下がる」か「ほぼ変わらない」かを予測します。

今まで数回の記事で日経平均に関する予測を行ってきましたが、実装にバグがあり数値が正しくないことが判明しました。
これまでやってきた層数の調整や評価をもう一度やり直すことになったので、せっかくなのでその全過程を1記事にまとめなおしておこう、という記事です。

ソースコード

初めに、今回の記事中で使うソースコードを置いておきます。
github.com

何を予測するのか

日経平均株価の終値が、その日の始値に比べて上がる(UP)のか、下がる(DOWN)のか、ほぼ変わらない(SAME)のかを予測します。つまり、UP/DOWN/SAMEの3つのクラスのうちの1つを予測として出力するようなモデルをニューラルネットで構築します。

では、始値からどのくらい価格が動くとUPやDOWNと言えるのでしょうか?

model.pyに定義されているStockData.get_daily_data()を使用して、過去5年分の日経平均をの始値~終値での変動率を取得し*1、実際に変動率がどのような分布になっているか確認してみます。

start_date = np.datetime64("2010-01-01")
end_date = np.datetime64("2015-12-31")
stockdata = StockData()
_, _, diff_data, _ = stockdata.get_daily_data(start_date=start_date, end_date=end_date, normalize=False, logreturn=False, fill_empty=False)
diff_data["N225"].hist(bins=100)

f:id:kanohk:20160831235356p:plain

ヒストグラムを見ると分かる通り、1%以上変動する日はほとんどありません。下記のコードで変動率が0.3%を基準にUP/DOWN/SAMEを設定すると、(驚くほど)綺麗にデータが3等分されることがわかります。

print("Up:", diff_data["N225"].loc[diff_data["N225"] >= 0.003].count())
print("Down:", diff_data["N225"].loc[diff_data["N225"] <= -0.003].count())
print("Same:",diff_data["N225"].loc[(diff_data["N225"] < 0.003) & (diff_data["N225"] > -0.003)].count())

Up: 495
Down: 495
Same: 496

よって、ある日の日経平均の始値 を O_t, 終値を  C_tとしたとき、それぞれのそれぞれのクラスに分類される条件を以下のように設定します。

  • [UP]  C_t / O_t - 1 \ge 0.003
  • [DOWN]  C_t / O_t - 1 \le -0.003
  • [SAME]  -0.003 \le C_t / O_t - 1 \le 0.003

何を入力とするか

世界の主な株価指数(ダウ30種とか、ハンセン指数とか)と為替情報を使用して日経平均の動きを予測します。日本の市場は開くのが早いので、世界の前日までの指数を使用して日経の動きを予測することになります。
具体的には、次の指標を使用します(stockdata.pyで定義されてます)。

INDEX_LIST = ["N225",   # Nikkei 225, Japan
               "HSI",   # Hang Seng, Hong Kong
               "AORD",  # All Ords, Australia
               "GDAXI", # DAX, German
               "DJI",   # Dow, US
               "GSPC",  # S&P 500, US
               "SSEC",  # Shanghai Composite Index (China)
               "BVSP"]  # BOVESPA, Brazil

CURRENCY_PAIR_LIST = ["USD-JPY", "EUR-JPY"]

日経平均を除いた9指数に関して*2、次の値を計算して入力とします。

  • 前日のと前々日の始値で計算した対数リターン =  \ln(O_{t-1} / O_{t-2})
  • 前日のと前々日の終値で計算した対数リターン =  \ln(C_{t-1} / C_{t-2})
  • 前日の始値から終値の変動率 =  C_{t-1} / O_{t-1} - 1
  • 前々日の終値から前日始値の変動率 =  O_{t-1} / C_{t-2} - 1

 O_t, C_tはそれぞれ予測したい当日の始値と終値で、添え字のt-1, t-2は前日と前々日を意味しています。
また、プログラム中では上から順に closing, opening, diff, jumpなどといった変数名で名付けてあります。

さらに、日経平均だけに関しては、前日終値から当日始値の変動率も入力として使用します(過去5年で、予測したいdiffとの相関係数0.15程度)。
まとめると、9指数それぞれに4つの入力 + 当日始値での変動率の、合計37個の値を入力として使用しています。

終値に関してだけですが、過去記事では世界の指数と日経平均の動きにどの程度相関があるかを調べていたことがあるので、興味のある方はそちらも参考にしていただけるといいかと思います。

隠れ層の数を調整する

この記事と同様の方法で、隠れ層の数をいろいろ変えて、損失関数の動きや訓練・テストデータでの精度をTensorBoardを使用して可視化しました。
隠れ層の数を調整してみる ~株価予測(4)~ - 今日も窓辺でプログラム

使用した

下記の期間のデータを使用しました。

  • 訓練データ:2010年1月~2014年12月
  • テストデータ:2015年1月~2016年7月

また、学習のイテレーションは3万回まわしています。

学習過程

コスト関数の動きは、このようになりました。グラフ中の各線は、隠れ層の総数や各層の個数がそれぞれ違うモデルです。
f:id:kanohk:20160919223035p:plain


訓練データ上での精度の変化は次のようになりました。
f:id:kanohk:20160919223241p:plain

一番良い結果を出したのが精度約47%の入力層37, 隠れ層が2層で50, 25, 出力層3という形のモデルでした。
似たようなグラフになるので画像は省略しますが、同モデルのテストデータでの精度は42.7%でした。

モデルの評価

テストデータでの精度は42.7%と分かりましたが、それぞれの予測を出した時のPrecisionや、バックテストを行った時の損益についても再度評価します。
評価方法は、こちらの記事の方法に準じます。
モデルの評価とバックテスト ~株価予測(6)~ - 今日も窓辺でプログラム

Precision

プログラムを実行すると、次のような結果が得られました。

Precision on each class:
   UP  = 0.4817073170731707
  DOWN = 0.43902439024390244
  SAME = 0.35714285714285715

Accuracy =  0.4273356401384083

前回評価した時と異なり、それぞれのクラスのPrecisionがどれも精度に近い値になっていることがわかります。
ちなみに、今回のコードだとこの評価はこんな形でできます。

test_start_date = np.datetime64("2015-01-01")
test_end_date = np.datetime64("2016-07-31")

# 既に同名のモデルが学習済みだと学習済みモデルがロードされるので、再度学習させる必要はない
model = NikkeiModel([50, 25], './model/2010-2015_37-50-25-3') 
model.prepare_test_data(test_start_date, test_end_date)
model.evaluate()

バックテスト

前回の評価時と同様にバックテストも行います。バックテストを行う際においている仮定などの詳細は、前回記事を参照してください。
モデルの評価とバックテスト ~株価予測(6)~ - 今日も窓辺でプログラム

テストを呼び出す部分のコードはこれだけです。

test_start_date = np.datetime64("2015-01-01")
test_end_date = np.datetime64("2016-07-31")
model = NikkeiModel([50, 25], './model/2010-2015_37-50-25-3') 
model.prepare_test_data(test_start_date, test_end_date)
model.backtest()

結果は19か月で +154% です。今回は、バックテストの結果もCSVで共有してみます。

細かいデータはCSVを直接見ていただけるといいと思うのですが、利益を出している日は162日、損失を出している日は118日で、トータルでプラスになっている感じです。
月別の利益を見てみると、次のようになります。エクセルシートのスクショですが、右列の数値が仮定通りに取引したものでのその月の損益額になります。
f:id:kanohk:20160919231032p:plain

2015年の間は結構まけていたりもして、1年間では+34%というパフォーマンスです。
2016年に入ってから、日経平均がどんどん下がっていく過程でうまいこと予測が当たって、+154%という数字になりました。
実際には、イギリスのEU離脱の国民投票の日など、実際には取引を避けるだろうという日も含まれているので、このモデルで運用をしたとしても見込める利益はもっと小さいものになると思います。

今後の方針

バグを修正できたのは良かったですが、正しい結果は前回の間違った結果よりも悪いものでした。
プライベートの都合で少し期間は空いてしまうかと思うのですが、今後は地道にモデルの改善をしていこうと思います。出来高や高値安値など、使用していない情報は結構あるので、それらの情報も入力として使用することで、結果がどう変化するか見ていきます。

また、バグが見つかる前に二日間ほど実際の資金で運用してみてわかったのですが、今回私が使っているAPIは日本時間の朝9時に前日の他の市場のデータをとってくることができません。日本市場が閉まることになって、ようやく前日の他の市場データが更新されるようです。なので、当日の朝9時前にパソコンの前に座り、前日の世界中の指標データを手入力する、という作業が必要でした。

これはちょっとイケていないので、前日のデータを何らかの手段で自動で集めてくる部分を書いたら、実資金の投入も再開していきたいと考えています。
自分のお小遣いがかかっているので、再開するまでの間必死にモデルの精度上げに励みます。。。


関連記事

テクニカル指標が予測に使えないか、簡単な分析をしてみました。
www.madopro.net

株価ではなくてボートレースの結果予測にも挑戦しました。
www.madopro.net

*1:日経平均のデータはQuandlというサービス経由でYahooファイナンスから取得しています。詳しくは過去記事にて:日経平均の終値が前日より上がるか下がるかをTensorFlowで予測する(2.5) - 今日も窓辺でプログラム

*2:自己相関がないことは既に検証済みなので、日経平均の予測に自身は使わないことにする:日経平均と相関のある経済指数は何か?(2) - 今日も窓辺でプログラム