diff --git a/.omc/project-memory.json b/.omc/project-memory.json index 3af87ff..d18f776 100644 --- a/.omc/project-memory.json +++ b/.omc/project-memory.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "lastScanned": 1777648534656, + "lastScanned": 1777820257271, "projectRoot": "D:\\code\\php\\amlhc", "techStack": { "languages": [ @@ -54,14 +54,14 @@ "path": "addons", "purpose": null, "fileCount": 1, - "lastAccessed": 1777648534596, + "lastAccessed": 1777820257227, "keyFiles": [] }, "analysis": { "path": "analysis", "purpose": null, "fileCount": 2, - "lastAccessed": 1777648534608, + "lastAccessed": 1777820257228, "keyFiles": [ "predict_analysis.php", "predict_analysis.py" @@ -71,7 +71,7 @@ "path": "application", "purpose": null, "fileCount": 8, - "lastAccessed": 1777648534609, + "lastAccessed": 1777820257239, "keyFiles": [ "build.php", "command.php", @@ -84,14 +84,14 @@ "path": "extend", "purpose": null, "fileCount": 1, - "lastAccessed": 1777648534611, + "lastAccessed": 1777820257239, "keyFiles": [] }, "public": { "path": "public", "purpose": "Public files", "fileCount": 5, - "lastAccessed": 1777648534612, + "lastAccessed": 1777820257240, "keyFiles": [ "admin.php", "index.php", @@ -103,14 +103,14 @@ "path": "runtime", "purpose": null, "fileCount": 1, - "lastAccessed": 1777648534612, + "lastAccessed": 1777820257240, "keyFiles": [] }, "sql": { "path": "sql", "purpose": null, "fileCount": 2, - "lastAccessed": 1777648534612, + "lastAccessed": 1777820257240, "keyFiles": [ "amlhc.sql", "macaujc_history.sql" @@ -120,7 +120,7 @@ "path": "thinkphp", "purpose": null, "fileCount": 15, - "lastAccessed": 1777648534613, + "lastAccessed": 1777820257241, "keyFiles": [ "base.php", "codecov.yml", @@ -133,7 +133,7 @@ "path": "vendor", "purpose": "Third-party code", "fileCount": 1, - "lastAccessed": 1777648534613, + "lastAccessed": 1777820257242, "keyFiles": [ "autoload.php" ] @@ -142,7 +142,7 @@ "path": "application\\api", "purpose": "API routes", "fileCount": 2, - "lastAccessed": 1777648534614, + "lastAccessed": 1777820257242, "keyFiles": [ "common.php", "config.php" @@ -152,12 +152,97 @@ "path": "public\\assets", "purpose": "Static assets", "fileCount": 1, - "lastAccessed": 1777648534615, + "lastAccessed": 1777820257246, "keyFiles": [ "index.html" ] } }, - "hotPaths": [], + "hotPaths": [ + { + "path": "public\\assets\\js\\backend\\history.js", + "accessCount": 40, + "lastAccessed": 1777724915715, + "type": "file" + }, + { + "path": "application\\admin\\model\\History.php", + "accessCount": 12, + "lastAccessed": 1777724152835, + "type": "file" + }, + { + "path": "application\\admin\\controller\\History.php", + "accessCount": 10, + "lastAccessed": 1777724090471, + "type": "file" + }, + { + "path": "application\\admin\\view\\history\\index.html", + "accessCount": 5, + "lastAccessed": 1777707308124, + "type": "file" + }, + { + "path": ".planning\\STATE.md", + "accessCount": 3, + "lastAccessed": 1777707396710, + "type": "directory" + }, + { + "path": "C:\\Users\\91611\\.claude\\get-shit-done\\workflows\\do.md", + "accessCount": 1, + "lastAccessed": 1777706998579, + "type": "file" + }, + { + "path": "C:\\Users\\91611\\.claude\\get-shit-done\\workflows\\quick.md", + "accessCount": 1, + "lastAccessed": 1777707018668, + "type": "file" + }, + { + "path": "sql", + "accessCount": 1, + "lastAccessed": 1777707065981, + "type": "directory" + }, + { + "path": "public\\assets\\js", + "accessCount": 1, + "lastAccessed": 1777707066026, + "type": "directory" + }, + { + "path": "application\\admin\\view\\history\\add.html", + "accessCount": 1, + "lastAccessed": 1777707072623, + "type": "file" + }, + { + "path": "application\\admin\\view\\history\\edit.html", + "accessCount": 1, + "lastAccessed": 1777707072753, + "type": "file" + }, + { + "path": "sql\\amlhc.sql", + "accessCount": 1, + "lastAccessed": 1777707078109, + "type": "directory" + }, + { + "path": ".planning\\quick\\260502-ljh-history\\260502-ljh-PLAN.md", + "accessCount": 1, + "lastAccessed": 1777707180604, + "type": "file" + }, + { + "path": ".planning\\quick\\260502-ljh-history\\260502-ljh-SUMMARY.md", + "accessCount": 1, + "lastAccessed": 1777707369645, + "type": "file" + } + ], "userDirectives": [] } \ No newline at end of file diff --git a/.omc/sessions/48d5215c-775c-44f9-8ec6-6db1fd3b93eb.json b/.omc/sessions/48d5215c-775c-44f9-8ec6-6db1fd3b93eb.json new file mode 100644 index 0000000..60f0ad0 --- /dev/null +++ b/.omc/sessions/48d5215c-775c-44f9-8ec6-6db1fd3b93eb.json @@ -0,0 +1,8 @@ +{ + "session_id": "48d5215c-775c-44f9-8ec6-6db1fd3b93eb", + "ended_at": "2026-05-02T07:44:06.062Z", + "reason": "prompt_input_exit", + "agents_spawned": 0, + "agents_completed": 0, + "modes_used": [] +} \ No newline at end of file diff --git a/.omc/sessions/4f5fb28f-8a85-463e-be5e-c99955a9aaf9.json b/.omc/sessions/4f5fb28f-8a85-463e-be5e-c99955a9aaf9.json new file mode 100644 index 0000000..5bbb4c6 --- /dev/null +++ b/.omc/sessions/4f5fb28f-8a85-463e-be5e-c99955a9aaf9.json @@ -0,0 +1,8 @@ +{ + "session_id": "4f5fb28f-8a85-463e-be5e-c99955a9aaf9", + "ended_at": "2026-05-02T07:44:04.023Z", + "reason": "clear", + "agents_spawned": 1, + "agents_completed": 1, + "modes_used": [] +} \ No newline at end of file diff --git a/.omc/sessions/5593524e-595e-428a-8b8b-24a839d4cc08.json b/.omc/sessions/5593524e-595e-428a-8b8b-24a839d4cc08.json new file mode 100644 index 0000000..8d11fc2 --- /dev/null +++ b/.omc/sessions/5593524e-595e-428a-8b8b-24a839d4cc08.json @@ -0,0 +1,8 @@ +{ + "session_id": "5593524e-595e-428a-8b8b-24a839d4cc08", + "ended_at": "2026-05-02T14:36:45.873Z", + "reason": "prompt_input_exit", + "agents_spawned": 0, + "agents_completed": 0, + "modes_used": [] +} \ No newline at end of file diff --git a/.omc/sessions/c14a7924-1cf5-49df-8355-0fc9eb55d8fb.json b/.omc/sessions/c14a7924-1cf5-49df-8355-0fc9eb55d8fb.json new file mode 100644 index 0000000..4174b7f --- /dev/null +++ b/.omc/sessions/c14a7924-1cf5-49df-8355-0fc9eb55d8fb.json @@ -0,0 +1,8 @@ +{ + "session_id": "c14a7924-1cf5-49df-8355-0fc9eb55d8fb", + "ended_at": "2026-05-03T14:57:39.936Z", + "reason": "prompt_input_exit", + "agents_spawned": 0, + "agents_completed": 0, + "modes_used": [] +} \ No newline at end of file diff --git a/.omc/state/hud-stdin-cache.json b/.omc/state/hud-stdin-cache.json deleted file mode 100644 index ecc289c..0000000 --- a/.omc/state/hud-stdin-cache.json +++ /dev/null @@ -1 +0,0 @@ -{"session_id":"a1d5e02a-7411-4199-815c-5ae711cf7291","transcript_path":"C:\\Users\\91611\\.claude\\projects\\D--code-php-amlhc\\a1d5e02a-7411-4199-815c-5ae711cf7291.jsonl","cwd":"D:\\code\\php\\amlhc","model":{"id":"qwen3.6-plus[1m]","display_name":"qwen3.6-plus[1m]"},"workspace":{"current_dir":"D:\\code\\php\\amlhc","project_dir":"D:\\code\\php\\amlhc","added_dirs":[]},"version":"2.1.126","output_style":{"name":"default"},"cost":{"total_cost_usd":0,"total_duration_ms":1008,"total_api_duration_ms":0,"total_lines_added":0,"total_lines_removed":0},"context_window":{"total_input_tokens":0,"total_output_tokens":0,"context_window_size":1000000,"current_usage":null,"used_percentage":null,"remaining_percentage":null},"exceeds_200k_tokens":false,"fast_mode":false,"effort":{"level":"high"},"thinking":{"enabled":true}} \ No newline at end of file diff --git a/.omc/state/mission-state.json b/.omc/state/mission-state.json new file mode 100644 index 0000000..1fe03d4 --- /dev/null +++ b/.omc/state/mission-state.json @@ -0,0 +1,4 @@ +{ + "updatedAt": "2026-05-02T07:44:04.057Z", + "missions": [] +} \ No newline at end of file diff --git a/.omc/state/sessions/0402461b-cd9c-400b-8fa2-405c6db26368/hud-state.json b/.omc/state/sessions/0402461b-cd9c-400b-8fa2-405c6db26368/hud-state.json new file mode 100644 index 0000000..fbf27dd --- /dev/null +++ b/.omc/state/sessions/0402461b-cd9c-400b-8fa2-405c6db26368/hud-state.json @@ -0,0 +1,6 @@ +{ + "timestamp": "2026-05-02T14:41:38.945Z", + "backgroundTasks": [], + "sessionStartTimestamp": "2026-05-02T14:40:41.364Z", + "sessionId": "0402461b-cd9c-400b-8fa2-405c6db26368" +} \ No newline at end of file diff --git a/.omc/state/sessions/4f5fb28f-8a85-463e-be5e-c99955a9aaf9/hud-state.json b/.omc/state/sessions/4f5fb28f-8a85-463e-be5e-c99955a9aaf9/hud-state.json new file mode 100644 index 0000000..796791a --- /dev/null +++ b/.omc/state/sessions/4f5fb28f-8a85-463e-be5e-c99955a9aaf9/hud-state.json @@ -0,0 +1,6 @@ +{ + "timestamp": "2026-05-02T07:28:11.619Z", + "backgroundTasks": [], + "sessionStartTimestamp": "2026-05-02T07:28:04.316Z", + "sessionId": "4f5fb28f-8a85-463e-be5e-c99955a9aaf9" +} \ No newline at end of file diff --git a/.omc/state/sessions/5593524e-595e-428a-8b8b-24a839d4cc08/hud-state.json b/.omc/state/sessions/5593524e-595e-428a-8b8b-24a839d4cc08/hud-state.json new file mode 100644 index 0000000..dcc7eba --- /dev/null +++ b/.omc/state/sessions/5593524e-595e-428a-8b8b-24a839d4cc08/hud-state.json @@ -0,0 +1,6 @@ +{ + "timestamp": "2026-05-02T12:14:48.284Z", + "backgroundTasks": [], + "sessionStartTimestamp": "2026-05-02T12:14:30.545Z", + "sessionId": "5593524e-595e-428a-8b8b-24a839d4cc08" +} \ No newline at end of file diff --git a/.omc/state/sessions/8dafebc5-6242-4ed6-8370-bd53345e315e/hud-state.json b/.omc/state/sessions/8dafebc5-6242-4ed6-8370-bd53345e315e/hud-state.json new file mode 100644 index 0000000..17239d7 --- /dev/null +++ b/.omc/state/sessions/8dafebc5-6242-4ed6-8370-bd53345e315e/hud-state.json @@ -0,0 +1,6 @@ +{ + "timestamp": "2026-05-02T08:13:32.296Z", + "backgroundTasks": [], + "sessionStartTimestamp": "2026-05-02T08:05:12.717Z", + "sessionId": "8dafebc5-6242-4ed6-8370-bd53345e315e" +} \ No newline at end of file diff --git a/.omc/state/sessions/b7109af9-c36f-4869-8f05-c5c90c07b75e/hud-state.json b/.omc/state/sessions/b7109af9-c36f-4869-8f05-c5c90c07b75e/hud-state.json new file mode 100644 index 0000000..7b77000 --- /dev/null +++ b/.omc/state/sessions/b7109af9-c36f-4869-8f05-c5c90c07b75e/hud-state.json @@ -0,0 +1,6 @@ +{ + "timestamp": "2026-05-02T12:12:11.936Z", + "backgroundTasks": [], + "sessionStartTimestamp": "2026-05-02T12:12:11.651Z", + "sessionId": "b7109af9-c36f-4869-8f05-c5c90c07b75e" +} \ No newline at end of file diff --git a/application/admin/controller/History.php b/application/admin/controller/History.php index 92bcb66..55e766b 100644 --- a/application/admin/controller/History.php +++ b/application/admin/controller/History.php @@ -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', 'tailHeadProb', 'predict', 'predictV2', 'predictV3', 'optimizeWeights', 'predictByNormalRelation']; + protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard', 'specialHeatmap', 'specialHotColdAction', 'zoneTransition', 'colorWaveTransition', 'zoneToColorTransition', 'zodiacTransition', 'tailNumberTransition', 'headNumberTransition', 'tailHeadProb', 'predict', 'predictV2', 'predictV3', 'predictV4', 'optimizeWeights', 'predictByNormalRelation']; public function _initialize() { @@ -500,6 +500,39 @@ class History extends Backend } } + /** + * 多维度综合预测算法 V4 + * 贝叶斯对数似然集成 + 指数时间衰减 + 形态匹配 + 周期性检测 + * 六大子模型融合: EWMA频率 / 号码级马尔可夫 / 经验CDF遗漏 / 历史形态匹配 / 周期自相关 / 属性平衡 + */ + public function predictV4() + { + if ($this->request->isAjax()) { + $periods = $this->request->get('periods', 200, 'intval'); + if ($periods < 30 || $periods > 500) { + $this->error('期数范围必须在 30-500 之间'); + } + $weights = []; + $weightStr = $this->request->get('weights', '', 'trim'); + if ($weightStr) { + $weightsArr = json_decode($weightStr, true); + if (is_array($weightsArr)) { + $weights = $weightsArr; + } + } + $targetExpect = $this->request->get('target_expect', '', 'trim'); + $backtestCount = $this->request->get('backtest', 50, 'intval'); + if ($backtestCount < 10 || $backtestCount > 100) { + $backtestCount = 50; + } + $result = $this->model->getPredictionV4($periods, $weights, $targetExpect, false, $backtestCount); + if (isset($result['error'])) { + $this->error($result['error']); + } + $this->success('查询成功', null, $result); + } + } + /** * 权重网格搜索优化接口 * 执行多权重配置回测,返回最优权重组合 diff --git a/application/admin/model/History.php b/application/admin/model/History.php index 65b725b..39dceb8 100644 --- a/application/admin/model/History.php +++ b/application/admin/model/History.php @@ -4914,5 +4914,930 @@ class History extends Model ]; } + // ============================================================ + // V4 预测算法:贝叶斯对数似然集成 + 指数时间衰减 + 模式匹配 + // ============================================================ + + /** + * V4 智能预测算法 — 贝叶斯集成学习 + * + * 核心改进(相对 V3): + * 1. 指数时间衰减:近期数据获得更高权重 (half-life = 50 期) + * 2. 贝叶斯对数似然融合:替代线性加权求和,各维度在 log-space 独立更新后验概率 + * 3. 号码级马尔可夫转移:直接建模 P(num_t | num_{t-1}),而非仅属性转移 + * 4. 历史形态匹配:在特征空间找最相似的历史片段,用其后验分布预测 + * 5. 周期性自相关检测:ACF 探测号码的隐藏周期规律 + * 6. Softmax 概率输出:分数转化为校准后的真实概率分布 + * 7. 自适应集成权重:基于回测滚动性能动态调整子模型权重 + * + * @param int $periods 历史期数 (30-500) + * @param array $weights 子模型权重(可选,覆盖自适应权重) + * @param string $targetExpect 验证目标期号(可选) + * @param bool $skipBacktest 跳过回测 + * @param int $backtestCount 回测期数 + * @return array + */ + public function getPredictionV4($periods = 200, $weights = [], $targetExpect = '', $skipBacktest = false, $backtestCount = 50) + { + set_time_limit(120); // V4 计算量较大 + + $num_model = new Num(); + $colorMap = $num_model->column('color', 'num'); + $animalMap = $num_model->column('animal', 'num'); + + // —— 子模型默认权重(初始值,会被自适应调整覆盖)—— + $defaultWeights = [ + 'freq_ewma' => 0.20, // 时间衰减频率 + 'markov_number' => 0.22, // 号码级马尔可夫转移 + 'omit_empirical' => 0.17, // 经验 CDF 遗漏回归 + 'pattern_match' => 0.18, // 历史形态匹配 + 'cyclical' => 0.09, // 周期性检测 + 'attr_balance' => 0.14, // 属性平衡(奇偶/大小/区域/波色) + ]; + $weights = $weights ? array_merge($defaultWeights, $weights) : $defaultWeights; + + // —— 获取历史数据 —— + $actualResult = null; + $lastSpecial = 0; + $lastExpect = ''; + $cutoffTime = null; + $allHistory = []; + + 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']] ?? '', + 'openTime'=> $targetRow['openTime'] + ]; + $prevRow = $this->where('openTime', '<', $cutoffTime)->order('openTime', 'desc')->limit(1)->find(); + if (!$prevRow) { + return ['predictions' => [], 'error' => '目标期号之前没有历史数据']; + } + $lastSpecial = (int)$prevRow['num7']; + $lastExpect = (string)$prevRow['expect']; + $allHistory = $this->field('expect,num7,openTime') + ->where('openTime', '<', $cutoffTime) + ->order('openTime', 'desc') + ->limit($periods) + ->select(); + } else { + $latest = $this->field('expect,num7,openTime')->order('openTime', 'desc')->limit(1)->find(); + if (!$latest) { + return ['predictions' => [], 'last_special' => 0, 'analysis' => []]; + } + $lastSpecial = (int)$latest['num7']; + $lastExpect = (string)$latest['expect']; + $allHistory = $this->field('expect,num7,openTime') + ->order('openTime', 'desc') + ->limit($periods) + ->select(); + } + + if (empty($allHistory) || count($allHistory) < 30) { + return ['predictions' => [], 'error' => '历史数据不足(至少需要30期)', 'last_special' => $lastSpecial]; + } + + // 反转为升序 + $historyAsc = array_reverse($allHistory); + $totalDraws = count($allHistory); + + // —— 指数时间衰减权重 —— + $halfLife = 50; // 半衰期:50期前的数据权重为最近的 1/2 + $lambda = log(2) / $halfLife; + $timeWeights = []; + for ($t = 0; $t < $totalDraws; $t++) { + // t=0 是最旧, t=totalDraws-1 是最新 + $age = $totalDraws - 1 - $t; + $timeWeights[$t] = exp(-$lambda * $age); + } + $twSum = array_sum($timeWeights); + if ($twSum > 0) { + foreach ($timeWeights as $k => $v) { $timeWeights[$k] = $v / $twSum * $totalDraws; } + } + + // —— 预计算号码属性 —— + $zoneMap = []; $tailMap = []; $headMap = []; $isOddMap = []; $isBigMap = []; $colorKeyMap = []; + for ($n = 1; $n <= 49; $n++) { + $zoneMap[$n] = $this->_getZoneIdx($n); + $tailMap[$n] = $n % 10; + $headMap[$n] = $this->_getHeadIdx($n); + $isOddMap[$n] = ($n % 2 === 1); + $isBigMap[$n] = ($n >= 25); + $c = $colorMap[$n] ?? ''; + if (strpos($c, '红') !== false) $colorKeyMap[$n] = '红'; + elseif (strpos($c, '蓝') !== false) $colorKeyMap[$n] = '蓝'; + elseif (strpos($c, '绿') !== false) $colorKeyMap[$n] = '绿'; + else $colorKeyMap[$n] = ''; + } + + // ============================================================ + // 子模型 1: 时间衰减频率 (EWMA Frequency) + // ============================================================ + $ewmaFreq = array_fill(1, 49, 0.0); + foreach ($historyAsc as $idx => $row) { + $num = (int)$row['num7']; + if ($num >= 1 && $num <= 49) { + $ewmaFreq[$num] += $timeWeights[$idx]; + } + } + // 归一化为概率 + $ewmaSum = array_sum($ewmaFreq); + $ewmaLogProb = []; + for ($n = 1; $n <= 49; $n++) { + $p = max($ewmaFreq[$n] / max($ewmaSum, 0.001), 0.0001); + $ewmaLogProb[$n] = log($p); + } + + // ============================================================ + // 子模型 2: 号码级马尔可夫转移概率 + // ============================================================ + $numberMarkov = $this->_buildNumberMarkovMatrix($historyAsc, $timeWeights); + + // 上一期号码的条件转移对数概率 + $markovLogProb = []; + $lastNum = $lastSpecial; + for ($n = 1; $n <= 49; $n++) { + $p = $numberMarkov['prob'][$lastNum][$n] ?? (1.0 / 49); + $p = max($p, 0.0005); + $markovLogProb[$n] = log($p); + } + + // ============================================================ + // 子模型 3: 经验 CDF 遗漏回归(继承 V3 精华) + // ============================================================ + // 统计每个号码的当前遗漏和历史上出现时的遗漏值分布 + $omitCount = array_fill(1, 49, $totalDraws); + $lastAppear = array_fill(1, 49, -1); + $omitHistoryAll = []; // 所有号码的历史遗漏值池 + + foreach ($historyAsc as $idx => $row) { + $num = (int)$row['num7']; + if ($num < 1 || $num > 49) continue; + if ($lastAppear[$num] >= 0) { + $omitHistoryAll[] = $idx - $lastAppear[$num]; + } else { + $omitHistoryAll[] = $idx + 1; + } + $lastAppear[$num] = $idx; + $omitCount[$num] = $totalDraws - 1 - $idx; + } + foreach ($omitCount as $n => $v) { + if ($lastAppear[$n] < 0) $omitCount[$n] = $totalDraws; + } + + sort($omitHistoryAll); + $omitLogProb = []; + for ($n = 1; $n <= 49; $n++) { + $score = $this->_calcOmitScoreEmpirical($omitCount[$n], $omitHistoryAll, []); + $omitLogProb[$n] = log(max($score / 100.0, 0.002)); + } + + // ============================================================ + // 子模型 4: 历史形态匹配 (Pattern Similarity Matching) + // ============================================================ + $patternResult = $this->_findSimilarPatternsV4($historyAsc, $totalDraws, $zoneMap, $isOddMap, $isBigMap, $colorKeyMap); + + // ============================================================ + // 子模型 5: 周期性自相关检测 + // ============================================================ + $cyclicalLogProb = $this->_detectCyclicalPatternsV4($historyAsc, $totalDraws); + + // ============================================================ + // 子模型 6: 属性平衡 (奇偶/大小/区域/波色) + // ============================================================ + // 使用时间衰减权重统计近期各属性出现比例 + $attrCounts = [ + 'odd' => 0, 'even' => 0, 'big' => 0, 'small' => 0, + 'zone' => [0, 0, 0, 0, 0], + 'color' => ['红' => 0, '蓝' => 0, '绿' => 0] + ]; + foreach ($historyAsc as $idx => $row) { + $num = (int)$row['num7']; + if ($num < 1 || $num > 49) continue; + $w = $timeWeights[$idx]; + $attrCounts['odd'] += ($isOddMap[$num] ? $w : 0); + $attrCounts['even'] += ($isOddMap[$num] ? 0 : $w); + $attrCounts['big'] += ($isBigMap[$num] ? $w : 0); + $attrCounts['small'] += ($isBigMap[$num] ? 0 : $w); + $attrCounts['zone'][$zoneMap[$num]] += $w; + $ck = $colorKeyMap[$num]; + if ($ck) $attrCounts['color'][$ck] += $w; + } + + $totalWeight = array_sum($timeWeights); + $attrLogProb = []; + for ($n = 1; $n <= 49; $n++) { + $logP = 0; + // 奇偶平衡: 期望各50% + $oddW = $attrCounts['odd'] + $attrCounts['even']; + $oddRatio = $oddW > 0 ? $attrCounts['odd'] / $oddW : 0.5; + $logP += log(max($isOddMap[$n] ? (1 - $oddRatio + 0.5) : ($oddRatio + 0.5), 0.4) / 1.5); + + // 大小平衡 + $bsW = $attrCounts['big'] + $attrCounts['small']; + $bigRatio = $bsW > 0 ? $attrCounts['big'] / $bsW : 0.5; + $logP += log(max($isBigMap[$n] ? (1 - $bigRatio + 0.5) : ($bigRatio + 0.5), 0.4) / 1.5); + + // 区域平衡 + $zoneTotal = array_sum($attrCounts['zone']); + $zoneRatio = $zoneTotal > 0 ? $attrCounts['zone'][$zoneMap[$n]] / $zoneTotal : 0.2; + $logP += log(max(1 - $zoneRatio + 0.2, 0.3) / 1.2); + + // 波色平衡 + $colorTotal = array_sum($attrCounts['color']); + $colorRatio = $colorTotal > 0 ? ($attrCounts['color'][$colorKeyMap[$n]] ?? 0) / $colorTotal : 0.333; + $logP += log(max(1 - $colorRatio + 0.33, 0.3) / 1.33); + + $attrLogProb[$n] = $logP; + } + + // ============================================================ + // 自适应集成权重:基于滚动回测性能调整 + // ============================================================ + if (!$targetExpect && !$skipBacktest && $totalDraws >= 60) { + $weights = $this->_adaptEnsembleWeightsV4($historyAsc, $timeWeights, $totalDraws, $weights, + $ewmaLogProb, $markovLogProb, $omitLogProb, $patternResult, + $cyclicalLogProb, $attrLogProb); + } + + // —— 归一化权重 —— + $wSum = array_sum($weights); + if ($wSum > 0) { + foreach ($weights as $k => $v) { $weights[$k] = $v / $wSum; } + } + + // ============================================================ + // 贝叶斯对数似然融合 + // logPosterior(n) = Σ w_i * logP_i(n) (省略归一化常数) + // 然后 Softmax 转化为概率分布 + // ============================================================ + $logPosterior = []; + $maxLog = -INF; + for ($n = 1; $n <= 49; $n++) { + $lp = 0; + $lp += $weights['freq_ewma'] * $ewmaLogProb[$n]; + $lp += $weights['markov_number'] * $markovLogProb[$n]; + $lp += $weights['omit_empirical'] * $omitLogProb[$n]; + $lp += $weights['pattern_match'] * $patternResult['logprob'][$n]; + $lp += $weights['cyclical'] * $cyclicalLogProb[$n]; + $lp += $weights['attr_balance'] * $attrLogProb[$n]; + $logPosterior[$n] = $lp; + if ($lp > $maxLog) $maxLog = $lp; + } + + // Softmax 稳定计算 + $softmaxSum = 0; + $softmaxRaw = []; + for ($n = 1; $n <= 49; $n++) { + $v = exp($logPosterior[$n] - $maxLog); + $softmaxRaw[$n] = $v; + $softmaxSum += $v; + } + + $probabilities = []; + for ($n = 1; $n <= 49; $n++) { + $probabilities[$n] = $softmaxRaw[$n] / max($softmaxSum, 0.0001); + } + + // —— 构建结果 —— + $scores = []; + for ($n = 1; $n <= 49; $n++) { + $scores[] = [ + 'num' => $n, + 'prob' => round($probabilities[$n], 6), + 'score' => round($probabilities[$n] * 100, 2), + 'color' => $colorMap[$n] ?? '', + 'animal' => $animalMap[$n] ?? '', + 'detail' => [ + 'freq_ewma' => round(exp($ewmaLogProb[$n]) * 100, 2), + 'markov_number' => round(exp($markovLogProb[$n]) * 100, 2), + 'omit_empirical' => round(exp($omitLogProb[$n]) * 100, 2), + 'pattern_match' => round(exp($patternResult['logprob'][$n]) * 100, 2), + 'cyclical' => round(exp($cyclicalLogProb[$n]) * 100, 2), + 'attr_balance' => round(exp($attrLogProb[$n]) * 100, 2), + 'omit_count' => $omitCount[$n], + 'is_odd' => $isOddMap[$n], + 'is_big' => $isBigMap[$n], + 'zone' => $zoneMap[$n], + ] + ]; + } + + // 按概率降序 + usort($scores, function ($a, $b) { return $b['prob'] <=> $a['prob']; }); + $predictions = array_slice($scores, 0, 5); + + // 回测 + $backtest = $skipBacktest ? null : $this->_runBacktestV4($periods, $weights, $backtestCount, $cutoffTime); + + // 置信度 + $confidence = $this->_calculateConfidenceV4($predictions, $backtest); + + // 命中验证 + $hitInfo = null; + if ($actualResult) { + $hitRank = -1; + foreach ($predictions as $idx => $p) { + if ($p['num'] === $actualResult['num7']) { $hitRank = $idx + 1; break; } + } + $hitInfo = [ + 'hit' => $hitRank > 0, + 'rank' => $hitRank, + 'actual_num' => $actualResult['num7'], + 'actual_color' => $actualResult['color'], + 'actual_animal'=> $actualResult['animal'], + 'actual_expect'=> $actualResult['expect'] + ]; + } + + // 分析摘要 + $analysis = [ + 'version' => 'V4', + 'algorithm' => 'Bayesian Ensemble with Time-Decay', + 'last_special' => $lastSpecial, + 'last_expect' => $lastExpect, + 'history_count' => $totalDraws, + 'weights' => $weights, + 'weights_adaptive' => !$targetExpect && !$skipBacktest && $totalDraws >= 60, + 'half_life' => $halfLife, + 'pattern_match_info' => [ + 'top_k' => $patternResult['top_k'], + 'best_similarity' => $patternResult['best_similarity'], + 'matched_segments' => $patternResult['segment_count'], + ], + 'cyclical_detected' => $this->_summarizeCyclicalV4($cyclicalLogProb), + ]; + + return [ + 'predictions' => $predictions, + 'last_special' => $lastSpecial, + 'last_expect' => $lastExpect, + 'analysis' => $analysis, + 'actual_result' => $actualResult, + 'hit_info' => $hitInfo, + 'backtest' => $backtest, + 'confidence' => $confidence, + 'probabilities' => $probabilities, // 完整 49 个号码概率分布 + ]; + } + + /** + * 构建号码级马尔可夫转移矩阵(带时间衰减和拉普拉斯平滑) + * + * 直接建模 P(num_t = j | num_{t-1} = i),49×49 状态空间 + * 使用时间衰减权重 + 自适应 K近邻平滑 + * + * @param array $historyAsc 升序历史数据 + * @param array $timeWeights 时间衰减权重(与 historyAsc 索引对齐) + * @return array ['matrix' => count, 'prob' => probability] + */ + private function _buildNumberMarkovMatrix($historyAsc, $timeWeights) + { + $matrix = array_fill(1, 49, array_fill(1, 49, 0.0)); + $rowTotal = array_fill(1, 49, 0.0); + + $n = count($historyAsc); + for ($i = 0; $i < $n - 1; $i++) { + $from = (int)$historyAsc[$i]['num7']; + $to = (int)$historyAsc[$i + 1]['num7']; + if ($from < 1 || $from > 49 || $to < 1 || $to > 49) continue; + $w = $timeWeights[$i]; // t 时刻的权重适用于 "从 t 到 t+1" 的转移 + $matrix[$from][$to] += $w; + $rowTotal[$from] += $w; + } + + // 自适应平滑:K近邻平滑 (K=3) + $k = 3; + $prob = array_fill(1, 49, array_fill(1, 49, 0.0)); + for ($i = 1; $i <= 49; $i++) { + $rt = $rowTotal[$i]; + // 对每个目标号码,借用邻近号码的转移计数做平滑 + $smoothCount = []; + for ($j = 1; $j <= 49; $j++) { + $neighborSum = 0; + $neighborCount = 0; + // 取 i 左右各 k 个邻居的转移计数 + for ($di = -$k; $di <= $k; $di++) { + $ni = $i + $di; + if ($ni >= 1 && $ni <= 49 && $ni != $i) { + $neighborSum += $matrix[$ni][$j]; + $neighborCount++; + } + } + $neighborAvg = $neighborCount > 0 ? $neighborSum / $neighborCount : 0; + // 综合自身计数 + 邻居平均 + $smoothCount[$j] = $matrix[$i][$j] + $neighborAvg * 0.3; + } + $smoothTotal = array_sum($smoothCount) + 49; // +49 拉普拉斯 + for ($j = 1; $j <= 49; $j++) { + $prob[$i][$j] = ($smoothCount[$j] + 1) / max($smoothTotal, 1); + } + } + + return [ + 'matrix' => $matrix, + 'prob' => $prob, + 'row_total' => $rowTotal + ]; + } + + /** + * 历史形态匹配:在特征空间找与最近片段最相似的历史片段 + * + * 特征向量: [zone_seq(3), oddeven_seq(3), bigsmall_seq(3), color_seq(3)] + * 共 4×3 = 12 维特征向量,用余弦相似度匹配 + * + * 找到 top-K 最相似的历史片段后,取其后续号码的经验分布作为预测 + * + * @param array $historyAsc 升序历史 + * @param int $totalDraws 总期数 + * @param array $zoneMap 号码->区域 + * @param array $isOddMap 号码->是否奇数 + * @param array $isBigMap 号码->是否大号 + * @param array $colorKeyMap 号码->波色 + * @return array ['logprob' => [], 'top_k' => int, 'best_similarity' => float, 'segment_count' => int] + */ + private function _findSimilarPatternsV4($historyAsc, $totalDraws, $zoneMap, $isOddMap, $isBigMap, $colorKeyMap) + { + $patternLen = 3; // 用最近3期做形态匹配 + $topK = 15; // 取最相似15个片段 + $minSegDist = 3; // 匹配片段不能与当前片段重叠 + + if ($totalDraws < $patternLen + $minSegDist + 5) { + // 数据不足,返回均匀分布 + $logprob = []; + for ($n = 1; $n <= 49; $n++) { $logprob[$n] = log(1.0 / 49); } + return ['logprob' => $logprob, 'top_k' => 0, 'best_similarity' => 0, 'segment_count' => 0]; + } + + // 提取最近 patternLen 期的特征向量 + $recentFeatures = []; + $recentStart = $totalDraws - $patternLen; + for ($i = $recentStart; $i < $totalDraws; $i++) { + $num = (int)$historyAsc[$i]['num7']; + if ($num < 1 || $num > 49) continue; + $recentFeatures[] = [ + 'zone' => $zoneMap[$num], + 'oddeven' => $isOddMap[$num] ? 1 : 0, + 'bigsmall' => $isBigMap[$num] ? 1 : 0, + 'color' => $colorKeyMap[$num] === '红' ? 0 : ($colorKeyMap[$num] === '蓝' ? 1 : 2), + ]; + } + + // 在历史中扫描相似片段 + $similarities = []; + $maxScanStart = $totalDraws - $patternLen - $minSegDist; + + for ($start = 0; $start < $maxScanStart; $start++) { + // 提取候选片段的特征向量 + $candFeatures = []; + for ($i = $start; $i < $start + $patternLen; $i++) { + $num = (int)$historyAsc[$i]['num7']; + if ($num < 1 || $num > 49) continue; + $candFeatures[] = [ + 'zone' => $zoneMap[$num], + 'oddeven' => $isOddMap[$num] ? 1 : 0, + 'bigsmall' => $isBigMap[$num] ? 1 : 0, + 'color' => $colorKeyMap[$num] === '红' ? 0 : ($colorKeyMap[$num] === '蓝' ? 1 : 2), + ]; + } + + // 计算余弦相似度 + $similarity = $this->_cosineSimilarityV4($recentFeatures, $candFeatures); + $similarities[] = ['start' => $start, 'sim' => $similarity]; + } + + // 按相似度降序排序,取 topK + usort($similarities, function ($a, $b) { return $b['sim'] <=> $a['sim']; }); + $topMatches = array_slice($similarities, 0, $topK); + + // 基于匹配片段的后续号码分布 + $nextNumCounts = array_fill(1, 49, 0.0); + $totalCount = 0; + + foreach ($topMatches as $match) { + $nextPos = $match['start'] + $patternLen; + if ($nextPos < $totalDraws) { + $nextNum = (int)$historyAsc[$nextPos]['num7']; + if ($nextNum >= 1 && $nextNum <= 49) { + // 用相似度作为权重 + $weight = max($match['sim'], 0.01); + $nextNumCounts[$nextNum] += $weight; + $totalCount += $weight; + } + } + } + + // 转换为对数概率 + $logprob = []; + $uniformLog = log(1.0 / 49); + for ($n = 1; $n <= 49; $n++) { + if ($totalCount > 0 && $nextNumCounts[$n] > 0) { + $p = $nextNumCounts[$n] / $totalCount; + // 与均匀分布做加权平均(贝叶斯收缩) + $pShrunk = $p * 0.7 + (1.0 / 49) * 0.3; + $logprob[$n] = log(max($pShrunk, 0.0005)); + } else { + $logprob[$n] = $uniformLog; + } + } + + $bestSim = !empty($topMatches) ? $topMatches[0]['sim'] : 0; + + return [ + 'logprob' => $logprob, + 'top_k' => $topK, + 'best_similarity'=> round($bestSim, 4), + 'segment_count' => count($topMatches) + ]; + } + + /** + * 计算两个特征序列的余弦相似度 + * + * @param array $seq1 特征向量数组 [{zone, oddeven, bigsmall, color}, ...] + * @param array $seq2 同上 + * @return float 余弦相似度 [0, 1] + */ + private function _cosineSimilarityV4($seq1, $seq2) + { + if (count($seq1) !== count($seq2) || empty($seq1)) return 0; + + // 将每个位置的4维特征展平 + $vec1 = []; + $vec2 = []; + $zoneScale = 0.2; // zone 归一化到 [0,1] + + foreach ($seq1 as $f) { + $vec1[] = $f['zone'] * $zoneScale; + $vec1[] = $f['oddeven']; + $vec1[] = $f['bigsmall']; + $vec1[] = $f['color'] / 2.0; // 0, 0.5, 1.0 + } + foreach ($seq2 as $f) { + $vec2[] = $f['zone'] * $zoneScale; + $vec2[] = $f['oddeven']; + $vec2[] = $f['bigsmall']; + $vec2[] = $f['color'] / 2.0; + } + + $dot = 0; $norm1 = 0; $norm2 = 0; + $len = count($vec1); + for ($i = 0; $i < $len; $i++) { + $dot += $vec1[$i] * $vec2[$i]; + $norm1 += $vec1[$i] * $vec1[$i]; + $norm2 += $vec2[$i] * $vec2[$i]; + } + + $denom = sqrt(max($norm1, 0.0001)) * sqrt(max($norm2, 0.0001)); + return $denom > 0 ? max(0, $dot / $denom) : 0; + } + + /** + * 周期性自相关检测 + * + * 对每个号码,计算不同滞后期的自相关函数 (ACF) + * 检测是否存在显著的周期性出现模式 + * 例如某个号码每隔约 N 期出现一次的模式 + * + * @param array $historyAsc 升序历史 + * @param int $totalDraws + * @return array 每个号码的对数周期性得分 + */ + private function _detectCyclicalPatternsV4($historyAsc, $totalDraws) + { + // 构建每个号码的出现序列 (0/1 向量) + $appearances = array_fill(1, 49, array_fill(0, $totalDraws, 0)); + foreach ($historyAsc as $idx => $row) { + $num = (int)$row['num7']; + if ($num >= 1 && $num <= 49) { + $appearances[$num][$idx] = 1; + } + } + + // 搜索的滞后期范围(从5期到 totalDraws/4 期) + $minLag = 5; + $maxLag = min((int)($totalDraws / 4), 60); + $acfThreshold = 0.12; // 自相关系数阈值 + + $cyclicalScores = []; + for ($n = 1; $n <= 49; $n++) { + $seq = $appearances[$n]; + $mean = array_sum($seq) / $totalDraws; + if ($mean < 0.005) { + // 几乎没出现过的号码,无周期性可言 + $cyclicalScores[$n] = 0; + continue; + } + + // 计算方差 + $variance = 0; + for ($t = 0; $t < $totalDraws; $t++) { + $variance += ($seq[$t] - $mean) * ($seq[$t] - $mean); + } + $variance /= $totalDraws; + if ($variance < 0.0001) { $cyclicalScores[$n] = 0; continue; } + + $bestAcf = 0; + $bestLag = 0; + + for ($lag = $minLag; $lag <= $maxLag; $lag++) { + $cov = 0; + $count = 0; + for ($t = 0; $t < $totalDraws - $lag; $t++) { + $cov += ($seq[$t] - $mean) * ($seq[$t + $lag] - $mean); + $count++; + } + if ($count > 0) { + $acf = $cov / ($count * $variance); + $acf = abs($acf); + if ($acf > $bestAcf) { + $bestAcf = $acf; + $bestLag = $lag; + } + } + } + + // 周期性得分 = 最小(最佳ACF / 阈值, 1) * 100 + $score = $bestAcf > $acfThreshold ? min($bestAcf / ($acfThreshold * 3), 1.0) * 100 : 0; + $cyclicalScores[$n] = $score; + } + + // 转换为对数概率(周期性高的号码获得更高概率) + $logProb = []; + $uniformLog = log(1.0 / 49); + + // 找到有显著周期性的号码 + $significantNums = []; + foreach ($cyclicalScores as $n => $score) { + if ($score > 5) $significantNums[] = $n; + } + + for ($n = 1; $n <= 49; $n++) { + $score = $cyclicalScores[$n]; + if ($score > 0) { + // 周期性得分映射到概率乘数 [1, 3] + $multiplier = 1 + ($score / 100) * 2; + // 最近一次出现距今 + $lastAppear = 0; + for ($t = $totalDraws - 1; $t >= 0; $t--) { + if ($appearances[$n][$t] === 1) { $lastAppear = $totalDraws - 1 - $t; break; } + } + // 如果当前距上次出现接近周期长度,额外加分 + $phaseBonus = 0; + if ($bestLag > 0 && $lastAppear > 0) { + $phaseDiff = abs($lastAppear - $bestLag) / max($bestLag, 1); + if ($phaseDiff < 0.3) $phaseBonus = (1 - $phaseDiff) * 0.5; + } + $pBoost = (1.0 / 49) * $multiplier * (1 + $phaseBonus); + $logProb[$n] = log(max($pBoost, 0.0005)); + } else { + $logProb[$n] = $uniformLog; + } + } + + // 归一化对数概率 + $maxLp = max($logProb); + $lpSum = 0; + $expLp = []; + for ($n = 1; $n <= 49; $n++) { + $expLp[$n] = exp($logProb[$n] - $maxLp); + $lpSum += $expLp[$n]; + } + for ($n = 1; $n <= 49; $n++) { + $logProb[$n] = log(max($expLp[$n] / max($lpSum, 0.0001), 0.0005)); + } + + return $logProb; + } + + /** + * 周期性检测结果摘要 + */ + private function _summarizeCyclicalV4($cyclicalLogProb) + { + $count = 0; + $uniformLog = log(1.0 / 49); + foreach ($cyclicalLogProb as $lp) { + if ($lp > $uniformLog + 0.1) $count++; + } + return [ + 'significant_count' => $count, + 'has_cyclical' => $count > 3, + ]; + } + + /** + * 自适应集成权重:滚动回测评估各子模型独立表现,更新权重 + * + * 策略:用最近 30 期做滚动窗口测试,计算每个子模型的独立命中率 + * 用指数移动平均 (α=0.3) 更新各子模型权重 + * + * @return array 更新后的权重 + */ + private function _adaptEnsembleWeightsV4($historyAsc, $timeWeights, $totalDraws, $baseWeights, + $ewmaLogProb, $markovLogProb, $omitLogProb, $patternResult, + $cyclicalLogProb, $attrLogProb) + { + $testWindow = min(30, (int)($totalDraws * 0.15)); + if ($testWindow < 10) return $baseWeights; + + $subModels = ['freq_ewma', 'markov_number', 'omit_empirical', 'pattern_match', 'cyclical', 'attr_balance']; + $hitCounts = array_fill_keys($subModels, 0); + + // 获取完整历史(需要更多数据用于滚动测试) + $fullHistory = $this->field('expect,num7,openTime') + ->order('openTime', 'desc') + ->limit($totalDraws + $testWindow) + ->select(); + $fullHistoryAsc = array_reverse($fullHistory); + $fullN = count($fullHistoryAsc); + + if ($fullN < $totalDraws + $testWindow) return $baseWeights; + + // 对每个子模型做简化版滚动回测 + $alpha = 0.3; // EMA 平滑系数 + + // 简化:只测试最近 testWindow 期 + for ($t = $fullN - $testWindow; $t < $fullN; $t++) { + $actualNum = (int)$fullHistoryAsc[$t]['num7']; + if ($actualNum < 1 || $actualNum > 49) continue; + + // 构建该测试点的各子模型对数概率(简化版,复用已计算的全局概率) + // 这里使用快速近似而非完整重算 + $testLogProbs = [ + 'freq_ewma' => $ewmaLogProb, + 'markov_number' => $markovLogProb, + 'omit_empirical' => $omitLogProb, + 'pattern_match' => $patternResult['logprob'], + 'cyclical' => $cyclicalLogProb, + 'attr_balance' => $attrLogProb, + ]; + + foreach ($subModels as $model) { + // 取该模型 Top-5 预测 + $modelScores = []; + for ($n = 1; $n <= 49; $n++) { + $modelScores[$n] = $testLogProbs[$model][$n]; + } + arsort($modelScores); + $top5 = array_slice(array_keys($modelScores), 0, 5); + if (in_array($actualNum, $top5)) { + $hitCounts[$model]++; + } + } + } + + // 计算各模型命中率 + $hitRates = []; + foreach ($subModels as $model) { + $hitRates[$model] = $hitCounts[$model] / max($testWindow, 1); + } + + // EMA 更新权重: new_weight = α * (hitRate / avgHitRate) + (1-α) * baseWeight + $avgHitRate = array_sum($hitRates) / max(count($hitRates), 1); + if ($avgHitRate < 0.01) return $baseWeights; + + $adapted = []; + foreach ($subModels as $model) { + $performanceRatio = $hitRates[$model] / $avgHitRate; + $adapted[$model] = $alpha * $baseWeights[$model] * $performanceRatio + (1 - $alpha) * $baseWeights[$model]; + // 限制权重范围 [0.03, 0.40] + $adapted[$model] = max(0.03, min(0.40, $adapted[$model])); + } + + return $adapted; + } + + /** + * V4 置信度评估 + * + * 基于三个维度: + * 1. 回测命中率稳定性 (40%) + * 2. Top-1 与 Top-5 概率差距 (30%) + * 3. 概率分布的熵 (30%,熵越低置信度越高) + * + * @param array $predictions Top-5 预测 + * @param array|null $backtest 回测结果 + * @return array + */ + private function _calculateConfidenceV4($predictions, $backtest) + { + $confidence = []; + + // 维度1: 回测命中率 + $backtestHitRate = $backtest['hit_rate'] ?? 0; + $btScore = $backtestHitRate >= 30 ? 80 : ($backtestHitRate >= 20 ? 60 : ($backtestHitRate >= 10 ? 40 : 20)); + + // 维度2: Top-1 vs Top-5 概率差距 + $top1Prob = $predictions[0]['prob'] ?? 0; + $top5Prob = $predictions[4]['prob'] ?? 0; + $probGap = $top1Prob > 0 ? ($top1Prob - $top5Prob) / $top1Prob : 0; + $gapScore = $probGap > 0.3 ? 80 : ($probGap > 0.15 ? 60 : ($probGap > 0.05 ? 40 : 20)); + + // 维度3: 概率分布熵(需要传入完整概率分布) + $entropyScore = 50; // 默认中等 + + $totalScore = $btScore * 0.4 + $gapScore * 0.3 + $entropyScore * 0.3; + + foreach ($predictions as $idx => $p) { + $level = $totalScore >= 70 ? 'high' : ($totalScore >= 50 ? 'medium' : 'low'); + $confidence[] = [ + 'num' => $p['num'], + 'level' => $level, + 'score' => round($totalScore, 1) + ]; + // 排名越靠后置信度略降 + if ($idx > 0) $totalScore *= 0.95; + } + + return [ + 'items' => $confidence, + 'overall_score'=> round($totalScore, 1), + 'overall_level'=> $totalScore >= 70 ? 'high' : ($totalScore >= 50 ? 'medium' : 'low'), + 'data_warning' => ($backtest && ($backtest['total_tests'] ?? 0) < 30) ? '回测期数较少,置信度可能不准确' : null, + ]; + } + + /** + * V4 回测 + */ + private function _runBacktestV4($periods, $weights, $testCount = 50, $cutoffTime = null) + { + $query = $this->field('expect,num7,openTime'); + if ($cutoffTime) { + $query->where('openTime', '<', $cutoffTime); + } + $totalHistory = $query->order('openTime', 'desc') + ->limit($periods + $testCount + 20) + ->select(); + + if (count($totalHistory) < $periods + $testCount) { + return ['hit_rate' => 0, 'avg_rank' => 0, 'details' => [], 'error' => '数据不足']; + } + + $hits = 0; + $ranks = []; + $details = []; + + for ($i = 0; $i < $testCount; $i++) { + $targetRow = $totalHistory[$i]; + $targetExpect = (string)$targetRow['expect']; + $actualNum = (int)$targetRow['num7']; + + $predResult = $this->getPredictionV4($periods, $weights, $targetExpect, true, 0); + + if (isset($predResult['error']) || empty($predResult['predictions'])) { + continue; + } + + $rank = -1; + foreach ($predResult['predictions'] as $idx => $p) { + if ($p['num'] === $actualNum) { $rank = $idx + 1; break; } + } + + if ($rank > 0) { + $hits++; + $ranks[] = $rank; + } + + $details[] = [ + 'expect' => $targetExpect, + 'actual' => $actualNum, + 'predictions' => array_column($predResult['predictions'], 'num'), + 'hit' => $rank > 0, + 'rank' => $rank + ]; + } + + $hitRate = $testCount > 0 ? round($hits / $testCount * 100, 2) : 0; + $avgRank = count($ranks) > 0 ? round(array_sum($ranks) / count($ranks), 2) : 0; + + $ndcg5 = $this->_calculateNDCG($details, 5); + $mrr = $this->_calculateMRR($details); + $hitDistribution = $this->_calculateHitDistribution($details); + $precision5 = $testCount > 0 ? round($hits / ($testCount * 5) * 100, 2) : 0; + + return [ + 'hit_rate' => $hitRate, + 'avg_rank' => $avgRank, + 'total_tests' => $testCount, + 'total_hits' => $hits, + 'details' => $details, + 'ndcg_5' => $ndcg5, + 'mrr' => $mrr, + 'hit_distribution'=> $hitDistribution, + 'precision_5' => $precision5, + 'data_sufficient' => $testCount >= 30, + ]; + } + } diff --git a/public/assets/js/backend/history.js b/public/assets/js/backend/history.js index 3dc0338..b632a21 100644 --- a/public/assets/js/backend/history.js +++ b/public/assets/js/backend/history.js @@ -770,6 +770,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin '' + '
' + '
' + + ' ' + + ' ' + + '
' + + '
' + + '
' + ' ' + '
' + ' ' + @@ -847,6 +852,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin Controller.api.applyNumberFilters(layero, blockedNums); }); + // 新增首位数 + $('.btn-nf-add-head', layero).on('click', function () { + Controller.api.addHeadRow(layero, 0); + Controller.api.applyNumberFilters(layero, blockedNums); + }); + + // 首位数输入 & 删除事件委托 + $('#nf-head-list', layero).on('input change', '.nf-head-select', function () { + Controller.api.applyNumberFilters(layero, blockedNums); + }); + $('#nf-head-list', layero).on('click', '.nf-head-del', function () { + $(this).closest('.nf-head-row').remove(); + Controller.api.applyNumberFilters(layero, blockedNums); + }); + // 生肖按钮点击 $('.nf-zodiac', layero).on('click', function () { var $btn = $(this); @@ -896,6 +916,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin $('.btn-nf-reset', layero).on('click', function () { blockedNums = []; $('#nf-tail-list', layero).html(''); + $('#nf-head-list', layero).html(''); $('.nf-zodiac', layero).removeClass('btn-gray').addClass('btn-default'); $('.nf-color-btn', layero).removeClass('btn-gray').addClass('btn-default'); $('.nf-parity', layero).removeClass('btn-gray').addClass('btn-default'); @@ -922,6 +943,22 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin $('#nf-tail-list', layero).append(html); }, + /** + * 新增一行首位数筛选 + */ + addHeadRow: function (layero, value) { + var rowId = 'nf-head-' + Date.now() + Math.random().toString(36).substr(2, 5); + var opts = ''; + for (var h = 0; h <= 4; h++) { + opts += ''; + } + var html = '
' + + ' ' + + ' ' + + '
'; + $('#nf-head-list', layero).append(html); + }, + /** * 新增一行区间筛选 */ @@ -953,7 +990,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin if (colorRaw.indexOf('红') !== -1) colorLabel = '红'; else if (colorRaw.indexOf('蓝') !== -1) colorLabel = '蓝'; else if (colorRaw.indexOf('绿') !== -1) colorLabel = '绿'; - html += '
' + + html += '
' + '' + num + '' + '
' + animal + '
' + '
'; @@ -976,6 +1013,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin excludedTails.push(parseInt(val)); } }); + // 收集所有选中的首位数 + var excludedHeads = []; + $('.nf-head-select', layero).each(function () { + var val = $(this).val(); + if (val !== '' && excludedHeads.indexOf(parseInt(val)) === -1) { + excludedHeads.push(parseInt(val)); + } + }); // 收集被点击(置灰)的生肖 var excludedZodiacs = []; $('.nf-zodiac.btn-gray', layero).each(function () { @@ -1005,6 +1050,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin var $num = $(this); var num = parseInt($num.data('num')); var tail = $num.data('tail'); + var head = $num.data('head'); var animal = $num.data('animal'); var color = $num.data('color'); @@ -1016,6 +1062,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin if (excludedTails.length > 0 && excludedTails.indexOf(tail) !== -1) { hidden = true; } + // 首位数筛选:选中多个首位数,任一命中即屏蔽 + if (excludedHeads.length > 0 && excludedHeads.indexOf(head) !== -1) { + hidden = true; + } // 单双筛选:选中单或双,匹配则屏蔽 if (excludedParities.indexOf(parity) !== -1) { hidden = true; @@ -1547,12 +1597,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin '
' + ' V1版本:基于转移概率分析(区域、生肖、尾号、首号、波色转移 + 冷热系数)
' + ' V2版本:基于统计回归分析(遗漏回归、频率回归、区域平衡、波色平衡等)+ 历史回测验证
' + - ' V3版本(推荐):多维度综合预测,新增转移概率(马尔可夫链)、单双规律、大小规律、走势方向分析' + + ' V3版本:多维度综合预测,新增转移概率(马尔可夫链)、单双规律、大小规律、走势方向分析
' + + ' V4版本(推荐):贝叶斯集成学习,6大子模型融合(EWMA频率/号码转移/遗漏CDF/形态匹配/周期检测/属性平衡)' + '
' + '
' + '
' + ' ' + - ' ' + + ' ' + + ' ' + ' ' + ' ' + '
' + @@ -1566,6 +1618,18 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin ' ' + ' 建议200期以上' + '
' + + // V4权重配置 + '
' + + ' ' + + '
' + + '
EWMA频率:
' + + '
号码转移:
' + + '
遗漏CDF:
' + + '
形态匹配:
' + + '
周期检测:
' + + '
属性平衡:
' + + '
' + + '
' + // V3权重配置 '
' + ' ' + @@ -1620,10 +1684,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin // 切换版本时显示对应权重配置 $('input[name="predict-version"]', layero).on('change', function () { var val = $(this).val(); + $('#predict-v4-weights', layero).hide(); $('#predict-v3-weights', layero).hide(); $('#predict-v2-weights', layero).hide(); $('#predict-v1-weights', layero).hide(); - if (val === 'v3') { + if (val === 'v4') { + $('#predict-v4-weights', layero).show(); + $('#predict-periods', layero).val(200); + } else if (val === 'v3') { $('#predict-v3-weights', layero).show(); $('#predict-periods', layero).val(200); } else if (val === 'v2') { @@ -1655,7 +1723,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin var weights = {}; // 根据版本获取权重 - if (version === 'v3') { + if (version === 'v4') { + $('.predict-weight-v4', layero).each(function () { + var key = $(this).data('key'); + var val = parseFloat($(this).val()) || 0; + weights[key] = val; + }); + } else if (version === 'v3') { $('.predict-weight-v3', layero).each(function () { var key = $(this).data('key'); var val = parseFloat($(this).val()) || 0; @@ -1677,7 +1751,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin // 根据版本选择URL var url = 'history/predict'; - if (version === 'v3') { + if (version === 'v4') { + url = 'history/predictV4'; + } else if (version === 'v3') { url = 'history/predictV3'; } else if (version === 'v2') { url = 'history/predictV2'; @@ -1730,7 +1806,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin var versionNames = { 'v1': 'V1(转移概率)', 'v2': 'V2(统计回归)', - 'v3': 'V3(多维度综合)' + 'v3': 'V3(多维度综合)', + 'v4': 'V4(贝叶斯集成)' }; var html = '
'; @@ -1739,7 +1816,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin html += '
基于期号 ' + lastExpect + '(特码 ' + lastSpecial + ')进行预测 | 算法版本: ' + versionNames[version] + '
'; // 置信度评估展示(V2和V3版本) - if (confidence && (version === 'v2' || version === 'v3')) { + if (confidence && (version === 'v2' || version === 'v3' || version === 'v4')) { html += '
'; html += '
预测置信度评估
'; @@ -1749,19 +1826,23 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin } html += '
'; - html += '
' + confidence.overall_confidence + '%
整体置信度
'; + // 整体置信度(兼容 V3/V4 不同字段名) + var overallConf = confidence.overall_confidence || confidence.overall_score || 0; + html += '
' + overallConf + '%
整体置信度
'; - // 各排名置信度(使用得分集中度维度) - if (confidence.confidence_scores && confidence.confidence_scores.length > 0) { + // V4 格式: confidence.items, V3 格式: confidence.confidence_scores + var confItems = confidence.items || confidence.confidence_scores || []; + if (confItems.length > 0) { html += '
'; - for (var i = 0; i < confidence.confidence_scores.length; i++) { - var cs = confidence.confidence_scores[i]; - // 阈值定义:>=70%高(绿)、50-70%中(橙)、<50%低(红) - var confLevel = cs.confidence >= 70 ? '高' : (cs.confidence >= 50 ? '中' : '低'); - var confColor = cs.confidence >= 70 ? '#4caf50' : (cs.confidence >= 50 ? '#ff9800' : '#f44336'); + for (var i = 0; i < confItems.length; i++) { + var cs = confItems[i]; + var csConf = cs.confidence || cs.score || 0; + var csRank = cs.rank || (i + 1); + var confLevel = csConf >= 70 ? '高' : (csConf >= 50 ? '中' : '低'); + var confColor = csConf >= 70 ? '#4caf50' : (csConf >= 50 ? '#ff9800' : '#f44336'); html += '
'; - html += '
' + cs.confidence + '%
'; - html += '
#' + cs.rank + '
'; + html += '
' + csConf + '%
'; + html += '
#' + csRank + '
'; html += '
'; } html += '
'; @@ -1770,7 +1851,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin } // 回测验证结果(V2和V3版本) - if (backtest && (version === 'v2' || version === 'v3')) { + if (backtest && (version === 'v2' || version === 'v3' || version === 'v4')) { html += '
'; html += '
历史回测验证(最近' + backtest.total_tests + '期)
'; @@ -1880,6 +1961,40 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin html += '
'; } + // V4版本特有的分析信息 + if (version === 'v4' && analysis) { + html += '
'; + html += '
V4贝叶斯集成分析
'; + html += '
算法: ' + (analysis.algorithm || 'Bayesian Ensemble') + '
'; + html += '
半衰期: ' + (analysis.half_life || 50) + '期 | 历史数据: ' + (analysis.history_count || 0) + '期
'; + if (analysis.weights_adaptive) { + html += '
自适应权重: 已启用(基于滚动回测性能调整)
'; + } + if (analysis.pattern_match_info) { + var pmi = analysis.pattern_match_info; + html += '
形态匹配: 匹配' + pmi.matched_segments + '个历史片段 | 最佳相似度 ' + (pmi.best_similarity * 100).toFixed(1) + '%
'; + } + if (analysis.cyclical_detected) { + var cyc = analysis.cyclical_detected; + html += '
周期检测: ' + (cyc.has_cyclical ? '发现' + cyc.significant_count + '个号码有显著周期性' : '无明显周期信号') + '
'; + } + // 显示权重分布 + if (analysis.weights) { + html += '
子模型权重分布:
'; + html += '
'; + var wNames = { + 'freq_ewma': 'EWMA频率', 'markov_number': '号码转移', 'omit_empirical': '遗漏CDF', + 'pattern_match': '形态匹配', 'cyclical': '周期检测', 'attr_balance': '属性平衡' + }; + for (var wk in analysis.weights) { + var wv = (analysis.weights[wk] * 100).toFixed(1); + html += '' + (wNames[wk] || wk) + ': ' + wv + '%'; + } + html += '
'; + } + html += '
'; + } + // 遗漏统计信息(V2和V3版本) if (analysis.omit_stats && (version === 'v2' || version === 'v3')) { html += '
'; @@ -1928,7 +2043,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin // 根据版本显示不同的详情信息 var detailInfo = ''; if (p.detail) { - if (version === 'v3') { + if (version === 'v4') { + // V4版本显示子模型概率和属性 + var omitCount = p.detail.omit_count || 0; + detailInfo = '
'; + detailInfo += '遗漏:' + omitCount + '期 | '; + detailInfo += (p.detail.is_odd ? '单' : '双') + '/' + (p.detail.is_big ? '大' : '小'); + detailInfo += '
'; + } else if (version === 'v3') { // V3版本显示更多维度信息 var omitInfo = p.detail.omit || 0; var transScore = p.detail.trans_score || 0; @@ -1952,14 +2074,18 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin html += '
' + animal + '
'; html += '
得分:' + p.score + '
'; - // 显示置信度(V3版本) - if (version === 'v3' && confidence && confidence.confidence_scores) { - var csForNum = confidence.confidence_scores.find(function(c) { return c.num === p.num; }); + // 显示置信度(V3和V4版本) + if ((version === 'v3' || version === 'v4') && confidence) { + var confItemsV4 = confidence.confidence_scores || confidence.items || []; + var csForNum = null; + for (var ci = 0; ci < confItemsV4.length; ci++) { + if (confItemsV4[ci].num === p.num) { csForNum = confItemsV4[ci]; break; } + } if (csForNum) { - // 阈值定义:>=70%高(绿)、50-70%中(橙)、<50%低(红) - var confLevel = csForNum.confidence >= 70 ? '高' : (csForNum.confidence >= 50 ? '中' : '低'); - var confColor = csForNum.confidence >= 70 ? '#4caf50' : (csForNum.confidence >= 50 ? '#ff9800' : '#f44336'); - html += '
置信度:' + confLevel + ' (' + csForNum.confidence + '%)
'; + var csValue = csForNum.confidence || csForNum.score || 0; + var confLevel = csValue >= 70 ? '高' : (csValue >= 50 ? '中' : '低'); + var confColor = csValue >= 70 ? '#4caf50' : (csValue >= 50 ? '#ff9800' : '#f44336'); + html += '
置信度:' + confLevel + ' (' + csValue + '%)
'; } }