LoginSignup
29
32

More than 3 years have passed since last update.

組合せ最適化による競馬必勝法

Last updated at Posted at 2020-08-18

これなに

競馬必勝法をプログラム化」を組合せ最適化で解いてみました。

元データ

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%儲かるところがすごいですね。

以上

29
32
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
32