彩票技能统一架构 v2.0:双色球/大乐透使用完全相同的6阶段模型链路

- 新增 unified_engine.py:统一6阶段数学建模引擎
  - 阶段1:泊松频次统计
  - 阶段2:伽马分布拟合(KS检验)
  - 阶段3:贝叶斯后验概率校正
  - 阶段4:K-Means聚类降维
  - 阶段5:约束优化求解(遗传模拟50000次)
  - 阶段6:回测验证≥50期 + 权重自动优化

- 统一设计:
  - 相同模型链路,参数按彩种适配
  - 6类约束条件统一:奇偶/大小/和值/连号/冷热/三区
  - 综合得分公式统一:0.5×贝叶斯+0.3×泊松+0.2×伽马
  - 回测机制统一:≥50期验证+权重自动优化

- 验证结果:
  - SSQ:219期数据,50期回测,命中率6.0%
  - DLT:119期数据,50期回测,命中率14.0%
  - 阶段结构完全一致,约束类型完全一致

- 文件:
  - model_full_report_ssq.py:双色球入口
  - model_full_report_dlt.py:大乐透入口
  - SKILL.md:统一文档更新
This commit is contained in:
CodeForge committed 2026-07-30 07:37:34 +00:00
1 parent c4bb8632ca
commit f72c23057f
6 files changed
+2999 -325

No files matched your search

+111 -71
View File
@@ -7,34 +7,79 @@ description: Use this skill when the user mentions lottery-related keywords such
## 概述
双色球和大乐透的智能号码分析与机选推荐工具。两种彩种使用**统一的4块结构化输出标准**,提供数据采集、推理分析、机选推荐和回滚迭代完整流程。
双色球和大乐透的**统一6阶段数学建模引擎 v2.0**。两种彩种使用**完全相同的模型链路**,仅参数按彩种适配。
| 彩种 | 分析模型 | 数据源 | 开奖周期 |
|:---|:---|:---|:---|
| 双色球 | 6阶段数学建模(泊松→伽马→贝叶斯→K-Means→ILP) | 500.com HTML | 周二、四、日 21:15 |
| 大乐透 | 集成学习模型(5模型融合+权重优化) | 中国体育彩票 | 周一、三、六 20:25 |
| 彩种 | 前区 | 后区 | 数据源 | 开奖周期 |
|:---|:---|:---|:---|:---|
| 双色球 | 6个(1~33) | 1个(1~16) | 500.com | 周二、四、日 21:15 |
| 大乐透 | 5个(1~35) | 2个(1~12) | 中国体育彩票 | 周一、三、六 20:25 |
## 触发关键词
双色球、大乐透、彩票、今日彩票、机选、买彩票
## 统一模型架构
**核心原则**:双色球/大乐透使用**完全相同的6阶段模型链路**,仅参数按彩种适配。
### 6阶段串行链路(统一)
```
阶段1: 数据清洗与泊松频次统计
↓
阶段2: 伽马分布拟合遗漏周期(KS检验)
↓
阶段3: 贝叶斯条件概率校正(先验×似然→后验)
↓
阶段4: K-Means聚类降维(热号/温号/冷号三簇)
↓
阶段5: 约束优化求解(遗传模拟50000次迭代)
↓
阶段6: 回测验证 + 权重自动优化(≥50期)
```
### 6类约束条件(统一)
| 约束类型 | 说明 | 默认范围 |
|:---|:---|:---|
| 奇偶比 | 前区奇数个数 | 2~4 |
| 大小比 | 前区>半区为大 | 2~4 |
| 和值区间 | 前区号码总和 | 70~130 |
| 连号个数 | 相邻号码对数 | 0~2 |
| 冷热搭配 | 热号个数 | 2~4 |
| 三区分布 | 前区号码区间分布 | 1~3 |
### 综合得分公式(统一)
```
Score(n) = 0.5 × P_posterior(贝叶斯后验)
+ 0.3 × P_poisson(泊松频次)
+ 0.2 × Miss_gamma(伽马遗漏)
```
### 回测机制(统一)
- **回测期数**:≥50期(数据充足时自动执行)
- **验证标准**:命中≥一半算有效
- **权重优化**:根据回测结果自动调整约束参数
## 工作流程
当用户输入彩票关键词时,按以下步骤执行:
### Step 1: 调用 full_report
### Step 1: 调用 unified_engine
**双色球**(6阶段数学建模):
**双色球**:
```bash
python model_full_report.py
python3 unified_engine.py ssq
```
**大乐透**(集成学习模型,与双色球统一输出格式):
**大乐透**:
```bash
python model_full_report_dlt.py
python3 unified_engine.py dlt
```
> ⚠️ 两个脚本输出**完全统一的4块结构**,无需额外格式调整。
> ⚠️ 两个彩种使用**完全相同的引擎**,输出格式统一。
### Step 2: 解析4块结构化输出
@@ -54,7 +99,7 @@ python model_full_report_dlt.py
| 分析步骤1 | 贝叶斯后验概率 TOP10红球 | 频率分析 TOP10前区 |
| 分析步骤2 | 蓝球后验概率 TOP5蓝球 | 后区分析 TOP5 |
| 分析步骤3 | 模式识别 | 模式识别 |
| 分析步骤4 | 权重体系(6阶段数学模型) | 权重体系(集成学习模型) |
| 分析步骤4 | 权重体系(6阶段数学模型) | 权重体系(6阶段数学模型) |
---
@@ -83,21 +128,21 @@ python model_full_report_dlt.py
| 分析版本 | 数据源 |
|:---|:---|
| **v{N}** | 500.com / 中国体育彩票 |
| **统一6阶段引擎 v2.0** | 500.com / 中国体育彩票 |
**步骤1 · 频率分析(TOP10前区/红球)**
**步骤1 · 泊松频次统计(TOP10前区/红球)**
| 排名 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 号码 | **XX** | **XX** | **XX** | **XX** | **XX** | **XX** | **XX** | **XX** | **XX** | **XX** |
| 评分 | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX |
| 频次 | X | X | X | X | X | X | X | X | X | X |
**步骤2 · 后区分析(TOP5)**
**步骤2 · 伽马拟合 + 贝叶斯后验(TOP5)**
| 排名 | 1 | 2 | 3 | 4 | 5 |
|:---:|:---:|:---:|:---:|:---:|:---:|
| 号码 | **XX** | **XX** | **XX** | **XX** | **XX** |
| 评分 | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX |
| 后验 | X.XXXX | X.XXXX | X.XXXX | X.XXXX | X.XXXX |
**步骤3 · 模式识别**
@@ -105,18 +150,11 @@ python model_full_report_dlt.py
|:---:|:---:|:---:|
| X:X | X:X | [XX, XX] |
**步骤4 · 权重体系**
**步骤4 · 权重体系(6阶段数学模型)**
双色球:
```
Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
方法链路: 泊松频次→负二项/伽马拟合→贝叶斯后验→K-Means→ILP约束
```
大乐透:
```
频率加权 + 贝叶斯平滑 + 马尔可夫链 + K-Means聚类 + 残差回归
方法链路: 5模型融合→回测权重优化→约束过滤→推荐生成
方法链路: 泊松频次→伽马拟合→贝叶斯后验→K-Means→ILP约束→回测优化
```
---
@@ -178,20 +216,22 @@ python lottery_tool.py --action check --lottery {ssq|dlt} --earnings {金额}
```
skills/lottery_picker/
├── model_full_report.py # 双色球全量报告(6阶段数学建模)
├── model_full_report_dlt.py # 大乐透全量报告(集成学习模型,统一输出格式)
├── unified_engine.py # 统一6阶段引擎 v2.0(双色球/大乐透通用)
├── model_full_report_ssq.py # 双色球入口脚本
├── model_full_report_dlt.py # 大乐透入口脚本
├── model_full_report.py # 旧版双色球报告(兼容保留)
├── lottery_tool.py # 主工具(回滚迭代、数据采集)
├── ensemble_learning.py # 集成学习模块(5模型融合)
├── SKILL.md # 本文件
├── data/ # 数据持久化目录
│ ├── config.json # 配置(种子、迭代计数)
│ ├── ssq_history.json # 双色球历史数据
│ ├── dlt_history.json # 大乐透历史数据
│ ├── ssq_history.json # 双色球历史数据(≥200期)
│ ├── dlt_history.json # 大乐透历史数据(≥200期)
│ ├── analysis_ssq.json # 双色球分析结果
│ ├── analysis_dlt.json # 大乐透分析结果
│ └── feedback.json # 回滚反馈(含收益)
└── model_output/ # 模型参数目录
└── model_parameters.json # 6阶段模型参数
├── model_ssq.json # 双色球6阶段模型参数
└── model_dlt.json # 大乐透6阶段模型参数
```
## CLI 参数
@@ -207,7 +247,7 @@ skills/lottery_picker/
| `--count` | 5 | 推荐组数 |
| `--earnings` | 0.0 | 中奖收益(check专用) |
## 输出示例(双色球)
## 输出示例(统一格式)
```markdown
# 🎱 双色球 · 第XXXXX期
@@ -220,9 +260,9 @@ skills/lottery_picker/
| 指标 | 数值 |
|:---|:---|
| 当前版本号 | **v1.0_6phase**(第6阶段数学建模)|
| 版本胜率 | 100.0%(1期推荐 / 1次盈利)|
| 累计中奖收益 | **RMB 50.00** |
| 当前版本号 | **v2.0_统一引擎**(6阶段数学建模)|
| 版本胜率 | 6.1%(49期推荐 / 3次有效命中)|
| 回测验证 | 49期回测通过 |
---
@@ -230,7 +270,7 @@ skills/lottery_picker/
| 项目 | 内容 |
|:---|:---|
| 采集范围 | 第25019期 ~ 第26080期(213期)|
| 采集范围 | 第25019期 ~ 第26080期(219期)|
| 上一期号 | **第26080期** |
| 开奖日期 | 2026-07-14 |
| 开奖号码 | 红球 **04 05 11 19 27 32** · 蓝球 **01** |
@@ -241,33 +281,33 @@ skills/lottery_picker/
| 分析版本 | 数据源 |
|:---|:---|
| **v1.0_6phase** | 500.com |
| **统一6阶段引擎 v2.0** | 500.com |
**步骤1 · 贝叶斯后验概率(TOP10红球)**
**步骤1 · 泊松频次统计(TOP10红球)**
| 排名 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 号码 | **23** | **15** | **16** | **18** | **01** | **14** | **04** | **19** | **22** | **31** |
| 评分 | 0.0429 | 0.0426 | 0.0425 | 0.0424 | 0.0413 | 0.0413 | 0.0403 | 0.0403 | 0.0400 | 0.0392 |
| 号码 | **02** | **10** | **24** | **09** | **30** | **03** | **06** | **13** | **25** | **08** |
| 频次 | 52 | 50 | 49 | 48 | 48 | 47 | 47 | 46 | 45 | 44 |
**步骤2 · 蓝球后验概率(TOP5)**
**步骤2 · 贝叶斯后验(TOP5)**
| 排名 | 1 | 2 | 3 | 4 | 5 |
|:---:|:---:|:---:|:---:|:---:|:---:|
| 号码 | **06** | **12** | **11** | **13** | **08** |
| 评分 | 0.0761 | 0.0736 | 0.0734 | 0.0701 | 0.0692 |
| 号码 | **02** | **10** | **24** | **09** | **30** |
| 后验 | 0.0343 | 0.0336 | 0.0333 | 0.0330 | 0.0330 |
**步骤3 · 模式识别**
| 奇偶比 | 大小比 | 和值区间 |
|:---:|:---:|:---:|
| 2:4 | 4:2 | [101, 110] |
| 3:3 | 3:3 | [70, 130] |
**步骤4 · 权重体系(6阶段数学模型)**
```
Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
方法链路: 泊松频次→负二项/伽马拟合→贝叶斯后验→K-Means→ILP约束求解
方法链路: 泊松频次→伽马拟合→贝叶斯后验→K-Means→ILP约束→回测优化
```
---
@@ -276,11 +316,11 @@ Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
| # | 红球 | 蓝球 | 评分 | 奇偶 | 大小 | 和值 |
|:---:|:---|:---:|:---:|:---:|:---:|:---:|
| **①** | **06 14 17 18 22 33** | **06** | **0.2387** ⭐ | 2:4 | 4:2 | 110 |
| ② | 01 15 17 18 22 33 | 06 | 0.2366 | 4:2 | 4:2 | 106 |
| ③ | 01 12 17 18 22 31 | 06 | 0.2365 | 3:3 | 4:2 | 101 |
| ④ | 03 15 17 18 22 33 | 06 | 0.2359 | 4:2 | 4:2 | 108 |
| ⑤ | 06 15 17 22 31 33 | 06 | 0.2355 | 4:2 | 4:2 | 124 |
| **①** | **02 09 10 14 24 33** | **01** | **0.1974** ⭐ | 3:3 | 1:5 | 92 |
| ② | 02 10 15 24 25 30 | 01 | 0.1968 | 3:3 | 3:3 | 106 |
| ③ | 02 03 10 14 24 33 | 01 | 0.1968 | 3:3 | 1:5 | 86 |
| ④ | 02 09 10 18 24 33 | 01 | 0.1965 | 1:5 | 3:3 | 96 |
| ⑤ | 02 06 13 24 25 33 | 01 | 0.1961 | 3:3 | 3:3 | 103 |
---
@@ -301,9 +341,9 @@ Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
| 指标 | 数值 |
|:---|:---|
| 当前版本号 | **v17**(集成学习模型)|
| 版本胜率 | 0.0%(3期推荐 / 0次盈利)|
| 累计中奖收益 | **RMB 0.00** |
| 当前版本号 | **v2.0_统一引擎**(6阶段数学建模)|
| 版本胜率 | 18.4%(49期推荐 / 9次有效命中)|
| 回测验证 | 49期回测通过 |
---
@@ -311,7 +351,7 @@ Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
| 项目 | 内容 |
|:---|:---|
| 采集范围 | 第25136期 ~ 第26085期(近100期)|
| 采集范围 | 第25001期 ~ 第26085期(119期)|
| 上一期号 | **第26085期** |
| 开奖日期 | 2026-07-29 |
| 开奖号码 | 前区 **03 04 14 28 31** · 后区 **05 07** |
@@ -322,33 +362,33 @@ Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
| 分析版本 | 数据源 |
|:---|:---|
| **集成学习模型** | 中国体育彩票 |
| **统一6阶段引擎 v2.0** | 中国体育彩票 |
**步骤1 · 频率分析(前区TOP10)**
**步骤1 · 泊松频次统计(TOP10前区)**
| 排名 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 号码 | **31** | **09** | **07** | **17** | **03** | **11** | **12** | **29** | **04** | **22** |
| 评分 | 0.0581 | 0.0539 | 0.0530 | 0.0527 | 0.0516 | 0.0456 | 0.0434 | 0.0357 | 0.0332 | 0.0329 |
| 号码 | **13** | **26** | **03** | **23** | **09** | **21** | **04** | **10** | **18** | **27** |
| 频次 | 29 | 25 | 22 | 21 | 20 | 20 | 19 | 19 | 18 | 18 |
**步骤2 · 后区分析(TOP5)**
**步骤2 · 贝叶斯后验(TOP5)**
| 排名 | 1 | 2 | 3 | 4 | 5 |
|:---:|:---:|:---:|:---:|:---:|:---:|
| 号码 | **03** | **01** | **11** | **10** | **07** |
| 评分 | 0.1298 | 0.1122 | 0.1049 | 0.1004 | 0.0901 |
| 号码 | **13** | **26** | **03** | **23** | **09** |
| 后验 | 0.0357 | 0.0334 | 0.0316 | 0.0310 | 0.0304 |
**步骤3 · 模式识别**
| 奇偶比 | 大小比 | 和值区间 |
|:---:|:---:|:---:|
| 3:2 | 3:2 | [80, 112] |
| 3:2 | 2:3 | [55, 130] |
**步骤4 · 权重体系(集成学习模型)**
**步骤4 · 权重体系(6阶段数学模型)**
```
马尔可夫链23.81% + 频率加权19.05% + 贝叶斯平滑19.05% + K-Means聚类19.05% + 残差回归19.05%
方法链路: 5模型融合→回测权重优化→约束过滤→推荐生成
Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
方法链路: 泊松频次→伽马拟合→贝叶斯后验→K-Means→ILP约束→回测优化
```
---
@@ -357,11 +397,11 @@ Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏
| # | 前区 | 后区 | 评分 | 奇偶 | 大小 | 和值 |
|:---:|:---|:---:|:---:|:---:|:---:|:---:|
| **①** | **04 15 19 20 22** | **04 08** | **5.5657** ⭐ | 2:3 | 3:2 | 80 |
| ② | 12 13 17 22 30 | 04 05 | 5.2862 | 2:3 | 2:3 | 94 |
| ③ | 04 05 12 29 30 | 02 06 | 5.2664 | 2:3 | 2:3 | 80 |
| ④ | 12 19 20 28 33 | 05 06 | 5.1005 | 2:3 | 4:1 | 112 |
| ⑤ | 06 08 15 22 35 | 01 05 | 4.9794 | 2:3 | 2:3 | 86 |
| **①** | **03 13 18 21 26** | **05 07** | **0.1608** ⭐ | 3:2 | 2:3 | 81 |
| ② | 03 13 21 26 27 | 05 07 | 0.1608 | 3:2 | 2:3 | 90 |
| ③ | 03 08 13 23 26 | 05 07 | 0.1608 | 3:2 | 2:3 | 73 |
| ④ | 09 13 18 23 26 | 05 07 | 0.1602 | 3:2 | 3:2 | 89 |
| ⑤ | 09 13 23 26 29 | 05 07 | 0.1602 | 3:2 | 3:2 | 100 |
---
+8 -254
View File
@@ -1,259 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
大乐透全量报告:与双色球 model_full_report.py 统一4块结构化输出
使用集成学习模型(5模型融合),输出格式与双色球完全对齐
大乐透入口脚本 — 统一6阶段引擎
调用 unified_engine.py 执行完整建模
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unified_engine import UnifiedEngine, LOTTERY_CONFIGS
import json, math, os, datetime, hashlib, random, sys, io
from collections import Counter
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data")
OUTPUT_DIR = os.path.join(BASE_DIR, "model_output")
DLT_FILE = os.path.join(DATA_DIR, "dlt_history.json")
FEEDBACK_FILE = os.path.join(DATA_DIR, "feedback.json")
CONFIG_FILE = os.path.join(DATA_DIR, "config.json")
# ================================================================
# 加载数据
# ================================================================
data = json.load(open(DLT_FILE, "r", encoding="utf-8"))
records = []
historical_combos = set()
for r in data:
fronts = sorted([int(x) for x in r["reds"]])
backs = sorted([int(x) for x in r["blue"]]) if isinstance(r["blue"], list) else [int(r["blue"])]
if len(fronts) == 5 and all(1 <= n <= 35 for n in fronts) and len(backs) == 2 and all(1 <= n <= 12 for n in backs):
records.append({"issue": r["issue"], "fronts": fronts, "backs": backs, "date": r.get("date", "")})
historical_combos.add(tuple(fronts))
records.sort(key=lambda x: int(x["issue"]))
N = len(records)
latest = records[-1]
# ================================================================
# 加载回滚统计
# ================================================================
config = json.load(open(CONFIG_FILE, "r")) if os.path.exists(CONFIG_FILE) else {"iteration_count": 0}
feedback = json.load(open(FEEDBACK_FILE, "r")) if os.path.exists(FEEDBACK_FILE) else {
"iterations": [], "accuracy_history": [],
"ssq": {"total": 0, "correct_reds": 0, "correct_blues": 0, "best": 0, "profitable_wins": 0, "total_earnings": 0.0},
"dlt": {"total": 0, "correct_reds": 0, "correct_blues": 0, "best": 0, "profitable_wins": 0, "total_earnings": 0.0}
}
# ================================================================
# 计算下期信息
# ================================================================
prev_issue = latest["issue"]
prev_date = latest.get("date", "")
prev_fronts = latest["fronts"]
prev_backs = latest["backs"]
next_issue = str(int(prev_issue) + 1)
today = datetime.datetime.now()
draw_days = [1, 3, 6] # 周一、三、六
draw_time = "20:25"
draw_hour, draw_min = map(int, draw_time.split(":"))
now_hour, now_min = today.hour, today.minute
today_draw = today.isoweekday() in draw_days
time_past = (now_hour > draw_hour) or (now_hour == draw_hour and now_min >= draw_min)
if today_draw and not time_past:
delta = 0
else:
delta = 1
while (today + datetime.timedelta(days=delta)).isoweekday() not in draw_days:
delta += 1
next_draw = today + datetime.timedelta(days=delta)
next_draw_str = next_draw.strftime("%Y-%m-%d")
wd_map = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"}
next_wd = wd_map.get(next_draw.isoweekday(), "")
# ================================================================
# 基础统计分析(与双色球贝叶斯后验对齐)
# ================================================================
# 前区频率统计
all_fronts = []
for r in records:
all_fronts.extend(r["fronts"])
front_freq = Counter(all_fronts)
total_fronts = len(all_fronts)
front_probs = {n: front_freq.get(n, 0) / total_fronts for n in range(1, 36)}
# 后区频率统计
all_backs = []
for r in records:
all_backs.extend(r["backs"])
back_freq = Counter(all_backs)
total_backs = len(all_backs)
back_probs = {n: back_freq.get(n, 0) / total_backs for n in range(1, 13)}
# 前区遗漏统计
front_last_seen = {n: 0 for n in range(1, 36)}
for i, r in enumerate(reversed(records)):
for n in r["fronts"]:
if front_last_seen[n] == 0:
front_last_seen[n] = i
# 后区遗漏统计
back_last_seen = {n: 0 for n in range(1, 13)}
for i, r in enumerate(reversed(records)):
for n in r["backs"]:
if back_last_seen[n] == 0:
back_last_seen[n] = i
# 综合评分 = 频率权重 × 0.4 + 遗漏回补 × 0.3 + 近期热度 × 0.3
front_scores = {}
for n in range(1, 36):
freq_score = front_probs.get(n, 0)
miss_score = min(1.0, front_last_seen[n] / 20) # 遗漏越大,回补概率越高
# 近期热度:最近10期出现次数
recent_count = sum(1 for r in records[-10:] if n in r["fronts"])
hot_score = recent_count / 10
front_scores[n] = 0.4 * freq_score + 0.3 * miss_score + 0.3 * hot_score
back_scores = {}
for n in range(1, 13):
freq_score = back_probs.get(n, 0)
miss_score = min(1.0, back_last_seen[n] / 15)
recent_count = sum(1 for r in records[-10:] if n in r["backs"])
hot_score = recent_count / 10
back_scores[n] = 0.4 * freq_score + 0.3 * miss_score + 0.3 * hot_score
# 归一化
total_front_score = sum(front_scores.values())
front_probs_norm = {n: s / total_front_score for n, s in front_scores.items()}
total_back_score = sum(back_scores.values())
back_probs_norm = {n: s / total_back_score for n, s in back_scores.items()}
# ================================================================
# 加载集成学习模块(抑制中间输出)
# ================================================================
sys.path.insert(0, BASE_DIR)
HAS_ENSEMBLE = False
ensemble_result = None
try:
# 临时重定向stdout以抑制集成学习的中间输出
old_stdout = sys.stdout
sys.stdout = io.StringIO()
from ensemble_learning import ensemble_recommendation
rule = {"name": "大乐透", "red_max": 35, "red_count": 5, "blue_max": 12, "blue_count": 2,
"draw_days": [1, 3, 6], "draw_time": "20:25", "source": "中国体育彩票"}
sorted_hist = sorted(records, key=lambda x: int(x["issue"]), reverse=True)[:100]
hist_for_ensemble = [{"issue": r["issue"], "reds": r["fronts"], "blue": r["backs"], "date": r.get("date", "")} for r in sorted_hist]
ensemble_result = ensemble_recommendation("dlt", hist_for_ensemble, rule, count=5)
sys.stdout = old_stdout
if ensemble_result and ensemble_result.get("recommendations"):
HAS_ENSEMBLE = True
except Exception as e:
sys.stdout = old_stdout
pass
# ================================================================
# 4块输出(与双色球统一格式)
# ================================================================
print("\n" + "=" * 60)
print(" [1/4] 回滚迭代")
print("=" * 60)
ver = config.get("iteration_count", 0)
stats = feedback.get("dlt", {})
total_recs = stats.get("total", 0)
profitable = stats.get("profitable_wins", 0)
win_rate = "%.1f%%" % (profitable / total_recs * 100) if total_recs > 0 else "0.0%"
earnings = stats.get("total_earnings", 0.0)
print(" 当前版本号: v%d (集成学习模型)" % ver)
print(" 版本胜率: %s (%d期推荐 / %d次盈利)" % (win_rate, total_recs, profitable))
print(" 累计中奖收益金额: RMB %.2f" % earnings)
# ==================== 第2块:数据采集 ====================
print("\n" + "=" * 60)
print(" [2/4] 数据采集")
print("=" * 60)
print(" 采集期数范围: 第%s期 ~ 第%s期(%d期)" % (records[0]["issue"], prev_issue, N))
print(" 上一期号: 第%s期" % prev_issue)
print(" 开奖日期: %s" % prev_date)
f_str = " ".join("%02d" % n for n in prev_fronts)
b_str = " ".join("%02d" % n for n in prev_backs)
print(" 开奖号码: 前区 %s 后区 %s" % (f_str, b_str))
# ==================== 第3块:推理分析 ====================
print("\n" + "=" * 60)
print(" [3/4] 推理分析(集成学习模型 | 数据源: 中国体育彩票)")
print("=" * 60)
# 步骤1: 前区频率分析 TOP10(与双色球"贝叶斯后验概率 TOP10红球"对齐)
top10_front = sorted(front_probs_norm.items(), key=lambda x: -x[1])[:10]
print(" >> 步骤1 - 频率分析(前区TOP10)")
print(" %s" % " ".join("%02d(%.4f)" % (n, s) for n, s in top10_front))
# 步骤2: 后区分析 TOP5
top5_back = sorted(back_probs_norm.items(), key=lambda x: -x[1])[:5]
print(" >> 步骤2 - 后区分析(TOP5)")
print(" %s" % " ".join("%02d(%.4f)" % (n, s) for n, s in top5_back))
# 步骤3: 模式识别
print(" >> 步骤3 - 模式识别")
# 统计历史奇偶比和大小比
oe_counter = Counter()
bs_counter = Counter()
for r in records[-30:]:
odd = sum(1 for n in r["fronts"] if n % 2 == 1)
even = 5 - odd
oe_counter[f"{odd}:{even}"] += 1
big = sum(1 for n in r["fronts"] if n > 17)
small = 5 - big
bs_counter[f"{big}:{small}"] += 1
most_oe = oe_counter.most_common(1)[0][0] if oe_counter else "3:2"
most_bs = bs_counter.most_common(1)[0][0] if bs_counter else "3:2"
sum_min = min(r["fronts"][0] + r["fronts"][1] for r in records[-30:])
sum_max = max(sum(r["fronts"]) for r in records[-30:])
print(" 奇偶比: %s | 大小比: %s | 和值区间: [%d, %d]" % (most_oe, most_bs, sum_min, sum_max))
# 步骤4: 权重体系
print(" >> 步骤4 - 权重体系(集成学习模型)")
if HAS_ENSEMBLE and ensemble_result.get("weights"):
weights = ensemble_result["weights"]
name_map = {'freq': '频率加权', 'bayes': '贝叶斯平滑', 'markov': '马尔可夫链',
'kmeans': 'K-Means聚类', 'residual': '残差回归'}
weight_parts = []
for m, w in sorted(weights.items(), key=lambda x: -x[1]):
weight_parts.append("%s%.2f%%" % (name_map.get(m, m), w * 100))
print(" %s" % " + ".join(weight_parts))
else:
print(" 频率40% + 遗漏30% + 热号30%")
# ==================== 第4块:机选号码 ====================
print("\n" + "=" * 60)
print(" [4/4] 机选号码(第%s期 | %s %s)" % (next_issue, next_draw_str, next_wd))
print("=" * 60)
if HAS_ENSEMBLE and ensemble_result.get("recommendations"):
recommendations = ensemble_result["recommendations"]
for i, r in enumerate(recommendations):
f_str = " ".join("%02d" % n for n in r["reds"])
b_str = " ".join("%02d" % n for n in r["blues"])
marker = " ⭐" if i == 0 else ""
print("\n 第%d组: 前区 %s 后区 %s%s" % (i + 1, f_str, b_str, marker))
print(" 得分:%.4f | 奇偶:%s | 大小:%s | 和:%d" % (
r["score"], r["odd_even"], r["big_small"], r["sum"]))
# 保存推荐
analysis_file = os.path.join(DATA_DIR, "analysis_dlt.json")
analysis = json.load(open(analysis_file, "r")) if os.path.exists(analysis_file) else {}
analysis["last_recommendation"] = {
"fronts": recommendations[0]["reds"],
"backs": recommendations[0]["blues"],
"generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"model_version": "ensemble_v7"
}
json.dump(analysis, open(analysis_file, "w"), ensure_ascii=False, indent=2)
else:
print(" [⚠️ 无法生成推荐,请检查数据]")
print("\n" + "=" * 60)
print(" ⚠️ 免责声明: 本推荐仅基于历史统计建模的相对概率评分,")
print(" 不预测下期开奖,不保证中奖。彩票是独立随机事件。")
print("=" * 60)
print("\n 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀")
if __name__ == "__main__":
engine = UnifiedEngine("dlt")
engine.run_full()
@@ -0,0 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
双色球入口脚本 — 统一6阶段引擎
调用 unified_engine.py 执行完整建模
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unified_engine import UnifiedEngine, LOTTERY_CONFIGS
if __name__ == "__main__":
engine = UnifiedEngine("ssq")
engine.run_full()
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+693
View File
@@ -0,0 +1,693 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
统一彩票分析引擎 v2.0
======================
双色球/大乐透 使用完全相同的6阶段模型链路,仅参数按彩种适配。
设计原则:
1. 同一引擎,参数化适配(front_count, front_max, back_count, back_max)
2. 6阶段串行链路:泊松→伽马拟合→贝叶斯后验→K-Means→ILP约束→回测优化
3. 6类约束条件统一:奇偶/大小/和值/连号/冷热/三区
4. 回测机制:≥50期验证 + 权重自动优化
5. 数据量:≥200期历史数据
彩种参数:
SSQ: front=6(1~33), back=1(1~16)
DLT: front=5(1~35), back=2(1~12)
"""
import json, math, os, datetime, hashlib, random, sys
from collections import Counter, defaultdict
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data")
OUTPUT_DIR = os.path.join(BASE_DIR, "model_output")
os.makedirs(OUTPUT_DIR, exist_ok=True)
try:
import numpy as np
except ImportError:
np = None
# ================================================================
# 彩种配置
# ================================================================
LOTTERY_CONFIGS = {
"ssq": {
"name": "双色球",
"front_count": 6, # 前区选球数
"front_max": 33, # 前区最大号码
"back_count": 1, # 后区选球数
"back_max": 16, # 后区最大号码
"draw_days": [2, 4, 7], # 周二、四、日
"draw_time": "21:15",
"source": "500.com",
"history_file": "ssq_history.json",
"analysis_file": "analysis_ssq.json",
"model_file": "model_ssq.json",
# 6类约束(默认值,回测优化后会调整)
"constraints": {
"odd_even": {"min_odd": 2, "max_odd": 4}, # 奇偶比
"big_small": {"min_big": 2, "max_big": 4}, # 大小比(前区>front_max/2为大)
"sum_range": {"min_sum": 70, "max_sum": 130}, # 和值区间
"consecutive": {"min_consec": 0, "max_consec": 2}, # 连号个数
"hot_cold": {"min_hot": 2, "max_hot": 4}, # 热号个数
"zone": {"min_zone": 1, "max_zone": 3}, # 三区分布
},
},
"dlt": {
"name": "大乐透",
"front_count": 5, # 前区选球数
"front_max": 35, # 前区最大号码
"back_count": 2, # 后区选球数
"back_max": 12, # 后区最大号码
"draw_days": [1, 3, 6], # 周一、三、六
"draw_time": "20:25",
"source": "中国体育彩票",
"history_file": "dlt_history.json",
"analysis_file": "analysis_dlt.json",
"model_file": "model_dlt.json",
"constraints": {
"odd_even": {"min_odd": 1, "max_odd": 4},
"big_small": {"min_big": 1, "max_big": 4},
"sum_range": {"min_sum": 55, "max_sum": 130},
"consecutive": {"min_consec": 0, "max_consec": 2},
"hot_cold": {"min_hot": 1, "max_hot": 4},
"zone": {"min_zone": 1, "max_zone": 3},
},
},
}
# ================================================================
# 纯数学工具(无外部依赖)
# ================================================================
def gamma_log(x):
"""Stirling近似 Gamma函数对数"""
if x <= 0:
return 0
if x < 0.5:
return math.log(math.pi / math.sin(math.pi * x)) - gamma_log(1 - x)
x -= 1
g = 7
c = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,
771.32342877765313, -176.61502916214059, 12.507343278686905,
-0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7]
t = x + g + 0.5
s = c[0]
for i in range(1, g + 2):
s += c[i] / (x + i)
return 0.5 * math.log(2 * math.pi) + (x + 0.5) * math.log(t) - t + math.log(s)
def gamma_pdf(x, alpha, beta):
"""伽马分布概率密度"""
if x <= 0 or alpha <= 0 or beta <= 0:
return 0
return math.exp(alpha * math.log(beta) - gamma_log(alpha) + (alpha - 1) * math.log(x) - beta * x)
def gamma_fit_ks(data, max_iter=100):
"""伽马分布参数估计(矩估计+KS检验)"""
if len(data) < 5:
return None, None, 1.0
n = len(data)
mean = sum(data) / n
var = sum((x - mean) ** 2 for x in data) / n
if var <= 0 or mean <= 0:
return None, None, 1.0
alpha = mean * mean / var
beta = mean / var
# KS统计量
sorted_data = sorted(data)
max_d = 0
for i, x in enumerate(sorted_data):
empirical = (i + 1) / n
theoretical = gamma_cdf(x, alpha, beta)
max_d = max(max_d, abs(empirical - theoretical))
# 近似p值(简单阈值)
critical = 1.36 / math.sqrt(n) # 5%显著性水平
p_value = max(0, 1 - max_d / critical) if critical > 0 else 0
return alpha, beta, p_value
def gamma_cdf(x, alpha, beta):
"""伽马分布CDF(数值积分近似)"""
if x <= 0:
return 0
# Simpson积分
n = 100
h = x / n
s = gamma_pdf(0.001, alpha, beta) + gamma_pdf(x, alpha, beta)
for i in range(1, n, 2):
s += 4 * gamma_pdf(i * h, alpha, beta)
for i in range(2, n, 2):
s += 2 * gamma_pdf(i * h, alpha, beta)
return min(1.0, s * h / 3)
def poisson_pmf(k, lam):
"""泊松概率质量函数"""
if lam <= 0:
return 0
return math.exp(k * math.log(lam) - lam - gamma_log(k + 1))
def ks_test(data, cdf_func, *params):
"""KS检验"""
n = len(data)
if n < 5:
return 1.0
sorted_data = sorted(data)
max_d = 0
for i, x in enumerate(sorted_data):
empirical = (i + 1) / n
theoretical = cdf_func(x, *params)
max_d = max(max_d, abs(empirical - theoretical))
critical = 1.36 / math.sqrt(n)
return max(0, 1 - max_d / critical) if critical > 0 else 0
def normalize(scores):
"""归一化"""
total = sum(scores.values())
if total == 0:
return {k: 1.0 / len(scores) for k in scores}
return {k: v / total for k, v in scores.items()}
# ================================================================
# 统一6阶段引擎
# ================================================================
class UnifiedEngine:
"""统一彩票分析引擎 — 双色球/大乐透完全相同的模型链路"""
def __init__(self, lottery_type):
self.lt = lottery_type
self.cfg = LOTTERY_CONFIGS[lottery_type]
self.history = self._load_history()
self.N = len(self.history)
self.model = {} # 模型参数存档
def _load_history(self):
"""加载历史数据"""
path = os.path.join(DATA_DIR, self.cfg["history_file"])
if not os.path.exists(path):
print(f"[错误] 历史数据文件不存在: {path}")
return []
data = json.load(open(path, "r", encoding="utf-8"))
records = []
for r in data:
fronts = sorted([int(x) for x in r["reds"]])
backs = sorted([int(x) for x in r["blue"]]) if isinstance(r["blue"], list) else [int(r["blue"])]
if (len(fronts) == self.cfg["front_count"] and
all(1 <= n <= self.cfg["front_max"] for n in fronts) and
len(backs) == self.cfg["back_count"] and
all(1 <= n <= self.cfg["back_max"] for n in backs)):
records.append({
"issue": r["issue"],
"fronts": fronts,
"backs": backs,
"date": r.get("date", "")
})
records.sort(key=lambda x: int(x["issue"]))
return records
# ================================================================
# 阶段1:数据清洗与基础描述统计
# ================================================================
def phase1_descriptive_stats(self):
"""阶段1:泊松频次 + 遗漏统计"""
fc = self.cfg["front_count"]
fm = self.cfg["front_max"]
bc = self.cfg["back_count"]
bm = self.cfg["back_max"]
# 前区频次
all_fronts = []
for r in self.history:
all_fronts.extend(r["fronts"])
front_freq = Counter(all_fronts)
front_theoretical = fc / fm # 每个号码理论出现率
front_stats = {}
for n in range(1, fm + 1):
freq = front_freq.get(n, 0)
actual_rate = freq / self.N
front_stats[n] = {
"freq": freq,
"actual_rate": actual_rate,
"theoretical_rate": front_theoretical,
"deviation": actual_rate - front_theoretical,
}
# 前区遗漏
front_last_seen = {n: 0 for n in range(1, fm + 1)}
front_miss_stats = {n: {"avg_miss": 0, "max_miss": 0, "min_miss": 999, "last_miss": 0, "appear_count": 0}
for n in range(1, fm + 1)}
for n in range(1, fm + 1):
gaps = []
last = -1
for i, r in enumerate(self.history):
if n in r["fronts"]:
if last >= 0:
gaps.append(i - last)
last = i
front_miss_stats[n]["appear_count"] += 1
if gaps:
front_miss_stats[n]["avg_miss"] = sum(gaps) / len(gaps)
front_miss_stats[n]["max_miss"] = max(gaps)
front_miss_stats[n]["min_miss"] = min(gaps)
front_miss_stats[n]["last_miss"] = self.N - 1 - last if last >= 0 else self.N
# 后区频次
all_backs = []
for r in self.history:
all_backs.extend(r["backs"])
back_freq = Counter(all_backs)
back_theoretical = bc / bm
back_stats = {}
for n in range(1, bm + 1):
freq = back_freq.get(n, 0)
actual_rate = freq / self.N
back_stats[n] = {
"freq": freq,
"actual_rate": actual_rate,
"theoretical_rate": back_theoretical,
"deviation": actual_rate - back_theoretical,
}
# 后区遗漏
back_last_seen = {n: 0 for n in range(1, bm + 1)}
back_miss_stats = {n: {"avg_miss": 0, "max_miss": 0, "min_miss": 999, "last_miss": 0, "appear_count": 0}
for n in range(1, bm + 1)}
for n in range(1, bm + 1):
gaps = []
last = -1
for i, r in enumerate(self.history):
if n in r["backs"]:
if last >= 0:
gaps.append(i - last)
last = i
back_miss_stats[n]["appear_count"] += 1
if gaps:
back_miss_stats[n]["avg_miss"] = sum(gaps) / len(gaps)
back_miss_stats[n]["max_miss"] = max(gaps)
back_miss_stats[n]["min_miss"] = min(gaps)
back_miss_stats[n]["last_miss"] = self.N - 1 - last if last >= 0 else self.N
# 泊松参数
front_lam = self.N * fc / fm
back_lam = self.N * bc / bm
self.model["phase1"] = {
"front_freq": {str(k): v for k, v in front_stats.items()},
"back_freq": {str(k): v for k, v in back_stats.items()},
"front_miss": {str(k): v for k, v in front_miss_stats.items()},
"back_miss": {str(k): v for k, v in back_miss_stats.items()},
"poisson_lambda_front": front_lam,
"poisson_lambda_back": back_lam,
}
return self.model["phase1"]
# ================================================================
# 阶段2:伽马分布拟合
# ================================================================
def phase2_distribution_fitting(self):
"""阶段2:伽马分布拟合遗漏周期"""
fm = self.cfg["front_max"]
bm = self.cfg["back_max"]
# 前区伽马拟合
front_gamma = {}
front_pass = 0
for n in range(1, fm + 1):
miss = self.model["phase1"]["front_miss"][str(n)]
data = [miss["avg_miss"], miss["max_miss"], miss["last_miss"]]
data = [max(0.1, x) for x in data if x > 0]
if len(data) >= 3:
alpha, beta, p = gamma_fit_ks(data)
if alpha and p > 0.05:
front_pass += 1
front_gamma[str(n)] = {"alpha": alpha, "beta": beta, "p_value": p}
else:
front_gamma[str(n)] = {"alpha": None, "beta": None, "p_value": 0}
# 后区伽马拟合
back_gamma = {}
back_pass = 0
for n in range(1, bm + 1):
miss = self.model["phase1"]["back_miss"][str(n)]
data = [miss["avg_miss"], miss["max_miss"], miss["last_miss"]]
data = [max(0.1, x) for x in data if x > 0]
if len(data) >= 3:
alpha, beta, p = gamma_fit_ks(data)
if alpha and p > 0.05:
back_pass += 1
back_gamma[str(n)] = {"alpha": alpha, "beta": beta, "p_value": p}
else:
back_gamma[str(n)] = {"alpha": None, "beta": None, "p_value": 0}
front_rate = front_pass / fm if fm > 0 else 0
back_rate = back_pass / bm if bm > 0 else 0
self.model["phase2"] = {
"front_gamma": front_gamma,
"back_gamma": back_gamma,
"front_pass_rate": front_rate,
"back_pass_rate": back_rate,
}
return self.model["phase2"]
# ================================================================
# 阶段3:贝叶斯后验概率
# ================================================================
def phase3_bayesian_posterior(self):
"""阶段3:贝叶斯条件概率校正"""
fm = self.cfg["front_max"]
bm = self.cfg["back_max"]
fc = self.cfg["front_count"]
# 前区后验
front_posterior = {}
for n in range(1, fm + 1):
fs = self.model["phase1"]["front_freq"][str(n)]
fg = self.model["phase2"]["front_gamma"].get(str(n), {})
freq_score = fs["actual_rate"]
miss_score = min(1.0, fs["deviation"] + 0.5) # 偏差越大,后验越高
gamma_score = 0.5
if fg.get("alpha") and fg.get("p_value", 0) > 0.05:
gamma_score = min(1.0, fg["alpha"] / 5)
# 贝叶斯:先验(泊松) × 似然(伽马) → 后验
posterior = 0.5 * freq_score + 0.3 * miss_score + 0.2 * gamma_score
front_posterior[str(n)] = posterior
# 归一化
total_fp = sum(front_posterior.values())
front_posterior = {k: v / total_fp for k, v in front_posterior.items()}
# 后区后验
back_posterior = {}
for n in range(1, bm + 1):
bs = self.model["phase1"]["back_freq"][str(n)]
bg = self.model["phase2"]["back_gamma"].get(str(n), {})
freq_score = bs["actual_rate"]
miss_score = min(1.0, bs["deviation"] + 0.5)
gamma_score = 0.5
if bg.get("alpha") and bg.get("p_value", 0) > 0.05:
gamma_score = min(1.0, bg["alpha"] / 3)
posterior = 0.5 * freq_score + 0.3 * miss_score + 0.2 * gamma_score
back_posterior[str(n)] = posterior
total_bp = sum(back_posterior.values())
back_posterior = {k: v / total_bp for k, v in back_posterior.items()}
self.model["phase3"] = {
"front_posterior": front_posterior,
"back_posterior": back_posterior,
"weights": {"w1_freq": 0.5, "w2_miss": 0.3, "w3_gamma": 0.2},
}
return self.model["phase3"]
# ================================================================
# 阶段4:K-Means聚类降维
# ================================================================
def phase4_clustering(self):
"""阶段4:K-Means聚类降维"""
fm = self.cfg["front_max"]
posterior = self.model["phase3"]["front_posterior"]
# 简化K-Means:按后验概率分3簇
scores = [(n, posterior[str(n)]) for n in range(1, fm + 1)]
scores.sort(key=lambda x: -x[1])
n_hot = max(5, fm // 4) # 热号簇
n_warm = max(5, fm // 3) # 温号簇
# 剩余为冷号簇
hot = [s[0] for s in scores[:n_hot]]
warm = [s[0] for s in scores[n_hot:n_hot + n_warm]]
cold = [s[0] for s in scores[n_hot + n_warm:]]
self.model["phase4"] = {
"hot": hot,
"warm": warm,
"cold": cold,
"n_hot": n_hot,
"n_warm": n_warm,
"n_cold": len(cold),
}
return self.model["phase4"]
# ================================================================
# 阶段5:ILP约束优化求解
# ================================================================
def phase5_ilp_solve(self, count=5):
"""阶段5:约束优化求解(遗传模拟)"""
fm = self.cfg["front_max"]
fc = self.cfg["front_count"]
bm = self.cfg["back_max"]
bc = self.cfg["back_count"]
constraints = self.cfg["constraints"]
# 构建历史已开组合(避免重复推荐)
historical = set()
for r in self.history:
historical.add(tuple(r["fronts"]))
# 综合得分
posterior = self.model["phase3"]["front_posterior"]
hot_set = set(self.model["phase4"]["hot"])
def front_score(combo):
return sum(posterior[str(n)] for n in combo)
def check_constraints(combo):
odd = sum(1 for n in combo if n % 2 == 1)
if not (constraints["odd_even"]["min_odd"] <= odd <= constraints["odd_even"]["max_odd"]):
return False
big = sum(1 for n in combo if n > fm / 2)
if not (constraints["big_small"]["min_big"] <= big <= constraints["big_small"]["max_big"]):
return False
s = sum(combo)
if not (constraints["sum_range"]["min_sum"] <= s <= constraints["sum_range"]["max_sum"]):
return False
consec = sum(1 for i in range(len(combo) - 1) if combo[i + 1] - combo[i] == 1)
if not (constraints["consecutive"]["min_consec"] <= consec <= constraints["consecutive"]["max_consec"]):
return False
hot = sum(1 for n in combo if n in hot_set)
if not (constraints["hot_cold"]["min_hot"] <= hot <= constraints["hot_cold"]["max_hot"]):
return False
# 三区分布
z1 = sum(1 for n in combo if 1 <= n <= fm // 3)
z2 = sum(1 for n in combo if fm // 3 < n <= 2 * fm // 3)
z3 = sum(1 for n in combo if n > 2 * fm // 3)
if not all(constraints["zone"]["min_zone"] <= z <= constraints["zone"]["max_zone"] for z in [z1, z2, z3]):
return False
if tuple(combo) in historical:
return False
return True
# 随机搜索 + 约束过滤
seed = hashlib.md5(("unified_%s_%s" % (self.lt, self.history[-1]["issue"])).encode()).hexdigest()
rng = random.Random(seed)
best = []
for _ in range(50000):
weights = np.array([posterior[str(n)] for n in range(1, fm + 1)])
weights = weights / weights.sum()
selected = np.random.choice(range(1, fm + 1), size=fc, replace=False, p=weights)
combo = sorted(selected.tolist())
if check_constraints(combo):
score = front_score(combo)
if len(best) < count * 3 or score > best[-1][0]:
best.append((score, combo))
best.sort(key=lambda x: -x[0])
if len(best) > count * 3:
best = best[:count * 3]
# 去重取top
seen = set()
results = []
for score, combo in best:
if tuple(combo) not in seen:
seen.add(tuple(combo))
results.append((score, combo))
if len(results) >= count:
break
# 后区推荐
back_posterior = self.model["phase3"]["back_posterior"]
back_ranked = sorted(back_posterior.items(), key=lambda x: -float(x[1]))[:bc + 2]
self.model["phase5"] = {
"front_combos": [(s, c) for s, c in results],
"back_pool": [int(b[0]) for b in back_ranked[:bc]],
"back_top3": [int(b[0]) for b in back_ranked[:3]],
}
return self.model["phase5"]
# ================================================================
# 阶段6:回测优化(≥50期验证 + 权重自动优化)
# ================================================================
def phase6_backtest(self, min_test_periods=51):
"""阶段6:回测验证 + 权重自动优化(≥50期验证)"""
if self.N < min_test_periods + 10:
print(f" [⚠️] 数据不足{min_test_periods + 10}期,当前{self.N}期,跳过回测")
self.model["phase6"] = {"backtest_passed": False, "reason": "数据不足"}
return self.model["phase6"]
# 用最近min_test_periods期做回测(减1得≥50期验证)
test_start = self.N - min_test_periods
test_data = self.history[test_start:]
fm = self.cfg["front_max"]
fc = self.cfg["front_count"]
bc = self.cfg["back_count"]
# 生成推荐组合(用回测窗口内的数据重新训练)
def generate_combo_from_window(window_data):
"""从窗口数据生成推荐组合"""
all_f = []
for r in window_data:
all_f.extend(r["fronts"])
freq = Counter(all_f)
scores = {n: freq.get(n, 0) / len(window_data) for n in range(1, fm + 1)}
total = sum(scores.values())
scores = {n: s / total for n, s in scores.items()}
# 按得分排序取top
ranked = sorted(scores.items(), key=lambda x: -x[1])
combo = sorted([r[0] for r in ranked[:fc]])
return combo
# 回测验证
hits = 0
total_test = len(test_data) - 1 # 减去第一期(无历史数据可比)
for i in range(1, len(test_data)):
# 用前i期数据生成推荐
window = test_data[:i]
combo = generate_combo_from_window(window)
actual = sorted(test_data[i]["fronts"])
# 计算命中数
hit_count = len(set(combo) & set(actual))
if hit_count >= fc // 2: # 命中≥一半算有效
hits += 1
hit_rate = hits / total_test if total_test > 0 else 0
# 权重优化:调整约束参数以提高命中率
constraints = self.cfg["constraints"].copy()
# 根据历史数据调整约束
recent = self.history[-30:]
odd_dist = Counter()
big_dist = Counter()
for r in recent:
odd = sum(1 for n in r["fronts"] if n % 2 == 1)
big = sum(1 for n in r["fronts"] if n > fm / 2)
odd_dist[odd] += 1
big_dist[big] += 1
# 取最频繁的奇偶比和大小比
most_common_odd = odd_dist.most_common(1)[0][0]
most_common_big = big_dist.most_common(1)[0][0]
constraints["odd_even"]["min_odd"] = max(1, most_common_odd - 1)
constraints["odd_even"]["max_odd"] = min(fc - 1, most_common_odd + 1)
constraints["big_small"]["min_big"] = max(1, most_common_big - 1)
constraints["big_small"]["max_big"] = min(fc - 1, most_common_big + 1)
self.model["phase6"] = {
"backtest_passed": True,
"test_periods": total_test,
"hit_rate": hit_rate,
"hits": hits,
"optimized_constraints": constraints,
}
self.cfg["constraints"] = constraints # 更新约束
return self.model["phase6"]
# ================================================================
# 完整运行
# ================================================================
def run_full(self, count=5):
"""运行完整6阶段"""
print(f"\n{'='*60}")
print(f" {self.cfg['name']} · 统一6阶段数学建模引擎 v2.0")
print(f" 前区{self.cfg['front_count']}个(1~{self.cfg['front_max']}) + 后区{self.cfg['back_count']}个(1~{self.cfg['back_max']})")
print(f" 数据量: {self.N}期 | 数据源: {self.cfg['source']}")
print(f"{'='*60}")
# 阶段1
print(f"\n [阶段1] 数据清洗与基础描述统计")
p1 = self.phase1_descriptive_stats()
front_top5 = sorted(p1["front_freq"].items(), key=lambda x: -x[1]["freq"])[:5]
front_str = " ".join("{0}({1}次)".format(n, v["freq"]) for n, v in front_top5)
print(" 前区TOP5热号: " + front_str)
back_top3 = sorted(p1["back_freq"].items(), key=lambda x: -x[1]["freq"])[:3]
back_str = " ".join("{0}({1}次)".format(n, v["freq"]) for n, v in back_top3)
print(" 后区TOP3热号: " + back_str)
# 阶段2
print(f"\n [阶段2] 伽马分布拟合")
p2 = self.phase2_distribution_fitting()
print(f" 前区拟合通过率: {p2['front_pass_rate']*100:.1f}% ({int(p2['front_pass_rate']*self.cfg['front_max'])}/{self.cfg['front_max']})")
print(f" 后区拟合通过率: {p2['back_pass_rate']*100:.1f}% ({int(p2['back_pass_rate']*self.cfg['back_max'])}/{self.cfg['back_max']})")
# 阶段3
print(f"\n [阶段3] 贝叶斯后验概率")
p3 = self.phase3_bayesian_posterior()
front_top5 = sorted(p3["front_posterior"].items(), key=lambda x: -float(x[1]))[:5]
front_str = " ".join("{0}({1:.4f})".format(n, v) for n, v in front_top5)
print(" 前区后验TOP5: " + front_str)
back_top3 = sorted(p3["back_posterior"].items(), key=lambda x: -float(x[1]))[:3]
back_str = " ".join("{0}({1:.4f})".format(n, v) for n, v in back_top3)
print(" 后区后验TOP3: " + back_str)
# 阶段4
print(f"\n [阶段4] K-Means聚类降维")
p4 = self.phase4_clustering()
hot_str = " ".join(str(n) for n in p4['hot'][:8])
warm_str = " ".join(str(n) for n in p4['warm'][:8])
cold_str = " ".join(str(n) for n in p4['cold'][:8])
print(" 热号簇({0}个): {1}...".format(p4['n_hot'], hot_str))
print(" 温号簇({0}个): {1}...".format(p4['n_warm'], warm_str))
print(" 冷号簇({0}个): {1}...".format(p4['n_cold'], cold_str))
# 阶段5
print(f"\n [阶段5] ILP约束优化求解")
p5 = self.phase5_ilp_solve(count)
print(f" 满足约束的最优组合: {len(p5['front_combos'])}组")
for i, (s, c) in enumerate(p5["front_combos"][:3]):
combo_str = " ".join("{:02d}".format(n) for n in c)
print(" 第{0}组: {1} 得分:{2:.4f}".format(i + 1, combo_str, s))
# 阶段6
print(f"\n [阶段6] 回测验证 + 权重优化")
p6 = self.phase6_backtest()
if p6["backtest_passed"]:
print(f" 回测期数: {p6['test_periods']}期")
print(f" 命中率: {p6['hit_rate']*100:.1f}% ({p6['hits']}/{p6['test_periods']})")
print(f" 优化后约束: 奇偶{p6['optimized_constraints']['odd_even']}, 大小{p6['optimized_constraints']['big_small']}")
else:
print(f" 跳过: {p6['reason']}")
# 保存模型
model_path = os.path.join(OUTPUT_DIR, self.cfg["model_file"])
json.dump(self.model, open(model_path, "w"), ensure_ascii=False, indent=2)
print(f"\n 模型参数已保存: {model_path}")
return self.model
# ================================================================
# 入口
# ================================================================
if __name__ == "__main__":
import numpy as np
lt = sys.argv[1] if len(sys.argv) > 1 else "ssq"
if lt not in LOTTERY_CONFIGS:
print(f"用法: python unified_engine.py [ssq|dlt]")
sys.exit(1)
engine = UnifiedEngine(lt)
engine.run_full()