技術ブログ

Developers's blog

Google Trendsの結果での株価予測には「カテゴリ」が重要

2019.12.20 小野 裕哉
利用事例 機械学習
Google Trendsの結果での株価予測には「カテゴリ」が重要

はじめに

 今回は世の中のトレンドを用いた株価予測をしてみたいと思います。今までに得た機械学習などの知識を実生活に用いてみたいと思っており、お金を稼ぐという意味で株価予測が候補にありました。以前、個人的にFXの値の上下予測を5分単位、1時間単位などで、テクニカル指標を用いて試してみたことがあったのですが、テクニカル指標だけでは期待した精度は出ませんでした。

 ということで、世の中のトレンドも特徴量として取り入れてみようと思い立ち、トレンドに左右されそうな株価で予測してみることにしました。

 よって、トレンドの影響具合を確認することをゴールとします。銘柄も絞ったほうがトレンドに対する影響が出やすいと考え、今回は任天堂を採用しました。任天堂を選んだ理由は特にないです。

結果

2015〜2019までの株価データを用い、過去6週のデータで次週の株価が上がるか下がるかの予測をし、

2015〜2018のデータで2019を予測:正解率60.4%

となりました!

取り組みの過程

以下で、取り組みの流れを説明します。

データを持ってくる

 皆さんは、Google Trendsというサービスをご存知でしょうか?トレンドを定量化する方法に悩んだ末たどり着いた方法がこれです。

 あるワードを入力すると、過去にGoogleでそのワードが検索された頻度(正規化済み)を示してくれます。CSVファイルとしてダウンロードもできるため、今回はこのサービスを活用しました。手軽さがこのサービスの良い点ですね。 カテゴリごとに分けることもできる(金融、ショッピングなど)ため、いくつかに分けてみました。

 最大で5年間のデータが持ってこれるようですが、1週間ごとになってしまいます。データ量が減ってしまうため、ここは少し残念です...

date

前処理など

以下の写真のように複数のカテゴリのデータも用います。 まずライブラリをインポートし、

 import pandas as pd
 import numpy as np
 import matplotlib.pyplot as plt
 import seaborn as sns
 %matplotlib inline

トレンドと、任天堂の株価のデータを一つにまとめます。

trend = pd.read_csv("multiTimeline_all.csv", header=1)
trend.columns = ["date", "value_all"]
trend["date"] = pd.to_datetime(trend["date"])

for cat in ["art_entertainment", "beauty", "business", "finance", "game", "law", "news", "shopping"]:
    cat_trend = pd.read_csv(f"multiTimeline_{cat}.csv", header=1)
    cat_trend.columns = ["date", f"value_{cat}"]
    cat_trend["date"] = pd.to_datetime(cat_trend["date"])
    trend = pd.merge(trend, cat_trend, on=["date"], how="left")

import pandas.tseries.offsets
trend["date"] = trend["date"] + offsets.Day(2)

import codecs
stock_price = pd.DataFrame()
for i in range(5):
    year = i + 2015
    with codecs.open(f"7974_{year}.csv", "r", "Shift-JIS", "ignore") as file:
        stock_price_each_year = pd.read_table(file, delimiter=",", header=1)
        stock_price = pd.concat([stock_price, stock_price_each_year], axis=0)

stock_price = stock_price[["日付", "出来高" , "終値調整値"]]
stock_price.columns = ["date", "volume", "close_price"]
stock_price["date"] = pd.to_datetime(stock_price["date"])

matrix = pd.merge(trend, stock_price, on=["date"], how="left")
matrix = matrix.dropna(subset=["volume"])

トレンドのデータは毎週日曜日の日付になっていたので、2日遅らせて火曜日としました(月曜日は休日で株価のデータがない場合が多いため)。 ここから1〜6週間前のトレンドを特徴量として作ります。

new_fe = []
for i in range(6):
    for col in ['value_all', 'value_art_entertainment', 'value_beauty',
       'value_business', 'value_finance', 'value_game', 'value_law',
       'value_news', 'value_shopping', 'volume', 'close_price']:
        matrix[f"{col}_{i+1}_weeks_ago"] = matrix[f"{col}"].shift(i+1)
        new_fe.append(f"{col}_{i+1}_weeks_ago")
matrix = matrix.drop([3, 4, 5, 6, 7])

そして、このnew_feの特徴量を予測に使います。

matrix["close_price_diff"] = matrix["close_price"] - matrix["close_price"].shift()
matrix["binary"] = matrix["close_price_diff"].apply(lambda x: 1 if x >= 0 else 0)

目的変数も作りました。

ちなみに、目的変数と特徴量(使わないもの含む)の相関の上位です。

date

モデルのトレーニングと予測

 モデルはLGBMを用いました。時系列データであるためLSTMも使ってみたかったのですが、データ量が少ないため断念しました。今回のLGBMのmetricはAUCです。

import lightgbm as lgb
from sklearn.metrics import accuracy_score

params = {'objective': 'binary',
          'max_depth': -1,
          'learning_rate': 0.01,
          "boosting_type": "gbdt",
          "bagging_seed": 44,
          "metric": "auc",
          "verbosity": -1,
          "random_state": 17
}
X = matrix[new_fe]
y = matrix["binary"]

columns = X.columns
y_oof = np.zeros(X.shape[0])
score = 0

feature_importances = pd.DataFrame()
feature_importances['feature'] = columns

X_train, X_valid = X[:X.shape[0]//5*4], X[X.shape[0]//5*4:]
y_train, y_valid = y[:X.shape[0]//5*4], y[X.shape[0]//5*4:]

dtrain = lgb.Dataset(X_train, label=y_train)
dvalid = lgb.Dataset(X_valid, label=y_valid)

clf = lgb.train(params, dtrain, 3000, valid_sets = [dtrain, dvalid], verbose_eval=100, early_stopping_rounds=400)

feature_importances[f'fold_{fold_n + 1}'] = clf.feature_importance(importance_type="gain")

def my_binary(x):
    if x >= 0:
        return 1
    else:
        return 0

y_pred_valid = clf.predict(X_valid)
y_oof[valid_index] = list(map(my_binary, y_pred_valid))


print(f"AC: {(accuracy_score(y_valid, list(map(my_binary, y_pred_valid))))}")

del X_train, X_valid, y_train, y_valid

AUCと正解率は以下です。

date

特徴量重要度の上位は以下のようになりました。

スクリーンショット 2019-12-13 15.35.23.png

また、この予測した通りに取引した場合の結果も計算しました。

def my_profit(diff, ans, pred):
    if ans == pred:
        return abs(diff)
    else:
        return abs(diff) * -1
sum(matrix.iloc[int(X.shape[0]/20):, :][["close_price_diff", "binary", "pred"]].apply(lambda x: my_profit(x[0], x[1], x[2]), axis=1).values)

結果は以下です。

date

つまり、毎回100株の取引をすると30万円の儲けです!これはなかなか嬉しいですね

結果の考察

 データが240と少ないため、断定はできませんが、トレンドと株価には何かしらの関係があるのではないかと考えられます。

 特徴量重要度をみると、「取引量」のほか、「アート、エンターテイメント」、「金融」、「美容、フィットネス」などのカテゴリのトレンドが重要であったことがわかります。また、6週間前の値が特徴量として効いていたりもするようで、非常に興味深いです。少し追加で調べていきます。

 まず、Google Trendsから得られるのはあくまで検索数の上下であり、それは任天堂のプラスなトレンドもマイナスなトレンドも同時に反映しています。にも関わらず、なぜ上下を60%で予測できたのかについて考えました。僕はこれについて、カテゴリごとにトレンドを分けた特徴量を加えたからではないかという考えに至りました。上に記載した特徴量重要度をみてみると、value_all、すなわち全体の検索数の特徴量があまり予測に寄与していないことがわかります。

date

これはカテゴリごとの時間の変化での検索数(正規化済み)の上下の1部のグラフですが、カテゴリによって動きが大きく違います。

 つまり、任天堂にとってプラスなトレンドを表すカテゴリ、逆にマイナスを表すカテゴリがあるのではないでしょうか。例えば、アート、エンターテイメントの検索はマイナスよりプラスな意味での検索をする人が多く、法律の検索は告訴した、告訴されたなどのマイナスな検索が多そうです。

date

上の写真は法律カテゴリの検索数ですが、2018年1月付近に急増加しています。1月10日はコロプラが、12月22日付で特許権侵害に関する訴訟を任天堂より提起され、1月9日に訴状内容を確認したことを発表した日です。

まとめ

 トレンドの特徴量だけでここまでの精度が出るとは思っていませんでした。トレンドの測り方はGoogle Trends 以外にもツイッターなどのSNSがあるため、これらを増やしていくことで長期間での株価の予測は可能なのではないでしょうか。株価は様々なものに影響を受けるものであるため、どんな特徴量を足していくか?が重要だと思われます。

お問い合わせはこちらから