每日算法 — 使用java实现赛马:蒙特卡洛模拟与期望值最优下注策略

引言

赛马是一项历史悠久的竞技运动,同时也是概率论与博弈论的经典应用场景。在赛马投注中,如何根据马匹的历史表现、赛道条件等因素,计算出最优的下注策略,是一个极具挑战性的算法问题。本文将使用Java实现一个赛马下注决策系统,核心采用蒙特卡洛模拟方法评估各马匹的胜率分布,并通过期望值计算找到最优下注组合。

问题建模

假设一场赛马有N匹马参赛,每匹马i具有以下属性:
– 历史平均速度 avgSpeed[i]
– 速度标准差 stdDev[i](反映发挥稳定性)
– 赛道适应性系数 trackFactor[i]
– 赔率 odds[i]

比赛结果由每匹马的实际表现速度决定,实际速度服从正态分布:

actualSpeed[i] ~ N(avgSpeed[i] * trackFactor[i], stdDev[i]^2)

核心算法设计

1. 蒙特卡洛模拟

通过大量随机采样(如100000次),模拟比赛结果,统计每匹马获胜的频率作为胜率估计:

public class HorseRacingMonteCarlo {
    private static final int SIMULATION_COUNT = 100000;
    private final Random random = new Random();

    /**
     * 单场比赛模拟:返回获胜马匹的索引
     */
    public int simulateRace(double[] avgSpeed, double[] stdDev, 
                           double[] trackFactor) {
        int n = avgSpeed.length;
        double bestSpeed = -1;
        int winner = -1;

        for (int i = 0; i < n; i++) {
            // 正态分布采样:Box-Muller变换
            double u1 = random.nextDouble();
            double u2 = random.nextDouble();
            double z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);

            double actualSpeed = avgSpeed[i] * trackFactor[i] + stdDev[i] * z;

            if (actualSpeed > bestSpeed) {
                bestSpeed = actualSpeed;
                winner = i;
            }
        }
        return winner;
    }

    /**
     * 蒙特卡洛模拟统计胜率
     */
    public double[] estimateWinProbabilities(double[] avgSpeed, double[] stdDev,
                                             double[] trackFactor) {
        int n = avgSpeed.length;
        int[] winCount = new int[n];

        for (int sim = 0; sim < SIMULATION_COUNT; sim++) {
            int winner = simulateRace(avgSpeed, stdDev, trackFactor);
            winCount[winner]++;
        }

        double[] probs = new double[n];
        for (int i = 0; i < n; i++) {
            probs[i] = (double) winCount[i] / SIMULATION_COUNT;
        }
        return probs;
    }
}

2. 期望值计算与最优下注策略

给定赔率 odds[i](表示下注1元,获胜后连本带利获得 odds[i] 元),下注金额为 bet[i],总预算为 B。

单注期望收益:

E[i] = probs[i] * (odds[i] - 1) * bet[i] - (1 - probs[i]) * bet[i]
     = bet[i] * (probs[i] * odds[i] - 1)

当 probs[i] * odds[i] > 1 时,该注具有正期望值(Value Bet)。

public class BettingStrategy {

    /**
     * 计算Kelly criterion最优下注比例
     * Kelly公式:f* = (p * b - q) / b
     * 其中 p为胜率, b为净赔率(odds-1), q=1-p
     */
    public double kellyCriterion(double winProb, double odds) {
        double b = odds - 1.0; // 净赔率
        double q = 1.0 - winProb;
        double f = (winProb * b - q) / b;
        return Math.max(0, Math.min(f, 1.0)); // 限制在[0,1]
    }

    /**
     * 基于期望值的贪心最优下注策略
     * 优先投注期望收益最高的组合
     */
    public double[] optimalBetting(double[] probs, double[] odds, double budget) {
        int n = probs.length;
        double[] bets = new double[n];

        // 计算每匹马的期望收益率
        double[] expectedReturns = new double[n];
        for (int i = 0; i < n; i++) {
            expectedReturns[i] = probs[i] * odds[i] - 1.0;
        }

        // 只投注正期望值的马匹,按Kelly比例分配
        double totalKelly = 0;
        double[] kellyFractions = new double[n];
        for (int i = 0; i < n; i++) {
            if (expectedReturns[i] > 0) {
                kellyFractions[i] = kellyCriterion(probs[i], odds[i]);
                totalKelly += kellyFractions[i];
            }
        }

        // 按比例分配预算
        if (totalKelly > 0) {
            for (int i = 0; i < n; i++) {
                if (kellyFractions[i] > 0) {
                    bets[i] = budget * kellyFractions[i] / totalKelly;
                }
            }
        }

        return bets;
    }

    /**
     * 计算组合下注的整体期望收益
     */
    public double calculateExpectedProfit(double[] probs, double[] odds, double[] bets) {
        double expectedProfit = 0;
        for (int i = 0; i < probs.length; i++) {
            expectedProfit += probs[i] * bets[i] * odds[i] - bets[i];
        }
        return expectedProfit;
    }
}

3. 完整模拟系统

public class HorseRacingSimulator {
    public static void main(String[] args) {
        // 示例数据:5匹马的比赛
        String[] names = {"闪电", "疾风", "雷霆", "烈焰", "流星"};
        double[] avgSpeed = {18.5, 19.0, 17.8, 18.2, 18.8};
        double[] stdDev = {0.8, 1.2, 0.5, 1.0, 0.9};
        double[] trackFactor = {1.0, 0.95, 1.05, 1.0, 0.98};
        double[] odds = {3.5, 2.8, 5.0, 4.2, 3.0};
        double budget = 1000.0;

        HorseRacingMonteCarlo mc = new HorseRacingMonteCarlo();
        double[] probs = mc.estimateWinProbabilities(avgSpeed, stdDev, trackFactor);

        System.out.println("=== 蒙特卡洛模拟胜率估计 ===");
        for (int i = 0; i < names.length; i++) {
            System.out.printf("%s: 胜率=%.4f, 赔率=%.2f, 价值指数=%.4f%n",
                names[i], probs[i], odds[i], probs[i] * odds[i]);
        }

        BettingStrategy strategy = new BettingStrategy();
        double[] bets = strategy.optimalBetting(probs, odds, budget);

        System.out.println("\n=== 最优下注策略 ===");
        double totalBet = 0;
        for (int i = 0; i < names.length; i++) {
            if (bets[i] > 0) {
                System.out.printf("%s: 下注 %.2f 元%n", names[i], bets[i]);
                totalBet += bets[i];
            }
        }
        System.out.printf("总下注: %.2f 元%n", totalBet);

        double expectedProfit = strategy.calculateExpectedProfit(probs, odds, bets);
        System.out.printf("期望收益: %.2f 元 (收益率: %.2f%%)%n",
            expectedProfit, expectedProfit / totalBet * 100);
    }
}

复杂度分析

算法模块 时间复杂度 空间复杂度
单次比赛模拟 O(N) O(1)
蒙特卡洛胜率估计 O(SIMULATION_COUNT × N) O(N)
最优下注计算 O(N) O(N)

其中N为参赛马匹数量,SIMULATION_COUNT为模拟次数(默认100000)。

算法扩展

  1. 关联分析:考虑马匹间的相互作用(如领跑策略影响后方马匹)
  2. 贝叶斯更新:根据新比赛结果动态更新马匹的能力参数
  3. 组合投注:支持连赢、位置等多种投注类型的联合优化
  4. 风险约束:引入方差约束,构建均值-方差最优投资组合

总结

本文通过蒙特卡洛模拟方法解决了赛马胜率估计问题,并利用Kelly准则和期望值最大化原理给出了最优下注策略。核心思路是将赛马问题转化为概率估计和投资组合优化问题,这种建模方法同样适用于其他竞技项目的预测与分析。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注