docs(predictV3): 添加predictV3算法优化研究文档和前端功能实现

- 完成Phase 11: predictV3算法优化研究文档,涵盖6个优化方向的技术分析
- 实现置信度评估功能,提供历史命中率、得分分布、多维度一致性置信度指标
- 扩展回测指标体系,新增NDCG@K、MRR、命中率分布等排名质量评估指标
- 优化转移概率算法,引入二阶马尔可夫链和多属性联合转移增强预测准确性
- 设计权重训练机制,支持网格搜索和遗传算法进行数据驱动的参数优化
- 集成组合特征挖掘功能,采用关联规则和序列模式发现号码间潜在关联
- 实现完整的前端交互界面,支持预测结果显示、置信度展示和回测验证功能
- 建立性能优化策略,包括预计算缓存、批量计算和降级策略保障响应速度
This commit is contained in:
2026-05-01 23:17:24 +08:00
parent 02b3ff3a22
commit 8b2590c5b5
26 changed files with 5407 additions and 2 deletions
+25 -1
View File
@@ -22,7 +22,7 @@ class History extends Backend
* 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录)
* @var array
*/
protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard', 'specialHeatmap', 'specialHotColdAction', 'zoneTransition', 'colorWaveTransition', 'zoneToColorTransition', 'zodiacTransition', 'tailNumberTransition', 'headNumberTransition', 'predict', 'predictV2', 'predictV3', 'optimizeWeights'];
protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard', 'specialHeatmap', 'specialHotColdAction', 'zoneTransition', 'colorWaveTransition', 'zoneToColorTransition', 'zodiacTransition', 'tailNumberTransition', 'headNumberTransition', 'predict', 'predictV2', 'predictV3', 'optimizeWeights', 'predictByNormalRelation'];
public function _initialize()
{
@@ -535,5 +535,29 @@ class History extends Backend
}
}
/**
* 基于正码关联规律的特码预测
* 核心规律:
* - 波色重复规律:90.67%特码波色与正码中某号码波色相同
* - 距离规律:94.13%特码与最近正码距离<=10
* - 区间覆盖规律:74.13%特码落在正码覆盖的区间
* - 正码范围规律:70.67%特码在正码min-max之间
*/
public function predictByNormalRelation()
{
if ($this->request->isAjax()) {
$periods = $this->request->get('periods', 100, 'intval');
if ($periods < 30 || $periods > 500) {
$periods = 100;
}
$targetExpect = $this->request->get('target_expect', '', 'trim');
$result = $this->model->getPredictionByNormalRelation($periods, $targetExpect);
if (isset($result['error'])) {
$this->error($result['error']);
}
$this->success('查询成功', null, $result);
}
}
}
+538
View File
@@ -4290,5 +4290,543 @@ class History extends Model
];
}
/**
* 基于正码关联规律的特码预测方法(修正版)
* 核心规律:上期正码 → 当期特码
* - 覆盖区间规律:91.44% 当期特码在上期正码覆盖的区间内
* - 正码±3距离:59.36% 当期特码与上期正码某号码距离≤3
* - 双波色预测:69.52% 当期特码波色在上期正码前2种主导波色内
* - 特码区间转移:77.54% 基于上期特码区间预测当期特码区间
* - 平均值±10:41.98% 当期特码在上期正码平均值±10范围
* - 尾数±2:50% 和值尾数与特码尾数差≤2
* @param int $periods 统计期数(用于验证历史命中率)
* @param string $targetExpect 目标期号(可选,用于回测验证)
* @return array {predictions: [], analysis: {}, hit_info: {}, backtest: {}}
*/
public function getPredictionByNormalRelation($periods = 100, $targetExpect = '')
{
$num_model = new Num();
$colorMap = $num_model->column('color', 'num');
$animalMap = $num_model->column('animal', 'num');
// 区间划分:大号(31-49)、中号(11-30)、小号(1-10)
$getBigZone = function ($num) {
if ($num <= 10) return 'small';
if ($num <= 30) return 'mid';
return 'big';
};
// 细区间划分:1-10, 11-20, 21-30, 31-40, 41-49
$getFineZone = function ($num) {
if ($num <= 10) return 0;
if ($num <= 20) return 1;
if ($num <= 30) return 2;
if ($num <= 40) return 3;
return 4;
};
// 确定预测基准
$actualResult = null;
$lastNormals = [];
$lastSpecial = 0;
$lastExpect = '';
$cutoffTime = null;
if ($targetExpect) {
$targetRow = $this->where('expect', $targetExpect)->find();
if (!$targetRow) {
return ['predictions' => [], 'error' => '期号不存在', 'target_expect' => $targetExpect];
}
$cutoffTime = $targetRow['openTime'];
$actualResult = [
'expect' => (string)$targetRow['expect'],
'num7' => (int)$targetRow['num7'],
'color' => $colorMap[$targetRow['num7']] ?? '',
'animal' => $animalMap[$targetRow['num7']] ?? '',
'bigZone' => $getBigZone($targetRow['num7']),
'openTime' => $targetRow['openTime']
];
// 获取上一期数据作为预测基准
$prevRow = $this->where('openTime', '<', $cutoffTime)->order('openTime', 'desc')->limit(1)->find();
if (!$prevRow) {
return ['predictions' => [], 'error' => '没有历史数据'];
}
for ($i = 1; $i <= 6; $i++) {
$lastNormals[] = (int)$prevRow['num' . $i];
}
$lastSpecial = (int)$prevRow['num7'];
$lastExpect = (string)$prevRow['expect'];
} else {
// 使用最新一期作为预测基准
$latest = $this->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
->order('openTime', 'desc')->limit(1)->find();
if (!$latest) {
return ['predictions' => [], 'error' => '没有历史数据'];
}
for ($i = 1; $i <= 6; $i++) {
$lastNormals[] = (int)$latest['num' . $i];
}
$lastSpecial = (int)$latest['num7'];
$lastExpect = (string)$latest['expect'];
}
// 分析上期正码特征
$normalMin = min($lastNormals);
$normalMax = max($lastNormals);
$normalAvg = round(array_sum($lastNormals) / 6, 2);
$normalSum = array_sum($lastNormals);
// 统计上期正码波色分布(找出前2种主导波色)
$colorCounts = ['红' => 0, '蓝' => 0, '绿' => 0];
foreach ($lastNormals as $n) {
$color = $colorMap[$n] ?? '';
if (strpos($color, '红') !== false) $colorCounts['红']++;
elseif (strpos($color, '蓝') !== false) $colorCounts['蓝']++;
elseif (strpos($color, '绿') !== false) $colorCounts['绿']++;
}
// 按数量排序,取前2种
$sortedColors = [];
foreach ($colorCounts as $c => $cnt) {
$sortedColors[] = ['color' => $c, 'count' => $cnt];
}
usort($sortedColors, function ($a, $b) { return $b['count'] - $a['count']; });
$top2Colors = [$sortedColors[0]['color'], $sortedColors[1]['color']];
// 获取上期正码覆盖的细区间
$normalFineZones = [];
foreach ($lastNormals as $n) {
$zoneIdx = $getFineZone($n);
if (!in_array($zoneIdx, $normalFineZones)) {
$normalFineZones[] = $zoneIdx;
}
}
// 获取上期正码覆盖的大区间
$normalBigZones = [];
foreach ($lastNormals as $n) {
$bigZone = $getBigZone($n);
if (!in_array($bigZone, $normalBigZones)) {
$normalBigZones[] = $bigZone;
}
}
// 上期特码所在大区间
$lastSpecialBigZone = $getBigZone($lastSpecial);
// 特码区间转移概率矩阵(基于历史分析)
$zoneTransMatrix = [
'big' => ['big' => 35.77, 'mid' => 37.96, 'small' => 26.28],
'mid' => ['big' => 33.77, 'mid' => 43.51, 'small' => 22.73],
'small' => ['big' => 42.17, 'mid' => 42.17, 'small' => 15.66]
];
// 计算每个号码的预测评分
$predictions = [];
for ($num = 1; $num <= 49; $num++) {
$numColor = $colorMap[$num] ?? '';
$numBigZone = $getBigZone($num);
$numFineZone = $getFineZone($num);
// 规律1:覆盖区间规律(91.44%命中)- 细区间覆盖
$fineZoneCovered = in_array($numFineZone, $normalFineZones);
$zoneCoverScore = $fineZoneCovered ? 91 : 0;
// 规律2:正码±3距离(59.36%命中)
$minDistance = 49;
foreach ($lastNormals as $n) {
$dist = abs($num - $n);
if ($dist < $minDistance) $minDistance = $dist;
}
$distScore = $minDistance <= 3 ? 59 : ($minDistance <= 5 ? 40 : 0);
// 规律3:双波色预测(69.52%命中)
$colorInTop2 = false;
foreach ($top2Colors as $tc) {
if (strpos($numColor, $tc) !== false) {
$colorInTop2 = true;
break;
}
}
$colorScore = $colorInTop2 ? 69 : 0;
// 规律4:特码区间转移(77.54%命中)
$transProb = $zoneTransMatrix[$lastSpecialBigZone][$numBigZone] ?? 0;
// 取该区间最高转移概率的2个区间
$transProbs = $zoneTransMatrix[$lastSpecialBigZone];
$sortedTrans = [];
foreach ($transProbs as $z => $p) {
$sortedTrans[] = ['zone' => $z, 'prob' => $p];
}
usort($sortedTrans, function ($a, $b) { return $b['prob'] - $a['prob']; });
$top2TransZones = [$sortedTrans[0]['zone'], $sortedTrans[1]['zone']];
$transMatch = in_array($numBigZone, $top2TransZones);
$transScore = $transMatch ? 77 : 0;
// 规律5:平均值±1041.98%命中)
$avgDiff = abs($num - $normalAvg);
$avgScore = $avgDiff <= 10 ? 42 : 0;
// 规律6:尾数±250%命中)
$numTail = $num % 10;
$sumTail = $normalSum % 10;
$tailDiff = abs($numTail - $sumTail);
$tailDiff = min($tailDiff, 10 - $tailDiff);
$tailScore = $tailDiff <= 2 ? 50 : ($tailDiff <= 3 ? 30 : 0);
// 综合评分(加权求和)
$totalScore = $zoneCoverScore * 0.30 // 覆盖区间权重最高
+ $transScore * 0.25 // 特码区间转移
+ $colorScore * 0.20 // 双波色
+ $distScore * 0.12 // 距离
+ $avgScore * 0.08 // 平均值
+ $tailScore * 0.05; // 尾数
$predictions[] = [
'num' => $num,
'score' => round($totalScore, 2),
'color' => $numColor,
'animal' => $animalMap[$num] ?? '',
'big_zone' => $numBigZone,
'fine_zone' => $numFineZone,
'zone_covered' => $fineZoneCovered,
'min_distance' => $minDistance,
'color_in_top2' => $colorInTop2,
'trans_match' => $transMatch,
'trans_prob' => $transProb,
'avg_diff' => round($avgDiff, 2),
'tail_diff' => $tailDiff
];
}
// 按评分降序排序
usort($predictions, function ($a, $b) {
return $b['score'] - $a['score'];
});
// 返回Top15推荐号码
$topPredictions = array_slice($predictions, 0, 15);
// 分析信息
$analysis = [
'last_expect' => $lastExpect,
'last_normals' => $lastNormals,
'last_special' => $lastSpecial,
'last_special_zone' => $lastSpecialBigZone,
'normal_min' => $normalMin,
'normal_max' => $normalMax,
'normal_avg' => $normalAvg,
'normal_sum' => $normalSum,
'top2_colors' => $top2Colors,
'color_counts' => $colorCounts,
'normal_fine_zones' => $normalFineZones,
'normal_big_zones' => $normalBigZones,
'zone_trans_matrix' => $zoneTransMatrix,
'rules' => [
['name' => '覆盖区间', 'rate' => '91.44%', 'desc' => '当期特码在上期正码覆盖的细区间内'],
['name' => '特码区间转移', 'rate' => '77.54%', 'desc' => '基于上期特码区间预测当期特码所在大区间'],
['name' => '双波色预测', 'rate' => '69.52%', 'desc' => '当期特码波色在上期正码前2种主导波色内'],
['name' => '正码±3距离', 'rate' => '59.36%', 'desc' => '当期特码与上期正码某号码距离≤3'],
['name' => '尾数±2', 'rate' => '50%', 'desc' => '上期正码和值尾数与当期特码尾数差≤2'],
['name' => '平均值±10', 'rate' => '41.98%', 'desc' => '当期特码在上期正码平均值±10范围']
],
'predict_next_expect' => $lastExpect ? (string)(intval($lastExpect) + 1) : ''
];
// 命中验证
$hitInfo = null;
if ($actualResult) {
$hitRank = -1;
foreach ($topPredictions as $idx => $p) {
if ($p['num'] === $actualResult['num7']) {
$hitRank = $idx + 1;
break;
}
}
$fullRank = -1;
foreach ($predictions as $idx => $p) {
if ($p['num'] === $actualResult['num7']) {
$fullRank = $idx + 1;
break;
}
}
// 分析实际结果的规律命中情况
$actualAnalysis = null;
foreach ($predictions as $p) {
if ($p['num'] === $actualResult['num7']) {
$actualAnalysis = $p;
break;
}
}
$hitInfo = [
'hit' => $hitRank > 0,
'rank_in_top' => $hitRank,
'rank_in_all' => $fullRank,
'actual_num' => $actualResult['num7'],
'actual_color' => $actualResult['color'],
'actual_animal' => $actualResult['animal'],
'actual_expect' => $actualResult['expect'],
'actual_zone_covered' => $actualAnalysis ? $actualAnalysis['zone_covered'] : false,
'actual_min_distance' => $actualAnalysis ? $actualAnalysis['min_distance'] : 99,
'actual_color_in_top2' => $actualAnalysis ? $actualAnalysis['color_in_top2'] : false,
'actual_trans_match' => $actualAnalysis ? $actualAnalysis['trans_match'] : false,
'actual_tail_diff' => $actualAnalysis ? $actualAnalysis['tail_diff'] : 99,
'actual_avg_diff' => $actualAnalysis ? $actualAnalysis['avg_diff'] : 99
];
}
// 回测验证(默认显示前50期命中详情)
$backtest = null;
if ($periods >= 30) {
$backtest = $this->_runBacktestNormalRelation($periods, 50);
}
return [
'predictions' => $topPredictions,
'all_predictions' => $predictions,
'analysis' => $analysis,
'actual_result' => $actualResult,
'hit_info' => $hitInfo,
'backtest' => $backtest
];
}
/**
* 执行正码关联规律的历史回测(修正版)
* 使用正确规律:上期正码 → 当期特码
* @param int $periods 回测期数
* @param int $detailLimit 返回详情条数
* @return array {hit_rate, avg_rank, details, rule_stats}
*/
private function _runBacktestNormalRelation($periods = 100, $detailLimit = 20)
{
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
->order('openTime', 'desc')
->limit($periods + 1)
->select();
if (count($history) < 2) {
return ['error' => '数据不足'];
}
$num_model = new Num();
$colorMap = $num_model->column('color', 'num');
// 大区间划分
$getBigZone = function ($num) {
if ($num <= 10) return 'small';
if ($num <= 30) return 'mid';
return 'big';
};
// 细区间划分
$getFineZone = function ($num) {
if ($num <= 10) return 0;
if ($num <= 20) return 1;
if ($num <= 30) return 2;
if ($num <= 40) return 3;
return 4;
};
// 特码区间转移概率矩阵
$zoneTransMatrix = [
'big' => ['big' => 35.77, 'mid' => 37.96, 'small' => 26.28],
'mid' => ['big' => 33.77, 'mid' => 43.51, 'small' => 22.73],
'small' => ['big' => 42.17, 'mid' => 42.17, 'small' => 15.66]
];
$hits = 0;
$ranks = [];
$details = [];
$ruleHits = [
'zone_cover' => 0,
'trans_match' => 0,
'color_top2' => 0,
'dist_3' => 0,
'tail_2' => 0,
'avg_10' => 0
];
for ($i = 0; $i < count($history) - 1; $i++) {
$currentRow = $history[$i];
$prevRow = $history[$i + 1];
// 使用上一期的正码预测当期的特码
$lastNormals = [];
for ($j = 1; $j <= 6; $j++) {
$lastNormals[] = (int)$prevRow['num' . $j];
}
$lastSpecial = (int)$prevRow['num7'];
$actualSpecial = (int)$currentRow['num7'];
// 分析上期正码特征
$normalMin = min($lastNormals);
$normalMax = max($lastNormals);
$normalAvg = array_sum($lastNormals) / 6;
$normalSum = array_sum($lastNormals);
// 波色分布
$colorCounts = ['红' => 0, '蓝' => 0, '绿' => 0];
foreach ($lastNormals as $n) {
$color = $colorMap[$n] ?? '';
if (strpos($color, '红') !== false) $colorCounts['红']++;
elseif (strpos($color, '蓝') !== false) $colorCounts['蓝']++;
elseif (strpos($color, '绿') !== false) $colorCounts['绿']++;
}
$sortedColors = [];
foreach ($colorCounts as $c => $cnt) {
$sortedColors[] = ['color' => $c, 'count' => $cnt];
}
usort($sortedColors, function ($a, $b) { return $b['count'] - $a['count']; });
$top2Colors = [$sortedColors[0]['color'], $sortedColors[1]['color']];
// 上期正码覆盖的细区间
$normalFineZones = [];
foreach ($lastNormals as $n) {
$zoneIdx = $getFineZone($n);
if (!in_array($zoneIdx, $normalFineZones)) {
$normalFineZones[] = $zoneIdx;
}
}
// 上期特码所在大区间
$lastSpecialBigZone = $getBigZone($lastSpecial);
// 计算每个号码评分
$scores = [];
for ($num = 1; $num <= 49; $num++) {
$numColor = $colorMap[$num] ?? '';
$numBigZone = $getBigZone($num);
$numFineZone = $getFineZone($num);
// 规律1:覆盖区间(91%
$zoneCovered = in_array($numFineZone, $normalFineZones);
$zoneCoverScore = $zoneCovered ? 91 : 0;
// 规律2:特码区间转移(77%
$transProbs = $zoneTransMatrix[$lastSpecialBigZone];
$sortedTrans = [];
foreach ($transProbs as $z => $p) {
$sortedTrans[] = ['zone' => $z, 'prob' => $p];
}
usort($sortedTrans, function ($a, $b) { return $b['prob'] - $a['prob']; });
$top2TransZones = [$sortedTrans[0]['zone'], $sortedTrans[1]['zone']];
$transMatch = in_array($numBigZone, $top2TransZones);
$transScore = $transMatch ? 77 : 0;
// 规律3:双波色(69%
$colorInTop2 = false;
foreach ($top2Colors as $tc) {
if (strpos($numColor, $tc) !== false) {
$colorInTop2 = true;
break;
}
}
$colorScore = $colorInTop2 ? 69 : 0;
// 规律4:距离≤359%
$minDist = 49;
foreach ($lastNormals as $n) {
$dist = abs($num - $n);
if ($dist < $minDist) $minDist = $dist;
}
$distScore = $minDist <= 3 ? 59 : ($minDist <= 5 ? 40 : 0);
// 规律5:尾数≤250%
$tailDiff = abs($num % 10 - $normalSum % 10);
$tailDiff = min($tailDiff, 10 - $tailDiff);
$tailScore = $tailDiff <= 2 ? 50 : ($tailDiff <= 3 ? 30 : 0);
// 规律6:平均值±1042%
$avgDiff = abs($num - $normalAvg);
$avgScore = $avgDiff <= 10 ? 42 : 0;
// 综合评分
$score = $zoneCoverScore * 0.30
+ $transScore * 0.25
+ $colorScore * 0.20
+ $distScore * 0.12
+ $avgScore * 0.08
+ $tailScore * 0.05;
$scores[$num] = $score;
}
// 排序找排名
$sorted = [];
for ($num = 1; $num <= 49; $num++) {
$sorted[] = ['num' => $num, 'score' => $scores[$num]];
}
usort($sorted, function ($a, $b) {
return $b['score'] - $a['score'];
});
$rank = -1;
foreach ($sorted as $idx => $item) {
if ($item['num'] === $actualSpecial) {
$rank = $idx + 1;
break;
}
}
if ($rank > 0 && $rank <= 15) {
$hits++;
}
$ranks[] = $rank;
// 统计各规律命中情况
$actualFineZone = $getFineZone($actualSpecial);
$actualBigZone = $getBigZone($actualSpecial);
$actualColor = $colorMap[$actualSpecial] ?? '';
$actualMinDist = 49;
foreach ($lastNormals as $n) {
$dist = abs($actualSpecial - $n);
if ($dist < $actualMinDist) $actualMinDist = $dist;
}
$actualTailDiff = abs($actualSpecial % 10 - $normalSum % 10);
$actualTailDiff = min($actualTailDiff, 10 - $actualTailDiff);
$actualAvgDiff = abs($actualSpecial - $normalAvg);
if (in_array($actualFineZone, $normalFineZones)) $ruleHits['zone_cover']++;
if (in_array($actualBigZone, $top2TransZones)) $ruleHits['trans_match']++;
$actualColorInTop2 = false;
foreach ($top2Colors as $tc) {
if (strpos($actualColor, $tc) !== false) $actualColorInTop2 = true;
}
if ($actualColorInTop2) $ruleHits['color_top2']++;
if ($actualMinDist <= 3) $ruleHits['dist_3']++;
if ($actualTailDiff <= 2) $ruleHits['tail_2']++;
if ($actualAvgDiff <= 10) $ruleHits['avg_10']++;
$details[] = [
'expect' => (string)$currentRow['expect'],
'actual' => $actualSpecial,
'rank' => $rank,
'hit' => $rank > 0 && $rank <= 15
];
}
$totalPeriods = count($ranks);
$hitRate = $totalPeriods > 0 ? round($hits / $totalPeriods * 100, 2) : 0;
$avgRank = $totalPeriods > 0 ? round(array_sum($ranks) / $totalPeriods, 2) : 0;
// 计算各规律实际命中率
$ruleStats = [];
foreach ($ruleHits as $rule => $count) {
$ruleStats[$rule] = [
'hits' => $count,
'rate' => $totalPeriods > 0 ? round($count / $totalPeriods * 100, 2) : 0
];
}
return [
'periods' => $totalPeriods,
'hits' => $hits,
'hit_rate' => $hitRate,
'avg_rank' => $avgRank,
'rule_stats' => $ruleStats,
'details' => array_slice($details, 0, $detailLimit)
];
}
}
@@ -19,6 +19,8 @@
<a href="javascript:;" class="btn btn-default btn-tailnums" title="{:__('Tail Numbers')}"><i class="fa fa-list-ol"></i> {:__('Tail Numbers')}</a>
<a href="javascript:;" class="btn btn-danger btn-specialhotcold" title="{:__('Special Hot/Cold')}"><i class="fa fa-fire"></i> 特码冷热</a>
<a href="javascript:;" class="btn btn-primary btn-numberfilter" title="筛号器"><i class="fa fa-filter"></i> 筛号器</a>
<a href="javascript:;" class="btn btn-success btn-predict" title="智能预测"><i class="fa fa-magic"></i> 智能预测</a>
<a href="javascript:;" class="btn btn-info btn-normal-relation" title="正码关联预测"><i class="fa fa-link"></i> 正码关联预测</a>
<!-- <a href="javascript:;" class="btn btn-success btn-dashboard" title="{:__('Dashboard')}"><i class="fa fa-tachometer"></i> {:__('Dashboard')}</a>-->
<!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('history/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>-->
</div>