これなに
「競馬必勝法をプログラム化」を組合せ最適化で解いてみました。
- 考え方は「うまめし.com 競馬必勝法」を参考にしてます。
- プログラムについては、「原油の製油の最適化 - モデル化のポイント」を参考にしてください。
元データ
2013年9月20日名古屋競馬1レースの単勝と馬単の表です。
この表の各行ごとに馬券を何枚買うかを求めます。
from ortoolpy import addbinvars, lpSum, model_min, pd
def sample_data_frame() -> pd.DataFrame:
"""2013年9月20日名古屋競馬1レースの表
:return: 「First=1着番号、Second=2着番号(-1なら単勝)、Odds=オッズ」の表
"""
ls = list(range(1, 8))
df1 = pd.DataFrame({ # 単勝
"First": ls,
"Second": [-1] * 7,
"Odds": [14.7, 72.6, 1.4, 2.3, 151.8, 18.0, 66.8],
})
df2 = pd.DataFrame({ # 馬単
'First': [i for i in ls for _ in ls[1:]],
'Second': [j for i in ls for j in ls if i != j],
'Odds': [
347.2, 37.4, 54.0, 662.7, 169.6, 607.5, 455.6, 177.8, 197.1,
1457.9, 331.4, 2429.8, 23.0, 86.8, 5.4, 50.7, 13.0, 177.8, 10.3,
49.3, 1.9, 90.0, 5.9, 123.6, 1214.9, 1457.9, 74.4, 455.6, 560.8,
3644.7, 119.5, 347.2, 27.6, 43.2, 486.0, 455.6, 1041.4, 2429.8,
1214.9, 911.2, 3644.7, 911.2
]
})
return pd.concat([df1, df2]).reset_index(drop=True)
df = sample_data_frame()
# First=1着番号、Second=2着番号(-1なら単勝)、Odds=オッズ
df[:3] # 先頭3行
First | Second | Odds | |
---|---|---|---|
0 | 1 | -1 | 14.7 |
1 | 2 | -1 | 72.6 |
2 | 3 | -1 | 1.4 |
最適化
def solve(df: pd.DataFrame, num: int, alpha: float) -> pd.DataFrame:
"""購買数を求める
:param df: 変数表
:param num: 購買数上限
:param alpha: リターン比
:return: 変数表(Val列が購買数)
"""
n = len(df[df.Second == -1]) # 単勝の行数
m = model_min(dfi=df) # 数理モデル
df["Mono"] = None # 単勝を買うかどうか
df.loc[: n - 1, "Mono"] = addbinvars(n)
m += lpSum(df.Var) # 目的関数(総購買数)
m += lpSum(df.Var) <= num # 購買数上限
for row in df[:n].itertuples():
m += row.Odds * row.Var >= num * alpha * row.Mono
df.loc[df.First == row.First, "Mono"] = row.Mono
for row in df[n:].itertuples():
m += row.Odds * row.Var >= num * alpha * (1 - row.Mono)
m.solve()
df["Prize"] = df.Odds * df.Val * 100
return df[df.Val > 0] if m.status == 1 else None
res = solve(df, num=1000, alpha=1.08)
res
First | Second | Odds | Var | Mono | Val | Prize | |
---|---|---|---|---|---|---|---|
1 | 2 | -1 | 72.6 | v000002 | v000051 | 15 | 108900 |
3 | 4 | -1 | 2.3 | v000004 | v000053 | 470 | 108100 |
4 | 5 | -1 | 151.8 | v000005 | v000054 | 8 | 121440 |
5 | 6 | -1 | 18 | v000006 | v000055 | 60 | 108000 |
7 | 1 | 2 | 347.2 | v000008 | v000050 | 4 | 138880 |
8 | 1 | 3 | 37.4 | v000009 | v000050 | 29 | 108460 |
9 | 1 | 4 | 54 | v000010 | v000050 | 20 | 108000 |
10 | 1 | 5 | 662.7 | v000011 | v000050 | 2 | 132540 |
11 | 1 | 6 | 169.6 | v000012 | v000050 | 7 | 118720 |
12 | 1 | 7 | 607.5 | v000013 | v000050 | 2 | 121500 |
19 | 3 | 1 | 23 | v000020 | v000052 | 47 | 108100 |
20 | 3 | 2 | 86.8 | v000021 | v000052 | 13 | 112840 |
21 | 3 | 4 | 5.4 | v000022 | v000052 | 200 | 108000 |
22 | 3 | 5 | 50.7 | v000023 | v000052 | 22 | 111540 |
23 | 3 | 6 | 13 | v000024 | v000052 | 84 | 109200 |
24 | 3 | 7 | 177.8 | v000025 | v000052 | 7 | 124460 |
43 | 7 | 1 | 1041.4 | v000044 | v000056 | 2 | 208280 |
44 | 7 | 2 | 2429.8 | v000045 | v000056 | 1 | 242980 |
45 | 7 | 3 | 1214.9 | v000046 | v000056 | 1 | 121490 |
46 | 7 | 4 | 911.2 | v000047 | v000056 | 2 | 182240 |
47 | 7 | 5 | 3644.7 | v000048 | v000056 | 1 | 364470 |
48 | 7 | 6 | 911.2 | v000049 | v000056 | 2 | 182240 |
- 購買数の上限は1000枚(
num
)です。1枚100円なので、軍資金は10万円です。 - どの馬が勝っても最低10万8千円(軍資金×
alpha
)の配当になるように制約条件をつけます。 - 結果として999枚買います(
res.Val.sum()
)。 - 1から7のどの馬が来ても
Prize
列が10万8千円を超えています。
alpha = 1.08
は2分探索で求めると良いでしょう。
机上の空論でなく、100%儲かるところがすごいですね。
以上