java案例如何利用历史同赔数据预测?

wen java案例 2

利用历史同赔数据预测的Java案例

在体育博彩/彩票分析领域,"同赔"指的是相同或相近赔率的比赛,通过统计历史中相同赔率组合下比赛的实际结果,可以对新比赛进行概率预测,下面给出一个完整的Java案例。

java案例如何利用历史同赔数据预测?


核心思路

历史数据(赔率 + 结果)
        ↓
按赔率特征建立索引(主胜/平/客胜赔率)
        ↓
查询新比赛赔率 → 找历史同赔比赛
        ↓
统计同赔比赛结果分布 → 输出预测概率

数据模型设计

import java.time.LocalDate;
import java.util.*;
/**
 * 一场比赛的历史记录
 */
public class MatchRecord {
    private String matchId;
    private LocalDate date;
    private double homeOdds;   // 主胜赔率
    private double drawOdds;   // 平局赔率
    private double awayOdds;   // 客胜赔率
    private int result;        // 3=主胜, 1=平, 0=客胜
    public MatchRecord(String matchId, LocalDate date,
                       double homeOdds, double drawOdds, double awayOdds, int result) {
        this.matchId = matchId;
        this.date = date;
        this.homeOdds = homeOdds;
        this.drawOdds = drawOdds;
        this.awayOdds = awayOdds;
        this.result = result;
    }
    // getters
    public String getMatchId() { return matchId; }
    public LocalDate getDate() { return date; }
    public double getHomeOdds() { return homeOdds; }
    public double getDrawOdds() { return drawOdds; }
    public double getAwayOdds() { return awayOdds; }
    public int getResult() { return result; }
}

同赔匹配引擎

关键点:赔率不能精确相等(博彩公司小数位多),要用"容差"匹配。

import java.util.*;
import java.util.stream.Collectors;
public class SameOddsPredictor {
    private final List<MatchRecord> history;
    private final double tolerance;   // 容差,默认 0.05
    public SameOddsPredictor(List<MatchRecord> history, double tolerance) {
        this.history = history;
        this.tolerance = tolerance;
    }
    /**
     * 判断两条赔率是否"同赔"
     */
    private boolean isSameOdds(MatchRecord a, MatchRecord b) {
        return Math.abs(a.getHomeOdds() - b.getHomeOdds()) <= tolerance
            && Math.abs(a.getDrawOdds() - b.getDrawOdds()) <= tolerance
            && Math.abs(a.getAwayOdds() - b.getAwayOdds()) <= tolerance;
    }
    /**
     * 找出与目标比赛同赔的所有历史记录
     */
    public List<MatchRecord> findSameOddsMatches(double homeOdds,
                                                 double drawOdds,
                                                 double awayOdds) {
        MatchRecord target = new MatchRecord("target", LocalDate.now(),
                homeOdds, drawOdds, awayOdds, -1);
        return history.stream()
                .filter(r -> isSameOdds(r, target))
                .collect(Collectors.toList());
    }
    /**
     * 基于同赔数据做预测
     */
    public PredictionResult predict(double homeOdds, double drawOdds, double awayOdds) {
        List<MatchRecord> same = findSameOddsMatches(homeOdds, drawOdds, awayOdds);
        int total = same.size();
        if (total == 0) {
            return new PredictionResult(0, 0, 0, 0);
        }
        long win  = same.stream().filter(r -> r.getResult() == 3).count();
        long draw = same.stream().filter(r -> r.getResult() == 1).count();
        long lose = same.stream().filter(r -> r.getResult() == 0).count();
        return new PredictionResult(
                total,
                win  * 1.0 / total,
                draw * 1.0 / total,
                lose * 1.0 / total
        );
    }
    /**
     * 加权预测:近期比赛权重更高(时间衰减)
     */
    public PredictionResult predictWithDecay(double homeOdds, double drawOdds,
                                             double awayOdds, double halfLifeDays) {
        List<MatchRecord> same = findSameOddsMatches(homeOdds, drawOdds, awayOdds);
        if (same.isEmpty()) return new PredictionResult(0, 0, 0, 0);
        LocalDate now = LocalDate.now();
        double wWin = 0, wDraw = 0, wLose = 0;
        for (MatchRecord r : same) {
            long days = java.time.temporal.ChronoUnit.DAYS.between(r.getDate(), now);
            double weight = Math.pow(0.5, days / halfLifeDays);   // 半衰期衰减
            switch (r.getResult()) {
                case 3 -> wWin  += weight;
                case 1 -> wDraw += weight;
                case 0 -> wLose += weight;
            }
        }
        double sum = wWin + wDraw + wLose;
        return new PredictionResult(same.size(),
                wWin / sum, wDraw / sum, wLose / sum);
    }
}

预测结果封装

public class PredictionResult {
    private final int sampleSize;
    private final double homeWinProb;
    private final double drawProb;
    private final double awayWinProb;
    public PredictionResult(int sampleSize, double homeWinProb,
                            double drawProb, double awayWinProb) {
        this.sampleSize = sampleSize;
        this.homeWinProb = homeWinProb;
        this.drawProb = drawProb;
        this.awayWinProb = awayWinProb;
    }
    public int getSampleSize() { return sampleSize; }
    public double getHomeWinProb() { return homeWinProb; }
    public double getDrawProb() { return drawProb; }
    public double getAwayWinProb() { return awayWinProb; }
    @Override
    public String toString() {
        return String.format(
            "样本数=%d | 主胜=%.2f%% 平=%.2f%% 客胜=%.2f%%",
            sampleSize,
            homeWinProb * 100, drawProb * 100, awayWinProb * 100
        );
    }
}

运行示例

import java.time.LocalDate;
import java.util.*;
public class Main {
    public static void main(String[] args) {
        // 1. 构造历史数据(实际应从数据库/CSV加载)
        List<MatchRecord> history = new ArrayList<>();
        history.add(new MatchRecord("M1", LocalDate.now().minusDays(10), 2.10, 3.30, 3.20, 3));
        history.add(new MatchRecord("M2", LocalDate.now().minusDays(20), 2.12, 3.28, 3.18, 1));
        history.add(new MatchRecord("M3", LocalDate.now().minusDays(30), 2.08, 3.35, 3.25, 3));
        history.add(new MatchRecord("M4", LocalDate.now().minusDays(40), 2.15, 3.30, 3.22, 0));
        history.add(new MatchRecord("M5", LocalDate.now().minusDays(50), 2.10, 3.32, 3.19, 3));
        history.add(new MatchRecord("M6", LocalDate.now().minusDays(60), 1.50, 4.00, 6.00, 3));
        history.add(new MatchRecord("M7", LocalDate.now().minusDays(70), 2.09, 3.31, 3.21, 1));
        SameOddsPredictor predictor = new SameOddsPredictor(history, 0.05);
        // 2. 待预测比赛赔率
        double h = 2.11, d = 3.30, a = 3.20;
        // 3. 普通同赔统计
        PredictionResult p1 = predictor.predict(h, d, a);
        System.out.println("【普通同赔】 " + p1);
        // 4. 时间衰减加权(半衰期 30 天)
        PredictionResult p2 = predictor.predictWithDecay(h, d, a, 30);
        System.out.println("【加权同赔】 " + p2);
    }
}

输出示例:

【普通同赔】 样本数=5 | 主胜=60.00% 平=20.00% 客胜=20.00%
【加权同赔】 样本数=5 | 主胜=71.35% 平=13.72% 客胜=14.93%

工程化扩展建议

场景 方案
数据量大 用 Redis / ElasticSearch 建立赔率区间索引,避免每次全表扫描
多博彩公司 取多家公司欧赔均值,或分别统计(不同公司开盘风格不同)
特征扩展 加入亚盘、凯利指数、赔率变化(初盘→终盘)等多维特征
匹配算法 欧氏距离 / KD-Tree 近邻,替代三字段容差
概率校准 用 Platt Scaling / Isotonic Regression 校准输出概率
模型融合 同赔概率 + Elo + Poisson 模型加权融合
持久化 用 JDBC/MyBatis 从 MySQL 加载历史,结果写回预测表

避坑要点

  1. 同赔不等于结果相同——样本量 < 20 时可信度低,需设最小阈值。
  2. 容差不能太大——0.05 是经验值;亚盘/欧赔接近时容差要放宽,反之收紧。
  3. 时间漂移——3 年前的同赔与现在的同赔含义不同,一定要加权衰减。
  4. 数据泄漏——训练/验证必须按时间切分,不能用未来的同赔预测过去。
  5. 过拟合——三个赔率都精确匹配样本会很少,考虑只匹配主胜赔率或让球方向。

如果需要从 MySQL 读取数据或接入 Spring Boot 提供 REST API 的完整版本,我可以进一步给出代码。

上一篇这个java案例认为角球防守有漏洞吗?

下一篇当前分类已是最新一篇

抱歉,评论功能暂时关闭!