[コード付き]誰も知らない関連銘柄を、機械学習を使って素早く見つける

機械学習で株価を予想できないかな〜と思った人に、以前まあまあ読まれたこの記事。
WEB屋の自分が機械学習株価予想プログラムを開発した結果
役に立ったかどうかはともかく、思った以上にたくさんの人に、そして長期的に読んでもらえました。一説によるとアマゾンで前日まで買われてた言語入門書トップが今まではJAVAとC#だったのにこの記事が公開された次の日からPythonに塗り変わったとか。真相はわかりませんが記事のほうもいまだに少なくないアクセスが毎日あります。
「あれどうなったの?」といまでも興味を持って聞いてくれる方もたくさんいますので、それならばと満を持して実用的な検索システムを作ってみることにしました。
いきなりシステムを試したい人はこちらへどうぞ。
この記事では、どういった経緯とどういった考え方で作っているかを紹介していきたいと思います。

“関連銘柄買い”という株の買い方があります。
「な〜んかこの株なら値動きが予想できそうだな」という銘柄に当たりを付けておいて、その銘柄と関連して動きそうな銘柄を押さえていくやり方だそうです。
例えば、「ぼくはゲームが好きだから、任天堂の株価なら高くなりそうか安くなりそうか、他の人よりも読めそうだぞ」と思ったときに、任天堂が儲かったなら任天堂に関連した銘柄、主に取引先なども連動して儲かるのではないか、という予想をたてていくわけですね。

で、このとき「上がりそうだぞ」という予想の密度が重要でして、絶対に上がると知っている場合はインサイダー取引などが疑われますし、逆になんとなくでは占いと変わらないわけです。というわけで、この予想の密度に機械学習が使えるのではないかと注目したのがことの始まりです。
なにせ機械は何も知らないので、あるのは膨大な情報から計算して出てくる結果を伝えるに過ぎませんから。

誰も知らない関連銘柄を素早く見つける、機械学習を使ってそんなサービスが出来たら良いなと思って開発を続けてきました。

そもそも3年のあいだ何をしていたか

「実用化に向けて3年間、開発してました!」って言えると格好が良いんですけど、残念ながらそこまで3年間をみっちりと開発に費やしていたわけではありません。では何していたかというと、自分で株式会社を作っていました。すごいですよね。機械学習で株価を予想しようと思ってたら株式会社を作ってたでござる
会社設立と今回の記事は直接の関係はないのですが、こういう思考回路で発展していったので、話の流れとしてまずは紹介してみたいと思います。そんなに対した情報でもないのでさらっと読み飛ばしてもらっても結構です。

  1. そもそもおさらいすると、株券は儲かると思うからみんな買ってる。
  2. 儲ける方法は大きく二つある。
  3. ひとつは売買の利ざやで稼ぐ方法。株で儲けると言ったとき、多くの人はこれを想像する。
  4. もうひとつは株券が本来持つ配当で稼ぐ方法。本来の株券の姿はこちら。
  5. ふたつを比較すると、利ざやの方がハイリスクハイリターン。配当金はローリスクローリターン。
  6. 前回の記事では利ざやを算出できないか試していた。
  7. 後述する理由で断念。
  8. そもそもやりたかった、配当金で稼ぐ方法を試してみた。こちらは結局機械学習は使わなかった。
  9. 直感、経験、人脈、さまざまな数値化できないパラメーターによって株券を購入。順調に配当金が支払われる。
  10. わかってたことだがローリスクリーリターンすぎる。どんな優良株でも、お小遣い程度でも配当を得るためには莫大な金額と時間を費やしてしまう。
  11. 特に時間は痛い。仮に10年かけて何億稼いだとしても、10才歳とったすげー金持ったおじさんにしか自分はならなさそう。何も残らない。誰もお前を愛さない。
  12. それよりはハイリスクハイリターンすぎるが、自分で会社作ると人生の経験値貯まるのでおすすめ。←イマココ

我ながら11番から12番までの思考がぶっとんでますが、おおむねこういう考え方でやってみました。ただ、繰り返しますがこの記事は会社設立をオススメする記事ではないので、あくまで株をどうやって機械学習させてみるかに絞ってみたいと思います。

前回の記事をふまえて

すごいそもそもな話をしますけど、上がってるか下がってるかを機械が判断できたからって、何の意味も無いんですよね。
どういうことか順番に説明します。

まず前提として、つぎに上がるか下がるかを予想することと、いま上がってるか下がってるかを機械が判断すること違うということ。
前回の記事では、いま上がってるか下がってるかを機械が判断できることを目指していましたが、つぎに上がるか下がるかの予想と混同していたのが大きな間違いでした。
もう少し説明を続けます。

まず最初にややこしいことに、上がってるか下がってるかを機械が判断できることそれ自体は価値があります

上がってるか下がってるか、人間ならローソクチャートを見れば一目瞭然です。ですが、今手元に4000件以上の銘柄データーがあるんですけど、これを全て目を通して判断するのはかなり骨の折れる作業です。ここを機械が瞬時に行ってくれるわけです。意味があります。価値もあります。

単に上がってるか下がってるかをプログラムで判断するだけなら機械学習を使わなくても終値の差分をみれば判断できますが、実際には複雑な動きをしながら実質上がってるのか下がってるのかを判断するには機械学習が最適です。ここにも意味があります。

ですが、この方法が行っているのは銘柄の抽出です。上がってる銘柄、下がってる銘柄を目の前に差し出してくれるだけです。私たちは銘柄があらかじめ抽出された状態で買うか買わないかの判断を迫られているだけです。これは、ドル/円という銘柄があらかじめ目の前にあって、上がると思ったら買う、下がると思ったら売る外貨為替と、ようやく同じ壇上に登ったにすぎないことがわかります。

結局のところ、上がってる銘柄を買って(予想に反して)損をして、下がってる銘柄を売って(予想に反して)損をしている、いつもやってることにようやく並んだに過ぎないのです。
これが、機械学習で銘柄を上がってるか下がってるか判断することと、上がるか下がるか予想することがぜんぜん違う、大きな理由になります。少なくとも私の力量では、機械学習でボールの動きは予想できますが株価の値動きを予想することは無理です。

極論を言えば、われわれは絶対に上がるとわかってる銘柄を買うことは出来ないわけで(先にも書きましたが本当にそうだとしたらインサイダー取引が疑われると思うので)、密度は別として上がると思うか下がると思うか、予想するしかないわけです。
ただ、その、機械学習が出した予想が正しいかどうかは、私の力では判断ができないわけです。判断できないものにお金をつぎ込むなら、占いとそんなにかわらないので、私はそういうことはやりたくありません。
ただ、精度はあきらめていますが、予想の密度は濃くすることが出来ます。これはあとで書きます。

結局のところ人間が判断しないといけない。機械学習はその判断を助けるツールであるべき

ちょっと説教くさくなるのがいやなのですが、自己反省として必要な段落でもありますのでさくっといきます。

前回の記事で言いたかったのが、「機械学習であわよくば予想とかできるんじゃね?→やってみた→だめでした」の流れを踏まえた上で、人間が予想しやすいツールを作ることが本来の目的でした。実際には自分で使う分にはすぐに出来てたんですが、ブログ用にまとめたりするのがおっくうになってて、早く追加記事書かなくちゃなあと思いながらも会社設立などのほかの用事にまみれて、後回しになっていたのがこの3年間となります。

やっと年末年始にまとまった時間がとれたので、もう一度株価のスクレイピングからやり直して、前回作りかけだった銘柄抽出ツールを誰でも使える形までもってこれました。そこで今回改めて紹介させてください。

ちなみに、自分で使う分にはできあがってるものを、WEBサービスとして公開するまでに至った技術的な話はこちらに紹介してみました。リンク先ももし興味がありましたら読んでみてください。

というわけで長い長い前置きが終わって本題に入ります。

似た値動きをする銘柄を抽出するツールを作ったよ

事の発端は、資生堂の銘柄を買いたいと思ったときに、高すぎて買えないことから、はじまりました。

資生堂 [4911]

資生堂が中国に進出すると聞いて、そういえば今まで中国人の女性があまり化粧をしていないことに気がつきました。
カシオの自撮りカメラから始まって、スマホの美顔アプリなど、中国50億人の約半分の女性が美容に興味を持っていることは明らかです。単純にいっぺんに全ての人が化粧に興味を持つとは思えないけど、一部の人たちが日本の化粧品を使い始めるとすごいことがおきそうです。これは資生堂くるんちゃうかと思って株価を見てみると、確かに2017年ごろから急激に伸びてます。ぼくも資生堂の株欲しい!と思ったんですが、今でも80万円くらい出さないと買えないっぽいです。これは私ではとても手の届かないものです。

そこで以前作ってた、「似た値動きのする銘柄を抽出する」ことをもっと使いやすくしようと作業を進めてみました。理屈は次の通りです。

  • いわゆる関連銘柄というやつを、何の知識も無く探し当てるのは骨の折れる作業。
  • 4000件以上ある銘柄のデーターから、似た値動きをする銘柄を探し当てられないか
  • 値動きが似てるということは、動きの予想も似てるかもしれない。外れるかもしれない。その判断は最終的には人間がするにしても、似てる銘柄を機械が並べてくるだけでも価値はあるかもしれない。

しかもここを機械学習が行うことが絶妙に良いのは、関連銘柄は「知ってる人」が有利なのに対して機械学習で出した銘柄は「何も知らない機会が膨大な情報の計算から出した結果」であることです。有利か不利かは置いておいて、情報の出所としてアドバンテージになり得ます。

というわけで、プログラムがやってることをさっそく見ていきましょう。

# %% [markdown]
# # 指定した銘柄から、似た値動きをする銘柄を抽出します。

# %%
# 実行パスをPythonとJupyterでどちらで動いても統一できるようにしておく
import os
current = os.path.dirname(os.path.abspath(__file__))
os.chdir(current)

# %%
# カレントパスも実行パスに通しておく
import sys
from pathlib import Path
import os
parent = Path(current).resolve().parent
sys.path.append(str(parent))

# %%
# 独自モジュールなど
import python.config_global as config
import python.search_stock as search_stock

# %%
import time
import numpy as np
import pandas as pd
import scipy as sp
from scipy.stats import pearsonr
from sklearn import cluster, preprocessing, mixture
from tqdm import tqdm
import matplotlib.pyplot as plt

# %%
def get_data(code):
  '''指定した銘柄のデーターをリターンインデックス込みで取得する'''
  data = search_stock.get_stock_data(code)
  if data is None:
    return None
  data = search_stock.get_ret_index(data)
  return data

# %%
def create_stock_table(stock_list, ret_index_table):
  '''渡ってきたデーターをDaraframeに変換する'''
  df = pd.DataFrame.from_dict(stock_list)
  # リターンインデックスのないデーターは除外する
  return df[df['code'].isin(ret_index_table.keys())]

# %%
def create_ret_index_table(stock_list, length):
  '''
  リターンインデックステーブルを作成する
  '''
  ret_index_table = {}
  for data in stock_list:
    code = data['code']
    new_data = get_data(code)
    if new_data is None:
      continue
    if len(new_data['ret_index']) != length:
      # 指定銘柄とデーターの長さが違う場合除外する
      continue
    ret_index_table = new_data['ret_index']
  return ret_index_table

# %%
def set_distance(target_data, stock_table, ret_index_table):
  '''対象とリターンインデックスがどれだけ離れているか計算する'''
  target = target_data['ret_index']
  stock_table['r'] = None
  stock_table['p'] = None
  stock_table['b'] = False
  for code, ret_index in ret_index_table.items():
    # r 相関係数は 1 に近いほど強い相関がある
    # p 有意確率 P 値は 0 に近いほどデータが偶然にそうなった可能性が低い
    r, p = pearsonr(target, ret_index)
    b = float(p) < float(0.05)
    data = stock_table[stock_table['code'] == code]
    stock_table.loc[stock_table['code'] == code, 'r'] = r
    stock_table.loc[stock_table['code'] == code, 'p'] = p
    stock_table.loc[stock_table['code'] == code, 'b'] = b
  # 有意確率の高いものだけに絞り込む
  return stock_table[stock_table['b'] == True]

# %%
def setup_ret_indexes_list(target_code, target_list, stock_table, ret_index_table):
  '''機械学習用にリターンインデックスのリストを作成します'''
  # 最初は必ず検索対象のリターンインデックス
  ret = [target_list]
  codes = [target_code]
  for code in stock_table['code']:
    r = ret_index_table
    ret.append(r)
    codes.append(code)
  return ret, codes

# %%
def check_vbgm(ret_indexes):
  '''機械学習でクラスタリングした結果を返す'''
  # データーをノーマライズさせる
  sc=preprocessing.StandardScaler()
  sc.fit(ret_indexes)
  ret_indexes_normarize = sc.transform(ret_indexes)
  n_components = min(10, len(ret_indexes))
  # VBGMMクラスタリング
  vbgm = mixture.BayesianGaussianMixture(
    n_components=n_components, max_iter=100,
    verbose=0,
  )
  vbgm=vbgm.fit(ret_indexes_normarize)
  labels=vbgm.predict(ret_indexes_normarize)
  return labels

# %%
def stock_group(labels, codes):
  '''銘柄コードを機械学習されたグループごとにわけます'''
  # 空のグループリストを作成
  groups = []
  for i in range(max(labels) + 1):
    groups.append([])
  # ラベルをインデックスとして、それぞれ銘柄コードをグループ化
  for label, code in zip(labels, codes):
    groups[label].append(code)
  return groups

# %%
def create_json_ret_index_table(target_code, target_data, ret_index_table):
  '''リターンインデックステーブルをJSON用に変換する'''
  # 最初に対象銘柄のデーターを挿入する
  ret = {
    target_code: target_data['ret_index'].values.tolist()
  }
  for code in ret_index_table.keys():
    ret = ret_index_table.values.tolist()
  return ret

# %%
def create_json_stock_table(stock_table):
  cols = stock_table.columns.values.tolist()
  t = []
  for row in stock_table.itertuples(name=None):
    t.append(row[1:])
  return {'keys': cols, 'data': t}

# %%
def start(target_code, stock_list_):
  '''API用にデーター生成する'''
  # ターゲットデーター作成
  target_data = get_data(target_code)
  target_length = len(target_data['ret_index'])
  y = target_data['date']
  # 各種リスト作成
  ret_index_table = create_ret_index_table(stock_list_, target_length)
  stock_table = create_stock_table(stock_list_, ret_index_table)
  # ピアソンの積率相関係数を作成
  stock_table = set_distance(target_data, stock_table, ret_index_table)
  # 相関係数の高い順にソート
  stock_table = stock_table.sort_values('r', ascending=False)
  # 機械学習用にリターンインデックスのリストを作る。どのリターンインデックスがどの銘柄コードか検索できるように、同じ並びで銘柄コードのリストも作成
  ret_list, code_list = setup_ret_indexes_list(target_code, target_data['ret_index'], stock_table, ret_index_table)
  # 機械学習でクラス分け
  labels = check_vbgm(ret_list)
  # ターゲット銘柄は必ず先頭なので、先頭のグループIDを取得
  target_group_id = labels[0]
  # グループごとに銘柄コードのリストを作成
  grouped = stock_group(labels, code_list)
  # JSONにできる形式に変換
  # リターンインデックステーブル
  j_ret_index_table = create_json_ret_index_table(target_code, target_data, ret_index_table)
  # グラフ用日付リスト
  j_y_label = y.values.tolist()
  j_target_data = {'code': target_code, 'name': search_stock.get_name(target_code)}
  j_stock_table = create_json_stock_table(stock_table)
  return {
    'grouped': grouped,
    'target_group_id': int(target_group_id),
    'y_label': j_y_label,
    'target_data': j_target_data,
    'stock_table': j_stock_table,
    'ret_index_table': j_ret_index_table,
  }
  
# %%
if __name__ == '__main__':
  time_start = time.time()
  low = 100000
  high = 200000
  target_code = '7974'
  stock_list = search_stock.get_stock_list(low, high, target_code)
  # ここまでがWEBから渡ってくる値
  g = start(target_code, stock_list)
  print(g)
  process_time = time.time() - time_start
  print(process_time)

パイソニスタやモヒカンからすると気持ち悪いコードだと思いますがお目汚しご容赦を。
とくにテクニカルなことはしてないので、データーさえ用意すればそのまま動くと思います。データーベースから銘柄を抽出して、機械学習にかけてクラスタリングして、同じクラスに属した銘柄が近い銘柄であると仮定して結果を表示させています。あとは人間がこの結果を見て、買うか買わないかの判断に役立てるもよし、役に立たないと判断するもよし、いろいろと便利に使えるのではないでしょうか。

画面は開発中のものです

試しに、「20万円から21万円以内で買える銘柄のうち、2019年12月から1ヶ月の値動きが、任天堂とな〜んか似た感じの動きをする銘柄」を抽出してみました。ここから任天堂との関連性を見つけて、任天堂の株価が上がると思えば買い、下がると思えば売りの判断をするのはキミだ!的なかんじで使えるかと思います。

「似てる」を判断するために、VBGMMクラスタリングという手法を用いています。この手法のメリットは、あらかじめグループ数を設定しなくていいことです。例えばあらかじめ3グループに分けなさい、といった類いの結果を求めているわけではないので、何グループで来てどのグループに属すか、を調べるにはちょうどいい方法でした。
デメリットとしては結果が毎回違ってくることです。有料サービスでこれはけっこう痛いのですが、今回目指したのが「人間が判断する手助けになるツール」ですので、これはもう計算結果をみてもらえればわかると思います。充分判断できる結果が出せるようになりました。

誰でも使えるサービスを作ったよ

で、ここからが本題。「データーさえあれば誰でも使えると思います」て書いておいて、そのデーターを用意するのがしんどいんじゃい!というお方。

なんとなんと、誰でも使えるようにサービスを作りました。

値動相似的銘柄健作君

使い方はこちらのビデオをご覧ください。

画面は開発中のものです

まずはじめに何円から何円までで買える範囲の金額を入力します。全ての銘柄を機械学習かけてもよかったんですが自分が買えない銘柄ばかり出てきても仕方が無いので、先に絞り込みをします。200件以上出てきた場合はランダムに200件を抽出します。このとき何らかの理由でデーターに欠損がある場合、その銘柄は省かれます。

画面は開発中のものです

次に関連づけたい銘柄のコードを入力します。「任天堂と似た値動きする銘柄を調べたいな〜」と思ったら任天堂の銘柄コード7974を入力してください。

画面は開発中のものです

「この範囲で買える銘柄を検索する」ボタンを押すとまずは「何円から何円の範囲で買える銘柄」が検索されます。このときはまだ機械学習はしていません。一覧で表示されます。CSVデーターとしてダウンロードも出来ます。なんとここまでが全て無料で出来ます。

画面は開発中のものです

次にいよいよここからが機械学習の出番です。
このブログで紹介した機械学習の結果を有料で表示できます。決済はAmazonPayを使っていますので、お持ちのアマゾンアカウントでログインしていただくと、アマゾンに登録された決済方法で支払うことが出来ます。支払い情報は全てアマゾン内で行われるので、業者(私)に支払いや住所などのアカウント情報が知られることはありません。

画面は開発中のものです

「な〜んだ、有料か」と思われた方は仕方ないのですが、機械学習の結果はけっこうバカにはできなくて、人が知らない情報を知るには非常に有意義なものになり得ます。

機械は関連企業なのかどうなのかは知らないわけですから、ただただ値動きのデーターをみて似た動きをするかどうかを見分けます。
もちろんたまたま似た値動きをしているだけの場合もあります。コードをみてもらうとわかるのですが、判断方法のひとつとしてピアソンの積率相関係数を用いています。ここで、たまたま似ているかどうかはわからないが似ている場合偶然とは思えないという指数があって、つまり偶然似ている可能性を排除するようにしています。なにかしら必然があって似ているのでは無いか、という銘柄に絞っているわけですね。

もちろん占いでも何でも無く、計算によるものなので誰でも計算すれば割り出せる数字です。そういった絞り込みを大量に行えるのがこのサービスの強みになると思います。
相関関係というのは、こういう機械学習とか興味ある方なら当然詳しいと思いますので今さら私が言うことでもありませんが、相関性と関連性は分けて考えるべきと統計の最初に教わると思います。ですので、値動きに相関があるからといって関連銘柄とすぐに判断するわけにはいきませんが、何かしらの指標にはなります。

機械に任せられることを探す

そんなこんなで、機械学習をどう活かすかの旅は、株価についてはひとまず関連銘柄探しに辿り着きました。

私が機械学習に興味を持ったのは、思えば数年前にAlpha Goの活躍を見たときからでした。
機械学習で鍛えられた計算機の塊が、今まで長い歴史の中で達人と呼ばれる人々が積み上げてきた定石を次々と打ち破る姿に、「人間が最適解だと思っていたものが、実は膨大な計算の結果もっと良い道が見つかるのかも知れない」という可能性にロマンを感じました。

まずは簡単な、株価が上がるか下がるかからはじめて、膨大なデーターから銘柄の関連性を見つけられるようになるまでの間で、機械学習が人間の手助けになる可能性に確信が持てました。
機械学習の良いところは、全てが公開されていて誰でも再現が可能だというところにあると私は考えます。技術そのものも本屋に行ったら売ってある技術書の範囲でまかなえますし、計算式もデーターも全て公開されているので、誰もが同じスタートラインから始められることが強いと思います。
私がやってることは、先人の知恵を追いかけているばかりであえて公開するほどのこともないのですが、それでもこうやって記事として公開すると誰かの何かには役に立つことが、すごく励みになります。
サービスそのものについての技術記事もこちらに書いてみましたので、よかったら読んでみてください。

「[コード付き]誰も知らない関連銘柄を、機械学習を使って素早く見つける」への6件のフィードバック

  1. 数年を経て結局「占い」みたいなことしかできてないっていうオチ
    irrelevant なハイパーパラメータが山ほどあって,それらを手でチューンしたりしてる時点で機械学習も従来のテクニカル分析と全く変わらないって気付こうね。いい大人なんだから

    1. ご指摘ありがとうございます。まさにおっしゃる通り、機械学習はあくまでも人間の判断材料のひとつを導き出すにすぎない、という趣旨の記事なんですが、長くて要点が掴みにくいのでわかりにくいですよね。
      それでも記事を公開することによってまだまだたくさんの気づきを得られます。気づいてなかったことに気づかされる、そんな日々の中で、死ぬまでに少しでも自分の憧れる大人に近づけるよう努めていますが、数年という長さは成長するにはまだまだ短すぎること恥いるばかりでございます。

  2. 補足しておくと、上のコメントは2016/11/9の記事の
    > 移動平均がどうとか、ボリンジャーバンドやゴールデンクロスにどうなったら売りのサインとか、パラメーターを変えたらどうとでもなる線をいい大人が参考にお金のやり取りをする様は最高に面白いです。(略) 本当に頭が悪そうで、私そういうの好きです。
    という文言が非常に受け入れがたかったために投稿したものです。
    移動平均線とかボリンジャーバンドは純粋に統計的な指標なので (それぞれただの平均とただの標準偏差)、これを否定しながら機械学習の結果は受け入れるというのはあまりにもナンセンスです。
    移動平均とかボリンジャーバンドにおいて主観で決めるものといえば「どの期間の平均・標準偏差をとるか」という1パラメータだけですが、あなたも機械学習を施すデータを自分で取捨選択していますので、全く同レベルです。

    > 長くて要点が掴みにくいのでわかりにくいですよね。
    長いとも思わないしわかりにくさも感じませんでした。その上で
    > 結局のところ人間が判断しないといけない。機械学習はその判断を助けるツールであるべき
    > 機械学習はあくまでも人間の判断材料のひとつを導き出すにすぎない
    というのは主観に過ぎるとは思いますが。
    (データ x に対して例えば買いか売りかを返す関数 f(x) を統計的に近似するのが機械学習ですが、それを改めて「判断」というフィルタにかけるかどうかはあなたの勝手であって、それを真理というかべき論として言われても、ということです)

    若気の至りなので許せ、ということなら分かりました。
    茶々を入れてすみませんでした。

exp へ返信する コメントをキャンセル

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