本文目录导读:

整体架构设计
flowchart TD
A[历史赔率数据库] --> B[数据预处理模块]
B --> C[同赔匹配引擎]
C --> D[概率统计模型]
D --> E[预测结果输出]
F[实时赔率输入] --> C
核心数据模型
// 赔率数据实体类
public class OddsData {
private String matchId; // 比赛ID
private String league; // 联赛名称
private double homeWinOdds; // 主胜赔率
private double drawOdds; // 平局赔率
private double awayWinOdds; // 客胜赔率
private String result; // 实际结果: H(主胜)/D(平局)/A(客胜)
private LocalDate matchDate; // 比赛日期
// 计算赔率组合唯一标识(用于同赔匹配)
public String getOddsKey() {
return String.format("%.2f_%.2f_%.2f",
homeWinOdds, drawOdds, awayWinOdds);
}
}
同赔匹配引擎(核心算法)
public class OddsMatcher {
// 容差范围,用于模糊匹配(如3%误差)
private static final double TOLERANCE = 0.03;
/**
* 查找历史同赔数据
*/
public List<HistoricalMatch> findSimilarOdds(
List<OddsData> historyData,
double targetHomeOdds,
double targetDrawOdds,
double targetAwayOdds) {
return historyData.stream()
.filter(data -> isSimilar(data.getHomeWinOdds(), targetHomeOdds)
&& isSimilar(data.getDrawOdds(), targetDrawOdds)
&& isSimilar(data.getAwayWinOdds(), targetAwayOdds))
.collect(Collectors.toList());
}
/**
* 判断两个赔率是否在容差范围内相同
*/
private boolean isSimilar(double odds1, double odds2) {
if (odds2 == 0) return false;
return Math.abs(odds1 - odds2) / odds2 <= TOLERANCE;
}
}
概率统计分析器
public class ProbabilityAnalyzer {
/**
* 根据历史同赔数据计算三种结果的概率
*/
public PredictionResult analyze(List<HistoricalMatch> similarMatches) {
if (similarMatches.isEmpty()) {
return new PredictionResult(0.33, 0.33, 0.33, 0);
}
long totalMatches = similarMatches.size();
long homeWins = similarMatches.stream()
.filter(m -> "H".equals(m.getResult())).count();
long draws = similarMatches.stream()
.filter(m -> "D".equals(m.getResult())).count();
long awayWins = similarMatches.stream()
.filter(m -> "A".equals(m.getResult())).count();
// 计算概率
double homeWinProb = (double) homeWins / totalMatches;
double drawProb = (double) draws / totalMatches;
double awayWinProb = (double) awayWins / totalMatches;
// 使用正态分布置信区间修正
double confidence = calculateConfidence(totalMatches);
return new PredictionResult(homeWinProb, drawProb,
awayWinProb, confidence, totalMatches);
}
/**
* 计算置信度(样本量越大越可信)
*/
private double calculateConfidence(int sampleSize) {
if (sampleSize < 5) return 0.3;
if (sampleSize < 20) return 0.5;
if (sampleSize < 50) return 0.7;
if (sampleSize < 100) return 0.85;
return 0.95;
}
/**
* 贝叶斯平滑(处理小样本情况)
*/
public double[] bayesianSmooth(double[] probs, double alpha) {
int n = probs.length;
double[] smoothed = new double[n];
double sum = 0;
for (double p : probs) {
sum += p;
}
for (int i = 0; i < n; i++) {
smoothed[i] = (probs[i] + alpha) / (sum + n * alpha);
}
return smoothed;
}
}
权重加权模型(综合多家赔率)
public class WeightedPredictionModel {
// 不同博彩公司的权重(信誉越高权重越大)
private static final Map<String, Double> COMPANY_WEIGHTS = Map.of(
"WilliamHill", 0.25,
"Bet365", 0.30,
"Pinnacle", 0.35,
"Ladbrokes", 0.10
);
/**
* 综合多公司赔率预测
*/
public PredictionResult combinedPrediction(
Map<String, OddsData> companyOdds) {
double totalWeight = companyOdds.entrySet().stream()
.mapToDouble(e -> COMPANY_WEIGHTS
.getOrDefault(e.getKey(), 0.2))
.sum();
double weightedHome = 0, weightedDraw = 0, weightedAway = 0;
for (Map.Entry<String, OddsData> entry : companyOdds.entrySet()) {
double weight = COMPANY_WEIGHTS
.getOrDefault(entry.getKey(), 0.2) / totalWeight;
// 赔率转概率(去除抽水)
double[] probs = convertOddsToProb(
entry.getValue().getHomeWinOdds(),
entry.getValue().getDrawOdds(),
entry.getValue().getAwayWinOdds());
weightedHome += probs[0] * weight;
weightedDraw += probs[1] * weight;
weightedAway += probs[2] * weight;
}
return new PredictionResult(weightedHome, weightedDraw,
weightedAway, 0.8);
}
/**
* 赔率转换为概率(去除博彩公司抽水利润)
*/
private double[] convertOddsToProb(double homeOdds, double drawOdds,
double awayOdds) {
double sum = 1/homeOdds + 1/drawOdds + 1/awayOdds;
return new double[]{
(1/homeOdds) / sum,
(1/drawOdds) / sum,
(1/awayOdds) / sum
};
}
}
完整预测系统实现
public class OddsPredictionSystem {
private OddsMatcher matcher = new OddsMatcher();
private ProbabilityAnalyzer analyzer = new ProbabilityAnalyzer();
private WeightedPredictionModel weightedModel = new WeightedPredictionModel();
private List<OddsData> historicalData; // 历史数据(从数据库加载)
/**
* 主预测流程
*/
public PredictionResult predict(
double homeOdds, double drawOdds, double awayOdds) {
// 1. 查找历史同赔数据
List<HistoricalMatch> similarMatches = matcher
.findSimilarOdds(historicalData, homeOdds, drawOdds, awayOdds);
// 2. 如果样本太少,扩大容差范围再试
if (similarMatches.size() < 5) {
matcher.setTolerance(0.05);
similarMatches = matcher.findSimilarOdds(
historicalData, homeOdds, drawOdds, awayOdds);
}
// 3. 批量分析
PredictionResult histPred = analyzer
.analyze(similarMatches);
// 4. 结合实时赔率隐含概率
double[] marketProbs = calculateMarketProb(
homeOdds, drawOdds, awayOdds);
// 5. 综合加权(历史数据60% + 市场数据40%)
return fuseResults(histPred, marketProbs);
}
/**
* 结果融合
*/
private PredictionResult fuseResults(
PredictionResult histPred, double[] marketProbs) {
double histWeight = histPred.getConfidence() * 0.6;
double marketWeight = 1 - histWeight;
double finalHome = histPred.getHomeWinProb() * histWeight
+ marketProbs[0] * marketWeight;
double finalDraw = histPred.getDrawProb() * histWeight
+ marketProbs[1] * marketWeight;
double finalAway = histPred.getAwayWinProb() * histWeight
+ marketProbs[2] * marketWeight;
return new PredictionResult(finalHome, finalDraw,
finalAway, histPred.getConfidence());
}
/**
* 市场赔率转换为概率
*/
private double[] calculateMarketProb(
double homeOdds, double drawOdds, double awayOdds) {
double margin = 1/homeOdds + 1/drawOdds + 1/awayOdds;
return new double[]{
(1/homeOdds) / margin,
(1/drawOdds) / margin,
(1/awayOdds) / margin
};
}
}
数据存储与加载(SQL示例)
-- 创建赔率历史表
CREATE TABLE odds_history (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
match_date DATE NOT NULL,
league VARCHAR(50),
home_team VARCHAR(100),
away_team VARCHAR(100),
home_win_odds DECIMAL(6,2),
draw_odds DECIMAL(6,2),
away_win_odds DECIMAL(6,2),
result CHAR(1), -- H/D/A
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_odds (home_win_odds, draw_odds, away_win_odds)
);
-- 查询同赔数据
SELECT * FROM odds_history
WHERE ABS(home_win_odds - ?) <= ? * ? -- 目标值,容差,目标值
AND ABS(draw_odds - ?) <= ? * ?
AND ABS(away_win_odds - ?) <= ? * ?
AND match_date < CURDATE() - INTERVAL 1 DAY; -- 排除当天数据避免未来数据
使用示例
public class Demo {
public static void main(String[] args) {
OddsPredictionSystem system = new OddsPredictionSystem();
// 加载历史数据(假设从数据库)
List<OddsData> history = loadHistoryFromDB();
system.setHistoricalData(history);
// 某场比赛实时赔率
double homeOdds = 1.75;
double drawOdds = 3.60;
double awayOdds = 4.50;
PredictionResult result = system.predict(homeOdds, drawOdds, awayOdds);
System.out.printf("主胜概率: %.2f%%%n", result.getHomeWinProb()*100);
System.out.printf("平局概率: %.2f%%%n", result.getDrawProb()*100);
System.out.printf("客胜概率: %.2f%%%n", result.getAwayWinProb()*100);
System.out.printf("推荐结果: %s%n", result.getRecommended());
System.out.printf("置信度: %.2f%%%n", result.getConfidence()*100);
}
}
优化与注意事项
- 数据质量:至少需要5000+条历史数据,且要过滤异常赔率
- 联赛细分:不同联赛的赔率规律差异大,最好按联赛分开建模
- 时间衰减:近期的比赛数据权重应该更高(例如最近3个月权重0.5,更早的0.3)
- 动态容差:根据样本量动态调整匹配精度
- 冷门检测:当预测概率与市场隐含概率差异过大时,提示可能冷门