はじめに
この記事ではJRAが開催する1年間の競馬の一つで、特定の馬券が当てる確率を算出しました。
経緯
Fate/Grand Orderというソシャゲの2025年イベントにおいて、話題になったツイートがありました。
こちらです
https://x.com/Yq1Ch/status/1974741781125407174
画像は馬券の照会結果なのですが、この馬券の詳細は以下になります。
- 京都大賞典(GⅡ)という競馬の11レース目の馬券
- 購入内容は5-8のワイド※1
- 1,000円購入→98,000円の払い戻し
- ワイドとは馬券の買い方の種類で、3着までに入る2頭を組み合わせることです。
馬の詳細
今回見るのは「人気順」です。対象の5番と8番だけ抜粋しました。
-
5番:サンライズアース
18頭中2番人気馬 -
8番:ヴェルミセル
18頭中15番人気馬
【余談】ツイート主の驚き度
ツイート元を見るとすごく驚いているのですが、ツイート主が5-8ワイドを購入した経緯が、Fate/Grand Orderに登場するサーヴァント『岡田以蔵』が色々あってイベント内で「次のレースは5-8やき」と言っていたため。
ただし『岡田以蔵』とはステータスの一つである『幸運』が一番低いため、賭け事では全く信用できないことから当たった自体が奇跡のようなものだからです。
確率算出
馬の前提を元に以下を確立算出条件としました。
- JRAが開催する1年間の競馬から1つ
- 12レース中11レース目の馬券が当たる
- 馬券は11レース目の5-8ワイドのみ
- 人気順を強さの指標とする
今回人気順を強さの指標にしたのは、過去の成績などから期待される=レースに強い馬である傾向が強いからです。
なお馬の実力はあれど、その日の調子や機嫌などで強さは変動します。
そのため人気順に合わせてスケールによる調整はしていません。人気が高いからと憶測で調整すると「人気順を強さの指標にする」にブレが生じるからです。
あくまでもこの確率算出では、人気順のみそのまま強さへ反映しているため実際の実力差とは異なると思います。
from itertools import permutations
def build_strengths_from_popularity_linear(entries):
field_size = len(entries)
if field_size < 8:
raise ValueError("field_size must be >= 8 (for horses #5 and #8).")
# 馬番
nums = [e["num"] for e in entries]
if sorted(nums) != list(range(1, field_size + 1)):
raise ValueError("horse numbers must be 1..N with no duplicates.")
# 人気
pops = [e["pop"] for e in entries]
if any((not isinstance(p, int)) or p < 1 or p > field_size for p in pops):
raise ValueError("popularity must be integer in 1..field_size.")
if len(set(pops)) != field_size:
raise ValueError("popularity ranks must be unique (no ties).")
strengths = [0.0] * field_size
for e in entries:
idx = e["num"] - 1
strengths[idx] = (field_size + 1) - e["pop"]
idx5 = 5 - 1
idx8 = 8 - 1
indices = {
"5": idx5,
"8": idx8,
"others": [i for i in range(field_size) if i not in (idx5, idx8)]
}
return strengths, indices
def pl_top3_probability_pair(strengths, i, j):
S_total = sum(strengths)
n = len(strengths)
others = [k for k in range(n) if k not in (i, j)]
prob = 0.0
for k in others:
for order in permutations([i, j, k], 3):
s1 = strengths[order[0]]
s2 = strengths[order[1]]
s3 = strengths[order[2]]
p = (s1 / S_total) * (s2 / (S_total - s1)) * (s3 / (S_total - s1 - s2))
prob += p
return prob
def wide_58_hit_probability(entries):
strengths, idx = build_strengths_from_popularity_linear(entries)
return pl_top3_probability_pair(strengths, idx["5"], idx["8"])
def overall_probability_per_year(total_races_per_year, entries):
p_wide = wide_58_hit_probability(entries)
return (1.0 / total_races_per_year) * p_wide, p_wide
if __name__ == "__main__":
entries = [
{"num": 1, "name": "サンライズソレイユ", "pop": 10},
{"num": 2, "name": "カネフラ", "pop": 18},
{"num": 3, "name": "ディープモンスター", "pop": 5},
{"num": 4, "name": "アドマイヤテラ", "pop": 1},
{"num": 5, "name": "サンライズアース", "pop": 2},
{"num": 6, "name": "プラダリア", "pop": 7},
{"num": 7, "name": "ドゥレッツァ", "pop": 4},
{"num": 8, "name": "ヴェルミセル", "pop": 15},
{"num": 9, "name": "ミクソロジー", "pop": 17},
{"num": 10, "name": "ブレイヴロッカー", "pop": 13},
{"num": 11, "name": "ヴェルテンベルク", "pop": 16},
{"num": 12, "name": "ワープスピード", "pop": 14},
{"num": 13, "name": "メイショウブレゲ", "pop": 11},
{"num": 14, "name": "サブマリーナ", "pop": 6},
{"num": 15, "name": "ジューンテイク", "pop": 9},
{"num": 16, "name": "ボルドグフーシュ", "pop": 8},
{"num": 17, "name": "アルナシーム", "pop": 12},
{"num": 18, "name": "ショウナンラプンタ", "pop": 3},
]
TOTAL_RACES_PER_YEAR = 288
p_overall, p_wide_only = overall_probability_per_year(
total_races_per_year=TOTAL_RACES_PER_YEAR,
entries=entries
)
print(f"[前提] 年間開催競走数 = {TOTAL_RACES_PER_YEAR}, 出走頭数 = {len(entries)}")
print(f"[単一レース内] 5-8ワイド的中確率 = {p_wide_only:.6%}")
print(f"[年間から1レース] 総合確率 = {p_overall:.9%}")
折角なので名前も入れます。ワープスピードって名前がチートっぽいけどちゃんと走ってるの良い。
でも処理には使いません。
実行結果
以下の数値になりました。
[単一レース内] 5-8ワイド的中確率 = 1.507526%
[年間から1レース] 総合確率 = 0.005234465%
単一レース内だけで見れば1.5%くらいはあるみたいです。でも低いですね。
おわりに
今回はJRA開催のレースに絞って算出しましたが、現実では地方競馬も開催されているためこの数値の限りではありません。
ですが年間を通してこの馬券が当たる確率は極めて低いということが分かりました。
確率算出条件にある「11レース目の馬券が当たる」は重賞レースしか見ない、買わない人からすれば不要かもしれません。
また重賞レース以外でも馬券は当然買えるので、1〜10、12レースを含めるとまた変わってきます。
良い馬券があったら確率を求めてみると運の良さに気付けるかもしれません。
ありがとうございました。