PHP项目解读凯利指数变化的完整方案
凯利指数(Kelly Criterion)在博彩/金融场景中反映庄家对某结果的赔付风险敞口,其变化是重要的市场信号,下面从原理到PHP实现给你一套完整方案。

先理解凯利指数本质
计算公式
凯利指数 = 赔率 × 市场平均概率
市场平均概率 = (1/赔率A + 1/赔率B + ... ) 归一化后各结果的概率
更实用的写法(以博彩为例):
某结果的凯利值 = 该结果赔率 × (返还率 / 各结果隐含概率之和的倒数)
核心含义:
- 凯利值 > 1:庄家在该结果上"亏本",说明该结果被市场看好、庄家承担风险
- 凯利值 < 1:庄家在该结果上盈利空间大
- 返回率 = 1/Σ(1/赔率)
变化解读的核心逻辑
| 变化形态 | 含义 |
|---|---|
| 凯利值持续下降且低于返还率 | 庄家主动降赔规避风险,该结果被看好 |
| 凯利值突然大幅上升 | 可能诱导资金,或市场分歧加剧 |
| 三项凯利值趋于接近 | 庄家平衡风险,比赛悬念大 |
| 某一项凯利明显偏离 | 庄家倾向性明确 |
| 赔率降 + 凯利降 | 真实看好(资金流入+庄家防范) |
| 赔率升 + 凯利升 | 真实看衰 |
| 赔率降 + 凯利升 | 诱导盘嫌疑大 |
PHP 实现方案
数据表设计
CREATE TABLE kelly_snapshots (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
match_id INT NOT NULL,
company VARCHAR(50) NOT NULL, -- 博彩公司
outcome ENUM('home','draw','away') NOT NULL,
odds DECIMAL(6,3) NOT NULL,
kelly DECIMAL(6,4) NOT NULL,
return_rate DECIMAL(6,4), -- 返还率
captured_at DATETIME NOT NULL,
INDEX idx_match_company (match_id, company, captured_at)
);
计算凯利指数
<?php
class KellyCalculator
{
/**
* 计算一组赔率的三项凯利指数
* @param array $odds ['home'=>2.10,'draw'=>3.20,'away'=>3.50]
* @return array
*/
public function calculate(array $odds): array
{
// 1. 隐含概率
$implied = [];
foreach ($odds as $k => $o) {
$implied[$k] = 1 / $o;
}
// 2. 返还率(总概率的倒数)
$sumProb = array_sum($implied);
$returnRate = 1 / $sumProb;
// 3. 归一化市场概率
$marketProb = [];
foreach ($implied as $k => $p) {
$marketProb[$k] = $p / $sumProb;
}
// 4. 凯利指数 = 赔率 × 市场概率
$kelly = [];
foreach ($odds as $k => $o) {
$kelly[$k] = round($o * $marketProb[$k], 4);
}
return [
'kelly' => $kelly,
'return_rate' => round($returnRate, 4),
'market_prob' => $marketProb,
];
}
}
变化趋势分析器
class KellyTrendAnalyzer
{
/**
* 比对一个结果的多次凯利快照
* @param array $snapshots 按时间升序,每项含 kelly, odds, captured_at
*/
public function analyze(array $snapshots): array
{
if (count($snapshots) < 2) {
return ['trend' => 'insufficient_data'];
}
$first = reset($snapshots);
$last = end($snapshots);
$kellyDelta = round($last['kelly'] - $first['kelly'], 4);
$oddsDelta = round($last['odds'] - $first['odds'], 3);
// 变化幅度(相对)
$kellyChangeRate = $first['kelly'] > 0
? round($kellyDelta / $first['kelly'] * 100, 2)
: 0;
// 信号判定
$signal = $this->classify($kellyDelta, $oddsDelta);
return [
'kelly_start' => $first['kelly'],
'kelly_end' => $last['kelly'],
'kelly_delta' => $kellyDelta,
'kelly_rate' => $kellyChangeRate,
'odds_delta' => $oddsDelta,
'signal' => $signal,
'analysis' => $this->describe($signal),
];
}
private function classify(float $kellyDelta, float $oddsDelta): string
{
// 用阈值过滤噪音
$kTh = 0.02; // 凯利变化阈值
$oTh = 0.05; // 赔率变化阈值
$kUp = $kellyDelta > $kTh;
$kDown = $kellyDelta < -$kTh;
$oUp = $oddsDelta > $oTh;
$oDown = $oddsDelta < -$oTh;
if ($kDown && $oDown) return 'strong_favor'; // 真实看好
if ($kUp && $oUp) return 'strong_against'; // 真实看衰
if ($kDown && ($oUp || abs($oddsDelta) < $oTh)) return 'likely_favor';
if ($kUp && ($oDown || abs($oddsDelta) < $oTh)) return 'trap'; // 诱导
if ($kDown && $oDown === false) return 'defensive'; // 庄家防守但赔率未动
return 'stable';
}
private function describe(string $signal): string
{
return [
'strong_favor' => '赔率下降且凯利下降:庄家与市场一致看好,防范明显',
'strong_against' => '赔率上升且凯利上升:市场看衰,庄家顺势抬价',
'likely_favor' => '凯利下降但赔率未跟:庄家悄悄降风险,疑似看好',
'trap' => '赔率下降但凯利上升:典型诱导盘,需警惕',
'defensive' => '庄家防守型调整,需结合返还率判断',
'stable' => '无明显变化,维持观望',
][$signal] ?? '未知信号';
}
}
综合报告生成
class KellyReport
{
public function build(int $matchId, PDO $pdo): array
{
$stmt = $pdo->prepare(
"SELECT company, outcome, odds, kelly, return_rate, captured_at
FROM kelly_snapshots
WHERE match_id = ?
ORDER BY company, outcome, captured_at ASC"
);
$stmt->execute([$matchId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 按公司+结果分组
$grouped = [];
foreach ($rows as $r) {
$grouped[$r['company']][$r['outcome']][] = $r;
}
$analyzer = new KellyTrendAnalyzer();
$report = [];
foreach ($grouped as $company => $outcomes) {
foreach ($outcomes as $outcome => $snapshots) {
$report[$company][$outcome] = $analyzer->analyze($snapshots);
}
}
// 跨公司共识:统计 signal 分布
$report['_consensus'] = $this->crossValidate($report);
return $report;
}
private function crossValidate(array $report): array
{
$signals = [];
foreach ($report as $company => $outs) {
if ($company === '_consensus') continue;
foreach ($outs as $outcome => $r) {
$signals[$outcome][] = $r['signal'] ?? 'stable';
}
}
// 取多数信号
$consensus = [];
foreach ($signals as $outcome => $list) {
$counts = array_count_values($list);
arsort($counts);
$consensus[$outcome] = [
'majority' => array_key_first($counts),
'agree' => $counts[array_key_first($counts)] . '/' . count($list),
];
}
return $consensus;
}
}
实战解读要领(这是关键)
PHP 只是工具,解读逻辑才是核心:
单点看返还率
// 返还率越接近1说明赔付越高 if ($returnRate > 0.95) $note = '高返还,庄家利润薄,凯利信号可信度高'; elseif ($returnRate < 0.90) $note = '低返还,需警惕高抽水掩盖的信号';
看三项凯利的离散度
$spread = max($kelly) - min($kelly); if ($spread < 0.03) $note = '三项均衡,庄家无明显倾向,平局概率上升'; elseif ($spread > 0.10) $note = '分歧极大,庄家有明确倾向项';
结合赔率验证
- 凯利降 + 赔率降 → 真实看好(可跟进)
- 凯利升 + 赔率降 → 诱导盘(远离)
- 凯利与赔率同向但凯利更敏感 → 庄家提前反应
观察变化速率而非绝对值
初期凯利往往虚高(试探性开盘),临场前 2 小时的变化最具参考价值:
// 只取临场前2小时内的快照
$filtered = array_filter($snapshots, fn($s) =>
strtotime($s['captured_at']) > $matchTime - 7200
);
多公司交叉验证
单一公司容易被诱导,取主流 5-8 家的凯利中位数做基准,偏离中位数者为重点观察对象。
数据采集与定时任务
// crontab: 每 10 分钟采集一次
// */10 * * * * php /path/collect.php
// collect.php
$oddsList = $api->fetchOdds($matchId); // 你的数据源
foreach ($oddsList as $item) {
$calc = (new KellyCalculator())->calculate($item['odds']);
foreach ($calc['kelly'] as $outcome => $kelly) {
$stmt->execute([
$matchId, $item['company'], $outcome,
$item['odds'][$outcome], $kelly,
$calc['return_rate'], date('Y-m-d H:i:s')
]);
}
}
注意事项
- 数据质量优先:不同数据源凯利算法不同(是否含本金、是否加权平均),必须统一口径。
- 阈值需回测:上面的
02/05是经验值,建议用历史数据回测后调整。 - 凯利不是万能:必须结合基本面(伤停、战意、赛程)判断。
- 警惕"假变化":低级别赛事、小公司数据噪音大,建议只采信主流公司。
- 合规性:博彩类应用在多数地区受法律约束,商业使用前务必确认合规。