競馬の解析をガチでやったら回収率が100%を超えた件
記事のタイトル通り、競馬で回収率100%を超える方法を見つけたので、その報告をする。
ちなみに、この記事では核心部分はぼかして書いてあるため、読み進めたとしても「競馬で回収率100%を超える方法」が具体的に何なのかを知ることはできない。(私は本当に有効な手法を何もメリットが無いのに公開するほどお人好しではないので)
本当に有効な手法を見つけたいのであれば、あなた自身がデータと向き合う以外の道は無い。
ただし、大まかな仕組み(あと多少のヒントも)だけは書いておくので、もしあなたが独力でデータ解析を行おうという気概のある人物なのであれば、この記事はあなたの助けとなるだろう。
ちなみに、これは前回の記事の続きなので、読んでない方はこちらからどうぞ。
stockedge.hatenablog.com
オッズの歪みを探す
さて、前回からの続きである。
前回の記事のブコメで「回収率を上げたいならオッズの歪みを探したほうが良い」という指摘を複数いただいた。私も同意見なので、その方法で儲けられるかためしてみることにしよう。
穴馬バイアス
実は経済学の文献の中では、競馬のオッズには「穴馬バイアス」と呼ばれる歪みが存在することが知られている。これは、馬券購入者達は穴馬(オッズが高い馬)を過剰に好むので相対的に穴馬の馬券が割高になり、逆に本命馬(オッズの低い馬)の馬券は割安になる、というアノマリーである。このアノマリーは世界各地の競馬で恒常的に観測されており、統計的にその存在が確かめられているものである。(参考:「穴馬への過剰な選好 (longshot bias)」に関するサーベイ)
まずは、netkeiba.comから引っ張ってきたデータを使って実際に穴馬バイアスが存在するのかどうか確かめてみよう。
ちなみにnetkeiba.comのデータは以下のスクリプトを使えばダウンロードできる。
GitHub - stockedge/netkeiba-scraper: netkeiba.comをスクレイピングして、競馬予測の素性を作成する。
まず最初に、比較対象として何も考えず全ての馬の単勝馬券を均一に購入した場合の回収率を見てみる。
sqlite> .headers on sqlite> .mode column sqlite> select ...> count(*) as 買い目数, ...> avg(case when order_of_finish = 1 then ...> odds ...> else ...> 0 ...> end) * 100.0 as 回収率 ...> from ...> (select * from race_result limit 100000) ...> where ...> -- 出走取消や競走除外を取り除く ...> cast(order_of_finish as int) <> 0; 買い目数 回収率 ---------- ---------------- 99076 73.5393031612094
回収率は約73%となっている。*1
次に、レース毎の馬の人気度別回収率を調べてみた。結果が以下である。
sqlite> select ...> popularity as 人気, ...> count(*) as 買い目数, ...> avg(case when order_of_finish = 1 then ...> odds ...> else ...> 0 ...> end) * 100.0 as 回収率 ...> from ...> (select * from race_result limit 100000) ...> where ...> cast(order_of_finish as int) <> 0 ...> group by ...> popularity; 人気 買い目数 回収率 ---------- ---------- ---------------- 1 7016 76.1929874572406 2 7025 80.7274021352312 3 7024 84.6597380410024 4 7003 79.850064258175 5 7009 84.5398773006135 6 7007 92.1749678892536 7 7001 78.2573918011712 8 6952 69.7827963176065 9 6828 75.9138840070299 10 6601 73.9160733222239 11 6285 77.7438345266507 12 5835 69.9194515852614 13 5206 76.0583941605839 14 4581 42.2877101069635 15 3769 26.869196073229 16 2827 35.9851432614078 17 627 85.7735247208931 18 480 20.25
人気度上位の回収率はほとんどが73%を上回っている。そして人気度が下に行くほどに回収率も下がっている。やはり日本の競馬にも穴馬バイアスが存在しているようだ。
馬齢による歪み
他にも、競馬ファンの間では「若い馬の方が儲かる」というアノマリーが知られているようだ。
競馬は若い馬の方が儲かる。馬の年齢と回収率・的中率の関連性 | ブエナの競馬ブログ〜馬券で負けないための知識
これも同様にnetkeiba.comのデータで確認してみよう。
sqlite> select ...> age as 馬齢, ...> count(*) as 買い目数, ...> avg(case when order_of_finish = 1 then ...> odds ...> else ...> 0 ...> end) * 100.0 as 回収率 ...> from ...> (select * from race_result limit 100000) ...> where ...> cast(order_of_finish as int) <> 0 ...> group by ...> age; 馬齢 買い目数 回収率 ---------- ---------- ---------------- 2 13675 72.4409506398537 3 43586 70.9782957830496 4 18167 76.9940001100897 5 12412 85.0217531421206 6 6787 57.6926477088552 7 3140 68.7261146496815 8 1007 121.072492552135 9 246 72.479674796748 10 42 0.0 11 8 40.0 12 5 44.0 13 1 0.0
5歳が回収率のピークで、そこから馬齢が高くなるほど回収率が悪くなる傾向が確認できる。
まぁ、こんな感じでデータとにらめっこしていけば、回収率と関係がある変数を見つけていくことができるわけである。
「おいしさ指数」を計算するモデルを組み立てる
人気度だけあるいは馬齢だけを単独で考慮しても、確かに回収率が高くはなるものの、100%を超えることは無い。回収率100%を超えるには、これだけでは足りないのだ。
ではどうするか。
人気度や馬齢など、回収率が上がりそうな複数の変数を総合的に考慮してオッズの割安度を評価するしかない。
(この辺りの話は卍氏の本にも詳しく書かれているので一読をおすすめする)
本記事ではオッズの歪みの度合いを、卍氏の本にならい「おいしさ指数」と呼ぶことにする。おいしさ指数が高いとは、オッズの割安度が大きいことを意味する。
おいしさ指数を計算する統計モデルを作成するために、私は三つの段階を踏んだ。
- 使う素性を決める
- おいしさ指数を計算するためのモデルを決めて、そのモデルのパラメータを学習データから推定する
- 2のモデルを使って計算したおいしさ指数がある閾値以上のときだけ馬券を買うとどうなるかをテストデータを使ってシミュレーションする
これらについて、上から順に解説していく。ここからは少しぼかした書き方になってしまうがご容赦いただきたい。
素性に関して
気を付けるべきなのは、着順の予測に使える素性と、オッズの歪みを見つけるのに使える素性は違うということだ(とはいえ全くの別物というわけではなく共通部分もある。このことについては後記)。オッズの歪みを捉えるためには、競馬をよく知ることも大事だが、むしろ人間の心理学的バイアスに詳しくなる必要がある。
ひとつだけヒントを言っておくと「前走は○○だった」(○○には何らかの「特殊な出来事」が入る)というパターンの素性には使えるものが多い。
例えば、前走でハナ差やクビ差で負けている馬の回収率は高い。着差がハナ差やクビ差の場合、勝った馬と負けた馬の実力はほぼ同じだったはずである。しかし人間は勝ち負けという結果を重視してしまうため、負けた方の馬が過小評価されることになる。
モデルとパラメータ推定に関して
今回はモデルのパラメータ推定には教師あり学習が使えないので、別の方法を使う必要がある。
なぜなら、教師あり学習は教師となるデータがなければ使うことができないからだ。
今回予測したいのは「オッズに歪みがあるかどうか」であり、そしてオッズが歪んでいるかどうかは、レースが終わった後でさえわからない。*2
(前回のように「レースの着順」を予測するのであれば、レースが終われば教師となるデータが得られるので、教師あり学習を使うことができるのだけど)
オッズに歪みがあるかどうかを調べるには、何らかの「馬券購入ルール」を想定し、その通りに馬券を買うと回収率が高くなるということを確認しなければならない。そうすることで、初めて歪みの存在を認識することができる。
つまり、個々のデータに対してではなく、ルールに対してしか報酬(という良し悪しの尺度)を与えることができないわけである。
このような状況では、教師あり学習ではなく、強化学習や遺伝的アルゴリズムを使って学習する必要がある。
私は実数値遺伝的アルゴリズムを使ってモデルのパラメータ推定を行っていた。
まず最初は、少し複雑なモデルのパラメータを実数値遺伝的アルゴリズム*3を使って最適化させていた。学習データのサンプル数は307364。遺伝的アルゴリズムの適応度は回収率の高さとした。
しかし、この方法はうまく行かなかった。学習データでは100%を超える高い回収率を出せるのだが、テストデータでは回収率が100%を下回ってしまっていた。要は過学習していたわけである。
遺伝的アルゴリズムの過学習を回避するテクニック*4をいくつか試してみたけど上手く行かなかったので、思い切って予測モデルを非常に単純なものに変えてみたところ、テストデータでの回収率が100%を超えるようになった。月並みな結論だが、試すなら極力単純な方法から試していった方がいいということだ。
テストデータでのシミュレーションに関して
私はテストデータ(サンプル数は10000)を使って、おいしさ指数がある閾値以上になったときだけ馬券を買うと回収率がどう変わるのかシミュレーションを行った。結果が以下である。
おいしさ指数: 30以上, 買い目数: 10000, 的中率: 7%, 回収率: 73.4%, 平均オッズ:65.02 おいしさ指数: 35以上, 買い目数: 9998, 的中率: 7%, 回収率: 73.4%, 平均オッズ:64.95 おいしさ指数: 40以上, 買い目数: 9993, 的中率: 7%, 回収率: 73.4%, 平均オッズ:64.86 おいしさ指数: 45以上, 買い目数: 9975, 的中率: 7%, 回収率: 73.5%, 平均オッズ:64.60 おいしさ指数: 50以上, 買い目数: 9947, 的中率: 7%, 回収率: 73.7%, 平均オッズ:64.12 おいしさ指数: 55以上, 買い目数: 9878, 的中率: 7%, 回収率: 73.9%, 平均オッズ:63.18 おいしさ指数: 60以上, 買い目数: 9783, 的中率: 7%, 回収率: 74.5%, 平均オッズ:62.42 おいしさ指数: 65以上, 買い目数: 9601, 的中率: 7%, 回収率: 75.8%, 平均オッズ:60.63 おいしさ指数: 70以上, 買い目数: 9314, 的中率: 7%, 回収率: 76.0%, 平均オッズ:58.20 おいしさ指数: 75以上, 買い目数: 8947, 的中率: 7%, 回収率: 76.6%, 平均オッズ:55.36 おいしさ指数: 80以上, 買い目数: 8392, 的中率: 8%, 回収率: 78.3%, 平均オッズ:51.96 おいしさ指数: 85以上, 買い目数: 7786, 的中率: 8%, 回収率: 80.8%, 平均オッズ:48.12 おいしさ指数: 90以上, 買い目数: 7070, 的中率: 8%, 回収率: 79.8%, 平均オッズ:45.26 おいしさ指数: 95以上, 買い目数: 6224, 的中率: 9%, 回収率: 78.1%, 平均オッズ:40.44 おいしさ指数:100以上, 買い目数: 5214, 的中率: 9%, 回収率: 78.3%, 平均オッズ:35.63 おいしさ指数:105以上, 買い目数: 4234, 的中率: 10%, 回収率: 76.9%, 平均オッズ:32.17 おいしさ指数:110以上, 買い目数: 3372, 的中率: 11%, 回収率: 83.6%, 平均オッズ:28.69 おいしさ指数:115以上, 買い目数: 2525, 的中率: 11%, 回収率: 84.0%, 平均オッズ:25.84 おいしさ指数:120以上, 買い目数: 1783, 的中率: 11%, 回収率: 88.1%, 平均オッズ:23.04 おいしさ指数:125以上, 買い目数: 1211, 的中率: 11%, 回収率: 94.5%, 平均オッズ:20.21 おいしさ指数:130以上, 買い目数: 758, 的中率: 13%, 回収率: 97.0%, 平均オッズ:17.56 おいしさ指数:135以上, 買い目数: 453, 的中率: 14%, 回収率:113.2%, 平均オッズ:15.25 おいしさ指数:140以上, 買い目数: 224, 的中率: 15%, 回収率:133.8%, 平均オッズ:13.67 おいしさ指数:145以上, 買い目数: 108, 的中率: 19%, 回収率:185.5%, 平均オッズ:13.77 おいしさ指数:150以上, 買い目数: 43, 的中率: 19%, 回収率:181.9%, 平均オッズ:14.27 おいしさ指数:155以上, 買い目数: 17, 的中率: 29%, 回収率:274.1%, 平均オッズ:14.15 おいしさ指数:160以上, 買い目数: 4, 的中率: 75%, 回収率:680.0%, 平均オッズ: 9.68
おいしさ指数が135を上回った時点で、回収率が100%を超えている。
注目してもらいたいのは、回収率が上昇するのに比例して的中率も上昇している点だ。
つまり競馬で回収率を高めるためには、やはり的中率の高さも重要だということだ。
競馬ファンには「的中率は重要ではない。本当に重要なのは回収率だ」という考えの人が多く見受けられるように思う。私も本当に重要なのは回収率だということは否定しないが、このシミュレーション結果を見る限りでは、的中率も同時に高めていかなければ回収率を高めることは難しいのではないか、と私は思う。
ちなみにおいしさ指数に反比例するように平均オッズも下がっているので、おいしさ指数が高い買い目の中には本命馬が多く含まれているようだ。この結果は穴馬バイアスと整合的である。
まとめ
というわけで、競馬の解析をガチでやったら回収率100%を超える予測モデルを作ることができた。一番重要なのは素性に何を使うかである。自身で競馬の解析をやってみようと思う方は私の記事を参考にして欲しい。
ちなみに、はずれ馬券裁判で話題になった卍氏が作成したモデルの性能がこの本に書いてあるのだけれど、私のモデルの方が少し負けている(私のモデルの方が買い目の数が少ない)ので、まだ伸びしろがあるようだ。なので、一月中はこのモデルをブラッシュアップすることに費やす予定。
その後は、何らかの形で予測を公開していこうかなと思ってます。
お楽しみに~。