初めに
競馬データの分析、流行ってるようですね。
ディープラーニングなどの AI 技術が広がってきたおかげで、競馬を資産運用に使える可能性が見えるようになってきました。そしてデータ分析に興味を持つ方も増えてきました。分析の目標が資産運用だとしたらどんなことに注目して分析していったらいいのでしょうか。
EDIT:2022/10/28
中央競馬の回収率を計算するサイトを作ってみました。よかったら参考にしてみてください。
回収率
現在巷では回収率200%以上などと華やかに謳うサイトや記事で溢れてます。確かに回収率200%を超えるのなんて簡単です。ただし以下のような条件であれば
- 後出し
- 短スパン
競馬を投資として考えるなら、競馬のサイクルに合わせてスパンも1年で考えるのが自然です。競馬は季節によってそれぞれの特色があります。特に夏は馬のデビューの時期で北海道とローカルが主戦場となり新馬に関する分析が必要になったりと季節に応じた分析の必要性も出てきます。
またどんなに高度な AI の手法をとっていても、分析するデータが少なければ意味を成しません。ネットをスクレイプした程度のデータであれば、AIでなくても手計算でいくらでも回収率の高い予想をするでっちあげることができます。
競走馬は大体5歳までには引退します。2歳から走り始めることを考えると、最低直近5年ぐらいのデータは用意したいところです。
なので以下の目標を達成したら勝ちと目標を定めて話を進めます。
- 1年を通じて100レース(週2レース平均)に10万円ずつ計1,000万円を投資して1,200万円を回収して200万円の利益を得る。
すなわち120%の回収率です。
ということは「年間100レース以上にマッチする回収率120%の指標」、が必要になります。
資金
競馬を資産運用に使う場合には資金も考慮する必要があります。
例えば 8レース(約一ヶ月)買ってみて、以下のような配当だったとします。
指標1:0 0 0 0 0 0 0 960
指標2:0 240 0 240 0 240 0 240
回収率は両方とも120%ですが的中率は指標1が12.5%指標2が50%です。
ここで大きく違うのは資金です。指標に応じて初期にある程度の資金を用意しておく必要があります。
上の例のケース1では最初に当たるまで80万円の資金が必要でしたが、ケース2では20万円でした。
実際の投資では最初に当たるまでのレース数は予測にすぎないのでこれの倍以上の資金を用意しておく必要があるでしょう。
ということは
「年間100レース以上にマッチする回収率120%の指標」
という条件に加えて
もし資金が40万円だとしたら、「的中率が50%以上」という条件が入ります。
もし資金が160万円だとしたら、「的中率が12.5%以上」という条件が入ります。
(的中率50%は夢です。もし実現できてたら私はこの記事を書いていないでしょう。競馬はたぶん全レースハンデ戦になると思います)
勝ち、負け、依存症
上で勝ちの定義をしましたが、勝てなかったら負けですが、実際には負け以下すなわちー依存症ーの定義も必要です。
以下に達したら依存症と定義しておきます。
- 最初に用意した資金がなくなっても投資し続ける
分析
ここまで来ればあとは分析です。
「年間100レース以上にマッチする回収率120%で的中率がN%以上」の指標を作成しましょう。
本題
さて、ここから本題です。
「年間100レース以上にマッチする回収率120%で的中率がN%以上」の指標を作成するには AI でやるにしてもレガシーな手法でやるにしても相当な数の過去のレースを分析する必要があります。スクレイプしますか?
JRA-VAN を使いましょう。有料ですがオフィシャルなので。
ちなみにちょっと宣伝です。JRA-VAN の蓄積系データを丸のまま MongoDB に格納するソフトを作っています。よかったら使ってやってください。
一旦手元の MongoDB に落としてしまえば、ネットワーク上の Mac からでも Windows からでも Python でも Javascript からでもJRAの競争データのほぼ全てにアクセスすることができます。
指標例
参考に2019,2020年度の未勝利戦(JyokenCD 703)から指標を抽出したものを例として載せておきます。
略称は次のとおりです。bn:馬主、ch:調教師、ks:騎手、hn:繁殖馬
プログラム例
上記 JV2Mongo を使って作成した MongoDB 上の DB からレースを引くプログラム例です。
普通のリレーショナルと違い、SQL 一発というわけにはいかないので紹介しておきます。
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
function
Dump( db, Year, JyoCD, Kaiji, Nichiji, RaceNum ) {
db.collection( 'RA' ).find(
{ 'head.DataKubun': '7'
, 'jvid.Year' : Year
, 'jvid.JyoCD' : JyoCD
, 'jvid.Kaiji' : Kaiji
, 'jvid.Nichiji' : Nichiji
, 'jvid.RaceNum' : RaceNum
}
).sort( { 'head.MakeDate.Year': -1, 'head.MakeDate.Month': -1, 'head.MakeDate.Day': -1 } ).limit( 1 ).toArray(
( err, docs ) => {
assert.equal( err, null )
assert.equal( docs.length, 1 )
console.log( docs[ 0 ][ "TorokuTosu" ] )
for ( let i = 1; i <= docs[ 0 ][ "TorokuTosu" ]; i++ ) {
db.collection( 'SE' ).find(
{ 'head.DataKubun': '7'
, 'jvid.Year' : Year
, 'jvid.JyoCD' : JyoCD
, 'jvid.Kaiji' : Kaiji
, 'jvid.Nichiji' : Nichiji
, 'jvid.RaceNum' : RaceNum
, 'Umaban' : ( i < 10 ? "0" : "" ) + i
}
).sort( { 'head.MakeDate.Year': -1, 'head.MakeDate.Month': -1, 'head.MakeDate.Day': -1 } ).limit( 1 ).toArray(
( err, docs ) => {
assert.equal( err, null )
assert.equal( docs.length, 1 )
console.log( i, docs[ 0 ][ "Bamei" ] )
}
)
}
}
)
}
MongoClient.connect(
'mongodb://localhost:27017'
, { useNewUrlParser: true }
, ( err, client ) => {
assert.equal( err, null )
Dump( client.db( 'JRA-VAN' ), '2018', '10', '01', '02', '04' )
// client.close(); // Causes Topology was destroyed
}
)
終わりに
自分の指標を作ったらちゃんと1年間運用して実績を見てみましょう。上の指標例でもわかるように、去年の指標が今年使えるとは限りません。
きちんとした分析ができる方が増えれば、また競馬の楽しみの一つが増える気がします。