docs(predictV3): 添加predictV3算法优化研究文档和前端功能实现
- 完成Phase 11: predictV3算法优化研究文档,涵盖6个优化方向的技术分析 - 实现置信度评估功能,提供历史命中率、得分分布、多维度一致性置信度指标 - 扩展回测指标体系,新增NDCG@K、MRR、命中率分布等排名质量评估指标 - 优化转移概率算法,引入二阶马尔可夫链和多属性联合转移增强预测准确性 - 设计权重训练机制,支持网格搜索和遗传算法进行数据驱动的参数优化 - 集成组合特征挖掘功能,采用关联规则和序列模式发现号码间潜在关联 - 实现完整的前端交互界面,支持预测结果显示、置信度展示和回测验证功能 - 建立性能优化策略,包括预计算缓存、批量计算和降级策略保障响应速度
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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:平均值±10(41.98%命中)
|
||||
$avgDiff = abs($num - $normalAvg);
|
||||
$avgScore = $avgDiff <= 10 ? 42 : 0;
|
||||
|
||||
// 规律6:尾数±2(50%命中)
|
||||
$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:距离≤3(59%)
|
||||
$minDist = 49;
|
||||
foreach ($lastNormals as $n) {
|
||||
$dist = abs($num - $n);
|
||||
if ($dist < $minDist) $minDist = $dist;
|
||||
}
|
||||
$distScore = $minDist <= 3 ? 59 : ($minDist <= 5 ? 40 : 0);
|
||||
|
||||
// 规律5:尾数≤2(50%)
|
||||
$tailDiff = abs($num % 10 - $normalSum % 10);
|
||||
$tailDiff = min($tailDiff, 10 - $tailDiff);
|
||||
$tailScore = $tailDiff <= 2 ? 50 : ($tailDiff <= 3 ? 30 : 0);
|
||||
|
||||
// 规律6:平均值±10(42%)
|
||||
$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>
|
||||
|
||||
Reference in New Issue
Block a user