利用历史同赔数据预测的Java案例实现
核心概念
同赔 = 相同赔率组合,通过查找历史上与当前比赛赔率完全一致(或接近)的比赛结果,统计主胜/平/客胜的分布,作为预测依据。

完整代码案例
数据模型
// 比赛记录
public class MatchRecord {
private String matchId;
private double homeOdds; // 主胜赔率
private double drawOdds; // 平局赔率
private double awayOdds; // 客胜赔率
private String result; // 结果: "H"(主胜) "D"(平) "A"(客胜)
private LocalDate matchDate;
// 构造函数、getter、setter省略
}
核心预测服务
import java.util.*;
import java.util.stream.Collectors;
public class SameOddsPredictor {
// 容忍度:允许赔率的小幅波动(如0.02代表±2%)
private static final double TOLERANCE = 0.02;
// 最小样本量阈值
private static final int MIN_SAMPLE = 10;
/**
* @param history 历史比赛列表
* @param target 待预测比赛(只有赔率)
* @return 预测结果及置信度
*/
public PredictionResult predict(List<MatchRecord> history, MatchRecord target) {
// 1. 筛选同赔比赛(精确匹配 + 容差匹配)
List<MatchRecord> sameOddsMatches = history.stream()
.filter(m -> isSameOdds(m, target))
.collect(Collectors.toList());
if (sameOddsMatches.size() < MIN_SAMPLE) {
return new PredictionResult(null, 0.0, sameOddsMatches.size(), "样本不足");
}
// 2. 统计各结果出现次数
Map<String, Long> counts = sameOddsMatches.stream()
.collect(Collectors.groupingBy(MatchRecord::getResult, Collectors.counting()));
long total = sameOddsMatches.size();
double pH = counts.getOrDefault("H", 0L) / (double) total;
double pD = counts.getOrDefault("D", 0L) / (double) total;
double pA = counts.getOrDefault("A", 0L) / (double) total;
// 3. 取最大概率作为预测
String predicted;
double confidence;
if (pH >= pD && pH >= pA) {
predicted = "H"; confidence = pH;
} else if (pD >= pA) {
predicted = "D"; confidence = pD;
} else {
predicted = "A"; confidence = pA;
}
return new PredictionResult(predicted, confidence, total,
String.format("主胜%.1f%% 平%.1f%% 客胜%.1f%%",
pH*100, pD*100, pA*100));
}
/**
* 判断两场比赛赔率是否"相同"(含容差)
*/
private boolean isSameOdds(MatchRecord a, MatchRecord b) {
return near(a.getHomeOdds(), b.getHomeOdds())
&& near(a.getDrawOdds(), b.getDrawOdds())
&& near(a.getAwayOdds(), b.getAwayOdds());
}
private boolean near(double x, double y) {
if (y == 0) return x == 0;
return Math.abs(x - y) / y <= TOLERANCE;
}
}
结果封装
public class PredictionResult {
private String predicted; // H/D/A
private double confidence; // 置信度
private int sampleSize; // 样本数
private String detail; // 概率明细
public PredictionResult(String predicted, double confidence,
int sampleSize, String detail) {
this.predicted = predicted;
this.confidence = confidence;
this.sampleSize = sampleSize;
this.detail = detail;
}
// getter...
}
使用示例
public class Demo {
public static void main(String[] args) {
List<MatchRecord> history = loadHistory(); // 从DB/CSV加载
MatchRecord target = new MatchRecord();
target.setHomeOdds(1.85);
target.setDrawOdds(3.40);
target.setAwayOdds(4.20);
SameOddsPredictor predictor = new SameOddsPredictor();
PredictionResult r = predictor.predict(history, target);
System.out.println("预测: " + r.getPredicted());
System.out.println("置信度: " + r.getConfidence());
System.out.println("样本: " + r.getSampleSize());
System.out.println("明细: " + r.getDetail());
}
}
工程化增强(建议)
赔率分桶匹配(避免容差失效)
真实数据中完全同赔可能样本极少,按区间分桶更实用:
public String bucketKey(MatchRecord m) {
// 精确到0.05的桶
return String.format("%.2f_%.2f_%.2f",
Math.round(m.getHomeOdds() / 0.05) * 0.05,
Math.round(m.getDrawOdds() / 0.05) * 0.05,
Math.round(m.getAwayOdds() / 0.05) * 0.05);
}
history.stream().filter(m -> bucketKey(m).equals(bucketKey(target)))。
加权历史(近期权重更高)
private double weight(MatchRecord m) {
long daysAgo = ChronoUnit.DAYS.between(m.getMatchDate(), LocalDate.now());
return Math.exp(-daysAgo / 365.0); // 一年衰减一半左右
}
然后统计时用 Map<String, Double> weighted 累加,而不是计数。
数据源与持久化
- 数据采集:爬虫/API获取(注意合规)
- 存储:MySQL表
match_history(match_id, home_odds, draw_odds, away_odds, result, match_date) - 加速:对赔率字段建索引,或预先按桶聚合缓存
评估指标
不要只看准确率,用回测:
// 时间序列分割:用过去数据预测未来,避免未来信息泄露 double accuracy = backtest(history, 0.8); // 前80%训练,后20%测试
重点评估:
- 准确率 vs 基线(永远猜主胜的准确率)
- 对数损失 / Brier分数(概率校准)
- 期望值EV(结合赔率:若 p_H × homeOdds > 1 才有投注价值)
避坑提醒
| 问题 | 说明 |
|---|---|
| 样本太少 | 同赔样本常<10,必须设阈值或放宽容差 |
| 幸存者偏差 | 只统计已完赛比赛 |
| 数据泄漏 | 必须用比赛前的赔率,不能用终盘 |
| 赔率随时间变化 | 初盘/终盘差异大,注意取哪个时点 |
| 过拟合 | 容差、样本阈值、权重参数需交叉验证 |
| 概率非承诺 | 30%概率事件也会发生,须管理风险 |
扩展方向
- 贝叶斯平滑:小样本时向先验概率收缩
- 逻辑回归 / XGBoost:把"同赔统计"作为特征之一
- 多博彩公司同赔:竞彩 + 主流欧赔一起看
- 赔率变动趋势:初盘→终盘的方向也是强信号
核心思想:同赔预测本质是条件频率统计,工程重点是数据质量、样本阈值和概率校准,而非算法复杂。