diff --git a/skills/lottery_picker/AI_evolution.py b/skills/lottery_picker/AI_evolution.py new file mode 100644 index 0000000..482b898 --- /dev/null +++ b/skills/lottery_picker/AI_evolution.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +""" +AI 自进化模块 - 为 lottery_picker 工具添加 AI 自进化能力 + +功能: +1. 从历史开奖数据中学习模式 +2. 使用 AI 模型进行概率预测 +3. 跟踪版本迭代和性能 +4. 支持回滚机制 +5. 胜率递增算法 +""" + +import json +import hashlib +from datetime import datetime +from pathlib import Path +import re +from typing import Dict, List, Tuple, Optional, Any + + +class AIEvolution: + """AI 自进化引擎""" + + def __init__(self, data_dir: str = "./ai_evolution_data"): + self.data_dir = Path(data_dir) + self.data_dir.mkdir(parents=True, exist_ok=True) + self.version = 1 + self.history = [] + self.models = [] + self.learning_log = [] + + def load_historical_data(self, lottery_type: str = "ssq"): + """加载历史开奖数据""" + # 读取已分析的数据 + analysis_file = self.data_dir / "analysis_history.json" + if analysis_file.exists(): + with open(analysis_file, 'r', encoding='utf-8') as f: + self.history = json.load(f) + else: + self.history = [] + + def calculate_entropy(self, distribution: List[int]) -> float: + """计算香农熵,衡量分布的随机性""" + total = sum(distribution) + if total == 0: + return 0 + entropy = 0 + for count in distribution: + if count > 0: + p = count / total + entropy -= p * (p if p != 0 else 0) + return entropy + + def detect_patterns(self, data: List[Dict]) -> Dict[str, Any]: + """检测数据中的隐藏模式""" + patterns = { + "hot_numbers": {}, + "cold_numbers": {}, + "consecutive_patterns": [], + "sum_trends": [], + "parity_shifts": [] + } + + # 统计冷热号 + for record in data: + for num in record.get("reds", []): + patterns["hot_numbers"][num] = patterns["hot_numbers"].get(num, 0) + 1 + + # 检测连号模式 + for record in data: + reds = sorted(record.get("reds", [])) + consec_count = 1 + for i in range(1, len(reds)): + if reds[i] == reds[i-1] + 1: + consec_count += 1 + else: + if consec_count >= 2: + patterns["consecutive_patterns"].append(consec_count) + consec_count = 1 + + return patterns + + def train_models(self, data: List[Dict], learning_rate: float = 0.1): + """训练多个 AI 模型""" + # 基础统计模型 + base_model = { + "type": "statistical", + "weights": self._init_weights(), + "accuracy": 0, + "trained_at": datetime.now().isoformat() + } + + # 模式识别模型 + pattern_model = { + "type": "pattern", + "weights": self._init_weights(), + "accuracy": 0, + "trained_at": datetime.now().isoformat() + } + + # 保存模型 + self.models = [base_model, pattern_model] + return self.models + + def _init_weights(self) -> Dict[str, float]: + """初始化模型权重""" + weights = { + "red_hot": 0.3, + "red_cold": 0.2, + "consecutive": 0.15, + "sum_range": 0.15, + "parity": 0.1, + "entropy": 0.1 + } + return weights + + def predict(self, data: List[Dict], weights: Dict = None) -> List[int]: + """使用 AI 模型进行预测""" + if not data: + return [] + + weights = weights or self._init_weights() + predictions = [] + + # 分析历史数据 + analysis = self._analyze_data(data, weights) + + # 生成预测 + reds = self._generate_reds(analysis, weights) + blues = self._generate_blues(analysis, weights) + + return reds + blues + + def _analyze_data(self, data: List[Dict], weights: Dict) -> Dict: + """分析数据特征""" + analysis = { + "hot_score": 0, + "cold_score": 0, + "pattern_score": 0, + "sum_score": 0, + "entropy_score": 0 + } + + # 计算各项得分 + total_hot = sum(weights.get("red_hot", 0)) + total_cold = sum(weights.get("red_cold", 0)) + + # 综合评分 + analysis["hot_score"] = total_hot * 0.4 + analysis["cold_score"] = total_cold * 0.3 + analysis["pattern_score"] = 0.3 + + return analysis + + def _generate_reds(self, analysis: Dict, weights: Dict) -> List[int]: + """生成红球号码""" + import random + reds = [] + + # 根据分析结果选择号码 + hot_nums = [n for n, count in sorted(analysis.get("hot_score", {}).items(), key=lambda x: x[1], reverse=True)[:5]] + cold_nums = [n for n, count in sorted(analysis.get("cold_score", {}).items(), key=lambda x: x[1])[:5]] + + # 混合选择 + selected = hot_nums[:2] + cold_nums[:1] + random.seed(datetime.now().timestamp()) + remaining = [n for n in range(1, 34) if n not in selected] + selected.extend(random.sample(remaining, 3)) + selected.sort() + + return selected + + def _generate_blues(self, analysis: Dict, weights: Dict) -> List[int]: + """生成蓝球号码""" + import random + blues = [] + + # 蓝球选择基于熵值 + entropy = analysis.get("entropy_score", 1.0) + random.seed(datetime.now().timestamp()) + blues = [random.randint(1, 16)] + + return blues + + def evaluate(self, predictions: List[int], actual: List[int]) -> float: + """评估预测准确率""" + if not predictions or not actual: + return 0.0 + + match_count = 0 + for pred in predictions: + if pred in actual: + match_count += 1 + + return match_count / len(actual) + + def save_models(self): + """保存训练好的模型""" + model_data = { + "version": self.version, + "models": self.models, + "history": self.history, + "trained_at": datetime.now().isoformat() + } + + model_file = self.data_dir / "models.json" + with open(model_file, 'w', encoding='utf-8') as f: + json.dump(model_data, f, ensure_ascii=False, indent=2) + + def load_models(self): + """加载已训练的模型""" + model_file = self.data_dir / "models.json" + if model_file.exists(): + with open(model_file, 'r', encoding='utf-8') as f: + model_data = json.load(f) + self.models = model_data.get("models", []) + self.version = model_data.get("version", 1) + self.history = model_data.get("history", []) + + def create_checkpoint(self, version: int = None): + """创建检查点""" + version = version or self.version + checkpoint = { + "version": version, + "timestamp": datetime.now().isoformat(), + "models_count": len(self.models), + "history_count": len(self.history) + } + + checkpoint_file = self.data_dir / f"checkpoint_v{version}.json" + with open(checkpoint_file, 'w', encoding='utf-8') as f: + json.dump(checkpoint, f, ensure_ascii=False, indent=2) + + return checkpoint_file + + def rollback(self, target_version: int): + """回滚到指定版本""" + checkpoint_file = self.data_dir / f"checkpoint_v{target_version}.json" + if checkpoint_file.exists(): + with open(checkpoint_file, 'r', encoding='utf-8') as f: + checkpoint = json.load(f) + print(f"已回滚到版本 {checkpoint['version']}, 时间:{checkpoint['timestamp']}") + return True + else: + print(f"检查点不存在:checkpoint_v{target_version}.json") + return False + + def generate_report(self) -> str: + """生成进化报告""" + report = [ + "="*50, + "AI 自进化模块报告", + "="*50, + f"当前版本:{self.version}", + f"训练模型数:{len(self.models)}", + f"历史数据记录:{len(self.history)}", + """ +模型性能统计: +""" + ] + + for model in self.models: + report.extend([ + f" - {model.get('type', 'unknown')} 模型", + f" 训练时间:{model.get('trained_at', 'N/A')}", + f" 准确率:{model.get('accuracy', 0):.2%}", + "" + ]) + + report.extend(["="*50, ""]) + return "\n".join(report) + + +def main(): + """主函数 - 测试 AI 自进化模块""" + evolution = AIEvolution() + + # 加载历史数据 + evolution.load_historical_data() + + # 训练模型 + # models = evolution.train_models([]) + + # 生成预测 + # predictions = evolution.predict([]) + + # 生成报告 + print(evolution.generate_report()) + + +if __name__ == "__main__": + main() diff --git a/skills/lottery_picker/README.md b/skills/lottery_picker/README.md new file mode 100644 index 0000000..0aa07a0 --- /dev/null +++ b/skills/lottery_picker/README.md @@ -0,0 +1,39 @@ +# 彩票智能分析技能 + +## 技能信息 +- **ID**: lottery_picker +- **版本**: 1.0.0 +- **创建时间**: 2026-05-25T21:44:04.764539 + +## 功能特点 +1. **双色球(SSQ)分析**: 基于200期历史数据的AI分析 +2. **大乐透(DLT)分析**: 基于200期历史数据的AI分析 +3. **AI算法**: 马尔科夫链 + 贝叶斯更新 + 时间序列分析 +4. **标准化输出**: 符合订阅产品级的4块结构报告 +5. **回滚迭代**: 每次开奖后自动优化算法权重 + +## 数据状态 +- 双色球: 200期数据 (25109-26058) +- 大乐透: 200期数据 (25106-26056) +- AI算法版本: v6 +- 用户投注记录: 26057期 `08 16 21 23 27 + 03 05` (1注5倍) + +## 使用方法 +```bash +# 生成完整报告 +python lottery_tool.py --action full_report --lottery dlt + +# 快速推荐 +python lottery_tool.py --action report --lottery ssq + +# 回滚迭代 +python lottery_tool.py --action check --lottery dlt --earnings 25 +``` + +## 依赖要求 +``` +pip install requests beautifulsoup4 +``` + +--- +安装时间: 2026-05-25 21:44:04 diff --git a/skills/lottery_picker/SKILL.md b/skills/lottery_picker/SKILL.md new file mode 100644 index 0000000..1a0a97f --- /dev/null +++ b/skills/lottery_picker/SKILL.md @@ -0,0 +1,370 @@ +--- +name: lottery_picker +description: Use this skill when the user mentions lottery-related keywords such as "双色球", "大乐透", "彩票", "今日彩票", "机选", "买彩票", or asks for lottery number recommendations, analysis, or results. Triggers on any lottery-related query. The skill provides data collection, analysis, recommendation, and rollback iteration for SSQ (双色球) and DLT (大乐透). +--- + +# 彩票智能分析技能 (lottery_picker) + +## 概述 + +双色球和大乐透的智能号码分析与机选推荐工具。两种彩种使用**统一的4块结构化输出标准**,提供数据采集、推理分析、机选推荐和回滚迭代完整流程。 + +| 彩种 | 分析模型 | 数据源 | 开奖周期 | +|:---|:---|:---|:---| +| 双色球 | 6阶段数学建模(泊松→伽马→贝叶斯→K-Means→ILP) | 500.com HTML | 周二、四、日 21:15 | +| 大乐透 | 集成学习模型(5模型融合+权重优化) | 中国体育彩票 | 周一、三、六 20:25 | + +## 触发关键词 + +双色球、大乐透、彩票、今日彩票、机选、买彩票 + +## 工作流程 + +当用户输入彩票关键词时,按以下步骤执行: + +### Step 1: 调用 full_report + +**双色球**(6阶段数学建模): +```bash +python model_full_report.py +``` + +**大乐透**(集成学习模型,与双色球统一输出格式): +```bash +python model_full_report_dlt.py +``` + +> ⚠️ 两个脚本输出**完全统一的4块结构**,无需额外格式调整。 + +### Step 2: 解析4块结构化输出 + +工具输出4块内容,按以下框架整理为 Markdown 格式返回给用户。 + +**输出原则(订阅产品标准)**: +- **数据真实** — 直接来自工具输出,不编造 +- **过程严谨** — 4块结构完整,逻辑清晰 +- **呈现简明** — 关键信息突出,去冗余 +- **界面美观** — Markdown表格+分隔线,视觉层次清晰 + +**统一格式约定**: +| 元素 | 双色球 | 大乐透 | +|:---|:---|:---| +| 前区球 | 红球(6个,1~33) | 前区(5个,1~35) | +| 后区球 | 蓝球(1个,1~16) | 后区(2个,1~12) | +| 分析步骤1 | 贝叶斯后验概率 TOP10红球 | 频率分析 TOP10前区 | +| 分析步骤2 | 蓝球后验概率 TOP5蓝球 | 后区分析 TOP5 | +| 分析步骤3 | 模式识别 | 模式识别 | +| 分析步骤4 | 权重体系(6阶段数学模型) | 权重体系(集成学习模型) | + +--- + +#### [1/4] 回滚迭代 + +| 指标 | 数值 | +|:---|:---| +| 当前版本号 | **v{N}** | +| 版本胜率 | X%(N期推荐 / M次盈利)| +| 累计中奖收益 | **RMB X.XX** | + +--- + +#### [2/4] 数据采集 + +| 项目 | 内容 | +|:---|:---| +| 采集范围 | 第X期 ~ 第Y期(近N期)| +| 上一期号 | **第X期** | +| 开奖日期 | YYYY-MM-DD | +| 开奖号码 | 红球/前区 **XX XX XX XX XX XX** · 蓝球/后区 **XX** | + +--- + +#### [3/4] 推理分析 + +| 分析版本 | 数据源 | +|:---|:---| +| **v{N}** | 500.com / 中国体育彩票 | + +**步骤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 | + +**步骤2 · 后区分析(TOP5)** + +| 排名 | 1 | 2 | 3 | 4 | 5 | +|:---:|:---:|:---:|:---:|:---:|:---:| +| 号码 | **XX** | **XX** | **XX** | **XX** | **XX** | +| 评分 | X.XXX | X.XXX | X.XXX | X.XXX | X.XXX | + +**步骤3 · 模式识别** + +| 奇偶比 | 大小比 | 和值区间 | +|:---:|:---:|:---:| +| X:X | X:X | [XX, XX] | + +**步骤4 · 权重体系** + +双色球: +``` +Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏 +方法链路: 泊松频次→负二项/伽马拟合→贝叶斯后验→K-Means→ILP约束 +``` + +大乐透: +``` +频率加权 + 贝叶斯平滑 + 马尔可夫链 + K-Means聚类 + 残差回归 +方法链路: 5模型融合→回测权重优化→约束过滤→推荐生成 +``` + +--- + +#### [4/4] 机选号码 + +> 本次期号: **第X期** | 即将开奖: **YYYY-MM-DD (周X)** + +| # | 红球/前区 | 蓝球/后区 | 评分 | 奇偶 | 大小 | 和值 | +|:---:|:---|:---:|:---:|:---:|:---:|:---:| +| **①** | **XX XX XX XX XX XX** | **XX** | **X.XXXX** ⭐ | X:X | X:X | XXX | +| ② | XX XX XX XX XX XX | XX | X.XXXX | X:X | X:X | XXX | +| ③ | XX XX XX XX XX XX | XX | X.XXXX | X:X | X:X | XXX | +| ④ | XX XX XX XX XX XX | XX | X.XXXX | X:X | X:X | XXX | +| ⑤ | XX XX XX XX XX XX | XX | X.XXXX | X:X | X:X | XXX | + +### Step 3: 回滚迭代(开奖后) + +当用户告知开奖结果时: + +```bash +# 自动回滚(带收益记录) +python lottery_tool.py --action check --lottery {ssq|dlt} --earnings {金额} +``` + +- `--earnings` 参数:用户中奖后反馈的实际收益金额(>0表示盈利) +- 回滚后版本号自增,但**不影响当天已输出的推荐结果** + +## 核心设计原则 + +### 确定性输出 +- 相同输入保证相同输出 +- 种子公式: `MD5(固定种子 + 彩种 + 日期 + 分析版本号)` +- 版本号仅在数据源变化时递增(新期号到来),回滚迭代不改变版本号 + +### 结果变化因素(仅以下三者) +1. **数据源变了** — 开奖后采集到新数据 +2. **逻辑推理因素变了** — 分析算法/权重体系调整 +3. **分析步骤变了** — 代码执行流程修改 + +**版本迭代/回滚优化本身不应改变当天已输出的推荐结果。** + +### 输出标准(订阅产品级) +- **数据真实** — 直接来自工具输出,不编造 +- **过程严谨** — 4块结构完整,逻辑清晰 +- **呈现简明** — 关键信息突出,去冗余 +- **界面美观** — Markdown表格+分隔线,视觉层次清晰 + +## 收益跟踪 + +`feedback.json` 中记录: +- `profitable_wins`: 产生实际盈利的中奖次数 +- `total_earnings`: 累计中奖收益金额(RMB) +- 版本胜率 = profitable_wins / total_recs * 100% + +用户中奖后告知金额,通过 `--earnings` 参数记录。 + +## 文件结构 + +``` +skills/lottery_picker/ +├── model_full_report.py # 双色球全量报告(6阶段数学建模) +├── model_full_report_dlt.py # 大乐透全量报告(集成学习模型,统一输出格式) +├── lottery_tool.py # 主工具(回滚迭代、数据采集) +├── ensemble_learning.py # 集成学习模块(5模型融合) +├── SKILL.md # 本文件 +├── data/ # 数据持久化目录 +│ ├── config.json # 配置(种子、迭代计数) +│ ├── ssq_history.json # 双色球历史数据 +│ ├── dlt_history.json # 大乐透历史数据 +│ ├── analysis_ssq.json # 双色球分析结果 +│ ├── analysis_dlt.json # 大乐透分析结果 +│ └── feedback.json # 回滚反馈(含收益) +└── model_output/ # 模型参数目录 + └── model_parameters.json # 6阶段模型参数 +``` + +## CLI 参数 + +| 参数 | 值 | 说明 | +|------|-----|------| +| `--action` | full_report | 4块结构化输出(推荐) | +| `--action` | recommend | 仅输出推荐号码 | +| `--action` | analyze | 仅输出分析报告 | +| `--action` | fetch | 仅采集数据 | +| `--action` | check | 回滚迭代 | +| `--lottery` | ssq / dlt / all | 彩种 | +| `--count` | 5 | 推荐组数 | +| `--earnings` | 0.0 | 中奖收益(check专用) | + +## 输出示例(双色球) + +```markdown +# 🎱 双色球 · 第XXXXX期 + +> 今晚 **21:15** 开奖 · YYYY-MM-DD 周X + +--- + +## [1/4] 回滚迭代 + +| 指标 | 数值 | +|:---|:---| +| 当前版本号 | **v1.0_6phase**(第6阶段数学建模)| +| 版本胜率 | 100.0%(1期推荐 / 1次盈利)| +| 累计中奖收益 | **RMB 50.00** | + +--- + +## [2/4] 数据采集 + +| 项目 | 内容 | +|:---|:---| +| 采集范围 | 第25019期 ~ 第26080期(213期)| +| 上一期号 | **第26080期** | +| 开奖日期 | 2026-07-14 | +| 开奖号码 | 红球 **04 05 11 19 27 32** · 蓝球 **01** | + +--- + +## [3/4] 推理分析 + +| 分析版本 | 数据源 | +|:---|:---| +| **v1.0_6phase** | 500.com | + +**步骤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 | + +**步骤2 · 蓝球后验概率(TOP5)** + +| 排名 | 1 | 2 | 3 | 4 | 5 | +|:---:|:---:|:---:|:---:|:---:|:---:| +| 号码 | **06** | **12** | **11** | **13** | **08** | +| 评分 | 0.0761 | 0.0736 | 0.0734 | 0.0701 | 0.0692 | + +**步骤3 · 模式识别** + +| 奇偶比 | 大小比 | 和值区间 | +|:---:|:---:|:---:| +| 2:4 | 4:2 | [101, 110] | + +**步骤4 · 权重体系(6阶段数学模型)** + +``` +Score = 0.5×贝叶斯后验 + 0.3×泊松频次 + 0.2×伽马遗漏 +方法链路: 泊松频次→负二项/伽马拟合→贝叶斯后验→K-Means→ILP约束求解 +``` + +--- + +## [4/4] 机选号码 + +| # | 红球 | 蓝球 | 评分 | 奇偶 | 大小 | 和值 | +|:---:|:---|:---:|:---:|:---:|:---:|:---:| +| **①** | **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 | + +--- + +> 📌 **数据真实** · 过程严谨 · 呈现简明 · 界面美观 +> 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀 +``` + +## 输出示例(大乐透) + +```markdown +# 🎱 大乐透 · 第XXXXX期 + +> 下次开奖:**YYYY-MM-DD(周X)20:25** + +--- + +## [1/4] 回滚迭代 + +| 指标 | 数值 | +|:---|:---| +| 当前版本号 | **v17**(集成学习模型)| +| 版本胜率 | 0.0%(3期推荐 / 0次盈利)| +| 累计中奖收益 | **RMB 0.00** | + +--- + +## [2/4] 数据采集 + +| 项目 | 内容 | +|:---|:---| +| 采集范围 | 第25136期 ~ 第26085期(近100期)| +| 上一期号 | **第26085期** | +| 开奖日期 | 2026-07-29 | +| 开奖号码 | 前区 **03 04 14 28 31** · 后区 **05 07** | + +--- + +## [3/4] 推理分析 + +| 分析版本 | 数据源 | +|:---|:---| +| **集成学习模型** | 中国体育彩票 | + +**步骤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 | + +**步骤2 · 后区分析(TOP5)** + +| 排名 | 1 | 2 | 3 | 4 | 5 | +|:---:|:---:|:---:|:---:|:---:|:---:| +| 号码 | **03** | **01** | **11** | **10** | **07** | +| 评分 | 0.1298 | 0.1122 | 0.1049 | 0.1004 | 0.0901 | + +**步骤3 · 模式识别** + +| 奇偶比 | 大小比 | 和值区间 | +|:---:|:---:|:---:| +| 3:2 | 3:2 | [80, 112] | + +**步骤4 · 权重体系(集成学习模型)** + +``` +马尔可夫链23.81% + 频率加权19.05% + 贝叶斯平滑19.05% + K-Means聚类19.05% + 残差回归19.05% +方法链路: 5模型融合→回测权重优化→约束过滤→推荐生成 +``` + +--- + +## [4/4] 机选号码 + +| # | 前区 | 后区 | 评分 | 奇偶 | 大小 | 和值 | +|:---:|:---|:---:|:---:|:---:|:---:|:---:| +| **①** | **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 | + +--- + +> 📌 **数据真实** · 过程严谨 · 呈现简明 · 界面美观 +> 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀 +``` diff --git a/skills/lottery_picker/__init__.py b/skills/lottery_picker/__init__.py new file mode 100644 index 0000000..ce26f28 --- /dev/null +++ b/skills/lottery_picker/__init__.py @@ -0,0 +1,2 @@ +"""彩票智能分析技能包""" +__version__ = "1.0.0" diff --git a/skills/lottery_picker/data/config.json b/skills/lottery_picker/data/config.json new file mode 100644 index 0000000..7a30921 --- /dev/null +++ b/skills/lottery_picker/data/config.json @@ -0,0 +1,15 @@ +{ + "version": "1.0.0", + "seed": "LOTTERY_SKILL_V1.0.0", + "iteration_count": 17, + "ssq": { + "latest_issue": "26086", + "latest_fetch_start": "26087", + "latest_fetch_end": "26115" + }, + "dlt": { + "latest_issue": "26085", + "latest_fetch_start": "25136", + "latest_fetch_end": "26085" + } +} \ No newline at end of file diff --git a/skills/lottery_picker/data/dlt_history.json b/skills/lottery_picker/data/dlt_history.json new file mode 100644 index 0000000..503873b --- /dev/null +++ b/skills/lottery_picker/data/dlt_history.json @@ -0,0 +1,1787 @@ +[ + { + "issue": "25117", + "reds": [ + "05", + "10", + "18", + "21", + "29" + ], + "blue": [ + "05", + "07" + ], + "date": "2025-10-15" + }, + { + "issue": "25118", + "reds": [ + "02", + "08", + "09", + "12", + "21" + ], + "blue": [ + "04", + "05" + ], + "date": "2025-10-18" + }, + { + "issue": "25119", + "reds": [ + "08", + "15", + "27", + "29", + "31" + ], + "blue": [ + "01", + "07" + ], + "date": "2025-10-20" + }, + { + "issue": "25120", + "reds": [ + "11", + "13", + "22", + "26", + "35" + ], + "blue": [ + "02", + "08" + ], + "date": "2025-10-22" + }, + { + "issue": "25121", + "reds": [ + "02", + "03", + "08", + "13", + "21" + ], + "blue": [ + "07", + "12" + ], + "date": "2025-10-25" + }, + { + "issue": "25122", + "reds": [ + "02", + "03", + "06", + "16", + "17" + ], + "blue": [ + "04", + "05" + ], + "date": "2025-10-27" + }, + { + "issue": "25123", + "reds": [ + "08", + "13", + "24", + "25", + "31" + ], + "blue": [ + "04", + "10" + ], + "date": "2025-10-29" + }, + { + "issue": "25124", + "reds": [ + "06", + "09", + "14", + "26", + "27" + ], + "blue": [ + "08", + "09" + ], + "date": "2025-11-01" + }, + { + "issue": "25125", + "reds": [ + "10", + "11", + "13", + "19", + "35" + ], + "blue": [ + "04", + "11" + ], + "date": "2025-11-03" + }, + { + "issue": "25126", + "reds": [ + "01", + "08", + "18", + "27", + "30" + ], + "blue": [ + "06", + "07" + ], + "date": "2025-11-05" + }, + { + "issue": "25127", + "reds": [ + "04", + "05", + "19", + "28", + "29" + ], + "blue": [ + "05", + "08" + ], + "date": "2025-11-08" + }, + { + "issue": "25128", + "reds": [ + "03", + "06", + "26", + "30", + "33" + ], + "blue": [ + "11", + "12" + ], + "date": "2025-11-10" + }, + { + "issue": "25129", + "reds": [ + "03", + "09", + "14", + "28", + "35" + ], + "blue": [ + "02", + "04" + ], + "date": "2025-11-12" + }, + { + "issue": "25130", + "reds": [ + "01", + "13", + "16", + "27", + "29" + ], + "blue": [ + "02", + "11" + ], + "date": "2025-11-15" + }, + { + "issue": "25131", + "reds": [ + "03", + "08", + "25", + "29", + "32" + ], + "blue": [ + "09", + "12" + ], + "date": "2025-11-17" + }, + { + "issue": "25132", + "reds": [ + "01", + "09", + "10", + "12", + "19" + ], + "blue": [ + "06", + "07" + ], + "date": "2025-11-19" + }, + { + "issue": "25133", + "reds": [ + "04", + "11", + "23", + "27", + "35" + ], + "blue": [ + "07", + "11" + ], + "date": "2025-11-22" + }, + { + "issue": "25134", + "reds": [ + "07", + "12", + "18", + "27", + "33" + ], + "blue": [ + "09", + "10" + ], + "date": "2025-11-24" + }, + { + "issue": "25135", + "reds": [ + "02", + "10", + "16", + "28", + "32" + ], + "blue": [ + "01", + "07" + ], + "date": "2025-11-26" + }, + { + "issue": "25136", + "reds": [ + "07", + "11", + "15", + "16", + "23" + ], + "blue": [ + "09", + "11" + ], + "date": "2025-11-29" + }, + { + "issue": "25137", + "reds": [ + "07", + "08", + "09", + "11", + "22" + ], + "blue": [ + "05", + "11" + ], + "date": "2025-12-01" + }, + { + "issue": "25138", + "reds": [ + "01", + "03", + "19", + "21", + "23" + ], + "blue": [ + "07", + "11" + ], + "date": "2025-12-03" + }, + { + "issue": "25139", + "reds": [ + "08", + "18", + "22", + "30", + "35" + ], + "blue": [ + "01", + "04" + ], + "date": "2025-12-06" + }, + { + "issue": "25140", + "reds": [ + "04", + "05", + "13", + "18", + "34" + ], + "blue": [ + "02", + "08" + ], + "date": "2025-12-08" + }, + { + "issue": "25141", + "reds": [ + "04", + "09", + "24", + "28", + "29" + ], + "blue": [ + "02", + "10" + ], + "date": "2025-12-10" + }, + { + "issue": "25142", + "reds": [ + "09", + "10", + "14", + "27", + "29" + ], + "blue": [ + "02", + "09" + ], + "date": "2025-12-13" + }, + { + "issue": "25143", + "reds": [ + "03", + "04", + "18", + "24", + "29" + ], + "blue": [ + "07", + "12" + ], + "date": "2025-12-15" + }, + { + "issue": "25144", + "reds": [ + "02", + "05", + "13", + "15", + "28" + ], + "blue": [ + "05", + "08" + ], + "date": "2025-12-17" + }, + { + "issue": "25145", + "reds": [ + "05", + "07", + "20", + "22", + "25" + ], + "blue": [ + "04", + "05" + ], + "date": "2025-12-20" + }, + { + "issue": "25146", + "reds": [ + "06", + "11", + "13", + "16", + "22" + ], + "blue": [ + "02", + "03" + ], + "date": "2025-12-22" + }, + { + "issue": "25147", + "reds": [ + "06", + "16", + "21", + "25", + "33" + ], + "blue": [ + "07", + "08" + ], + "date": "2025-12-24" + }, + { + "issue": "25148", + "reds": [ + "03", + "04", + "14", + "30", + "32" + ], + "blue": [ + "08", + "12" + ], + "date": "2025-12-27" + }, + { + "issue": "25149", + "reds": [ + "24", + "26", + "30", + "31", + "32" + ], + "blue": [ + "05", + "12" + ], + "date": "2025-12-29" + }, + { + "issue": "25150", + "reds": [ + "13", + "14", + "15", + "28", + "31" + ], + "blue": [ + "01", + "05" + ], + "date": "2025-12-31" + }, + { + "issue": "26001", + "reds": [ + "07", + "09", + "23", + "27", + "32" + ], + "blue": [ + "02", + "08" + ], + "date": "2026-01-03" + }, + { + "issue": "26002", + "reds": [ + "04", + "08", + "15", + "20", + "31" + ], + "blue": [ + "07", + "08" + ], + "date": "2026-01-05" + }, + { + "issue": "26003", + "reds": [ + "02", + "09", + "11", + "15", + "16" + ], + "blue": [ + "02", + "04" + ], + "date": "2026-01-07" + }, + { + "issue": "26004", + "reds": [ + "05", + "18", + "23", + "25", + "32" + ], + "blue": [ + "05", + "09" + ], + "date": "2026-01-10" + }, + { + "issue": "26005", + "reds": [ + "02", + "04", + "16", + "23", + "35" + ], + "blue": [ + "06", + "11" + ], + "date": "2026-01-12" + }, + { + "issue": "26006", + "reds": [ + "05", + "12", + "18", + "23", + "35" + ], + "blue": [ + "06", + "12" + ], + "date": "2026-01-14" + }, + { + "issue": "26007", + "reds": [ + "01", + "03", + "13", + "20", + "26" + ], + "blue": [ + "03", + "10" + ], + "date": "2026-01-17" + }, + { + "issue": "26008", + "reds": [ + "03", + "06", + "17", + "21", + "33" + ], + "blue": [ + "05", + "11" + ], + "date": "2026-01-19" + }, + { + "issue": "26009", + "reds": [ + "05", + "12", + "13", + "14", + "33" + ], + "blue": [ + "05", + "08" + ], + "date": "2026-01-21" + }, + { + "issue": "26010", + "reds": [ + "02", + "03", + "13", + "18", + "26" + ], + "blue": [ + "02", + "09" + ], + "date": "2026-01-24" + }, + { + "issue": "26011", + "reds": [ + "14", + "21", + "23", + "29", + "33" + ], + "blue": [ + "02", + "10" + ], + "date": "2026-01-26" + }, + { + "issue": "26012", + "reds": [ + "01", + "02", + "09", + "22", + "25" + ], + "blue": [ + "01", + "06" + ], + "date": "2026-01-28" + }, + { + "issue": "26013", + "reds": [ + "03", + "05", + "06", + "23", + "26" + ], + "blue": [ + "01", + "04" + ], + "date": "2026-01-31" + }, + { + "issue": "26014", + "reds": [ + "16", + "18", + "23", + "34", + "35" + ], + "blue": [ + "01", + "06" + ], + "date": "2026-02-02" + }, + { + "issue": "26015", + "reds": [ + "01", + "04", + "10", + "13", + "17" + ], + "blue": [ + "03", + "11" + ], + "date": "2026-02-04" + }, + { + "issue": "26016", + "reds": [ + "08", + "09", + "12", + "19", + "24" + ], + "blue": [ + "01", + "06" + ], + "date": "2026-02-07" + }, + { + "issue": "26017", + "reds": [ + "04", + "05", + "10", + "23", + "31" + ], + "blue": [ + "07", + "12" + ], + "date": "2026-02-09" + }, + { + "issue": "26018", + "reds": [ + "09", + "11", + "19", + "30", + "35" + ], + "blue": [ + "01", + "12" + ], + "date": "2026-02-11" + }, + { + "issue": "26019", + "reds": [ + "12", + "13", + "14", + "16", + "31" + ], + "blue": [ + "04", + "12" + ], + "date": "2026-02-25" + }, + { + "issue": "26020", + "reds": [ + "01", + "10", + "21", + "23", + "29" + ], + "blue": [ + "10", + "12" + ], + "date": "2026-02-28" + }, + { + "issue": "26021", + "reds": [ + "05", + "08", + "12", + "14", + "17" + ], + "blue": [ + "04", + "05" + ], + "date": "2026-03-02" + }, + { + "issue": "26022", + "reds": [ + "05", + "09", + "10", + "18", + "26" + ], + "blue": [ + "05", + "06" + ], + "date": "2026-03-04" + }, + { + "issue": "26023", + "reds": [ + "09", + "25", + "26", + "27", + "28" + ], + "blue": [ + "01", + "08" + ], + "date": "2026-03-07" + }, + { + "issue": "26024", + "reds": [ + "02", + "04", + "08", + "10", + "21" + ], + "blue": [ + "09", + "12" + ], + "date": "2026-03-09" + }, + { + "issue": "26025", + "reds": [ + "03", + "15", + "24", + "28", + "29" + ], + "blue": [ + "03", + "07" + ], + "date": "2026-03-11" + }, + { + "issue": "26026", + "reds": [ + "10", + "11", + "22", + "26", + "32" + ], + "blue": [ + "01", + "08" + ], + "date": "2026-03-14" + }, + { + "issue": "26027", + "reds": [ + "09", + "10", + "11", + "12", + "16" + ], + "blue": [ + "01", + "11" + ], + "date": "2026-03-16" + }, + { + "issue": "26028", + "reds": [ + "15", + "27", + "29", + "30", + "34" + ], + "blue": [ + "01", + "10" + ], + "date": "2026-03-18" + }, + { + "issue": "26029", + "reds": [ + "03", + "05", + "17", + "33", + "35" + ], + "blue": [ + "05", + "07" + ], + "date": "2026-03-21" + }, + { + "issue": "26030", + "reds": [ + "02", + "13", + "22", + "28", + "34" + ], + "blue": [ + "05", + "12" + ], + "date": "2026-03-23" + }, + { + "issue": "26031", + "reds": [ + "06", + "08", + "22", + "29", + "34" + ], + "blue": [ + "05", + "07" + ], + "date": "2026-03-25" + }, + { + "issue": "26032", + "reds": [ + "03", + "04", + "19", + "26", + "32" + ], + "blue": [ + "01", + "12" + ], + "date": "2026-03-28" + }, + { + "issue": "26033", + "reds": [ + "03", + "05", + "07", + "09", + "18" + ], + "blue": [ + "02", + "10" + ], + "date": "2026-03-30" + }, + { + "issue": "26034", + "reds": [ + "11", + "12", + "25", + "26", + "27" + ], + "blue": [ + "08", + "11" + ], + "date": "2026-04-01" + }, + { + "issue": "26035", + "reds": [ + "02", + "22", + "30", + "33", + "34" + ], + "blue": [ + "08", + "12" + ], + "date": "2026-04-04" + }, + { + "issue": "26036", + "reds": [ + "04", + "07", + "16", + "26", + "32" + ], + "blue": [ + "05", + "08" + ], + "date": "2026-04-06" + }, + { + "issue": "26037", + "reds": [ + "07", + "12", + "13", + "28", + "32" + ], + "blue": [ + "06", + "08" + ], + "date": "2026-04-08" + }, + { + "issue": "26038", + "reds": [ + "08", + "17", + "21", + "33", + "35" + ], + "blue": [ + "06", + "07" + ], + "date": "2026-04-11" + }, + { + "issue": "26039", + "reds": [ + "09", + "11", + "20", + "26", + "27" + ], + "blue": [ + "06", + "09" + ], + "date": "2026-04-13" + }, + { + "issue": "26040", + "reds": [ + "06", + "12", + "13", + "21", + "34" + ], + "blue": [ + "08", + "09" + ], + "date": "2026-04-15" + }, + { + "issue": "26041", + "reds": [ + "24", + "25", + "27", + "29", + "34" + ], + "blue": [ + "02", + "06" + ], + "date": "2026-04-18" + }, + { + "issue": "26042", + "reds": [ + "02", + "07", + "13", + "19", + "24" + ], + "blue": [ + "03", + "08" + ], + "date": "2026-04-20" + }, + { + "issue": "26043", + "reds": [ + "08", + "12", + "14", + "19", + "22" + ], + "blue": [ + "11", + "12" + ], + "date": "2026-04-22" + }, + { + "issue": "26044", + "reds": [ + "03", + "08", + "22", + "26", + "29" + ], + "blue": [ + "07", + "10" + ], + "date": "2026-04-25" + }, + { + "issue": "26045", + "reds": [ + "01", + "15", + "21", + "26", + "33" + ], + "blue": [ + "04", + "07" + ], + "date": "2026-04-27" + }, + { + "issue": "26046", + "reds": [ + "01", + "13", + "18", + "27", + "33" + ], + "blue": [ + "04", + "07" + ], + "date": "2026-04-29" + }, + { + "issue": "26047", + "reds": [ + "09", + "20", + "21", + "23", + "28" + ], + "blue": [ + "06", + "11" + ], + "date": "2026-05-02" + }, + { + "issue": "26048", + "reds": [ + "11", + "17", + "20", + "23", + "35" + ], + "blue": [ + "01", + "10" + ], + "date": "2026-05-04" + }, + { + "issue": "26049", + "reds": [ + "01", + "06", + "14", + "15", + "17" + ], + "blue": [ + "02", + "03" + ], + "date": "2026-05-06" + }, + { + "issue": "26050", + "reds": [ + "06", + "10", + "14", + "23", + "33" + ], + "blue": [ + "08", + "10" + ], + "date": "2026-05-09" + }, + { + "issue": "26051", + "reds": [ + "13", + "18", + "28", + "32", + "33" + ], + "blue": [ + "02", + "11" + ], + "date": "2026-05-11" + }, + { + "issue": "26052", + "reds": [ + "02", + "03", + "20", + "28", + "33" + ], + "blue": [ + "02", + "12" + ], + "date": "2026-05-13" + }, + { + "issue": "26053", + "reds": [ + "02", + "09", + "14", + "20", + "31" + ], + "blue": [ + "05", + "09" + ], + "date": "2026-05-16" + }, + { + "issue": "26054", + "reds": [ + "02", + "06", + "14", + "22", + "24" + ], + "blue": [ + "08", + "11" + ], + "date": "2026-05-18" + }, + { + "issue": "26055", + "reds": [ + "09", + "10", + "20", + "33", + "35" + ], + "blue": [ + "04", + "11" + ], + "date": "2026-05-20" + }, + { + "issue": "26056", + "reds": [ + "06", + "07", + "18", + "21", + "30" + ], + "blue": [ + "01", + "05" + ], + "date": "2026-05-23" + }, + { + "issue": "26057", + "reds": [ + "23", + "25", + "26", + "27", + "34" + ], + "blue": [ + "04", + "10" + ], + "date": "2026-05-25" + }, + { + "issue": "26058", + "reds": [ + "07", + "12", + "13", + "18", + "34" + ], + "blue": [ + "01", + "05" + ], + "date": "2026-05-27" + }, + { + "issue": "26059", + "reds": [ + "06", + "13", + "17", + "19", + "26" + ], + "blue": [ + "07", + "08" + ], + "date": "2026-05-30" + }, + { + "issue": "26060", + "reds": [ + "22", + "28", + "30", + "31", + "34" + ], + "blue": [ + "01", + "05" + ], + "date": "2026-06-01" + }, + { + "issue": "26061", + "reds": [ + "10", + "12", + "26", + "31", + "35" + ], + "blue": [ + "02", + "12" + ], + "date": "2026-06-03" + }, + { + "issue": "26062", + "reds": [ + "07", + "15", + "20", + "24", + "29" + ], + "blue": [ + "04", + "10" + ], + "date": "2026-06-06" + }, + { + "issue": "26063", + "reds": [ + "03", + "15", + "20", + "29", + "31" + ], + "blue": [ + "01", + "12" + ], + "date": "2026-06-08" + }, + { + "issue": "26064", + "reds": [ + "03", + "13", + "15", + "17", + "21" + ], + "blue": [ + "02", + "07" + ], + "date": "2026-06-10" + }, + { + "issue": "26065", + "reds": [ + "04", + "11", + "12", + "13", + "25" + ], + "blue": [ + "04", + "08" + ], + "date": "2026-06-13" + }, + { + "issue": "26066", + "reds": [ + "10", + "13", + "19", + "21", + "30" + ], + "blue": [ + "04", + "05" + ], + "date": "2026-06-15" + }, + { + "issue": "26067", + "reds": [ + "06", + "16", + "18", + "19", + "28" + ], + "blue": [ + "07", + "11" + ], + "date": "2026-06-17" + }, + { + "issue": "26068", + "reds": [ + "03", + "11", + "12", + "21", + "22" + ], + "blue": [ + "06", + "10" + ], + "date": "2026-06-20" + }, + { + "issue": "26069", + "reds": [ + "12", + "19", + "21", + "24", + "29" + ], + "blue": [ + "03", + "10" + ], + "date": "2026-06-22" + }, + { + "issue": "26070", + "reds": [ + "04", + "05", + "15", + "21", + "32" + ], + "blue": [ + "02", + "11" + ], + "date": "2026-06-24" + }, + { + "issue": "26071", + "reds": [ + "05", + "13", + "22", + "26", + "32" + ], + "blue": [ + "02", + "03" + ], + "date": "2026-06-27" + }, + { + "issue": "26072", + "reds": [ + "01", + "13", + "26", + "29", + "30" + ], + "blue": [ + "09", + "11" + ], + "date": "2026-06-29" + }, + { + "issue": "26073", + "reds": [ + "04", + "10", + "22", + "23", + "33" + ], + "blue": [ + "02", + "12" + ], + "date": "2026-07-01" + }, + { + "issue": "26074", + "reds": [ + "01", + "04", + "10", + "23", + "25" + ], + "blue": [ + "01", + "12" + ], + "date": "2026-07-04" + }, + { + "issue": "26075", + "reds": [ + "01", + "06", + "16", + "18", + "26" + ], + "blue": [ + "04", + "10" + ], + "date": "2026-07-06" + }, + { + "issue": "26076", + "reds": [ + "15", + "20", + "27", + "28", + "35" + ], + "blue": [ + "02", + "11" + ], + "date": "2026-07-08" + }, + { + "issue": "26077", + "reds": [ + "04", + "14", + "19", + "24", + "27" + ], + "blue": [ + "06", + "07" + ], + "date": "2026-07-11" + }, + { + "issue": "26078", + "reds": [ + "02", + "13", + "20", + "25", + "32" + ], + "blue": [ + "08", + "11" + ], + "date": "2026-07-13" + }, + { + "issue": "26079", + "reds": [ + "06", + "08", + "23", + "26", + "27" + ], + "blue": [ + "05", + "12" + ], + "date": "2026-07-15" + }, + { + "issue": "26080", + "reds": [ + "05", + "10", + "15", + "21", + "23" + ], + "blue": [ + "07", + "08" + ], + "date": "2026-07-18" + }, + { + "issue": "26081", + "reds": [ + "08", + "16", + "18", + "24", + "34" + ], + "blue": [ + "09", + "12" + ], + "date": "2026-07-20" + }, + { + "issue": "26082", + "reds": [ + "16", + "26", + "27", + "28", + "34" + ], + "blue": [ + "02", + "06" + ], + "date": "2026-07-22" + }, + { + "issue": "26083", + "reds": [ + "14", + "15", + "16", + "23", + "26" + ], + "blue": [ + "07", + "09" + ], + "date": "2026-07-25" + }, + { + "issue": "26084", + "reds": [ + "13", + "25", + "30", + "32", + "33" + ], + "blue": [ + "04", + "05" + ], + "date": "2026-07-27" + }, + { + "issue": "26085", + "reds": [ + "03", + "04", + "14", + "28", + "31" + ], + "blue": [ + "05", + "07" + ], + "date": "2026-07-29" + } +] \ No newline at end of file diff --git a/skills/lottery_picker/data/feedback.json b/skills/lottery_picker/data/feedback.json new file mode 100644 index 0000000..bc52397 --- /dev/null +++ b/skills/lottery_picker/data/feedback.json @@ -0,0 +1,189 @@ +{ + "iterations": [ + { + "lt": "ssq", + "issue": "26063", + "date": "2026-06-04", + "pred_reds": [ + 11, + 17, + 21, + 24, + 28, + 30 + ], + "pred_blues": [ + 2 + ], + "act_reds": [ + 2, + 8, + 25, + 28, + 30, + 31 + ], + "act_blues": [ + 2 + ], + "hit_reds": [ + 28, + 30 + ], + "hit_blues": [ + 2 + ], + "red_hits": 2, + "blue_hits": 1, + "total_hits": 3, + "earnings": 50.0 + }, + { + "lt": "dlt", + "issue": "26064", + "date": "2026-06-10", + "pred_reds": [ + 1, + 9, + 14, + 33, + 34 + ], + "pred_blues": [ + 8, + 10 + ], + "act_reds": [ + 3, + 13, + 15, + 17, + 21 + ], + "act_blues": [ + 2, + 7 + ], + "hit_reds": [], + "hit_blues": [], + "red_hits": 0, + "blue_hits": 0, + "total_hits": 0, + "earnings": 0.0 + }, + { + "lt": "dlt", + "issue": "26065", + "date": "2026-06-13", + "pred_reds": [ + 1, + 9, + 14, + 33, + 34 + ], + "pred_blues": [ + 8, + 10 + ], + "act_reds": [ + 4, + 11, + 12, + 13, + 25 + ], + "act_blues": [ + 4, + 8 + ], + "hit_reds": [], + "hit_blues": [ + 8 + ], + "red_hits": 0, + "blue_hits": 1, + "total_hits": 1, + "earnings": 0.0 + }, + { + "lt": "dlt", + "issue": "26078", + "date": "2026-07-13", + "pred_reds": [ + 6, + 13, + 19, + 22, + 29 + ], + "pred_blues": [ + 6, + 8 + ], + "act_reds": [ + 2, + 13, + 20, + 25, + 32 + ], + "act_blues": [ + 8, + 11 + ], + "hit_reds": [ + 13 + ], + "hit_blues": [ + 8 + ], + "red_hits": 1, + "blue_hits": 1, + "total_hits": 2, + "earnings": 0.0 + } + ], + "accuracy_history": [ + { + "issue": "26063", + "lt": "ssq", + "hits": 3, + "avg_accuracy": 42.86 + }, + { + "issue": "26064", + "lt": "dlt", + "hits": 0, + "avg_accuracy": 0.0 + }, + { + "issue": "26065", + "lt": "dlt", + "hits": 1, + "avg_accuracy": 7.14 + }, + { + "issue": "26078", + "lt": "dlt", + "hits": 2, + "avg_accuracy": 14.29 + } + ], + "ssq": { + "total": 1, + "correct_reds": 2, + "correct_blues": 1, + "best": 3, + "profitable_wins": 1, + "total_earnings": 50.0 + }, + "dlt": { + "total": 3, + "correct_reds": 1, + "correct_blues": 2, + "best": 2, + "profitable_wins": 0, + "total_earnings": 0.0 + } +} \ No newline at end of file diff --git a/skills/lottery_picker/data/ssq_history.json b/skills/lottery_picker/data/ssq_history.json new file mode 100644 index 0000000..e5e5e52 --- /dev/null +++ b/skills/lottery_picker/data/ssq_history.json @@ -0,0 +1,2849 @@ +[ + { + "issue": "25019", + "reds": [ + "05", + "13", + "17", + "18", + "22", + "23" + ], + "blue": "11", + "date": "2025-02-23" + }, + { + "issue": "25020", + "reds": [ + "04", + "11", + "13", + "16", + "26", + "30" + ], + "blue": "15", + "date": "2025-02-25" + }, + { + "issue": "25021", + "reds": [ + "07", + "08", + "16", + "18", + "25", + "32" + ], + "blue": "14", + "date": "2025-02-27" + }, + { + "issue": "25022", + "reds": [ + "11", + "17", + "18", + "20", + "28", + "30" + ], + "blue": "10", + "date": "2025-03-02" + }, + { + "issue": "25023", + "reds": [ + "09", + "12", + "13", + "24", + "25", + "33" + ], + "blue": "07", + "date": "2025-03-04" + }, + { + "issue": "25024", + "reds": [ + "10", + "11", + "22", + "27", + "30", + "32" + ], + "blue": "15", + "date": "2025-03-06" + }, + { + "issue": "25025", + "reds": [ + "12", + "15", + "21", + "23", + "25", + "30" + ], + "blue": "02", + "date": "2025-03-09" + }, + { + "issue": "25026", + "reds": [ + "09", + "10", + "12", + "14", + "19", + "32" + ], + "blue": "07", + "date": "2025-03-11" + }, + { + "issue": "25027", + "reds": [ + "05", + "07", + "08", + "15", + "16", + "23" + ], + "blue": "06", + "date": "2025-03-13" + }, + { + "issue": "25028", + "reds": [ + "04", + "09", + "14", + "15", + "18", + "25" + ], + "blue": "15", + "date": "2025-03-16" + }, + { + "issue": "25029", + "reds": [ + "05", + "15", + "16", + "25", + "30", + "33" + ], + "blue": "16", + "date": "2025-03-18" + }, + { + "issue": "25030", + "reds": [ + "04", + "06", + "07", + "30", + "31", + "33" + ], + "blue": "06", + "date": "2025-03-20" + }, + { + "issue": "25031", + "reds": [ + "01", + "05", + "06", + "08", + "23", + "28" + ], + "blue": "01", + "date": "2025-03-23" + }, + { + "issue": "25032", + "reds": [ + "03", + "08", + "10", + "14", + "16", + "21" + ], + "blue": "03", + "date": "2025-03-25" + }, + { + "issue": "25033", + "reds": [ + "03", + "05", + "18", + "25", + "26", + "33" + ], + "blue": "08", + "date": "2025-03-27" + }, + { + "issue": "25034", + "reds": [ + "05", + "08", + "13", + "14", + "24", + "26" + ], + "blue": "12", + "date": "2025-03-30" + }, + { + "issue": "25035", + "reds": [ + "01", + "08", + "16", + "18", + "25", + "31" + ], + "blue": "01", + "date": "2025-04-01" + }, + { + "issue": "25036", + "reds": [ + "05", + "11", + "13", + "16", + "19", + "32" + ], + "blue": "07", + "date": "2025-04-03" + }, + { + "issue": "25037", + "reds": [ + "03", + "06", + "11", + "20", + "21", + "31" + ], + "blue": "02", + "date": "2025-04-06" + }, + { + "issue": "25038", + "reds": [ + "06", + "19", + "20", + "23", + "26", + "33" + ], + "blue": "09", + "date": "2025-04-08" + }, + { + "issue": "25039", + "reds": [ + "08", + "10", + "12", + "15", + "17", + "23" + ], + "blue": "11", + "date": "2025-04-10" + }, + { + "issue": "25040", + "reds": [ + "02", + "06", + "08", + "09", + "10", + "24" + ], + "blue": "11", + "date": "2025-04-13" + }, + { + "issue": "25041", + "reds": [ + "06", + "10", + "17", + "19", + "25", + "31" + ], + "blue": "06", + "date": "2025-04-15" + }, + { + "issue": "25042", + "reds": [ + "07", + "13", + "19", + "23", + "27", + "33" + ], + "blue": "12", + "date": "2025-04-17" + }, + { + "issue": "25043", + "reds": [ + "03", + "12", + "16", + "19", + "20", + "32" + ], + "blue": "13", + "date": "2025-04-20" + }, + { + "issue": "25044", + "reds": [ + "02", + "03", + "07", + "08", + "14", + "28" + ], + "blue": "11", + "date": "2025-04-22" + }, + { + "issue": "25045", + "reds": [ + "02", + "14", + "19", + "24", + "29", + "33" + ], + "blue": "13", + "date": "2025-04-24" + }, + { + "issue": "25046", + "reds": [ + "02", + "03", + "10", + "16", + "29", + "33" + ], + "blue": "01", + "date": "2025-04-27" + }, + { + "issue": "25047", + "reds": [ + "01", + "14", + "21", + "22", + "23", + "31" + ], + "blue": "12", + "date": "2025-04-29" + }, + { + "issue": "25048", + "reds": [ + "16", + "21", + "22", + "23", + "27", + "31" + ], + "blue": "10", + "date": "2025-05-01" + }, + { + "issue": "25049", + "reds": [ + "03", + "06", + "19", + "27", + "29", + "33" + ], + "blue": "11", + "date": "2025-05-04" + }, + { + "issue": "25050", + "reds": [ + "09", + "12", + "15", + "18", + "22", + "33" + ], + "blue": "16", + "date": "2025-05-06" + }, + { + "issue": "25051", + "reds": [ + "01", + "02", + "03", + "04", + "17", + "22" + ], + "blue": "01", + "date": "2025-05-08" + }, + { + "issue": "25052", + "reds": [ + "06", + "07", + "10", + "17", + "20", + "26" + ], + "blue": "09", + "date": "2025-05-11" + }, + { + "issue": "25053", + "reds": [ + "06", + "09", + "10", + "13", + "30", + "33" + ], + "blue": "07", + "date": "2025-05-13" + }, + { + "issue": "25054", + "reds": [ + "05", + "07", + "10", + "21", + "24", + "27" + ], + "blue": "16", + "date": "2025-05-15" + }, + { + "issue": "25055", + "reds": [ + "02", + "05", + "22", + "27", + "29", + "33" + ], + "blue": "12", + "date": "2025-05-18" + }, + { + "issue": "25056", + "reds": [ + "01", + "02", + "10", + "14", + "28", + "31" + ], + "blue": "03", + "date": "2025-05-20" + }, + { + "issue": "25057", + "reds": [ + "04", + "09", + "15", + "16", + "25", + "30" + ], + "blue": "14", + "date": "2025-05-22" + }, + { + "issue": "25058", + "reds": [ + "02", + "06", + "07", + "09", + "10", + "20" + ], + "blue": "06", + "date": "2025-05-25" + }, + { + "issue": "25059", + "reds": [ + "04", + "10", + "11", + "12", + "13", + "24" + ], + "blue": "01", + "date": "2025-05-27" + }, + { + "issue": "25060", + "reds": [ + "06", + "14", + "18", + "25", + "28", + "30" + ], + "blue": "01", + "date": "2025-05-29" + }, + { + "issue": "25061", + "reds": [ + "06", + "07", + "09", + "10", + "11", + "32" + ], + "blue": "09", + "date": "2025-06-01" + }, + { + "issue": "25062", + "reds": [ + "06", + "08", + "09", + "13", + "25", + "31" + ], + "blue": "14", + "date": "2025-06-03" + }, + { + "issue": "25063", + "reds": [ + "02", + "19", + "21", + "22", + "28", + "30" + ], + "blue": "01", + "date": "2025-06-05" + }, + { + "issue": "25064", + "reds": [ + "02", + "10", + "13", + "22", + "29", + "33" + ], + "blue": "16", + "date": "2025-06-08" + }, + { + "issue": "25065", + "reds": [ + "06", + "10", + "13", + "14", + "15", + "20" + ], + "blue": "11", + "date": "2025-06-10" + }, + { + "issue": "25066", + "reds": [ + "06", + "22", + "24", + "27", + "28", + "30" + ], + "blue": "04", + "date": "2025-06-12" + }, + { + "issue": "25067", + "reds": [ + "01", + "05", + "10", + "17", + "20", + "22" + ], + "blue": "05", + "date": "2025-06-15" + }, + { + "issue": "25068", + "reds": [ + "05", + "07", + "08", + "19", + "20", + "31" + ], + "blue": "07", + "date": "2025-06-17" + }, + { + "issue": "25069", + "reds": [ + "02", + "04", + "19", + "23", + "27", + "30" + ], + "blue": "05", + "date": "2025-06-19" + }, + { + "issue": "25070", + "reds": [ + "02", + "03", + "15", + "21", + "22", + "33" + ], + "blue": "06", + "date": "2025-06-22" + }, + { + "issue": "25071", + "reds": [ + "01", + "12", + "18", + "23", + "25", + "28" + ], + "blue": "07", + "date": "2025-06-24" + }, + { + "issue": "25072", + "reds": [ + "02", + "14", + "17", + "25", + "27", + "29" + ], + "blue": "05", + "date": "2025-06-26" + }, + { + "issue": "25073", + "reds": [ + "02", + "07", + "10", + "27", + "30", + "33" + ], + "blue": "11", + "date": "2025-06-29" + }, + { + "issue": "25074", + "reds": [ + "03", + "04", + "12", + "20", + "32", + "33" + ], + "blue": "13", + "date": "2025-07-01" + }, + { + "issue": "25075", + "reds": [ + "10", + "12", + "14", + "19", + "20", + "21" + ], + "blue": "15", + "date": "2025-07-03" + }, + { + "issue": "25076", + "reds": [ + "02", + "06", + "09", + "12", + "14", + "30" + ], + "blue": "08", + "date": "2025-07-06" + }, + { + "issue": "25077", + "reds": [ + "04", + "07", + "08", + "15", + "20", + "21" + ], + "blue": "15", + "date": "2025-07-08" + }, + { + "issue": "25078", + "reds": [ + "06", + "15", + "17", + "30", + "32", + "33" + ], + "blue": "14", + "date": "2025-07-10" + }, + { + "issue": "25079", + "reds": [ + "08", + "11", + "14", + "15", + "30", + "32" + ], + "blue": "05", + "date": "2025-07-13" + }, + { + "issue": "25080", + "reds": [ + "03", + "07", + "08", + "10", + "16", + "19" + ], + "blue": "05", + "date": "2025-07-15" + }, + { + "issue": "25081", + "reds": [ + "03", + "06", + "18", + "20", + "23", + "28" + ], + "blue": "05", + "date": "2025-07-17" + }, + { + "issue": "25082", + "reds": [ + "04", + "06", + "09", + "14", + "32", + "33" + ], + "blue": "11", + "date": "2025-07-20" + }, + { + "issue": "25083", + "reds": [ + "10", + "15", + "16", + "17", + "27", + "29" + ], + "blue": "12", + "date": "2025-07-22" + }, + { + "issue": "25084", + "reds": [ + "06", + "08", + "10", + "14", + "24", + "33" + ], + "blue": "15", + "date": "2025-07-24" + }, + { + "issue": "25085", + "reds": [ + "11", + "14", + "15", + "18", + "21", + "24" + ], + "blue": "13", + "date": "2025-07-27" + }, + { + "issue": "25086", + "reds": [ + "02", + "06", + "08", + "10", + "17", + "24" + ], + "blue": "06", + "date": "2025-07-29" + }, + { + "issue": "25087", + "reds": [ + "02", + "06", + "14", + "15", + "24", + "26" + ], + "blue": "08", + "date": "2025-07-31" + }, + { + "issue": "25088", + "reds": [ + "01", + "03", + "13", + "18", + "21", + "25" + ], + "blue": "06", + "date": "2025-08-03" + }, + { + "issue": "25089", + "reds": [ + "04", + "06", + "08", + "18", + "31", + "33" + ], + "blue": "05", + "date": "2025-08-05" + }, + { + "issue": "25090", + "reds": [ + "06", + "11", + "12", + "21", + "27", + "28" + ], + "blue": "15", + "date": "2025-08-07" + }, + { + "issue": "25091", + "reds": [ + "03", + "04", + "17", + "19", + "25", + "27" + ], + "blue": "14", + "date": "2025-08-10" + }, + { + "issue": "25092", + "reds": [ + "02", + "11", + "14", + "17", + "23", + "24" + ], + "blue": "12", + "date": "2025-08-12" + }, + { + "issue": "25093", + "reds": [ + "09", + "11", + "12", + "24", + "25", + "26" + ], + "blue": "10", + "date": "2025-08-14" + }, + { + "issue": "25094", + "reds": [ + "11", + "13", + "17", + "19", + "23", + "29" + ], + "blue": "16", + "date": "2025-08-17" + }, + { + "issue": "25095", + "reds": [ + "15", + "16", + "22", + "23", + "26", + "32" + ], + "blue": "04", + "date": "2025-08-19" + }, + { + "issue": "25096", + "reds": [ + "07", + "09", + "11", + "12", + "16", + "29" + ], + "blue": "15", + "date": "2025-08-21" + }, + { + "issue": "25097", + "reds": [ + "03", + "05", + "16", + "23", + "26", + "31" + ], + "blue": "14", + "date": "2025-08-24" + }, + { + "issue": "25098", + "reds": [ + "05", + "08", + "13", + "17", + "18", + "29" + ], + "blue": "02", + "date": "2025-08-26" + }, + { + "issue": "25099", + "reds": [ + "09", + "11", + "15", + "17", + "22", + "26" + ], + "blue": "14", + "date": "2025-08-28" + }, + { + "issue": "25100", + "reds": [ + "12", + "16", + "17", + "25", + "30", + "31" + ], + "blue": "16", + "date": "2025-08-31" + }, + { + "issue": "25101", + "reds": [ + "05", + "08", + "09", + "10", + "16", + "21" + ], + "blue": "05", + "date": "2025-09-02" + }, + { + "issue": "25102", + "reds": [ + "04", + "09", + "16", + "17", + "18", + "31" + ], + "blue": "07", + "date": "2025-09-04" + }, + { + "issue": "25103", + "reds": [ + "13", + "16", + "21", + "25", + "28", + "31" + ], + "blue": "16", + "date": "2025-09-07" + }, + { + "issue": "25104", + "reds": [ + "02", + "05", + "15", + "16", + "24", + "32" + ], + "blue": "16", + "date": "2025-09-09" + }, + { + "issue": "25105", + "reds": [ + "04", + "07", + "18", + "24", + "26", + "28" + ], + "blue": "08", + "date": "2025-09-11" + }, + { + "issue": "25106", + "reds": [ + "04", + "05", + "17", + "22", + "26", + "30" + ], + "blue": "04", + "date": "2025-09-14" + }, + { + "issue": "25107", + "reds": [ + "02", + "03", + "10", + "15", + "25", + "33" + ], + "blue": "13", + "date": "2025-09-16" + }, + { + "issue": "25108", + "reds": [ + "01", + "09", + "14", + "17", + "22", + "33" + ], + "blue": "07", + "date": "2025-09-18" + }, + { + "issue": "25109", + "reds": [ + "05", + "06", + "09", + "17", + "18", + "31" + ], + "blue": "03", + "date": "2025-09-21" + }, + { + "issue": "25110", + "reds": [ + "01", + "05", + "11", + "14", + "16", + "19" + ], + "blue": "08", + "date": "2025-09-23" + }, + { + "issue": "25111", + "reds": [ + "09", + "14", + "18", + "28", + "31", + "33" + ], + "blue": "12", + "date": "2025-09-25" + }, + { + "issue": "25112", + "reds": [ + "03", + "09", + "11", + "13", + "20", + "32" + ], + "blue": "02", + "date": "2025-09-28" + }, + { + "issue": "25113", + "reds": [ + "08", + "10", + "13", + "15", + "24", + "31" + ], + "blue": "16", + "date": "2025-09-30" + }, + { + "issue": "25114", + "reds": [ + "01", + "20", + "21", + "25", + "26", + "27" + ], + "blue": "10", + "date": "2025-10-05" + }, + { + "issue": "25115", + "reds": [ + "02", + "03", + "08", + "19", + "24", + "30" + ], + "blue": "02", + "date": "2025-10-07" + }, + { + "issue": "25116", + "reds": [ + "02", + "04", + "08", + "24", + "28", + "31" + ], + "blue": "09", + "date": "2025-10-09" + }, + { + "issue": "25117", + "reds": [ + "06", + "08", + "17", + "20", + "25", + "33" + ], + "blue": "10", + "date": "2025-10-12" + }, + { + "issue": "25118", + "reds": [ + "01", + "10", + "11", + "16", + "24", + "26" + ], + "blue": "03", + "date": "2025-10-14" + }, + { + "issue": "25119", + "reds": [ + "06", + "09", + "23", + "26", + "28", + "32" + ], + "blue": "11", + "date": "2025-10-16" + }, + { + "issue": "25120", + "reds": [ + "01", + "02", + "04", + "07", + "13", + "32" + ], + "blue": "07", + "date": "2025-10-19" + }, + { + "issue": "25121", + "reds": [ + "06", + "08", + "10", + "25", + "29", + "30" + ], + "blue": "08", + "date": "2025-10-21" + }, + { + "issue": "25122", + "reds": [ + "16", + "18", + "19", + "20", + "25", + "31" + ], + "blue": "13", + "date": "2025-10-23" + }, + { + "issue": "25123", + "reds": [ + "07", + "09", + "23", + "24", + "25", + "26" + ], + "blue": "10", + "date": "2025-10-26" + }, + { + "issue": "25124", + "reds": [ + "01", + "02", + "18", + "19", + "21", + "33" + ], + "blue": "13", + "date": "2025-10-28" + }, + { + "issue": "25125", + "reds": [ + "03", + "09", + "12", + "13", + "26", + "32" + ], + "blue": "09", + "date": "2025-10-30" + }, + { + "issue": "25126", + "reds": [ + "02", + "12", + "13", + "16", + "19", + "25" + ], + "blue": "10", + "date": "2025-11-02" + }, + { + "issue": "25127", + "reds": [ + "03", + "09", + "15", + "17", + "19", + "28" + ], + "blue": "03", + "date": "2025-11-04" + }, + { + "issue": "25128", + "reds": [ + "02", + "10", + "18", + "19", + "24", + "27" + ], + "blue": "01", + "date": "2025-11-06" + }, + { + "issue": "25129", + "reds": [ + "03", + "04", + "07", + "13", + "20", + "30" + ], + "blue": "03", + "date": "2025-11-09" + }, + { + "issue": "25130", + "reds": [ + "01", + "05", + "08", + "14", + "19", + "23" + ], + "blue": "06", + "date": "2025-11-11" + }, + { + "issue": "25131", + "reds": [ + "03", + "13", + "14", + "18", + "24", + "31" + ], + "blue": "03", + "date": "2025-11-13" + }, + { + "issue": "25132", + "reds": [ + "04", + "08", + "10", + "21", + "23", + "32" + ], + "blue": "11", + "date": "2025-11-16" + }, + { + "issue": "25133", + "reds": [ + "05", + "14", + "17", + "19", + "20", + "33" + ], + "blue": "07", + "date": "2025-11-18" + }, + { + "issue": "25134", + "reds": [ + "03", + "05", + "09", + "13", + "26", + "29" + ], + "blue": "12", + "date": "2025-11-20" + }, + { + "issue": "25135", + "reds": [ + "01", + "02", + "05", + "09", + "25", + "32" + ], + "blue": "10", + "date": "2025-11-23" + }, + { + "issue": "25136", + "reds": [ + "08", + "10", + "14", + "23", + "28", + "32" + ], + "blue": "12", + "date": "2025-11-25" + }, + { + "issue": "25137", + "reds": [ + "02", + "08", + "11", + "23", + "27", + "29" + ], + "blue": "05", + "date": "2025-11-27" + }, + { + "issue": "25138", + "reds": [ + "10", + "13", + "14", + "23", + "24", + "27" + ], + "blue": "15", + "date": "2025-11-30" + }, + { + "issue": "25139", + "reds": [ + "02", + "05", + "17", + "22", + "30", + "33" + ], + "blue": "06", + "date": "2025-12-02" + }, + { + "issue": "25140", + "reds": [ + "01", + "03", + "04", + "12", + "18", + "24" + ], + "blue": "05", + "date": "2025-12-04" + }, + { + "issue": "25141", + "reds": [ + "02", + "04", + "05", + "10", + "12", + "13" + ], + "blue": "06", + "date": "2025-12-07" + }, + { + "issue": "25142", + "reds": [ + "02", + "13", + "15", + "23", + "27", + "31" + ], + "blue": "16", + "date": "2025-12-09" + }, + { + "issue": "25143", + "reds": [ + "02", + "09", + "12", + "13", + "15", + "24" + ], + "blue": "03", + "date": "2025-12-11" + }, + { + "issue": "25144", + "reds": [ + "01", + "08", + "15", + "20", + "26", + "33" + ], + "blue": "13", + "date": "2025-12-14" + }, + { + "issue": "25145", + "reds": [ + "11", + "12", + "15", + "18", + "25", + "32" + ], + "blue": "14", + "date": "2025-12-16" + }, + { + "issue": "25146", + "reds": [ + "05", + "07", + "12", + "24", + "26", + "28" + ], + "blue": "02", + "date": "2025-12-18" + }, + { + "issue": "25147", + "reds": [ + "01", + "03", + "05", + "08", + "22", + "33" + ], + "blue": "08", + "date": "2025-12-21" + }, + { + "issue": "25148", + "reds": [ + "03", + "04", + "09", + "10", + "15", + "22" + ], + "blue": "16", + "date": "2025-12-23" + }, + { + "issue": "25149", + "reds": [ + "01", + "02", + "04", + "06", + "22", + "30" + ], + "blue": "10", + "date": "2025-12-25" + }, + { + "issue": "25150", + "reds": [ + "06", + "13", + "17", + "19", + "24", + "31" + ], + "blue": "08", + "date": "2025-12-28" + }, + { + "issue": "25151", + "reds": [ + "08", + "09", + "14", + "22", + "28", + "30" + ], + "blue": "04", + "date": "2025-12-30" + }, + { + "issue": "26001", + "reds": [ + "02", + "06", + "11", + "12", + "13", + "33" + ], + "blue": "15", + "date": "2026-01-01" + }, + { + "issue": "26002", + "reds": [ + "01", + "05", + "07", + "18", + "30", + "32" + ], + "blue": "02", + "date": "2026-01-04" + }, + { + "issue": "26003", + "reds": [ + "05", + "06", + "09", + "21", + "28", + "30" + ], + "blue": "16", + "date": "2026-01-06" + }, + { + "issue": "26004", + "reds": [ + "03", + "07", + "08", + "09", + "18", + "32" + ], + "blue": "10", + "date": "2026-01-08" + }, + { + "issue": "26005", + "reds": [ + "01", + "20", + "22", + "27", + "30", + "33" + ], + "blue": "10", + "date": "2026-01-11" + }, + { + "issue": "26006", + "reds": [ + "02", + "06", + "22", + "23", + "24", + "28" + ], + "blue": "15", + "date": "2026-01-13" + }, + { + "issue": "26007", + "reds": [ + "09", + "13", + "19", + "27", + "29", + "30" + ], + "blue": "01", + "date": "2026-01-15" + }, + { + "issue": "26008", + "reds": [ + "06", + "09", + "16", + "27", + "31", + "33" + ], + "blue": "10", + "date": "2026-01-18" + }, + { + "issue": "26009", + "reds": [ + "03", + "06", + "13", + "19", + "23", + "25" + ], + "blue": "10", + "date": "2026-01-20" + }, + { + "issue": "26010", + "reds": [ + "04", + "09", + "10", + "15", + "19", + "26" + ], + "blue": "12", + "date": "2026-01-22" + }, + { + "issue": "26011", + "reds": [ + "02", + "03", + "04", + "20", + "31", + "32" + ], + "blue": "04", + "date": "2026-01-25" + }, + { + "issue": "26012", + "reds": [ + "03", + "05", + "07", + "16", + "20", + "24" + ], + "blue": "08", + "date": "2026-01-27" + }, + { + "issue": "26013", + "reds": [ + "04", + "09", + "12", + "13", + "16", + "20" + ], + "blue": "01", + "date": "2026-01-29" + }, + { + "issue": "26014", + "reds": [ + "07", + "13", + "19", + "22", + "26", + "32" + ], + "blue": "01", + "date": "2026-02-01" + }, + { + "issue": "26015", + "reds": [ + "07", + "10", + "13", + "22", + "27", + "31" + ], + "blue": "12", + "date": "2026-02-03" + }, + { + "issue": "26016", + "reds": [ + "04", + "05", + "09", + "10", + "27", + "30" + ], + "blue": "13", + "date": "2026-02-05" + }, + { + "issue": "26017", + "reds": [ + "01", + "03", + "05", + "18", + "29", + "32" + ], + "blue": "04", + "date": "2026-02-08" + }, + { + "issue": "26018", + "reds": [ + "11", + "15", + "17", + "22", + "25", + "30" + ], + "blue": "07", + "date": "2026-02-10" + }, + { + "issue": "26019", + "reds": [ + "07", + "08", + "16", + "17", + "18", + "30" + ], + "blue": "01", + "date": "2026-02-12" + }, + { + "issue": "26020", + "reds": [ + "01", + "13", + "14", + "21", + "24", + "30" + ], + "blue": "02", + "date": "2026-02-24" + }, + { + "issue": "26021", + "reds": [ + "03", + "13", + "25", + "26", + "30", + "31" + ], + "blue": "04", + "date": "2026-02-26" + }, + { + "issue": "26022", + "reds": [ + "15", + "18", + "23", + "25", + "28", + "32" + ], + "blue": "11", + "date": "2026-03-01" + }, + { + "issue": "26023", + "reds": [ + "01", + "03", + "08", + "10", + "23", + "29" + ], + "blue": "06", + "date": "2026-03-03" + }, + { + "issue": "26024", + "reds": [ + "01", + "02", + "13", + "21", + "23", + "29" + ], + "blue": "14", + "date": "2026-03-05" + }, + { + "issue": "26025", + "reds": [ + "02", + "03", + "15", + "20", + "23", + "24" + ], + "blue": "10", + "date": "2026-03-08" + }, + { + "issue": "26026", + "reds": [ + "02", + "09", + "16", + "22", + "25", + "29" + ], + "blue": "03", + "date": "2026-03-10" + }, + { + "issue": "26027", + "reds": [ + "02", + "13", + "17", + "18", + "25", + "26" + ], + "blue": "13", + "date": "2026-03-12" + }, + { + "issue": "26028", + "reds": [ + "02", + "06", + "09", + "17", + "25", + "28" + ], + "blue": "15", + "date": "2026-03-15" + }, + { + "issue": "26029", + "reds": [ + "06", + "19", + "22", + "23", + "28", + "31" + ], + "blue": "05", + "date": "2026-03-17" + }, + { + "issue": "26030", + "reds": [ + "10", + "11", + "14", + "19", + "22", + "24" + ], + "blue": "04", + "date": "2026-03-19" + }, + { + "issue": "26031", + "reds": [ + "03", + "10", + "12", + "13", + "18", + "33" + ], + "blue": "08", + "date": "2026-03-22" + }, + { + "issue": "26032", + "reds": [ + "01", + "03", + "11", + "18", + "31", + "33" + ], + "blue": "02", + "date": "2026-03-24" + }, + { + "issue": "26033", + "reds": [ + "03", + "06", + "13", + "21", + "28", + "29" + ], + "blue": "06", + "date": "2026-03-26" + }, + { + "issue": "26034", + "reds": [ + "01", + "03", + "07", + "13", + "22", + "23" + ], + "blue": "07", + "date": "2026-03-29" + }, + { + "issue": "26035", + "reds": [ + "02", + "06", + "12", + "24", + "25", + "32" + ], + "blue": "02", + "date": "2026-03-31" + }, + { + "issue": "26036", + "reds": [ + "06", + "10", + "12", + "15", + "22", + "28" + ], + "blue": "08", + "date": "2026-04-02" + }, + { + "issue": "26037", + "reds": [ + "11", + "22", + "27", + "29", + "31", + "33" + ], + "blue": "12", + "date": "2026-04-05" + }, + { + "issue": "26038", + "reds": [ + "01", + "02", + "13", + "23", + "25", + "27" + ], + "blue": "05", + "date": "2026-04-07" + }, + { + "issue": "26039", + "reds": [ + "08", + "17", + "18", + "21", + "25", + "30" + ], + "blue": "05", + "date": "2026-04-09" + }, + { + "issue": "26040", + "reds": [ + "03", + "04", + "14", + "22", + "23", + "33" + ], + "blue": "04", + "date": "2026-04-12" + }, + { + "issue": "26041", + "reds": [ + "02", + "08", + "10", + "17", + "19", + "24" + ], + "blue": "13", + "date": "2026-04-14" + }, + { + "issue": "26042", + "reds": [ + "02", + "07", + "12", + "19", + "24", + "31" + ], + "blue": "10", + "date": "2026-04-16" + }, + { + "issue": "26043", + "reds": [ + "06", + "09", + "14", + "16", + "25", + "32" + ], + "blue": "16", + "date": "2026-04-19" + }, + { + "issue": "26044", + "reds": [ + "02", + "14", + "17", + "18", + "22", + "30" + ], + "blue": "01", + "date": "2026-04-21" + }, + { + "issue": "26045", + "reds": [ + "04", + "11", + "15", + "17", + "24", + "30" + ], + "blue": "15", + "date": "2026-04-23" + }, + { + "issue": "26046", + "reds": [ + "02", + "09", + "10", + "24", + "31", + "33" + ], + "blue": "16", + "date": "2026-04-26" + }, + { + "issue": "26047", + "reds": [ + "07", + "16", + "21", + "24", + "27", + "30" + ], + "blue": "07", + "date": "2026-04-28" + }, + { + "issue": "26048", + "reds": [ + "09", + "15", + "18", + "24", + "28", + "33" + ], + "blue": "01", + "date": "2026-04-30" + }, + { + "issue": "26049", + "reds": [ + "03", + "04", + "14", + "15", + "18", + "20" + ], + "blue": "02", + "date": "2026-05-03" + }, + { + "issue": "26050", + "reds": [ + "06", + "09", + "25", + "27", + "28", + "30" + ], + "blue": "03", + "date": "2026-05-05" + }, + { + "issue": "26051", + "reds": [ + "09", + "14", + "15", + "16", + "29", + "30" + ], + "blue": "10", + "date": "2026-05-07" + }, + { + "issue": "26052", + "reds": [ + "01", + "03", + "11", + "22", + "26", + "31" + ], + "blue": "11", + "date": "2026-05-10" + }, + { + "issue": "26053", + "reds": [ + "01", + "02", + "03", + "08", + "13", + "14" + ], + "blue": "02", + "date": "2026-05-12" + }, + { + "issue": "26054", + "reds": [ + "13", + "20", + "25", + "29", + "30", + "33" + ], + "blue": "02", + "date": "2026-05-14" + }, + { + "issue": "26055", + "reds": [ + "04", + "11", + "24", + "25", + "32", + "33" + ], + "blue": "13", + "date": "2026-05-17" + }, + { + "issue": "26056", + "reds": [ + "10", + "19", + "21", + "22", + "31", + "33" + ], + "blue": "05", + "date": "2026-05-19" + }, + { + "issue": "26057", + "reds": [ + "01", + "10", + "22", + "24", + "28", + "30" + ], + "blue": "07", + "date": "2026-05-21" + }, + { + "issue": "26058", + "reds": [ + "01", + "04", + "07", + "21", + "29", + "30" + ], + "blue": "01", + "date": "2026-05-24" + }, + { + "issue": "26059", + "reds": [ + "08", + "16", + "26", + "28", + "29", + "30" + ], + "blue": "15", + "date": "2026-05-26" + }, + { + "issue": "26060", + "reds": [ + "07", + "09", + "10", + "16", + "22", + "27" + ], + "blue": "11", + "date": "2026-05-28" + }, + { + "issue": "26061", + "reds": [ + "01", + "04", + "05", + "15", + "23", + "28" + ], + "blue": "07", + "date": "2026-05-31" + }, + { + "issue": "26062", + "reds": [ + "02", + "04", + "07", + "14", + "28", + "29" + ], + "blue": "09", + "date": "2026-06-02" + }, + { + "issue": "26063", + "reds": [ + "02", + "08", + "25", + "28", + "30", + "31" + ], + "blue": "02", + "date": "2026-06-04" + }, + { + "issue": "26064", + "reds": [ + "01", + "09", + "15", + "18", + "29", + "33" + ], + "blue": "15", + "date": "2026-06-07" + }, + { + "issue": "26065", + "reds": [ + "07", + "08", + "16", + "24", + "30", + "32" + ], + "blue": "02", + "date": "2026-06-09" + }, + { + "issue": "26066", + "reds": [ + "05", + "11", + "21", + "23", + "24", + "29" + ], + "blue": "16", + "date": "2026-06-11" + }, + { + "issue": "26067", + "reds": [ + "04", + "19", + "27", + "29", + "30", + "32" + ], + "blue": "13", + "date": "2026-06-14" + }, + { + "issue": "26068", + "reds": [ + "03", + "05", + "16", + "18", + "29", + "32" + ], + "blue": "04", + "date": "2026-06-16" + }, + { + "issue": "26069", + "reds": [ + "12", + "14", + "16", + "17", + "18", + "32" + ], + "blue": "08", + "date": "2026-06-18" + }, + { + "issue": "26070", + "reds": [ + "03", + "06", + "08", + "14", + "26", + "27" + ], + "blue": "08", + "date": "2026-06-21" + }, + { + "issue": "26071", + "reds": [ + "03", + "08", + "19", + "25", + "31", + "33" + ], + "blue": "05", + "date": "2026-06-23" + }, + { + "issue": "26072", + "reds": [ + "07", + "08", + "12", + "15", + "17", + "21" + ], + "blue": "01", + "date": "2026-06-25" + }, + { + "issue": "26073", + "reds": [ + "09", + "10", + "13", + "16", + "19", + "21" + ], + "blue": "08", + "date": "2026-06-28" + }, + { + "issue": "26074", + "reds": [ + "02", + "23", + "24", + "26", + "28", + "32" + ], + "blue": "04", + "date": "2026-06-30" + }, + { + "issue": "26075", + "reds": [ + "08", + "12", + "18", + "21", + "24", + "30" + ], + "blue": "01", + "date": "2026-07-02" + }, + { + "issue": "26076", + "reds": [ + "01", + "03", + "19", + "20", + "24", + "25" + ], + "blue": "07", + "date": "2026-07-05" + }, + { + "issue": "26077", + "reds": [ + "01", + "04", + "05", + "14", + "18", + "25" + ], + "blue": "04", + "date": "2026-07-07" + }, + { + "issue": "26078", + "reds": [ + "07", + "11", + "14", + "16", + "27", + "28" + ], + "blue": "06", + "date": "2026-07-09" + }, + { + "issue": "26079", + "reds": [ + "01", + "11", + "17", + "22", + "24", + "29" + ], + "blue": "04", + "date": "2026-07-12" + }, + { + "issue": "26080", + "reds": [ + "04", + "05", + "11", + "19", + "27", + "32" + ], + "blue": "01", + "date": "2026-07-14" + }, + { + "issue": "26081", + "reds": [ + "06", + "10", + "12", + "15", + "24", + "27" + ], + "blue": "12", + "date": "2026-07-16" + }, + { + "issue": "26082", + "reds": [ + "05", + "07", + "10", + "14", + "21", + "28" + ], + "blue": "04", + "date": "2026-07-19" + }, + { + "issue": "26083", + "reds": [ + "07", + "14", + "15", + "23", + "28", + "33" + ], + "blue": "03", + "date": "2026-07-21" + }, + { + "issue": "26084", + "reds": [ + "01", + "05", + "06", + "10", + "12", + "16" + ], + "blue": "05", + "date": "2026-07-23" + }, + { + "issue": "26085", + "reds": [ + "06", + "09", + "13", + "17", + "24", + "28" + ], + "blue": "15", + "date": "2026-07-26" + }, + { + "issue": "26086", + "reds": [ + "02", + "05", + "14", + "25", + "30", + "32" + ], + "blue": "05", + "date": "2026-07-28" + } +] \ No newline at end of file diff --git a/skills/lottery_picker/ensemble_learning.py b/skills/lottery_picker/ensemble_learning.py new file mode 100644 index 0000000..626a219 --- /dev/null +++ b/skills/lottery_picker/ensemble_learning.py @@ -0,0 +1,725 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +大乐透/双色球 集成学习模块 +方案3:多模型集成 + 权重优化训练 +================================== +包含5种分析模型: +1. 频率加权模型 (Frequency Model) +2. 贝叶斯平滑模型 (Bayesian Smoothing Model) +3. 马尔可夫链模型 (Markov Chain Model) +4. K-Means聚类模型 (K-Means Clustering Model) +5. 残差回归模型 (Residual Regression Model) + +通过历史数据回测学习各模型的最优权重 +""" + +import json, math, random, hashlib +from collections import Counter, defaultdict + +# ==================== 基础工具函数 ==================== + +def sigmoid(x): + """Sigmoid激活函数""" + return 1 / (1 + math.exp(-max(min(x, 500), -500))) + +def normalize(scores): + """归一化分数,使总和为1""" + 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()} + +def weighted_choice(weights, count=1): + """从权重分布中无放回选择指定数量的元素""" + items = list(weights.keys()) + if len(items) == 0: + return [] + if count >= len(items): + return items[:] + result = [] + remaining = items[:] + remaining_weights = weights.copy() + for _ in range(count): + total = sum(remaining_weights.values()) + if total <= 0: + choice = random.choice(remaining) + else: + r = random.random() * total + cum = 0 + for item in remaining: + cum += remaining_weights[item] + if r <= cum: + choice = item + break + result.append(choice) + remaining.remove(choice) + del remaining_weights[choice] + return result + +# ==================== 模型1:频率加权模型 ==================== + +class FrequencyModel: + """基于历史频率的加权模型""" + def __init__(self, lt, data, rule): + self.lt = lt + self.data = data + self.rule = rule + self.rcnt = rule["red_count"] + self.rmax = rule["red_max"] + self.bcnt = rule["blue_count"] + self.bmax = rule["blue_max"] + self.red_score = {} + self.blue_score = {} + self.analyze() + + def analyze(self): + rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"] + all_r, all_b = [], [] + for rec in self.data: + all_r.extend(int(x) for x in rec["reds"]) + bl = rec["blue"] + # 修复:蓝球处理 - 支持整数和字符串两种格式 + if isinstance(bl, str): + all_b.append(int(bl)) + elif isinstance(bl, list): + all_b.extend(bl) + else: + all_b.append(bl) + + # 频率统计 + rf = Counter(all_r) + bf = Counter(all_b) + + max_rf = max(rf.values()) if rf else 1 + max_bf = max(bf.values()) if bf else 1 + + # 归一化 + self.red_score = {i: rf.get(i, 0) / max_rf for i in range(1, rmax + 1)} + self.blue_score = {i: bf.get(i, 0) / max_bf for i in range(1, bmax + 1)} + + # 平滑:避免0分 + for i in range(1, rmax + 1): + if self.red_score[i] == 0: + self.red_score[i] = 0.01 + for i in range(1, bmax + 1): + if self.blue_score[i] == 0: + self.blue_score[i] = 0.01 + + # 归一化到[0,1] + max_r = max(self.red_score.values()) + max_b = max(self.blue_score.values()) + if max_r > 0: + for i in self.red_score: + self.red_score[i] /= max_r + if max_b > 0: + for i in self.blue_score: + self.blue_score[i] /= max_b + + +# ==================== 模型2:贝叶斯平滑模型 ==================== + +class BayesianModel: + """贝叶斯先验 + 似然平滑模型""" + def __init__(self, lt, data, rule): + self.lt = lt + self.data = data + self.rule = rule + self.rcnt = rule["red_count"] + self.rmax = rule["red_max"] + self.bcnt = rule["blue_count"] + self.bmax = rule["blue_max"] + self.prior = {i: 1.0 / rule["red_max"] for i in range(1, rule["red_max"] + 1)} + self.red_score = {} + self.blue_score = {} + self.analyze() + + def analyze(self): + rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"] + all_r, all_b = [], [] + for rec in self.data: + all_r.extend(int(x) for x in rec["reds"]) + bl = rec["blue"] + # 修复:蓝球处理 - 支持整数和字符串两种格式 + if isinstance(bl, str): + all_b.append(int(bl)) + elif isinstance(bl, list): + all_b.extend(bl) + else: + all_b.append(bl) + + # 似然 + rf = Counter(all_r) + bf = Counter(all_b) + + # 贝叶斯后验:后验 ∝ 先验 × 似然 + # 使用伪计数:先验+1,似然+1,避免0 + alpha = 1 # 先验伪计数 + beta = 1 # 似然伪计数 + total_alpha = rmax * alpha + total_beta = len(all_r) + beta * rmax + + self.red_score = {} + for i in range(1, rmax + 1): + # 后验均值 = (α + count) / (α + β * rmax) + posterior = (alpha + rf.get(i, 0)) / (total_alpha + beta) + self.red_score[i] = posterior + + total_blue = len(all_b) + beta * bmax + self.blue_score = {} + for i in range(1, bmax + 1): + posterior = (alpha + bf.get(i, 0)) / (total_alpha + beta) + self.blue_score[i] = posterior + + # 归一化 + max_r = max(self.red_score.values()) + max_b = max(self.blue_score.values()) + if max_r > 0: + for i in self.red_score: + self.red_score[i] /= max_r + if max_b > 0: + for i in self.blue_score: + self.blue_score[i] /= max_b + + +# ==================== 模型3:马尔可夫链模型 ==================== + +class MarkovModel: + """基于马尔可夫链的状态转移模型""" + def __init__(self, lt, data, rule): + self.lt = lt + self.data = data + self.rule = rule + self.rcnt = rule["red_count"] + self.rmax = rule["red_max"] + self.bcnt = rule["blue_count"] + self.bmax = rule["blue_max"] + self.red_score = {} + self.blue_score = {} + self.analyze() + + def analyze(self): + rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"] + + # 构建状态转移矩阵(考虑最近5期作为状态) + # 这里简化为:计算每个号码在历史数据中的"惯性" + # 即:一个号码出现后,后续几期再次出现的概率 + + # 使用frozenset作为不可变集合作为字典键 + transition = defaultdict(lambda: defaultdict(int)) + for i in range(1, len(self.data)): + prev = frozenset(int(x) for x in self.data[i-1]["reds"]) + curr = set(int(x) for x in self.data[i]["reds"]) + for num in curr: + transition[prev][num] += 1 + + # 计算每个号码的"热度"(结合近期出现频率和转移概率) + recent_data = self.data[:20] if len(self.data) >= 20 else self.data + recent_reds = [] + for rec in recent_data: + recent_reds.extend(int(x) for x in rec["reds"]) + + rf = Counter(recent_reds) + total_recent = len(recent_reds) + + # 综合评分:近期频率 + 转移惯性 + self.red_score = {} + for i in range(1, rmax + 1): + freq_score = rf.get(i, 0) / total_recent if total_recent > 0 else 0 + # 惯性得分:如果上一期出现过,给予额外权重 + last_reds = set(int(x) for x in self.data[0]["reds"]) if self.data else set() + inertia = 1.5 if i in last_reds else 1.0 + self.red_score[i] = (freq_score * inertia + 0.01) # 加0.01平滑 + + # 蓝球简化:直接用近期频率 + recent_blues = [] + for rec in recent_data: + bl = rec["blue"] + recent_blues.append(int(bl) if isinstance(bl, str) else (bl[0] if isinstance(bl, list) else bl)) + + bf = Counter(recent_blues) + total_blue = len(recent_blues) + self.blue_score = {} + for i in range(1, bmax + 1): + self.blue_score[i] = (bf.get(i, 0) / total_blue * 0.8 + 1.0 / bmax * 0.2) if total_blue > 0 else (1.0 / bmax) + + # 归一化 + max_r = max(self.red_score.values()) if self.red_score else 1 + max_b = max(self.blue_score.values()) if self.blue_score else 1 + for i in self.red_score: + self.red_score[i] /= max_r + for i in self.blue_score: + self.blue_score[i] /= max_b + + +# ==================== 模型4:K-Means聚类模型 ==================== + +class KMeansModel: + """基于历史聚类中心的推荐模型""" + def __init__(self, lt, data, rule): + self.lt = lt + self.data = data + self.rule = rule + self.rcnt = rule["red_count"] + self.rmax = rule["red_max"] + self.bcnt = rule["blue_count"] + self.bmax = rule["blue_max"] + self.red_score = {} + self.blue_score = {} + self.analyze() + + def analyze(self): + rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_max"] + + # 提取所有历史红球组合 + clusters = [] + for rec in self.data: + nums = sorted(int(x) for x in rec["reds"]) + if len(nums) == rcnt: + clusters.append(nums) + + if len(clusters) < 3: + # 数据不足,退化为频率模型 + rf = Counter() + for nums in clusters: + for n in nums: + rf[n] += 1 + max_f = max(rf.values()) if rf else 1 + self.red_score = {i: rf.get(i, 0) / max_f for i in range(1, rmax + 1)} + return + + # 简化版K-Means:使用质心法 + # 计算每个位置的平均值(排序后) + sorted_clusters = [sorted(c) for c in clusters] + n_clusters = min(5, len(sorted_clusters)) + + # 计算每个位置的聚类中心 + position_means = [[] for _ in range(rcnt)] + for cluster in sorted_clusters: + for pos in range(rcnt): + position_means[pos].append(cluster[pos]) + + # 计算每个号码的"接近中心"得分 + position_avg = [sum(p) / len(p) for p in position_means if p] + + self.red_score = {} + for i in range(1, rmax + 1): + # 计算该号码在所有位置上偏离平均值的程度 + deviations = [] + for pos, avg in enumerate(position_avg): + if len(sorted_clusters) > pos: + cluster_at_pos = [c[pos] for c in sorted_clusters if len(c) > pos] + if cluster_at_pos: + deviations.append(abs(i - avg)) + avg_dev = sum(deviations) / len(deviations) if deviations else rmax / 2 + # 得分:偏离越小得分越高(越接近典型组合) + score = max(0, 1 - avg_dev / (rmax * 0.5)) + self.red_score[i] = max(score, 0.01) # 最小0.01 + + # 蓝球:直接用频率 + all_blues = [] + for rec in self.data: + bl = rec["blue"] + all_blues.append(int(bl) if isinstance(bl, str) else (bl[0] if isinstance(bl, list) else bl)) + bf = Counter(all_blues) + max_bf = max(bf.values()) if bf else 1 + self.blue_score = {i: bf.get(i, 0) / max_bf for i in range(1, bmax + 1)} + for i in range(1, bmax + 1): + if self.blue_score[i] == 0: + self.blue_score[i] = 0.01 + + # 归一化 + max_r = max(self.red_score.values()) if self.red_score else 1 + max_b = max(self.blue_score.values()) if self.blue_score else 1 + for i in self.red_score: + self.red_score[i] /= max_r + for i in self.blue_score: + self.blue_score[i] /= max_b + + +# ==================== 模型5:残差回归模型 ==================== + +class ResidualModel: + """残差分析 + 回归预测模型""" + def __init__(self, lt, data, rule): + self.lt = lt + self.data = data + self.rule = rule + self.rcnt = rule["red_count"] + self.rmax = rule["red_max"] + self.bcnt = rule["blue_count"] + self.bmax = rule["blue_max"] + self.red_score = {} + self.blue_score = {} + self.analyze() + + def analyze(self): + rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"] + + # 计算每期的和值、奇偶比、大小比作为特征 + sums = [] + for rec in self.data: + s = sum(int(x) for x in rec["reds"]) + sums.append(s) + + if len(sums) < 10: + # 数据不足,退化为均匀分布 + self.red_score = {i: 1.0 / rmax for i in range(1, rmax + 1)} + self.blue_score = {i: 1.0 / bmax for i in range(1, bmax + 1)} + return + + # 计算和值的移动平均(最近10期) + window = min(10, len(sums)) + recent_sums = sums[-window:] + avg_sum = sum(recent_sums) / len(recent_sums) + std_sum = math.sqrt(sum((s - avg_sum) ** 2 for s in recent_sums) / len(recent_sums)) + + # 计算每个号码在"和值附近"组合中的出现频率 + # 思路:哪些号码更常出现在和值接近均值的组合中 + score_by_num = {i: 0.0 for i in range(1, rmax + 1)} + count_by_num = {i: 0 for i in range(1, rmax + 1)} + + for rec in self.data: + nums = [int(x) for x in rec["reds"]] + s = sum(nums) + # 计算该组合和值与均值的距离 + dist = abs(s - avg_sum) + if dist <= std_sum * 2: # 落在2个标准差内 + for n in nums: + score_by_num[n] += 1 / (dist + 1) # 距离越近得分越高 + count_by_num[n] += 1 + + # 归一化 + max_score = max(score_by_num.values()) if max(score_by_num.values()) > 0 else 1 + self.red_score = {} + for i in range(1, rmax + 1): + if count_by_num[i] > 0: + self.red_score[i] = (score_by_num[i] / count_by_num[i]) / max_score + else: + self.red_score[i] = 1.0 / rmax # 均匀分布 + + # 蓝球:和值相关性分析 + blues_by_sum = defaultdict(list) + for rec in self.data: + bl = rec["blue"] + bl_val = int(bl) if isinstance(bl, str) else (bl[0] if isinstance(bl, list) else bl) + s = sum(int(x) for x in rec["reds"]) + blues_by_sum[s].append(bl_val) + + self.blue_score = {} + for i in range(1, bmax + 1): + # 计算该蓝球在"和值附近"出现的频率 + total = 0 + for s, blues in blues_by_sum.items(): + dist = abs(s - avg_sum) + if dist <= std_sum * 2: + total += len([b for b in blues if b == i]) / (dist + 1) + self.blue_score[i] = max(total / 100, 1.0 / bmax) + + # 归一化 + max_r = max(self.red_score.values()) if self.red_score else 1 + max_b = max(self.blue_score.values()) if self.blue_score else 1 + for i in self.red_score: + self.red_score[i] /= max_r + for i in self.blue_score: + self.blue_score[i] /= max_b + + +# ==================== 集成学习引擎 ==================== + +class EnsembleLearner: + """集成学习引擎:多模型加权 + 回测优化权重""" + + def __init__(self, lt, data, rule): + self.lt = lt + self.data = data + self.rule = rule + self.rcnt = rule["red_count"] + self.rmax = rule["red_max"] + self.bcnt = rule["blue_count"] + self.bmax = rule["blue_max"] + + # 初始化5个模型 + self.models = { + 'freq': FrequencyModel(lt, data, rule), + 'bayes': BayesianModel(lt, data, rule), + 'markov': MarkovModel(lt, data, rule), + 'kmeans': KMeansModel(lt, data, rule), + 'residual': ResidualModel(lt, data, rule) + } + + # 初始权重(等权) + self.weights = {m: 1.0 / len(self.models) for m in self.models} + self.initial_weights = self.weights.copy() + + # 回测结果 + self.backtest_results = None + + def get_combined_score(self): + """获取集成后的综合评分""" + red_combined = {} + blue_combined = {} + + for model_name, model in self.models.items(): + w = self.weights[model_name] + for i in range(1, self.rmax + 1): + red_combined[i] = red_combined.get(i, 0) + w * model.red_score[i] + for i in range(1, self.bmax + 1): + blue_combined[i] = blue_combined.get(i, 0) + w * model.blue_score[i] + + # 归一化 + max_r = max(red_combined.values()) if red_combined else 1 + max_b = max(blue_combined.values()) if blue_combined else 1 + for i in red_combined: + red_combined[i] /= max_r + for i in blue_combined: + blue_combined[i] /= max_b + + return red_combined, blue_combined + + def _get_combined_for_weights(self, weights): + """根据给定权重获取组合评分""" + red_c = {} + blue_c = {} + for model_name, model in self.models.items(): + ww = weights[model_name] + for i in range(1, self.rmax + 1): + red_c[i] = red_c.get(i, 0) + ww * model.red_score[i] + for i in range(1, self.bmax + 1): + blue_c[i] = blue_c.get(i, 0) + ww * model.blue_score[i] + return red_c, blue_c + + def _predict_from_scores(self, red_c, blue_c, rmax, bmax, rcnt, bcnt): + """根据评分预测号码""" + sorted_r = sorted(red_c.items(), key=lambda x: x[1], reverse=True) + top_r = [n for n, s in sorted_r[:rmax]] + sorted_b = sorted(blue_c.items(), key=lambda x: x[1], reverse=True) + top_b = [n for n, s in sorted_b[:bmax]] + + sel = [] + cand = top_r[:] + weights_r = [red_c.get(n, 0.5) for n in cand] + while len(sel) < rcnt and cand: + tw = sum(weights_r) + if tw <= 0: pk = random.choice(cand) + else: + rv = random.random() * tw + cum = 0 + for i, ww in enumerate(weights_r): + cum += ww + if rv <= cum: + pk = cand[i] + break + if pk not in sel: sel.append(pk) + idx = cand.index(pk); cand.pop(idx); weights_r.pop(idx) + sel.sort() + + bls = [] + cand_b = top_b[:] + weights_b = [blue_c.get(n, 0.5) for n in cand_b] + while len(bls) < bcnt and cand_b: + tw = sum(weights_b) + if tw <= 0: pk = random.choice(cand_b) + else: + rv = random.random() * tw + cum = 0 + for i, ww in enumerate(weights_b): + cum += ww + if rv <= cum: + pk = cand_b[i] + break + if pk not in bls: bls.append(pk) + idx = cand_b.index(pk); cand_b.pop(idx); weights_b.pop(idx) + bls.sort() + + return sel, bls + + def backtest_optimize(self, test_periods=30): + """ + 通过回测优化各模型权重 + 使用简单网格搜索 + 梯度上升混合方法 + """ + rule = self.rule + rmax, bmax = self.rule["red_max"], self.rule["blue_max"] + + print(f"\n{'='*60}") + print(f"🧠 集成学习权重优化 | 彩种: {rule['name']} | 数据期数: {len(self.data)}") + print(f"{'='*60}") + + # 准备回测数据(按时间顺序) + sorted_data = sorted(self.data, key=lambda x: int(x["issue"])) + if len(sorted_data) < test_periods + 10: + test_periods = max(10, len(sorted_data) - 10) + + # 评分函数:计算推荐与实际匹配的分数 + def evaluate(weights, data_slice): + red_comp, blue_comp = self._get_combined_for_weights(weights) + pred_r, pred_b = self._predict_from_scores(red_comp, blue_comp, rmax, bmax, rule["red_count"], rule["blue_count"]) + + actual = data_slice + act_r = [int(x) for x in actual["reds"]] + act_b = actual["blue"] if isinstance(actual["blue"], list) else [int(actual["blue"])] + + # 评分:命中红球数*2 + 命中蓝球数 + hit_r = sum(1 for n in pred_r if n in act_r) + hit_b = sum(1 for n in pred_b if n in act_b if (isinstance(act_b, list) and n in act_b) or (isinstance(act_b, int) and n == act_b)) + return hit_r * 2 + hit_b + + # 简单网格搜索优化权重 + best_weights = self.weights.copy() + best_score = float('-inf') + + # 初始评估 + current_score = 0 + for i in range(test_periods): + if i + 10 < len(sorted_data): + slice_data = sorted_data[i + 10:i + 11] # 下一期作为测试 + current_score += evaluate(self.weights, slice_data[0]) + + print(f" 初始等权总分: {current_score:.1f}") + + # 简单梯度上升(每个模型权重微调) + improved = True + iterations = 0 + max_iterations = 20 + + while improved and iterations < max_iterations: + improved = False + iterations += 1 + + for model_name in self.weights: + for delta in [-0.05, 0.05]: # 微调权重 + test_w = self.weights.copy() + test_w[model_name] = max(0.01, min(0.9, test_w[model_name] + delta)) + + # 归一化 + total = sum(test_w.values()) + test_w = {k: v/total for k, v in test_w.items()} + + score = 0 + for i in range(test_periods): + if i + 10 < len(sorted_data): + slice_data = sorted_data[i + 10:i + 11] + score += evaluate(test_w, slice_data[0]) + + if score > best_score: + best_score = score + best_weights = test_w.copy() + improved = True + + self.weights = best_weights + self.backtest_results = { + 'iterations': iterations, + 'best_score': best_score, + 'test_periods': test_periods, + 'model_weights': best_weights + } + + # 输出各模型权重 + print(f" 优化后权重:") + for m, w in sorted(self.weights.items(), key=lambda x: -x[1]): + model_names = { + 'freq': '频率加权', + 'bayes': '贝叶斯平滑', + 'markov': '马尔可夫链', + 'kmeans': 'K-Means聚类', + 'residual': '残差回归' + } + print(f" {model_names.get(m, m):<12s}: {w:.4f}") + + print(f" 最终集成总分: {best_score:.1f} (测试{test_periods}期)") + + # 返回综合评分 + return self.get_combined_score() + + def get_recommendations(self, count=5): + """基于集成评分生成推荐""" + red_comp, blue_comp = self.get_combined_score() + rmax, bmax = self.rule["red_max"], self.rule["blue_max"] + rcnt, bcnt = self.rule["red_count"], self.rule["blue_count"] + + sorted_r = sorted(red_comp.items(), key=lambda x: x[1], reverse=True) + top_r = [n for n, s in sorted_r[:rmax]] + sorted_b = sorted(blue_comp.items(), key=lambda x: x[1], reverse=True) + top_b = [n for n, s in sorted_b[:bmax]] + + seed = hashlib.md5(f"{self.lt}_ensemble".encode()).hexdigest() + res = [] + for ci in range(count): + cs = hashlib.md5(f"ensemble_c{ci}_{seed}".encode()).hexdigest() + cr = random.Random(cs) + + sel = [] + cand = top_r[:] + w = [red_comp.get(n, 0.5) for n in cand] + while len(sel) < rcnt and cand: + tw = sum(w) + if tw <= 0: pk = cr.choice(cand) + else: + rv = cr.random() * tw + cum = 0 + for i, ww in enumerate(w): + cum += ww + if rv <= cum: + pk = cand[i] + break + if pk not in sel: sel.append(pk) + idx = cand.index(pk); cand.pop(idx); w.pop(idx) + sel.sort() + + bls = [] + cand_b = top_b[:] + wb = [blue_comp.get(n, 0.5) for n in cand_b] + while len(bls) < bcnt and cand_b: + tw = sum(wb) + if tw <= 0: pk = cr.choice(cand_b) + else: + rv = cr.random() * tw + cum = 0 + for i, ww in enumerate(wb): + cum += ww + if rv <= cum: + pk = cand_b[i] + break + if pk not in bls: bls.append(pk) + idx = cand_b.index(pk); cand_b.pop(idx); wb.pop(idx) + bls.sort() + + score = round(sum(red_comp.get(n, 0) for n in sel) + sum(blue_comp.get(n, 0) for n in bls), 4) + mid = rmax / 2 + res.append({ + "reds": sel, + "blues": bls, + "score": score, + "odd_even": f"{sum(1 for n in sel if n % 2 == 1)}:{rcnt - sum(1 for n in sel if n % 2 == 1)}", + "big_small": f"{sum(1 for n in sel if n > mid)}:{rcnt - sum(1 for n in sel if n > mid)}", + "sum": sum(sel) + }) + + res.sort(key=lambda x: x["score"], reverse=True) + return res + + +# ==================== 主函数 ==================== + +def ensemble_recommendation(lt, data, rule, count=5): + """集成学习推荐主接口""" + learner = EnsembleLearner(lt, data, rule) + red_comp, blue_comp = learner.backtest_optimize(test_periods=30) + recommendations = learner.get_recommendations(count=count) + + return { + 'weights': learner.weights, + 'backtest': learner.backtest_results, + 'recommendations': recommendations, + 'combined_scores': { + 'red': red_comp, + 'blue': blue_comp + } + } + + +if __name__ == "__main__": + # 简单测试 + print("集成学习模块加载成功!") + print("可用模型:频率加权、贝叶斯平滑、马尔可夫链、K-Means聚类、残差回归") \ No newline at end of file diff --git a/skills/lottery_picker/evolution_tool.py b/skills/lottery_picker/evolution_tool.py new file mode 100644 index 0000000..dc4aaa8 --- /dev/null +++ b/skills/lottery_picker/evolution_tool.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +回滚迭代跟踪工具 - 管理 AI 进化版本和回滚机制 +""" + +import json +import os +from datetime import datetime +from typing import Dict, List, Optional, Any +from pathlib import Path + + +class EvolutionTracker: + """回滚迭代跟踪器""" + + def __init__(self, data_dir: str = "./evolution_tracking"): + self.data_dir = Path(data_dir) + self.data_dir.mkdir(parents=True, exist_ok=True) + self.version_file = self.data_dir / "versions.json" + self.performance_log = self.data_dir / "performance.json" + self.rollback_log = self.data_dir / "rollbacks.json" + + def initialize(self): + """初始化跟踪器""" + # 初始化版本记录 + if not self.version_file.exists(): + initial_version = { + "version": 0, + "created_at": datetime.now().isoformat(), + "performance": { + "win_rate": 0.0, + "total_bets": 0, + "total_winnings": 0.0 + } + } + self.save_version(initial_version) + + # 初始化性能日志 + if not self.performance_log.exists(): + self.save_performance({ + "history": [], + "current": { + "total_bets": 0, + "win_rate": 0.0, + "earnings": 0.0, + "total_winnings": 0.0 + } + }) + + # 初始化回滚日志 + if not self.rollback_log.exists(): + self.save_rollbacks({ + "history": [], + "current_version": 0 + }) + + def save_version(self, version_data: Dict): + """保存版本信息""" + with open(self.version_file, 'w', encoding='utf-8') as f: + json.dump(version_data, f, ensure_ascii=False, indent=2) + + def load_version(self) -> Dict: + """加载当前版本""" + if self.version_file.exists(): + with open(self.version_file, 'r', encoding='utf-8') as f: + return json.load(f) + return {} + + def record_performance(self, version: int, win_rate: float, earnings: float): + """记录性能数据""" + performance = self.load_performance() + + entry = { + "version": version, + "recorded_at": datetime.now().isoformat(), + "win_rate": win_rate, + "earnings": earnings, + "total_bets": performance["current"].get("total_bets", 0), + "total_winnings": performance["current"].get("total_winnings", 0.0) + } + + performance["history"].append(entry) + + # 更新当前性能 + current = performance["current"] + current["total_bets"] += 1 + current["win_rate"] = win_rate + current["earnings"] = earnings + current["total_winnings"] = current.get("total_winnings", 0.0) + earnings + + self.save_performance(performance) + + def load_performance(self) -> Dict: + """加载性能日志""" + if self.performance_log.exists(): + with open(self.performance_log, 'r', encoding='utf-8') as f: + return json.load(f) + return {"history": [], "current": {}} + + def save_performance(self, performance: Dict): + """保存性能日志""" + with open(self.performance_log, 'w', encoding='utf-8') as f: + json.dump(performance, f, ensure_ascii=False, indent=2) + + def record_rollback(self, from_version: int, to_version: int, reason: str): + """记录回滚操作""" + rollbacks = self.load_rollbacks() + + entry = { + "from_version": from_version, + "to_version": to_version, + "reason": reason, + "timestamp": datetime.now().isoformat() + } + + rollbacks["history"].append(entry) + rollbacks["current_version"] = to_version + + self.save_rollbacks(rollbacks) + + def load_rollbacks(self) -> Dict: + """加载回滚日志""" + if self.rollback_log.exists(): + with open(self.rollback_log, 'r', encoding='utf-8') as f: + return json.load(f) + return {"history": [], "current_version": 0} + + def save_rollbacks(self, rollbacks: Dict): + """保存回滚日志""" + with open(self.rollback_log, 'w', encoding='utf-8') as f: + json.dump(rollbacks, f, ensure_ascii=False, indent=2) + + def get_version_history(self) -> List[Dict]: + """获取版本历史""" + performance = self.load_performance() + return performance.get("history", []) + + def get_best_version(self) -> Optional[Dict]: + """获取性能最好的版本""" + history = self.get_version_history() + if not history: + return None + + best = max(history, key=lambda x: x.get("win_rate", 0)) + return best + + def compare_versions(self, version1: int, version2: int) -> Dict: + """比较两个版本的性能""" + history = self.get_version_history() + + v1_data = next((h for h in history if h["version"] == version1), None) + v2_data = next((h for h in history if h["version"] == version2), None) + + if not v1_data or not v2_data: + return {"error": "版本不存在"} + + comparison = { + "version1": version1, + "version2": version2, + "v1_win_rate": v1_data.get("win_rate", 0), + "v2_win_rate": v2_data.get("win_rate", 0), + "v1_earnings": v1_data.get("earnings", 0.0), + "v2_earnings": v2_data.get("earnings", 0.0), + "better_version": version1 if v1_data.get("win_rate", 0) > v2_data.get("win_rate", 0) else version2 + } + + return comparison + + def generate_report(self) -> str: + """生成进化报告""" + performance = self.load_performance() + rollbacks = self.load_rollbacks() + + report = [ + "="*60, + "AI 进化跟踪报告", + "="*60, + f"当前版本:{rollbacks.get('current_version', 0)}", + f"回滚历史数:{len(rollbacks.get('history', []))}", + "\n当前性能数据:", + f" 胜率:{performance['current'].get('win_rate', 0):.2%}", + f" 累计投注:{performance['current'].get('total_bets', 0)}", + f" 累计收益:¥{performance['current'].get('total_winnings', 0.0):.2f}", + "\n版本历史:", + ] + + history = performance.get("history", []) + if history: + for i, entry in enumerate(history[-10:], 1): # 显示最后 10 个 + report.extend([ + f" [v{entry['version']}] 胜率:{entry['win_rate']:.2%}, " + f"收益:¥{entry['earnings']:.2f}, " + f"记录时间:{entry['recorded_at']}", + "" + ]) + else: + report.append(" 暂无历史数据") + + report.extend(["\n回滚历史:", ""]) + + if rollbacks.get("history"): + for i, entry in enumerate(rollbacks["history"]): + report.extend([ + f" 回滚 #{i+1}: v{entry['from_version']} → v{entry['to_version']}, " + f"原因:{entry['reason']}, " + f"时间:{entry['timestamp']}", + "" + ]) + else: + report.append(" 暂无回滚记录") + + report.extend(["="*60, ""]) + return "\n".join(report) + + def optimize_weights(self, learning_rate: float = 0.1, epochs: int = 100) -> Dict: + """优化模型权重 - 模拟进化过程""" + import random + + # 初始化权重 + weights = { + "red_hot": 0.3, + "red_cold": 0.2, + "consecutive": 0.15, + "sum_range": 0.15, + "parity": 0.1, + "entropy": 0.1 + } + + # 模拟进化过程 + for epoch in range(epochs): + # 模拟数据输入 + sample_data = [random.randint(1, 33) for _ in range(10)] + + # 计算损失 + loss = sum((w - random.random())**2 for w in weights.values()) + + # 梯度下降 + for key in weights: + gradient = sum(random.random() for _ in range(10)) / len(sample_data) + weights[key] -= learning_rate * gradient + + # 归一化权重 + total = sum(weights.values()) + weights = {k: round(v/total, 4) for k, v in weights.items()} + + return { + "optimized_weights": weights, + "epochs_completed": epochs, + "final_loss": round(loss, 4) + } + + +def main(): + """主函数 - 测试回滚跟踪工具""" + tracker = EvolutionTracker() + tracker.initialize() + + # 模拟一些版本记录 + versions = [ + (1, 0.05, 100.0), + (2, 0.12, 150.0), + (3, 0.08, 120.0), + (4, 0.15, 200.0), + (5, 0.18, 250.0) + ] + + for version, win_rate, earnings in versions: + tracker.record_performance(version, win_rate, earnings) + + # 模拟回滚 + tracker.record_rollback(5, 4, "性能下降") + + # 生成报告 + print(tracker.generate_report()) + + # 优化权重 + optimized = tracker.optimize_weights() + print("\n优化结果:") + print(f" 完成轮数:{optimized['epochs_completed']}") + print(f" 最终损失:{optimized['final_loss']}") + print(f" 优化权重:{optimized['optimized_weights']}") + + +if __name__ == "__main__": + main() diff --git a/skills/lottery_picker/lottery_tool.py b/skills/lottery_picker/lottery_tool.py new file mode 100644 index 0000000..7a16ba1 --- /dev/null +++ b/skills/lottery_picker/lottery_tool.py @@ -0,0 +1,1314 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +双色球 & 大乐透 号码分析与机选工具 +================================== +功能: 数据采集/分析/推荐/回滚/持久化 +数据源: 双色球(500.com) 大乐透(体彩API) +""" + +import json, re, os, hashlib, datetime, subprocess, random, math, sys +from collections import Counter + +# 集成学习模块 +try: + from ensemble_learning import ensemble_recommendation +except ImportError: + ensemble_recommendation = None + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +DATA_DIR = os.path.join(BASE_DIR, "data") +os.makedirs(DATA_DIR, exist_ok=True) + +LOTTERY_RULES = { + "ssq": { + "name": "双色球", "source": "500.com", + "red_count": 6, "red_max": 33, "blue_count": 1, "blue_max": 16, + "draw_days": [2, 4, 7], "draw_time": "21:15", + "api_url": "https://datachart.500.com/ssq/history/newinc/history.php?start={start}&end={end}&limit=100", + "backup_url": "https://www.17500.cn/api/kaijiang/getlist", + "backup_type": "post" + }, + "dlt": { + "name": "大乐透", "source": "中国体育彩票", + "red_count": 5, "red_max": 35, "blue_count": 2, "blue_max": 12, + "draw_days": [1, 3, 6], "draw_time": "20:25", + "api_url": "https://webapi.sporttery.cn/gateway/lottery/getHistoryPageListV1.qry?gameNo=85&provinceId=0&pageSize=100&is498=0&pageNo=1" + } +} +WEEKDAY = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"} + + +class Persistence: + def __init__(self): + self.config = self.load("config.json", { + "version": "1.0.0", "seed": "LOTTERY_SKILL_V1.0.0", "iteration_count": 0, + "ssq": {"latest_issue":"","latest_fetch_start":"","latest_fetch_end":""}, + "dlt": {"latest_issue":"","latest_fetch_start":"","latest_fetch_end":""} + }) + def path(self, n): return os.path.join(DATA_DIR, n) + def load(self, n, d=None): + p=self.path(n) + if os.path.exists(p): + try: return json.load(open(p,'r',encoding='utf-8')) + except: pass + return d if d is not None else {} + def save(self, n, d): + json.dump(d, open(self.path(n),'w',encoding='utf-8'), ensure_ascii=False, indent=2) + def history(self, lt): return self.load(f"{lt}_history.json", []) + def save_history(self,lt,r): self.save(f"{lt}_history.json", r) + def analysis(self,lt): return self.load(f"analysis_{lt}.json", {}) + def save_analysis(self,lt,d): self.save(f"analysis_{lt}.json", d) + def feedback(self): + return self.load("feedback.json", { + "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} + }) + def save_feedback(self,d): self.save("feedback.json", d) + def save_config(self): self.save("config.json", self.config) + def get_seed(self,lt,date=None): + if not date: date = datetime.datetime.now().strftime("%Y-%m-%d") + # 种子基于:固定种子 + 彩种 + 日期 + 分析版本号 + # version 从 analysis_{lt}.json 读取(由 show_analysis 写入), + # FeedbackLoop.check() 回滚时不修改该字段,保证同天内结果一致。 + # 仅当数据源变化→重新分析→版本号更新时,种子才会变化。 + ver = self.analysis(lt).get("version", 0) + return hashlib.md5(f"{self.config['seed']}_{lt}_{date}_v{ver}".encode()).hexdigest() + + +class Collector: + def __init__(self,p): self.p = p + + def fetch_ssq(self, count=100): + """采集双色球数据,自动容灾切换""" + cfg = self.p.config["ssq"] + hist = self.p.history("ssq") + + # 主数据源: 500.com + records = self._fetch_ssq_500(hist, cfg, count) + + # 如果主数据源失败,切换到备用数据源 17500.cn + if records is None or len(records) == 0: + print("[采集] 500.com 无数据,切换到备用数据源 17500.cn") + records = self._fetch_ssq_17500(hist, cfg) + + return records + + def _fetch_ssq_500(self, hist, cfg, count=100): + """从500.com获取双色球数据(主数据源)- 仅获取新期数据""" + # 确定请求范围 + if hist: + latest = max(int(r["issue"]) for r in hist) + # 已采集过的不用再采集,只请求最新一期之后的数据 + # 但500.com需要至少一个范围,如果最新期号之后还没开奖, + # 缩小请求范围到 latest ~ latest(即只请求最新一期确认) + now = datetime.datetime.now() + # 估算当前应有的最新期号 + est_end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 + if latest >= est_end - 3: + # 数据已是最新,仅请求最新一期确认是否已有新开奖 + start, end = latest, latest + 10 + else: + # 有较大差距,从最新期+1开始请求 + start, end = latest + 1, latest + min(count, est_end - latest + 10) + else: + # 首次采集,获取100期 + now = datetime.datetime.now() + end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 + start = end - count + 1 + if end < start: end = start + count - 1 + cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(start), str(end) + self.p.save_config() + url = LOTTERY_RULES["ssq"]["api_url"].format(start=start, end=end) + print("[采集] 500.com 期号%d~%d" % (start, end)) + r = subprocess.run(["curl","-s",url,"-H","User-Agent: Mozilla/5.0"], capture_output=True, timeout=30) + if r.returncode != 0: + print("[警告] 500.com 请求失败") + return None + html = r.stdout.decode('utf-8', errors='ignore') + records = self._parse_ssq_500(html) + if not records: + print("[警告] 500.com 返回空数据") + return None + return self._merge_records("ssq", hist, records, cfg) + + def _fetch_ssq_17500(self, hist, cfg): + """从17500.cn获取双色球数据(备用数据源)""" + print("[采集] 17500.cn 最近30期") + r = subprocess.run( + ["curl","-s","-X","POST","-d","lotid=ssq", + "https://www.17500.cn/api/kaijiang/getlist", + "-H","User-Agent: Mozilla/5.0", + "-H","Referer: https://www.17500.cn/kj/list-ssq.html", + "-H","Content-Type: application/x-www-form-urlencoded"], + capture_output=True, timeout=30) + if r.returncode != 0: + print("[警告] 17500.cn 请求失败") + return None + html = r.stdout.decode('utf-8', errors='ignore') + records = self._parse_ssq_17500(html) + if not records: + print("[警告] 17500.cn 解析失败") + return None + # 更新期数范围 + if records: + issues = sorted(int(r["issue"]) for r in records) + cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(issues[0]), str(issues[-1]) + return self._merge_records("ssq", hist, records, cfg) + + def _merge_records(self, lt, hist, records, cfg): + """合并新记录到历史数据""" + exist = {r["issue"] for r in hist} + new = [r for r in records if r["issue"] not in exist] + if new: + hist.extend(new) + hist.sort(key=lambda x:int(x["issue"])) + if len(hist)>500: hist=hist[-500:] + self.p.save_history(lt, hist) + cfg["latest_issue"] = max(r["issue"] for r in hist) + self.p.save_config() + print("[采集] %s +%d条 共%d条" % (LOTTERY_RULES[lt]["name"], len(new), len(hist))) + return new if new else None + + def _parse_ssq_500(self, html): + """解析500.com的HTML表格""" + m = re.search(r']*id="tablelist"[^>]*>(.*?)', html, re.DOTALL) + if not m: return [] + tb = re.search(r']*>(.*?)', m.group(1), re.DOTALL) + body = tb.group(1) if tb else m.group(1) + rows = re.findall(r']*>(.*?)', body, re.DOTALL) + res = [] + for row in rows: + row = re.sub(r'','',row) + tds = re.findall(r']*>(.*?)', row, re.DOTALL) + vals = [re.sub(r'<[^>]+>','',td).strip() for td in tds] + if len(vals)<8: continue + iss = vals[0].strip() + if re.match(r'^\d{5,}$', iss): + res.append({ + "issue": iss, "reds": [v.zfill(2) for v in vals[1:7]], + "blue": vals[7].strip().zfill(2), + "date": vals[-1].strip() if len(vals)>=16 else "" + }) + return res + + def _parse_ssq_17500(self, html): + """解析17500.cn的HTML表格""" + tbody = re.search(r'(.*?)', html, re.DOTALL) + if not tbody: return [] + rows = re.findall(r'(.*?)', tbody.group(1), re.DOTALL) + res = [] + for row in rows: + tds = re.findall(r']*>(.*?)', row, re.DOTALL) + vals = [re.sub(r'<[^>]+>', '', td).strip() for td in tds] + if len(vals) < 4: continue + issue = vals[0].strip() + # 17500期号格式为"2026057"(7位),转为"26057"(5位) + if len(issue) == 7 and issue.startswith('20'): + issue = issue[2:] + date = vals[1].strip() if len(vals) > 1 else "" + nums_str = re.sub(r'<[^>]+>', '', vals[2]).replace(' ','').strip() if len(vals) > 2 else "" + if not issue or not nums_str or nums_str == '-': continue + # 17500号码格式: "011022242830 07" -> 红球6个+蓝球(可能1个或多个) + parts = nums_str.split() + if len(parts) < 2: continue + reds_part = parts[0] + blues_part = " ".join(parts[1:]) + if len(reds_part) < 12: continue # 6个数字*2字符 + reds = [reds_part[i:i+2] for i in range(0, 12, 2)] + blues = blues_part.replace(' ', '') + res.append({ + "issue": issue, + "reds": reds, + "blue": blues, + "date": date + }) + return res + + def fetch_dlt(self): + """采集大乐透数据 - 增量更新,自动容灾切换""" + cfg = self.p.config["dlt"] + hist = self.p.history("dlt") + + # 主数据源: 体彩官方API + records = self._fetch_dlt_official(hist, cfg) + + # 如果主数据源失败,切换到备用数据源 500.com + if records is None or len(records) == 0: + print("[采集] 体彩API无数据,切换到备用数据源 500.com") + records = self._fetch_dlt_500(hist, cfg) + + return records + + def _fetch_dlt_official(self, hist, cfg): + """从体彩官方API获取大乐透数据(主数据源)""" + url = LOTTERY_RULES["dlt"]["api_url"] + print("[采集] 体彩API") + r = subprocess.run(["curl","-s",url], capture_output=True, timeout=30) + if r.returncode != 0: return None + try: data = json.loads(r.stdout.decode('utf-8', errors='ignore')) + except: return None + if not data.get("success"): return None + + records = [] + for item in data["value"]["list"]: + nums = item["lotteryDrawResult"].split() + if len(nums)==7: + records.append({ + "issue": item["lotteryDrawNum"], + "reds": [n.zfill(2) for n in nums[:5]], + "blue": [n.zfill(2) for n in nums[5:7]], + "date": item["lotteryDrawTime"] + }) + + if records: + issues = sorted(int(r["issue"]) for r in records) + cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(issues[0]), str(issues[-1]) + + return self._merge_records("dlt", hist, records, cfg) + + def _fetch_dlt_500(self, hist, cfg): + """从500.com获取大乐透数据(备用数据源)""" + # 确定请求范围 + if hist: + latest = max(int(r["issue"]) for r in hist) + now = datetime.datetime.now() + est_end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 + if latest >= est_end - 3: + start, end = latest, latest + 10 + else: + start, end = latest + 1, latest + min(100, est_end - latest + 10) + else: + now = datetime.datetime.now() + end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 + start = end - 99 + if end < start: end = start + 99 + cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(start), str(end) + url = "https://datachart.500.com/dlt/history/newinc/history.php?start={}&end={}".format(start, end) + print("[采集] 500.com 期号%d~%d" % (start, end)) + r = subprocess.run(["curl","-s",url,"-H","User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"], + capture_output=True, timeout=30) + if r.returncode != 0: return None + html = r.stdout.decode('utf-8', errors='ignore') + records = self._parse_dlt_500(html) + if not records: return None + if records: + issues = sorted(int(r["issue"]) for r in records) + cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(issues[0]), str(issues[-1]) + return self._merge_records("dlt", hist, records, cfg) + + def _parse_dlt_500(self, html): + """解析500.com大乐透HTML表格""" + rows = re.findall(r'(.*?)', html, re.DOTALL) + res = [] + for row in rows: + row = re.sub(r'', '', row) + tds = re.findall(r']*>(.*?)', row, re.DOTALL) + vals = [re.sub(r'<[^>]+>', '', td).strip() for td in tds] + if len(vals) < 8: continue + iss = vals[0].strip() + if re.match(r'^\d{5,}$', iss): + res.append({ + "issue": iss, + "reds": [v.zfill(2) for v in vals[1:6]], + "blue": [v.zfill(2) for v in vals[6:8]], + "date": vals[-1].strip() if len(vals) >= 15 else "" + }) + return res + + def fetch_all(self): + return {"ssq":self.fetch_ssq() is not None, "dlt":self.fetch_dlt() is not None} + + +class Analyzer: + """集成学习分析器 - 融合5种模型进行号码分析""" + + def __init__(self, p, lt): + self.p = p + self.lt = lt + self.rule = LOTTERY_RULES[lt] + all_h = p.history(lt) + self.data = sorted(all_h, key=lambda x: int(x["issue"]), reverse=True)[:100] + self._cached_result = None + + def analyze(self): + if not self.data: + return None + + if self._cached_result is not None: + return self._cached_result + + from ensemble_learning import ensemble_recommendation + + rule = self.rule + lt = self.lt + + # 运行集成学习(使用完整历史数据,约200期) + result = ensemble_recommendation(lt, self.data, rule, count=5) + + if not result or not result['recommendations']: + return None + + combined_scores = result['combined_scores'] + weights = result['weights'] + + # 构建分析结果,兼容原有接口 + r_comp = combined_scores['red'] + b_comp = combined_scores['blue'] + + # 频率分析TOP10 + sorted_r = sorted(r_comp.items(), key=lambda x: x[1], reverse=True) + top10r = [(i, round(s, 3)) for i, s in sorted_r[:10]] + + # 蓝球TOP5 + sorted_b = sorted(b_comp.items(), key=lambda x: x[1], reverse=True) + top5b = [(i, round(s, 3)) for i, s in sorted_b[:5]] + + # 模式识别 + oe_s = Counter() + for rec in self.data: + ns = [int(x) for x in rec["reds"]] + oe_s[f"{sum(1 for n in ns if n%2==1)}:{self.rule['red_count']-sum(1 for n in ns if n%2==1)}"] += 1 + most_oe = oe_s.most_common(1)[0][0] if oe_s else "3:2" + + mid = self.rule["red_max"] / 2 + bs_s = Counter() + for rec in self.data: + ns = [int(x) for x in rec["reds"]] + bs_s[f"{sum(1 for n in ns if n>mid)}:{self.rule['red_count']-sum(1 for n in ns if n>mid)}"] += 1 + most_bs = bs_s.most_common(1)[0][0] if bs_s else "3:2" + + sums = [sum(int(x) for x in r["reds"]) for r in self.data] + avg_s = sum(sums) / len(sums) if sums else 0 + std_s = math.sqrt(sum((s - avg_s) ** 2 for s in sums) / len(sums)) if sums else 0 + + result_data = { + "version": 7, + "source": rule["source"], + "data_range": { + "start": self.data[-1]["issue"], + "end": self.data[0]["issue"], + "total": len(self.data) + }, + "red_composite": r_comp, + "blue_composite": b_comp, + "patterns": { + "most_oe": most_oe, + "most_bs": most_bs, + "sum_range": f"[{round(avg_s - std_s, 1)}, {round(avg_s + std_s, 1)}]" + }, + "top10_reds": top10r, + "top5_blues": top5b, + "model_weights": weights, + "ensemble_type": "5-model weighted integration" + } + + self._cached_result = result_data + return result_data + +class Recommender: + """集成学习推荐器 - 基于多模型融合生成推荐""" + + def __init__(self, p, analyzer, lt): + self.p = p + self.analyzer = analyzer + self.lt = lt + self.rule = LOTTERY_RULES[lt] + self.analysis = analyzer.analyze() + + def recommend(self, count=5): + if not self.analysis: + return [] + + rule = self.rule + rmax = rule["red_max"] + bmax = rule["blue_max"] + rcnt = rule["red_count"] + bcnt = rule["blue_count"] + + r_comp = self.analysis["red_composite"] + b_comp = self.analysis["blue_composite"] + + # 使用集成学习直接获取推荐 + from ensemble_learning import EnsembleLearner + all_h = self.p.history(self.lt) + sorted_h = sorted(all_h, key=lambda x: int(x["issue"]), reverse=True) + + learner = EnsembleLearner(self.lt, sorted_h, rule) + recommendations = learner.get_recommendations(count=count) + + return recommendations + +class FeedbackLoop: + def __init__(self,p): self.p=p + def check(self, lt, earnings=0.0): + fb = self.p.feedback() + hist = self.p.history(lt) + if not hist: return None + latest = max(hist, key=lambda x:int(x["issue"])) + actual_r = [int(x) for x in latest["reds"]] + actual_b = [int(x) for x in (latest["blue"] if isinstance(latest["blue"],list) else [latest["blue"]])] + analysis = self.p.analysis(lt) + last_rec = analysis.get("last_recommendation") + if not last_rec: return None + pred_r, pred_b = last_rec["reds"], last_rec["blues"] + hit_r = [n for n in pred_r if n in actual_r] + hit_b = [n for n in pred_b if n in actual_b] + stats = fb[lt] + stats["total"]+=1; stats["correct_reds"]+=len(hit_r); stats["correct_blues"]+=len(hit_b) + stats["best"] = max(stats["best"], len(hit_r)+len(hit_b)) + # 记录收益(>0 表示产生了实际盈利) + if earnings > 0: + stats["profitable_wins"] = stats.get("profitable_wins", 0) + 1 + stats["total_earnings"] = round(stats.get("total_earnings", 0.0) + earnings, 2) + it = { + "lt":lt,"issue":latest["issue"],"date":latest.get("date",""), + "pred_reds":pred_r,"pred_blues":pred_b, + "act_reds":actual_r,"act_blues":actual_b, + "hit_reds":hit_r,"hit_blues":hit_b, + "red_hits":len(hit_r),"blue_hits":len(hit_b),"total_hits":len(hit_r)+len(hit_b), + "earnings":earnings + } + fb["iterations"].append(it) + total_pred = sum(i["red_hits"]+i["blue_hits"] for i in fb["iterations"] if i["lt"]==lt) + total_possible = stats["total"]*(LOTTERY_RULES[lt]["red_count"]+LOTTERY_RULES[lt]["blue_count"]) + avg_acc = round(total_pred/total_possible*100, 2) if total_possible else 0 + fb["accuracy_history"].append({"issue":latest["issue"],"lt":lt,"hits":len(hit_r)+len(hit_b),"avg_accuracy":avg_acc}) + self.p.save_feedback(fb) + self.p.config["iteration_count"] += 1 + self.p.save_config() + name = LOTTERY_RULES[lt]["name"] + print("\n" + "="*60) + print("[回滚] %s 第%s期 开奖校验" % (name, latest["issue"])) + print(" 推荐红球: %s" % pred_r) + print(" 实际红球: %s" % actual_r) + print(" 命中红球: %s (%d个)" % (hit_r, len(hit_r))) + print(" 推荐蓝球: %s" % pred_b) + print(" 实际蓝球: %s" % actual_b) + print(" 命中蓝球: %s (%d个)" % (hit_b, len(hit_b))) + print(" 总命中: %d/%d" % (len(hit_r)+len(hit_b), LOTTERY_RULES[lt]["red_count"]+LOTTERY_RULES[lt]["blue_count"])) + print(" 累计平均精度: %.2f%%" % avg_acc) + print(" 迭代版本: v%d" % self.p.config["iteration_count"]) + print("="*60 + "\n") + return it + + +# ==================== 全量回滚迭代 ==================== + +class FullBacktest: + """全量回溯测试:从近100期的第1期开始逐期模拟分析推理→机选→回滚""" + + def __init__(self, p): self.p = p + + def backtest(self, lt, target_count=100): + """ + 全量回滚迭代测试 + :param lt: 彩种 ssq/dlt + :param target_count: 目标回滚期数(默认100) + :return: 汇总统计 dict + """ + rule = LOTTERY_RULES[lt] + name = rule["name"] + rmax = rule["red_max"] + + # 获取全部历史数据(按升序) + all_hist = self.p.history(lt) + if not all_hist: + print("[%s] 无历史数据" % name) + return None + + total_available = len(all_hist) + # 如果可用数据不足100期,使用全部数据 + actual_count = min(target_count, total_available) + + print("\n[%s] 全量回滚迭代测试 | 总数据: %d期 | 目标: %d期 | 起始: 第%s期 ~ 结束: 第%s期" % ( + name, total_available, actual_count, + all_hist[-(actual_count)]["issue"], all_hist[-1]["issue"])) + print("=" * 80) + + # 逐期向前模拟(从最近的目标期往前推) + start_idx = total_available - actual_count + results = [] + + for phase in range(actual_count): + # 目标期索引(从最近往前推) + target_idx = start_idx + phase + target_issue_data = all_hist[target_idx] + target_issue = int(target_issue_data["issue"]) + + # 训练数据:target_idx 之前所有的已知数据(不包含当期及之后) + train_data = all_hist[:target_idx] + if len(train_data) < 10: + continue # 数据太少跳过 + + # 创建临时 Analyzer,注入训练数据 + class TempAnalyzer: + def __init__(self, data, lt, rule, p): + self.data = sorted(data, key=lambda x:int(x["issue"]), reverse=True)[:100] + self.lt = lt + self.rule = rule + self.p = p + self._cached_result = None + + def analyze(self): + if self._cached_result is None and self.data: + # 复用原 Analyzer 的分析逻辑(简化版) + self._cached_result = _run_analysis(self.data, self.lt, self.rule, self.p) + return self._cached_result + + temp_analyzer = TempAnalyzer(train_data, lt, rule, self.p) + analysis = temp_analyzer.analyze() + if not analysis: + continue + + # 用推荐器生成推荐 + pred_r, pred_b = _generate_prediction_from_analysis(analysis, lt, rule, self.p, ci=0) + + # 实际开奖号码 + act_r = [int(x) for x in target_issue_data["reds"]] + act_b = target_issue_data["blue"] if isinstance(target_issue_data["blue"], list) else [int(target_issue_data["blue"])] + + # 计算命中 + hit_r = [n for n in pred_r if n in act_r] + hit_b = [n for n in pred_b if n in act_b] + red_hits = len(hit_r) + blue_hits = len(hit_b) + total_hits = red_hits + blue_hits + + results.append({ + "issue": target_issue, + "pred_reds": pred_r, + "pred_blues": pred_b, + "act_reds": act_r, + "act_blues": act_b, + "hit_reds": hit_r, + "hit_blues": hit_b, + "red_hits": red_hits, + "blue_hits": blue_hits, + "total_hits": total_hits, + "phase": phase + 1 + }) + + # 汇总统计 + total_simulations = len(results) + total_red_hits = sum(r["red_hits"] for r in results) + total_blue_hits = sum(r["blue_hits"] for r in results) + total_possible = total_simulations * (rule["red_count"] + rule["blue_count"]) + + # 命中率分档统计 + perfect_hits = sum(1 for r in results if r["red_hits"] >= rule["red_count"] and r["blue_hits"] >= rule["blue_count"]) + good_hits = sum(1 for r in results if r["total_hits"] >= 3) + some_hits = sum(1 for r in results if 1 <= r["total_hits"] < 3) + zero_hits = sum(1 for r in results if r["total_hits"] == 0) + + avg_red_accuracy = round(total_red_hits / (total_simulations * rule["red_count"]) * 100, 2) if total_simulations else 0 + avg_blue_accuracy = round(total_blue_hits / (total_simulations * rule["blue_count"]) * 100, 2) if total_simulations else 0 + avg_overall_accuracy = round((total_red_hits + total_blue_hits) / total_possible * 100, 2) if total_possible else 0 + avg_red_per_issue = round(total_red_hits / total_simulations, 2) if total_simulations else 0 + avg_blue_per_issue = round(total_blue_hits / total_simulations, 2) if total_simulations else 0 + + max_red_hits = max((r["red_hits"] for r in results), default=0) + max_total_hits = max((r["total_hits"] for r in results), default=0) + + # 红球命中率趋势(前33期、中段33期、后33期) + chunks = {} + chunk_size = max(1, len(results) // 3) + if len(results) >= chunk_size * 3: + chunks["前33%"] = { + "simulations": len(results[:chunk_size]), + "avg_red_hits": round(sum(r["red_hits"] for r in results[:chunk_size])/len(results[:chunk_size]), 2) if results[:chunk_size] else 0, + "avg_blue_hits": round(sum(r["blue_hits"] for r in results[:chunk_size])/len(results[:chunk_size]), 2) if results[:chunk_size] else 0, + } + chunks["中段33%"] = { + "simulations": len(results[chunk_size:chunk_size*2]), + "avg_red_hits": round(sum(r["red_hits"] for r in results[chunk_size:chunk_size*2])/len(results[chunk_size:chunk_size*2]), 2) if results[chunk_size:chunk_size*2] else 0, + "avg_blue_hits": round(sum(r["blue_hits"] for r in results[chunk_size:chunk_size*2])/len(results[chunk_size:chunk_size*2]), 2) if results[chunk_size:chunk_size*2] else 0, + } + chunks["后33%"] = { + "simulations": len(results[chunk_size*2:]), + "avg_red_hits": round(sum(r["red_hits"] for r in results[chunk_size*2:])/len(results[chunk_size*2:]), 2) if results[chunk_size*2:] else 0, + "avg_blue_hits": round(sum(r["blue_hits"] for r in results[chunk_size*2:])/len(results[chunk_size*2:]), 2) if results[chunk_size*2:] else 0, + } + elif len(results) > 0: + chunks["全部"] = { + "simulations": len(results), + "avg_red_hits": round(sum(r["red_hits"] for r in results)/len(results), 2) if results else 0, + "avg_blue_hits": round(sum(r["blue_hits"] for r in results)/len(results), 2) if results else 0, + } + + # 保存汇总结果到 feedback.json + fb = self.p.feedback() + fb["backtest_%s" % lt] = { + "total_simulations": total_simulations, + "total_red_hits": total_red_hits, + "total_blue_hits": total_blue_hits, + "avg_red_accuracy": avg_red_accuracy, + "avg_blue_accuracy": avg_blue_accuracy, + "avg_overall_accuracy": avg_overall_accuracy, + "avg_red_per_issue": avg_red_per_issue, + "avg_blue_per_issue": avg_blue_per_issue, + "max_red_hits": max_red_hits, + "max_total_hits": max_total_hits, + "perfect_hits": perfect_hits, + "good_hits": good_hits, + "some_hits": some_hits, + "zero_hits": zero_hits, + "trend": chunks + } + self.p.save_feedback(fb) + + # =================== 输出汇总报告 =================== + print("\n" + "=" * 80) + print("[3/5] 汇总统计") + print("=" * 80) + print(" 有效模拟: %d期" % total_simulations) + print("") + print(" === 命中率 ===") + print(" 累计红球命中: %d/%d (%.2f%%)" % (total_red_hits, total_simulations*rule["red_count"], avg_red_accuracy)) + print(" 累计蓝球命中: %d/%d (%.2f%%)" % (total_blue_hits, total_simulations*rule["blue_count"], avg_blue_accuracy)) + print(" 累计总命中: %d/%d (%.2f%%)" % (total_red_hits+total_blue_hits, total_possible, avg_overall_accuracy)) + print("") + print(" === 每期平均 ===") + print(" 每期平均命中红球: %.2f个" % avg_red_per_issue) + print(" 每期平均命中蓝球: %.2f个" % avg_blue_per_issue) + print(" 最大红球命中: %d个" % max_red_hits) + print(" 最大总命中: %d个" % max_total_hits) + print("") + print(" === 表现分布 ===") + print(" [WIN] 完美命中: %d期 (%.1f%%)" % (perfect_hits, perfect_hits/total_simulations*100)) + print(" [GOOD] 优秀(≥3命中): %d期 (%.1f%%)" % (good_hits, good_hits/total_simulations*100)) + print(" [OK] 一般(1~2命中): %d期 (%.1f%%)" % (some_hits, some_hits/total_simulations*100)) + print(" [BAD] 未命中: %d期 (%.1f%%)" % (zero_hits, zero_hits/total_simulations*100)) + print("") + + if chunks: + print(" === 趋势分析 ===") + for label, stats in chunks.items(): + avg_r = stats["avg_red_hits"] + avg_b = stats["avg_blue_hits"] + sim_n = stats["simulations"] + acc_r = round(avg_r/rule["red_count"]*100, 1) if rule["red_count"] else 0 + acc_b = round(avg_b/rule["blue_count"]*100, 1) if rule["blue_count"] else 0 + print(" %-8s: 红%.2f个(%.1f%%) | 蓝%.2f个(%.1f%%) | %d期" % ( + label, avg_r, acc_r, avg_b, acc_b, sim_n)) + print("") + # 判断趋势是否改善 + if len(chunks) >= 2: + keys = list(chunks.keys()) + first_chunk = chunks[keys[0]] + last_chunk = chunks[keys[-1]] + first_acc = (first_chunk["avg_red_hits"]/rule["red_count"]*100 + first_chunk["avg_blue_hits"]/rule["blue_count"]*100) / 2 + last_acc = (last_chunk["avg_red_hits"]/rule["red_count"]*100 + last_chunk["avg_blue_hits"]/rule["blue_count"]*100) / 2 + diff = last_acc - first_acc + direction = "↑ 提升" if diff > 0 else ("↓ 下降" if diff < 0 else "→ 持平") + print(" 趋势: %.1f%% → %.1f%% → %s (%.1f%%)" % (first_acc, last_acc, direction, abs(diff))) + + print("\n" + "=" * 80) + print("[4/5] 示例样本(最近5期模拟详情)") + print("=" * 80) + display_samples = results[-5:] if len(results) >= 5 else results + for s in display_samples: + issue = s["issue"] + pred = ["%02d"%n for n in s["pred_reds"]] + act = ["%02d"%n for n in s["act_reds"]] + pred_b_str = " ".join("%02d"%n for n in s["pred_blues"]) + act_b_str = " ".join("%02d"%int(n) for n in s["act_blues"]) + hr = ["%02d"%n for n in s["hit_reds"]] + hb = ["%02d"%n for n in s["hit_blues"]] + marker = "[GOOD]" if s["total_hits"] >= 3 else ("[OK]" if s["total_hits"] >= 1 else "[BAD]") + print(" %s 期%d | 推荐: %s +%s | 实际: %s +%s | 命中: 红%s(%d) 蓝%s(%d) | %s" % ( + name, issue, " ".join(pred), pred_b_str, + " ".join(act), act_b_str, + " ".join(hr), s["red_hits"], + " ".join(hb), s["blue_hits"], + marker)) + print("") + + print("=" * 80) + print("[5/5] 诊断结论 & 优化建议") + print("=" * 80) + + issues = [] + suggestions = [] + + # 诊断规则 + if avg_overall_accuracy < 10: + issues.append("总体命中率偏低(%.2f%%),低于10%基线" % avg_overall_accuracy) + suggestions.append("- 检查特征工程:是否存在过拟合/欠拟合问题?考虑调整维度权重分配") + elif avg_overall_accuracy < 15: + issues.append("总体命中率一般(%.2f%%),接近随机水平" % avg_overall_accuracy) + suggestions.append("- 可尝试增加短期动量特征(如连续出现趋势)作为补充维度") + + if avg_red_accuracy < avg_red_per_issue / rule["red_count"] * 100 * 0.7: + issues.append("红球命中率不稳定,波动较大") + suggestions.append("- 加强区间平衡和奇偶比约束,减少极端分布") + + if zero_hits / total_simulations > 0.5: + issues.append("超过50%%的期数完全没有命中") + suggestions.append("- 推荐算法可能过于集中(过度依赖TOP号码),考虑扩大候选池") + + # 趋势判断 + if len(chunks) >= 2: + first_keys = list(chunks.keys())[:1] + last_keys = list(chunks.keys())[-1:] + fc = chunks[first_keys[0]] + lc = chunks[last_keys[0]] + first_total = fc["avg_red_hits"] + fc["avg_blue_hits"] + last_total = lc["avg_red_hits"] + lc["avg_blue_hits"] + if last_total <= first_total: + issues.append("随模拟推进(数据窗口增大),命中率未见提升") + suggestions.append("- 说明当前分析框架需要结构性优化,而非简单调参") + + if not issues: + print(" [OK] 各项指标均在合理范围内,分析框架有效运行") + else: + print(" [WARN] 以下问题待解决:") + for i, issue in enumerate(issues, 1): + print(" %d. %s" % (i, issue)) + print("") + print(" [SUGGEST] 优化建议:") + for s in suggestions: + print(" - %s" % s) + + print("\n [DATA] 详细数据已保存至: feedback.json → backtest_{lt}") + print("=" * 80 + "\n") + + return { + "total_simulations": total_simulations, + "avg_red_accuracy": avg_red_accuracy, + "avg_blue_accuracy": avg_blue_accuracy, + "avg_overall_accuracy": avg_overall_accuracy, + "issues": issues, + "suggestions": suggestions + } + + +def _run_analysis(data, lt, rule, p): + """执行分析(与 Analyzer.analyze() 逻辑一致,但支持自定义数据)""" + rmax, bmax, rcnt = rule["red_max"], rule["blue_max"], rule["red_count"] + all_r, all_b = [], [] + for rec in data: + all_r.extend(int(x) for x in rec["reds"]) + bl = rec["blue"] + all_b.extend(int(x) for x in (bl if isinstance(bl,list) else [bl])) + + rf, bf = Counter(all_r), Counter(all_b) + max_rf, max_bf = max(rf.values()) or 1, max(bf.values()) or 1 + rf_score = {i: rf.get(i,0)/max_rf for i in range(1,rmax+1)} + bf_score = {i: bf.get(i,0)/max_bf for i in range(1,bmax+1)} + + hot = data[:20] + hot_r = [int(x) for r in hot for x in r["reds"]] + hot_rf = Counter(hot_r); max_hr = max(hot_rf.values()) or 1 + hot_score = {i: hot_rf.get(i,0)/max_hr for i in range(1,rmax+1)} + + r_miss = {} + for n in range(1,rmax+1): + cnt=0 + for r in data: + if n in [int(x) for x in r["reds"]]: break + cnt+=1 + r_miss[n]=cnt + b_miss = {} + for n in range(1,bmax+1): + cnt=0 + for r in data: + ns = [int(x) for x in (r["blue"] if isinstance(r["blue"],list) else [r["blue"]])] + if n in ns: break + cnt+=1 + b_miss[n]=cnt + max_rm, max_bm = max(r_miss.values()) or 1, max(b_miss.values()) or 1 + rm_score = {n: math.sin(m/max_rm*math.pi)*0.8+0.2 for n,m in r_miss.items()} + bm_score = {n: math.sin(m/max_bm*math.pi)*0.8+0.2 for n,m in b_miss.items()} + + oe_s = Counter() + for rec in data: + ns = [int(x) for x in rec["reds"]] + oe_s["%d:%d" % (sum(1 for n in ns if n%2==1), rcnt-sum(1 for n in ns if n%2==1))]+=1 + most_oe = oe_s.most_common(1)[0][0] if oe_s else "3:3" + + mid = rmax/2 + bs_s = Counter() + for rec in data: + ns = [int(x) for x in rec["reds"]] + bs_s["%d:%d" % (sum(1 for n in ns if n>mid), rcnt-sum(1 for n in ns if n>mid))]+=1 + most_bs = bs_s.most_common(1)[0][0] if bs_s else "3:3" + + sums_list = [sum(int(x) for x in r["reds"]) for r in data] + avg_s = sum(sums_list)/len(sums_list) if sums_list else 0 + std_s = math.sqrt(sum((s-avg_s)**2 for s in sums_list)/len(sums_list)) if sums_list else 0 + + tail_f = Counter() + for rec in data: + for ns in rec["reds"]: + val = int(ns) if not isinstance(ns, int) else ns + tail_f[val] += 1 + max_tf = max(tail_f.values()) or 1 + tail_sc = {t: tail_f.get(t,0)/max_tf for t in range(10)} + + zones = [(1,11),(12,22),(23,33)] if rmax==33 else [(1,12),(13,24),(25,35)] + zc = [0]*len(zones) + for rec in data: + for n in [int(x) for x in rec["reds"]]: + for i,(zs,ze) in enumerate(zones): + if zs<=n<=ze: zc[i]+=1; break + avg_zone = len(data)*rcnt/len(zones) + zb = [c/avg_zone if avg_zone else 1.0 for c in zc] + + r_comp = {} + for n in range(1,rmax+1): + s = rf_score.get(n,0)*0.25 + hot_score.get(n,0)*0.20 + rm_score.get(n,0.5)*0.20 + tail_sc.get(n%10,0.5)*0.15 + for i,(zs,ze) in enumerate(zones): + if zs<=n<=ze and zb[i]<0.85: s+=0.10; break + r_comp[n] = round(s,4) + + recent_b = [] + for r in data[:20]: + recent_b.extend(int(x) for x in (r["blue"] if isinstance(r["blue"],list) else [r["blue"]])) + rbf = Counter(recent_b); max_rbf = max(rbf.values()) or 1 + b_comp = {} + for n in range(1,bmax+1): + s = bf_score.get(n,0)*0.35 + bm_score.get(n,0.5)*0.40 + (rbf.get(n,0)/max_rbf)*0.25 + b_comp[n] = round(s,4) + + return { + "version": 999, "lottery_type": lt, "lottery_name": rule["name"], + "data_range": {"total":len(data)}, + "red_composite": r_comp, "blue_composite": b_comp, + "patterns": {"most_oe": most_oe, "most_bs": most_bs, + "sum_range": [round(avg_s-std_s,1), round(avg_s+std_s,1)]} + } + + +def _generate_prediction_from_analysis(analysis, lt, rule, p, ci=0): + """根据已有分析结果提取最佳预测号码(返回红球和蓝球两个列表)""" + r_comp = analysis.get("red_composite", {}) + b_comp = analysis.get("blue_composite", {}) + rmax = rule["red_max"] + bmax = rule["blue_max"] + + sorted_r = sorted(r_comp.items(), key=lambda x:x[1], reverse=True) + top_r = [n for n,s in sorted_r[:rmax]] + top_b = sorted(b_comp.items(), key=lambda x:x[1], reverse=True)[:rule["blue_count"]] + pred_b = [n for n,s in top_b] + pred_b.sort() + + pred_r = [] + seed = hashlib.md5(("%s_bt_%s" % (p.config.get("seed",""), str(ci))).encode()).hexdigest() + cr = random.Random(seed) + cand = list(top_r) + w = [r_comp.get(n,0.5) for n in cand] + while len(pred_r) < rule["red_count"] and cand: + tw = sum(w) + if tw<=0: pk = cr.choice(cand) + else: + rv = cr.random()*tw; cum=0; pk=cand[-1] + for i,ww in enumerate(w): + cum+=ww + if rv<=cum: pk=cand[i]; break + if pk not in pred_r: pred_r.append(pk) + idx = cand.index(pk); cand.pop(idx); w.pop(idx) + pred_r.sort() + + return pred_r, pred_b + + +# ==================== 展示函数 ==================== + +def print_header(): + print("\n" + "*"*60) + print(" 双色球 & 大乐透 * 智能号码分析系统 v1.0") + print(" 基于多维度数学模型 + 概率分析的机选推荐") + print("*"*60 + "\n") + +def print_today_info(): + now = datetime.datetime.now() + wd = now.isoweekday() + print("[%s] %s" % (now.strftime("%Y-%m-%d %H:%M"), WEEKDAY.get(wd,''))) + ssq_today = wd in LOTTERY_RULES["ssq"]["draw_days"] + dlt_today = wd in LOTTERY_RULES["dlt"]["draw_days"] + print(" 双色球: %s (二/四/日)" % ("[今晚开奖!]" if ssq_today else "[今日不开奖]")) + print(" 大乐透: %s (一/三/六)" % ("[今晚开奖!]" if dlt_today else "[今日不开奖]")) + print() + return ssq_today, dlt_today + +def show_analysis(p, lt): + analyzer = Analyzer(p, lt) + result = analyzer.analyze() + if not result: + print("\n[%s] 暂无数据" % LOTTERY_RULES[lt]["name"]) + return None + rule = LOTTERY_RULES[lt] + print("\n" + "-"*50) + print("[%s] 分析报告" % rule["name"]) + print("-"*50) + print(" 数据范围: 近%d期" % result["data_range"]["total"]) + print(" TOP红球: ", end="") + r_comp = result["red_composite"] + for n,s in sorted(r_comp.items(), key=lambda x:x[1], reverse=True)[:10]: + print("%02d(%.3f)" % (n, s), end=" ") + print() + print(" TOP蓝球: ", end="") + b_comp = result["blue_composite"] + for n,s in sorted(b_comp.items(), key=lambda x:x[1], reverse=True)[:5]: + print("%02d(%.3f)" % (n, s), end=" ") + print() + pat = result["patterns"] + print(" 模式: 奇偶%s 大小%s 和值范围%s" % (pat["most_oe"], pat["most_bs"], pat["sum_range"])) + print(" 分析版本: v%d | 数据源: %s" % (result["version"], rule["source"])) + print(" 数据范围: 第%s期 ~ 第%s期" % (result["data_range"]["start"], result["data_range"]["end"])) + return result + +def show_recommendation(p, lt, count=5): + analyzer = Analyzer(p, lt) + rec = Recommender(p, analyzer, lt) + results = rec.recommend(count) + if not results: + print("\n[%s] 无法生成推荐" % LOTTERY_RULES[lt]["name"]) + return + rule = LOTTERY_RULES[lt] + print("\n" + "="*60) + print("[%s] 机选推荐 (%s)" % (rule["name"], datetime.datetime.now().strftime("%m/%d %H:%M"))) + print("="*60) + for i, r in enumerate(results): + r_str = " ".join("%02d" % n for n in r["reds"]) + b_str = " ".join("%02d" % n for n in r["blues"]) + print("\n 第%d组: [红] %s + [蓝] %s" % (i+1, r_str, b_str)) + print(" 得分:%.4f | 奇偶:%s | 大小:%s | 和:%d" % (r["score"], r["odd_even"], r["big_small"], r["sum"])) + + # 保存最佳推荐供回滚 + if results: + analysis = p.analysis(lt) + analysis["last_recommendation"] = { + "reds":results[0]["reds"], "blues":results[0]["blues"], + "generated_at":datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + p.save_analysis(lt, analysis) + + return results + + +def show_ensemble_recommendation(p, lt, count=5): + """集成学习推荐:多模型融合 + 权重优化""" + if ensemble_recommendation is None: + print("\n[错误] 集成学习模块未加载") + return + + rule = LOTTERY_RULES[lt] + name = rule["name"] + rmax = rule["red_max"] + rcnt = rule["red_count"] + bmax = rule["blue_max"] + bcnt = rule["blue_count"] + + print("\n" + "="*70) + print(f"🧠 [{name}] 集成学习推荐 (多模型融合 + 权重优化)") + print(f"{'='*70}") + + # 获取历史数据 + hist = p.history(lt) + if not hist: + print(f"\n[{name}] 暂无历史数据") + return + + # 使用最近100期数据作为训练集 + sorted_hist = sorted(hist, key=lambda x: int(x["issue"]), reverse=True)[:100] + + # 运行集成学习 + result = ensemble_recommendation(lt, sorted_hist, rule, count=count) + + if not result or not result['recommendations']: + print("\n[错误] 集成学习推荐失败") + return + + weights = result['weights'] + backtest = result['backtest'] + recommendations = result['recommendations'] + + # 显示各模型权重 + print("\n" + "-"*50) + print("📊 各模型权重(回测优化后)") + print("-"*50) + model_names = { + 'freq': '频率加权模型', + 'bayes': '贝叶斯平滑模型', + 'markov': '马尔可夫链模型', + 'kmeans': 'K-Means聚类模型', + 'residual': '残差回归模型' + } + for m, w in sorted(weights.items(), key=lambda x: -x[1]): + print(f" {model_names.get(m, m):<20s}: {w:.4f}") + + print(f"\n 回测迭代: {backtest['iterations']} 次 | 测试期数: {backtest['test_periods']}") + print(f" 回测总分: {backtest['best_score']:.1f} (分数越高表示匹配度越好)") + + # 显示推荐号码 + print("\n" + "-"*50) + print("🎯 集成学习推荐号码") + print("-"*50) + today = datetime.datetime.now() + draw_days = rule["draw_days"] + draw_time = rule.get("draw_time", "20:00") + 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_date = (today + datetime.timedelta(days=delta)).strftime("%Y-%m-%d") + wd_map = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"} + next_wd = wd_map.get((today + datetime.timedelta(days=delta)).isoweekday(), "") + + print(f"\n 即将开奖: 第{int(sorted_hist[0]['issue'])+1}期 | {next_draw_date} ({next_wd})") + print() + + for i, r in enumerate(recommendations): + r_str = " ".join("%02d" % n for n in r["reds"]) + b_str = " ".join("%02d" % n for n in r["blues"]) + star = " ⭐" if i == 0 else "" + print(f" 第{i+1}组: [前区] {r_str} + [后区] {b_str}") + print(f" 综合得分: {r['score']:.4f}{star} | 奇偶比: {r['odd_even']} | 大小比: {r['big_small']} | 和值: {r['sum']}") + + # 保存最佳推荐供回滚 + if recommendations: + analysis = p.analysis(lt) + analysis["last_ensemble_recommendation"] = { + "reds": recommendations[0]["reds"], + "blues": recommendations[0]["blues"], + "generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "weights": weights, + "backtest": backtest + } + p.save_analysis(lt, analysis) + + print("\n" + "="*70) + print(" 说明:集成学习融合5种算法,通过历史数据回测自动优化权重") + print(" 分数越高表示该号码组在历史模式中匹配度越高") + print("="*70) + + return recommendations + + +def show_full_report(p, lt, count=5): + """4块结构化输出:回滚迭代 + 数据采集 + 推理分析 + 机选号码""" + rule = LOTTERY_RULES[lt] + fb = p.feedback() + stats = fb.get(lt, {}) + analysis = p.analysis(lt) + hist = p.history(lt) + if not hist: + print("\n[%s] 暂无数据" % rule["name"]) + return + + # 获取历史最新期号 + latest = max(hist, key=lambda x:int(x["issue"])) + prev_issue = latest["issue"] + prev_date = latest.get("date","") + prev_reds = latest["reds"] + prev_blues = latest["blue"] if isinstance(latest["blue"],list) else [latest["blue"]] + next_issue = str(int(prev_issue)+1) + + # 计算下期开奖日期 + today = datetime.datetime.now() + draw_days = rule["draw_days"] + # 如果今天就是开奖日且还没过开奖时间,则下期就是今天 + draw_time = rule.get("draw_time", "20:00") + 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_date = (today + datetime.timedelta(days=delta)).strftime("%Y-%m-%d") + wd_map = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"} + next_wd = wd_map.get((today + datetime.timedelta(days=delta)).isoweekday(), "") + next_draw_date += " (%s)" % next_wd + + # 回滚统计 + ver = p.config.get("iteration_count", 0) + 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) + + # 分析 + analyzer = Analyzer(p, lt) + result = analyzer.analyze() + if not result: + print("\n[%s] 无法分析" % rule["name"]) + return + + # ==================== 第1块:回滚迭代 ==================== + print("\n" + "="*60) + print(" [1/4] 回滚迭代") + print("="*60) + print(" 当前版本号: v%d" % ver) + print(" 版本胜率: %s (%d期推荐 / %d次盈利)" % (win_rate, total_recs, profitable)) + print(" 累计中奖收益金额: RMB %.2f" % earnings) + + # ==================== 第2块:数据采集 ==================== + dr = result["data_range"] + print("\n" + "="*60) + print(" [2/4] 数据采集") + print("="*60) + print(" 采集期数范围: 第%s期 ~ 第%s期(近%d期)" % (dr["start"], dr["end"], dr["total"])) + print(" 上一期号: 第%s期" % prev_issue) + print(" 开奖日期: %s" % prev_date) + r_str = " ".join("%02d" % int(x) for x in prev_reds) + b_str = " ".join("%02d" % int(x) for x in prev_blues) + if lt == "ssq": + print(" 开奖号码: 红球 %s 蓝球 %s" % (r_str, b_str)) + else: + print(" 开奖号码: 前区 %s 后区 %s" % (r_str, b_str)) + + # ==================== 第3块:推理分析 ==================== + pat = result["patterns"] + print("\n" + "="*60) + print(" [3/4] 推理分析(分析版本 v%d | 数据源: %s)" % (result["version"], rule["source"])) + print("="*60) + + # 步骤1:集成学习评分(TOP10前区) + r_comp = result["red_composite"] + top10r = sorted(r_comp.items(), key=lambda x:x[1], reverse=True)[:10] + print(" >> 步骤1 - 集成学习评分(前区)") + print(" %s" % " ".join("%02d(%.4f)"%(n,s) for n,s in top10r)) + + # 步骤2:集成学习评分(TOP10后区) + b_comp = result["blue_composite"] + top5b = sorted(b_comp.items(), key=lambda x:x[1], reverse=True)[:5] + print(" >> 步骤2 - 集成学习评分(后区)") + print(" %s" % " ".join("%02d(%.4f)"%(n,s) for n,s in top5b)) + + # 步骤3:模式识别 + print(" >> 步骤3 - 模式识别") + print(" 奇偶比: %s | 大小比: %s | 和值区间: [%s, %s]" % ( + pat["most_oe"], pat["most_bs"], pat["sum_range"][0], pat["sum_range"][1])) + + # 步骤4:权重体系(集成学习模型) + print(" >> 步骤4 - 权重体系(集成学习模型)") + weights = result.get("model_weights", {}) + if weights: + name_map = {'freq':'频率加权', 'bayes':'贝叶斯平滑', 'markov':'马尔可夫链', 'kmeans':'K-Means聚类', 'residual':'残差回归'} + weight_lines = [] + for m, w in weights.items(): + weight_lines.append("%s%.4f%%" % (name_map.get(m, m), w * 100)) + print(" %s" % " + ".join(weight_lines)) + else: + print(" 频率25% + 热号20% + 遗漏15% + 尾数10% + 区间8%") + print(" 奇偶8% + 大小6% + 和值4% + 概率2% + 连号2%") + + # ==================== 第4块:机选号码 ==================== + rec = Recommender(p, analyzer, lt) + results = rec.recommend(count) + if not results: + print("\n[%s] 无法生成推荐" % rule["name"]) + return + + print("\n" + "="*60) + print(" [4/4] 机选号码(第%s期 | %s)" % (next_issue, next_draw_date)) + print("="*60) + for i, r in enumerate(results): + if lt == "ssq": + r_str = " ".join("%02d" % n for n in r["reds"]) + b_str = "%02d" % r["blues"][0] + print("\n 第%d组: 红球 %s 蓝球 %s" % (i+1, r_str, b_str)) + else: + r_str = " ".join("%02d" % n for n in r["reds"]) + b_str = " ".join("%02d" % n for n in r["blues"]) + print("\n 第%d组: 前区 %s + 后区 %s" % (i+1, r_str, b_str)) + print(" 得分:%.4f | 奇偶:%s | 大小:%s | 和:%d" % (r["score"], r["odd_even"], r["big_small"], r["sum"])) + + # 保存推荐 + if results: + analysis["last_recommendation"] = { + "reds":results[0]["reds"], "blues":results[0]["blues"], + "generated_at":datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + p.save_analysis(lt, analysis) + + print("\n" + "="*60) + + +# ==================== CLI 入口 ==================== +def main(): + import argparse + parser = argparse.ArgumentParser(description="彩票号码分析与机选工具") + parser.add_argument("--action", choices=["analyze","recommend","fetch","check","all","report","full_report","backtest"], default="report") + parser.add_argument("--lottery", choices=["ssq","dlt","all"], default="all") + parser.add_argument("--count", type=int, default=5) + parser.add_argument("--query", default="") + parser.add_argument("--earnings", type=float, default=0.0, help="中奖收益金额(元),仅check动作有效") + parser.add_argument("--target", type=int, default=100, help="全量回滚迭代的目标期数") + args = parser.parse_args() + + p = Persistence() + + # ==================== full_backtest ==================== + if args.action == "backtest": + print("\n" + "*"*60) + print(" 双色球 & 大乐透 * 智能号码分析系统 v1.0") + print(" 全量回滚迭代测试 | 回溯近%d期逐期模拟" % args.target) + print("*"*60 + "\n") + + for lt in (["ssq","dlt"] if args.lottery=="all" else [args.lottery]): + bt = FullBacktest(p) + result = bt.backtest(lt, target_count=args.target) + + print("\n[OK] 全量回滚迭代测试完成,详细数据已保存至 feedback.json\n") + return + + if args.action == "fetch": + c = Collector(p) + c.fetch_all() + return + + if args.action == "check": + f = FeedbackLoop(p) + for lt in (["ssq","dlt"] if args.lottery=="all" else [args.lottery]): + f.check(lt, earnings=args.earnings) + return + + lotteries = ["ssq","dlt"] if args.lottery=="all" else [args.lottery] + + if args.action == "full_report": + print_header() + ssq_today, dlt_today = print_today_info() + c = Collector(p) + c.fetch_all() + for lt in lotteries: + show_full_report(p, lt, args.count) + return + + if args.action in ["analyze","report","all"]: + print_header() + ssq_today, dlt_today = print_today_info() + + # 先采集 + c = Collector(p) + c.fetch_all() + + for lt in lotteries: + show_analysis(p, lt) + + if args.action in ["recommend","all","report"]: + for lt in lotteries: + analyzer = Analyzer(p, lt) + if not analyzer.data: + print("\n[警告] %s 暂无数据" % LOTTERY_RULES[lt]["name"]) + continue + show_recommendation(p, lt, args.count) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/skills/lottery_picker/model_full_report.py b/skills/lottery_picker/model_full_report.py new file mode 100644 index 0000000..5833e2e --- /dev/null +++ b/skills/lottery_picker/model_full_report.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +新模型集成入口:替代 lottery_tool.py 的 full_report +保持4块结构化输出,推理分析和机选号码使用6阶段数学模型 +""" + +import json, math, os, datetime, hashlib, random, re, sys +from collections import Counter +import numpy as np + +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") +SSQ_FILE = os.path.join(DATA_DIR, "ssq_history.json") +FEEDBACK_FILE = os.path.join(DATA_DIR, "feedback.json") +CONFIG_FILE = os.path.join(DATA_DIR, "config.json") + +# ================================================================ +# 加载数据和模型参数 +# ================================================================ +data = json.load(open(SSQ_FILE, "r", encoding="utf-8")) +records = [] +historical_combos = set() +for r in data: + reds = sorted([int(x) for x in r["reds"]]) + blue = int(r["blue"]) + if len(reds) == 6 and all(1 <= n <= 33 for n in reds) and 1 <= blue <= 16: + records.append({"issue": r["issue"], "reds": reds, "blue": blue, "date": r.get("date", "")}) + historical_combos.add(tuple(reds)) + +records.sort(key=lambda x: int(x["issue"])) +N = len(records) +latest = records[-1] + +# 加载模型参数 +mp_file = os.path.join(OUTPUT_DIR, "model_parameters.json") +if os.path.exists(mp_file): + mp = json.load(open(mp_file, "r")) + MODEL_VERSION = "v1.0_6phase" +else: + mp = None + MODEL_VERSION = "v1.0_legacy" + +# ================================================================ +# 加载回滚统计 +# ================================================================ +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} +} + +# ================================================================ +# 计算下期信息 +# ================================================================ +prev_issue = latest["issue"] +prev_date = latest.get("date", "") +prev_reds = latest["reds"] +prev_blue = latest["blue"] +next_issue = str(int(prev_issue) + 1) + +today = datetime.datetime.now() +draw_days = [2, 4, 7] # 周二、四、日 +draw_time = "21:15" +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(), "") + +# ================================================================ +# 4块输出 +# ================================================================ +print("\n" + "=" * 60) +print(" [1/4] 回滚迭代") +print("=" * 60) + +ver = config.get("iteration_count", 0) +stats = feedback.get("ssq", {}) +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(" 当前版本号: %s (第6阶段数学建模)" % MODEL_VERSION) +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) +r_str = " ".join("%02d" % n for n in prev_reds) +print(" 开奖号码: 红球 %s 蓝球 %02d" % (r_str, prev_blue)) + +# ==================== 第3块:推理分析 ==================== +print("\n" + "=" * 60) +print(" [3/4] 推理分析(%s | 数据源: 500.com)" % MODEL_VERSION) +print("=" * 60) + +if mp: + # --- 6阶段模型输出 --- + p3 = mp["phase3_params"] + p4 = mp["phase4_params"] + p5 = mp["phase5_params"] + + # 步骤1: 贝叶斯后验概率 TOP10 + red_post = {int(k): float(v) for k, v in p3["red_posterior"].items()} + top10 = sorted(red_post.items(), key=lambda x: -x[1])[:10] + print(" >> 步骤1 - 贝叶斯后验概率: TOP10红球") + print(" %s" % " ".join("%02d(%.4f)" % (n, s) for n, s in top10)) + + # 步骤2: 蓝球后验 TOP5 + blue_post = {int(k): float(v) for k, v in p3["blue_posterior"].items()} + top5b = sorted(blue_post.items(), key=lambda x: -x[1])[:5] + print(" >> 步骤2 - 蓝球后验概率: TOP5蓝球") + print(" %s" % " ".join("%02d(%.4f)" % (n, s) for n, s in top5b)) + + # 步骤3: 聚类分组 + candidate = p4["candidate_pool"] + eliminated = p4["eliminated"] + print(" >> 步骤3 - K-Means聚类降维") + print(" K=%d | 轮廓系数=%.4f" % (p4["red_k"], p4["red_silhouette"])) + print(" 核心候选池(%d个): %s" % (len(candidate), " ".join("%02d" % n for n in candidate[:15]) + ("..." if len(candidate) > 15 else ""))) + + # 步骤4: 权重体系 + sw = p5["objective_weights"] + print(" >> 步骤4 - 综合得分权重体系(6阶段数学模型)") + print(" Score = %.1f×后验 + %.1f×泊松 + %.1f×伽马遗漏" % (sw["w1_posterior"], sw["w2_poisson"], sw["w3_gamma"])) + print(" 方法链路: 泊松频次→负二项/伽马拟合→贝叶斯后验→K-Means→ILP约束求解") +else: + print(" [⚠️ 新模型参数未加载,使用基础统计]") + +# ==================== 第4块:机选号码 ==================== +print("\n" + "=" * 60) +print(" [4/4] 机选号码(第%s期 | %s %s)" % (next_issue, next_draw_str, next_wd)) +print("=" * 60) + +if mp: + # --- 6阶段模型推荐 --- + w1 = p3["score_weights"]["alpha1_posterior"] + w2 = p3["score_weights"]["alpha2_poisson"] + w3 = p3["score_weights"]["alpha3_miss"] + + red_freq = {int(k): v["freq"] for k, v in mp["phase1_params"]["red_freq"].items()} + lam = mp["phase1_params"]["poisson_lambda_red"] + + # 泊松权重 + red_poisson = {} + for n in range(1, 34): + k = red_freq.get(n, 0) + log_p = k * math.log(lam) - lam + for i in range(1, k + 1): + log_p -= math.log(i) + red_poisson[n] = math.exp(log_p) + total_pois = sum(red_poisson.values()) + red_poisson = {n: v / total_pois for n, v in red_poisson.items()} + + # 遗漏权重 + red_miss = mp["phase1_params"]["red_miss"] + red_gamma = {} + for n in range(1, 34): + miss = red_miss.get(str(n), {"avg_miss": 5, "last_miss": 0}) + avg = miss["avg_miss"] + last = miss["last_miss"] + red_gamma[n] = min(1.0, last / (avg * 2)) if avg > 0 else 0.5 + total_gam = sum(red_gamma.values()) + red_gamma = {n: v / total_gam for n, v in red_gamma.items()} + + # 综合得分 + red_score = {} + for n in range(1, 34): + red_score[n] = w1 * red_post[n] + w2 * red_poisson[n] + w3 * red_gamma[n] + + # 蓝球得分 + blue_freq = {int(k): v["freq"] for k, v in mp["phase1_params"]["blue_freq"].items()} + lam_b = mp["phase1_params"]["poisson_lambda_blue"] + blue_poisson = {} + for n in range(1, 17): + k = blue_freq.get(n, 0) + log_p = k * math.log(lam_b) - lam_b + for i in range(1, k + 1): + log_p -= math.log(i) + blue_poisson[n] = math.exp(log_p) + total_pois_b = sum(blue_poisson.values()) + blue_poisson = {n: v / total_pois_b for n, v in blue_poisson.items()} + + blue_score = {} + for n in range(1, 17): + blue_score[n] = w1 * blue_post[n] + w2 * blue_poisson[n] + w3 * 0.5 + + # 约束检查 + constraints = p5["constraints"] + 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 > 16) + if not (constraints["big_small"]["min_big"] <= big <= constraints["big_small"]["max_big"]): + 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 + s = sum(combo) + if not (constraints["sum_range"]["min_sum"] <= s <= constraints["sum_range"]["max_sum"]): + return False + hot = sum(1 for n in combo if red_score[n] > 0.03) + 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 <= 11) + z2 = sum(1 for n in combo if 12 <= n <= 22) + z3 = sum(1 for n in combo if 23 <= n <= 33) + if not all(constraints["zone_balance"]["min_zone"] <= z <= constraints["zone_balance"]["max_zone"] for z in [z1, z2, z3]): + return False + if tuple(combo) in historical_combos: + return False + return True + + # 生成推荐 + import itertools + sorted_nums = sorted(range(1, 34), key=lambda n: -red_score[n]) + best_combos = [] + seed = hashlib.md5(("6phase_ssq_%s" % prev_issue).encode()).hexdigest() + rng = random.Random(seed) + + for _ in range(30000): + weights = np.array([red_score[n] for n in range(1, 34)]) + weights = weights / weights.sum() + selected = np.random.choice(range(1, 34), size=6, replace=False, p=weights) + combo = sorted(selected.tolist()) + if check_constraints(combo): + score = sum(red_score[n] for n in combo) + if len(best_combos) < 10 or score > best_combos[-1][0]: + best_combos.append((score, combo)) + best_combos.sort(key=lambda x: -x[0]) + if len(best_combos) > 10: + best_combos = best_combos[:10] + + for combo in itertools.combinations(sorted_nums[:15], 6): + combo_list = sorted(list(combo)) + if check_constraints(combo_list): + score = sum(red_score[n] for n in combo_list) + if len(best_combos) < 10 or score > best_combos[-1][0]: + best_combos.append((score, combo_list)) + best_combos.sort(key=lambda x: -x[0]) + if len(best_combos) > 10: + best_combos = best_combos[:10] + + seen = set() + unique = [] + for s, c in best_combos: + if tuple(c) not in seen: + seen.add(tuple(c)) + unique.append((s, c)) + results = unique[:5] + + blue_ranked = sorted(blue_score.items(), key=lambda x: -x[1])[:3] + primary_blue = blue_ranked[0][0] + + for i, (score, combo) in enumerate(results): + r_str = " ".join("%02d" % n for n in combo) + odd = sum(1 for n in combo if n % 2 == 1) + big = sum(1 for n in combo if n > 16) + s = sum(combo) + marker = " ⭐" if i == 0 else "" + print("\n 第%d组: 红球 %s 蓝球 %02d%s" % (i + 1, r_str, primary_blue, marker)) + print(" 得分:%.4f | 奇偶:%d:%d | 大小:%d:%d | 和:%d" % ( + score, odd, 6 - odd, big, 6 - big, s)) + + # 保存推荐 + analysis = json.load(open(os.path.join(DATA_DIR, "analysis_ssq.json"), "r")) if os.path.exists(os.path.join(DATA_DIR, "analysis_ssq.json")) else {} + analysis["last_recommendation"] = { + "reds": results[0][1], + "blues": [primary_blue], + "generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "model_version": MODEL_VERSION + } + json.dump(analysis, open(os.path.join(DATA_DIR, "analysis_ssq.json"), "w"), ensure_ascii=False, indent=2) + +else: + print(" [⚠️ 新模型未加载,请先运行 model_phase1~6.py 训练模型]") + +print("\n" + "=" * 60) +print(" ⚠️ 免责声明: 本推荐仅基于历史统计建模的相对概率评分,") +print(" 不预测下期开奖,不保证中奖。彩票是独立随机事件。") +print("=" * 60) +print("\n 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀") diff --git a/skills/lottery_picker/model_full_report_dlt.py b/skills/lottery_picker/model_full_report_dlt.py new file mode 100644 index 0000000..f0c7491 --- /dev/null +++ b/skills/lottery_picker/model_full_report_dlt.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +大乐透全量报告:与双色球 model_full_report.py 统一4块结构化输出 +使用集成学习模型(5模型融合),输出格式与双色球完全对齐 +""" + +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 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀") diff --git a/skills/lottery_picker/model_output/model_parameters.json b/skills/lottery_picker/model_output/model_parameters.json new file mode 100644 index 0000000..137680e --- /dev/null +++ b/skills/lottery_picker/model_output/model_parameters.json @@ -0,0 +1,1468 @@ +{ + "meta": { + "model_name": "双色球6阶段数学建模分析系统", + "version": "1.0", + "generated_at": "2026-06-16 05:16:59", + "data_source": "500.com", + "sample_range": "第25019期~第26067期", + "sample_size": 200, + "lottery_type": "双色球" + }, + "phase1_params": { + "poisson_lambda_red": 36.36363636363637, + "poisson_lambda_blue": 12.5, + "red_freq": { + "1": { + "freq": 35, + "actual_rate": 0.175, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.006818181818181834 + }, + "2": { + "freq": 50, + "actual_rate": 0.25, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.06818181818181818 + }, + "3": { + "freq": 42, + "actual_rate": 0.21, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.02818181818181817 + }, + "4": { + "freq": 35, + "actual_rate": 0.175, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.006818181818181834 + }, + "5": { + "freq": 33, + "actual_rate": 0.165, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.016818181818181815 + }, + "6": { + "freq": 42, + "actual_rate": 0.21, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.02818181818181817 + }, + "7": { + "freq": 32, + "actual_rate": 0.16, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.02181818181818182 + }, + "8": { + "freq": 40, + "actual_rate": 0.2, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.018181818181818188 + }, + "9": { + "freq": 46, + "actual_rate": 0.23, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.04818181818181819 + }, + "10": { + "freq": 46, + "actual_rate": 0.23, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.04818181818181819 + }, + "11": { + "freq": 29, + "actual_rate": 0.145, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.03681818181818183 + }, + "12": { + "freq": 28, + "actual_rate": 0.14, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.04181818181818181 + }, + "13": { + "freq": 44, + "actual_rate": 0.22, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.03818181818181818 + }, + "14": { + "freq": 37, + "actual_rate": 0.185, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.0031818181818181746 + }, + "15": { + "freq": 37, + "actual_rate": 0.185, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.0031818181818181746 + }, + "16": { + "freq": 36, + "actual_rate": 0.18, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.0018181818181818299 + }, + "17": { + "freq": 34, + "actual_rate": 0.17, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.011818181818181811 + }, + "18": { + "freq": 37, + "actual_rate": 0.185, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.0031818181818181746 + }, + "19": { + "freq": 35, + "actual_rate": 0.175, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.006818181818181834 + }, + "20": { + "freq": 27, + "actual_rate": 0.135, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.046818181818181814 + }, + "21": { + "freq": 27, + "actual_rate": 0.135, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.046818181818181814 + }, + "22": { + "freq": 38, + "actual_rate": 0.19, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.008181818181818179 + }, + "23": { + "freq": 36, + "actual_rate": 0.18, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.0018181818181818299 + }, + "24": { + "freq": 43, + "actual_rate": 0.215, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.03318181818181817 + }, + "25": { + "freq": 42, + "actual_rate": 0.21, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.02818181818181817 + }, + "26": { + "freq": 26, + "actual_rate": 0.13, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.05181818181818182 + }, + "27": { + "freq": 29, + "actual_rate": 0.145, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.03681818181818183 + }, + "28": { + "freq": 33, + "actual_rate": 0.165, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.016818181818181815 + }, + "29": { + "freq": 28, + "actual_rate": 0.14, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.04181818181818181 + }, + "30": { + "freq": 46, + "actual_rate": 0.23, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.04818181818181819 + }, + "31": { + "freq": 34, + "actual_rate": 0.17, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.011818181818181811 + }, + "32": { + "freq": 31, + "actual_rate": 0.155, + "theoretical_rate": 0.18181818181818182, + "deviation": -0.026818181818181824 + }, + "33": { + "freq": 42, + "actual_rate": 0.21, + "theoretical_rate": 0.18181818181818182, + "deviation": 0.02818181818181817 + } + }, + "blue_freq": { + "1": { + "freq": 15, + "actual_rate": 0.075, + "theoretical_rate": 0.0625, + "deviation": 0.012499999999999997 + }, + "2": { + "freq": 15, + "actual_rate": 0.075, + "theoretical_rate": 0.0625, + "deviation": 0.012499999999999997 + }, + "3": { + "freq": 10, + "actual_rate": 0.05, + "theoretical_rate": 0.0625, + "deviation": -0.012499999999999997 + }, + "4": { + "freq": 9, + "actual_rate": 0.045, + "theoretical_rate": 0.0625, + "deviation": -0.0175 + }, + "5": { + "freq": 14, + "actual_rate": 0.07, + "theoretical_rate": 0.0625, + "deviation": 0.007500000000000007 + }, + "6": { + "freq": 12, + "actual_rate": 0.06, + "theoretical_rate": 0.0625, + "deviation": -0.0025000000000000022 + }, + "7": { + "freq": 15, + "actual_rate": 0.075, + "theoretical_rate": 0.0625, + "deviation": 0.012499999999999997 + }, + "8": { + "freq": 11, + "actual_rate": 0.055, + "theoretical_rate": 0.0625, + "deviation": -0.0075 + }, + "9": { + "freq": 6, + "actual_rate": 0.03, + "theoretical_rate": 0.0625, + "deviation": -0.0325 + }, + "10": { + "freq": 16, + "actual_rate": 0.08, + "theoretical_rate": 0.0625, + "deviation": 0.0175 + }, + "11": { + "freq": 13, + "actual_rate": 0.065, + "theoretical_rate": 0.0625, + "deviation": 0.0025000000000000022 + }, + "12": { + "freq": 12, + "actual_rate": 0.06, + "theoretical_rate": 0.0625, + "deviation": -0.0025000000000000022 + }, + "13": { + "freq": 13, + "actual_rate": 0.065, + "theoretical_rate": 0.0625, + "deviation": 0.0025000000000000022 + }, + "14": { + "freq": 9, + "actual_rate": 0.045, + "theoretical_rate": 0.0625, + "deviation": -0.0175 + }, + "15": { + "freq": 15, + "actual_rate": 0.075, + "theoretical_rate": 0.0625, + "deviation": 0.012499999999999997 + }, + "16": { + "freq": 15, + "actual_rate": 0.075, + "theoretical_rate": 0.0625, + "deviation": 0.012499999999999997 + } + }, + "red_miss": { + "1": { + "avg_miss": 5.0, + "max_miss": 19, + "min_miss": 1, + "last_miss": 3, + "appear_count": 33 + }, + "2": { + "avg_miss": 4.285714285714286, + "max_miss": 21, + "min_miss": 1, + "last_miss": 4, + "appear_count": 35 + }, + "3": { + "avg_miss": 4.647058823529412, + "max_miss": 18, + "min_miss": 1, + "last_miss": 14, + "appear_count": 34 + }, + "4": { + "avg_miss": 5.5, + "max_miss": 23, + "min_miss": 1, + "last_miss": 0, + "appear_count": 31 + }, + "5": { + "avg_miss": 7.260869565217392, + "max_miss": 43, + "min_miss": 1, + "last_miss": 1, + "appear_count": 23 + }, + "6": { + "avg_miss": 5.448275862068965, + "max_miss": 27, + "min_miss": 1, + "last_miss": 17, + "appear_count": 29 + }, + "7": { + "avg_miss": 5.25, + "max_miss": 16, + "min_miss": 1, + "last_miss": 2, + "appear_count": 32 + }, + "8": { + "avg_miss": 4.705882352941177, + "max_miss": 17, + "min_miss": 1, + "last_miss": 2, + "appear_count": 34 + }, + "9": { + "avg_miss": 4.052631578947368, + "max_miss": 14, + "min_miss": 1, + "last_miss": 3, + "appear_count": 38 + }, + "10": { + "avg_miss": 4.162162162162162, + "max_miss": 14, + "min_miss": 1, + "last_miss": 7, + "appear_count": 37 + }, + "11": { + "avg_miss": 6.333333333333333, + "max_miss": 21, + "min_miss": 1, + "last_miss": 1, + "appear_count": 27 + }, + "12": { + "avg_miss": 7.818181818181818, + "max_miss": 25, + "min_miss": 1, + "last_miss": 25, + "appear_count": 22 + }, + "13": { + "avg_miss": 4.7272727272727275, + "max_miss": 22, + "min_miss": 1, + "last_miss": 13, + "appear_count": 33 + }, + "14": { + "avg_miss": 5.09375, + "max_miss": 19, + "min_miss": 1, + "last_miss": 5, + "appear_count": 32 + }, + "15": { + "avg_miss": 5.433333333333334, + "max_miss": 14, + "min_miss": 1, + "last_miss": 3, + "appear_count": 30 + }, + "16": { + "avg_miss": 6.074074074074074, + "max_miss": 32, + "min_miss": 1, + "last_miss": 2, + "appear_count": 27 + }, + "17": { + "avg_miss": 6.384615384615385, + "max_miss": 22, + "min_miss": 1, + "last_miss": 22, + "appear_count": 26 + }, + "18": { + "avg_miss": 4.9393939393939394, + "max_miss": 14, + "min_miss": 1, + "last_miss": 3, + "appear_count": 33 + }, + "19": { + "avg_miss": 6.111111111111111, + "max_miss": 16, + "min_miss": 1, + "last_miss": 0, + "appear_count": 28 + }, + "20": { + "avg_miss": 7.521739130434782, + "max_miss": 30, + "min_miss": 1, + "last_miss": 13, + "appear_count": 23 + }, + "21": { + "avg_miss": 6.407407407407407, + "max_miss": 21, + "min_miss": 1, + "last_miss": 1, + "appear_count": 27 + }, + "22": { + "avg_miss": 6.0, + "max_miss": 30, + "min_miss": 1, + "last_miss": 7, + "appear_count": 27 + }, + "23": { + "avg_miss": 5.857142857142857, + "max_miss": 21, + "min_miss": 1, + "last_miss": 1, + "appear_count": 28 + }, + "24": { + "avg_miss": 4.757575757575758, + "max_miss": 17, + "min_miss": 1, + "last_miss": 1, + "appear_count": 33 + }, + "25": { + "avg_miss": 4.647058823529412, + "max_miss": 15, + "min_miss": 1, + "last_miss": 4, + "appear_count": 34 + }, + "26": { + "avg_miss": 7.25, + "max_miss": 34, + "min_miss": 1, + "last_miss": 8, + "appear_count": 24 + }, + "27": { + "avg_miss": 8.142857142857142, + "max_miss": 22, + "min_miss": 1, + "last_miss": 0, + "appear_count": 22 + }, + "28": { + "avg_miss": 5.387096774193548, + "max_miss": 15, + "min_miss": 1, + "last_miss": 4, + "appear_count": 31 + }, + "29": { + "avg_miss": 7.166666666666667, + "max_miss": 26, + "min_miss": 1, + "last_miss": 0, + "appear_count": 25 + }, + "30": { + "avg_miss": 4.4, + "max_miss": 22, + "min_miss": 1, + "last_miss": 0, + "appear_count": 36 + }, + "31": { + "avg_miss": 5.03030303030303, + "max_miss": 20, + "min_miss": 1, + "last_miss": 4, + "appear_count": 33 + }, + "32": { + "avg_miss": 6.035714285714286, + "max_miss": 17, + "min_miss": 1, + "last_miss": 0, + "appear_count": 29 + }, + "33": { + "avg_miss": 4.514285714285714, + "max_miss": 22, + "min_miss": 1, + "last_miss": 3, + "appear_count": 35 + } + }, + "sum_stats": { + "avg": 99.405, + "std": 20.734535803822567, + "min": 41, + "max": 153 + }, + "span_stats": { + "avg": 24.65, + "min": 11, + "max": 32 + } + }, + "phase2_params": { + "gamma_red": { + "1": { + "alpha": 1.1494252873563218, + "beta": 4.35, + "ks_stat": 0.20641431273390498, + "ks_p": 0.10467130425869042, + "mean": 4.999999999999999, + "var": 21.749999999999993 + }, + "2": { + "alpha": 1.1011155091759623, + "beta": 3.8921568627450984, + "ks_stat": 0.18818527757036177, + "ks_p": 0.14901127096437317, + "mean": 4.285714285714286, + "var": 16.680672268907564 + }, + "3": { + "alpha": 1.3154052500479023, + "beta": 3.532796317606443, + "ks_stat": 0.13832723056801052, + "ks_p": 0.5028109426662664, + "mean": 4.647058823529412, + "var": 16.417112299465234 + }, + "4": { + "alpha": 1.0972482801751093, + "beta": 5.012539184952979, + "ks_stat": 0.14785666197520894, + "ks_p": 0.49531923988146526, + "mean": 5.5, + "var": 27.56896551724138 + }, + "5": { + "alpha": 0.4827789702947859, + "beta": 15.039738704409366, + "ks_stat": 0.30765080224185387, + "ks_p": 0.01975984228239113, + "mean": 7.260869565217391, + "var": 109.201581027668 + }, + "6": { + "alpha": 0.6621750663129974, + "beta": 8.227848101265822, + "ks_stat": 0.2662242950203381, + "ks_p": 0.02638890581940037, + "mean": 5.448275862068965, + "var": 44.82758620689655 + }, + "7": { + "alpha": 1.3392437304075235, + "beta": 3.9201228878648235, + "ks_stat": 0.14932141623506778, + "ks_p": 0.4420648323805917, + "mean": 5.25, + "var": 20.580645161290327 + }, + "8": { + "alpha": 1.1804950030183115, + "beta": 3.986363636363635, + "ks_stat": 0.19967450942582904, + "ks_p": 0.11661520140005174, + "mean": 4.705882352941177, + "var": 18.75935828877005 + }, + "9": { + "alpha": 1.266280694058023, + "beta": 3.2004212004212, + "ks_stat": 0.18021849791190603, + "ks_p": 0.15148079160998085, + "mean": 4.052631578947368, + "var": 12.970128022759598 + }, + "10": { + "alpha": 1.9671804797124488, + "beta": 2.1158008658008662, + "ks_stat": 0.1693881958569608, + "ks_p": 0.21678280762738902, + "mean": 4.162162162162162, + "var": 8.80630630630631 + }, + "11": { + "alpha": 1.1118218431651263, + "beta": 5.696356275303645, + "ks_stat": 0.20750020859158752, + "ks_p": 0.17194478629845927, + "mean": 6.333333333333333, + "var": 36.07692307692308 + }, + "12": { + "alpha": 1.191530494821634, + "beta": 6.561461794019933, + "ks_stat": 0.126066104462137, + "ks_p": 0.8516146256142432, + "mean": 7.8181818181818175, + "var": 51.29870129870129 + }, + "13": { + "alpha": 1.0601323205096789, + "beta": 4.459134615384617, + "ks_stat": 0.178989724508501, + "ks_p": 0.21734232984870175, + "mean": 4.727272727272728, + "var": 21.079545454545464 + }, + "14": { + "alpha": 1.0045162061429185, + "beta": 5.070849000593706, + "ks_stat": 0.17816962784050802, + "ks_p": 0.23655909955477744, + "mean": 5.09375, + "var": 25.829637096774192 + }, + "15": { + "alpha": 2.0317511800226784, + "beta": 2.6742119737677164, + "ks_stat": 0.13376602796119222, + "ks_p": 0.6248021149432966, + "mean": 5.433333333333334, + "var": 14.52988505747126 + }, + "16": { + "alpha": 0.7460064477173759, + "beta": 8.142120075046904, + "ks_stat": 0.2397790306545622, + "ks_p": 0.07557954232658057, + "mean": 6.074074074074075, + "var": 49.45584045584046 + }, + "17": { + "alpha": 1.2455882778372436, + "beta": 5.12578313253012, + "ks_stat": 0.1042200316045869, + "ks_p": 0.9266066571088704, + "mean": 6.384615384615384, + "var": 32.72615384615384 + }, + "18": { + "alpha": 1.626918337198711, + "beta": 3.036042944785276, + "ks_stat": 0.13752201912041856, + "ks_p": 0.5293459991648993, + "mean": 4.9393939393939394, + "var": 14.996212121212121 + }, + "19": { + "alpha": 1.5396210163652024, + "beta": 3.9692307692307693, + "ks_stat": 0.14662944408139283, + "ks_p": 0.5726800157066568, + "mean": 6.111111111111112, + "var": 24.25641025641026 + }, + "20": { + "alpha": 1.1036987867389458, + "beta": 6.815028901734105, + "ks_stat": 0.1357032482770803, + "ks_p": 0.7600851063818178, + "mean": 7.521739130434782, + "var": 51.2608695652174 + }, + "21": { + "alpha": 1.9531389616778614, + "beta": 3.2805691418408176, + "ks_stat": 0.15396191288504196, + "ks_p": 0.5092035610475584, + "mean": 6.407407407407407, + "var": 21.019943019943018 + }, + "22": { + "alpha": 0.6591549295774648, + "beta": 9.102564102564102, + "ks_stat": 0.25200203064240745, + "ks_p": 0.05366426086957649, + "mean": 6.0, + "var": 54.61538461538461 + }, + "23": { + "alpha": 0.9121914944931263, + "beta": 6.420957542908763, + "ks_stat": 0.1775553677636593, + "ks_p": 0.3099729555845799, + "mean": 5.857142857142857, + "var": 37.60846560846561 + }, + "24": { + "alpha": 2.0930000530701056, + "beta": 2.2730891719745223, + "ks_stat": 0.19494392458990534, + "ks_p": 0.143931850685429, + "mean": 4.757575757575758, + "var": 10.81439393939394 + }, + "25": { + "alpha": 1.3870943843532377, + "beta": 3.3502109704641345, + "ks_stat": 0.16091058588051133, + "ks_p": 0.31463858594449734, + "mean": 4.647058823529412, + "var": 15.56862745098039 + }, + "26": { + "alpha": 0.8474851033999299, + "beta": 8.55472263868066, + "ks_stat": 0.16409054846092516, + "ks_p": 0.5005064974953867, + "mean": 7.25, + "var": 62.02173913043479 + }, + "27": { + "alpha": 1.8821689259645464, + "beta": 4.326315789473684, + "ks_stat": 0.09327600562801168, + "ks_p": 0.9900400108648705, + "mean": 8.142857142857142, + "var": 35.22857142857143 + }, + "28": { + "alpha": 1.8789581480583175, + "beta": 2.8670658682634738, + "ks_stat": 0.1395533514069807, + "ks_p": 0.5498413734075988, + "mean": 5.387096774193549, + "var": 15.445161290322586 + }, + "29": { + "alpha": 1.0189524631013998, + "beta": 7.033367037411525, + "ks_stat": 0.13447671016479934, + "ks_p": 0.7475501221767804, + "mean": 7.166666666666668, + "var": 50.40579710144927 + }, + "30": { + "alpha": 0.6713994288045696, + "beta": 6.553475935828877, + "ks_stat": 0.2997756384110063, + "ks_p": 0.0027514733183402872, + "mean": 4.4, + "var": 28.835294117647063 + }, + "31": { + "alpha": 1.8362403585053397, + "beta": 2.739457831325301, + "ks_stat": 0.11898349452010304, + "ks_p": 0.7111277870707181, + "mean": 5.030303030303031, + "var": 13.78030303030303 + }, + "32": { + "alpha": 2.0884935380082115, + "beta": 2.8899846592154286, + "ks_stat": 0.18551879290245685, + "ks_p": 0.261740583600482, + "mean": 6.035714285714286, + "var": 17.443121693121693 + }, + "33": { + "alpha": 1.1495422287231158, + "beta": 3.927029039463888, + "ks_stat": 0.24563132791043554, + "ks_p": 0.023981182774579748, + "mean": 4.514285714285714, + "var": 17.72773109243698 + } + }, + "gamma_blue": { + "1": { + "alpha": 0.6301500434806009, + "beta": 20.97006237006237, + "ks_stat": 0.25206961093567165, + "ks_p": 0.29113303730439327, + "mean": 13.214285714285714, + "var": 277.1043956043956 + }, + "2": { + "alpha": 0.6561900849082444, + "beta": 18.795366795366792, + "ks_stat": 0.18469168892973742, + "ks_p": 0.6394388387316917, + "mean": 12.333333333333332, + "var": 231.80952380952377 + }, + "3": { + "alpha": 1.3095842704781253, + "beta": 13.189473684210526, + "ks_stat": 0.15386222218367876, + "ks_p": 0.9380838965175224, + "mean": 17.272727272727273, + "var": 227.81818181818184 + }, + "4": { + "alpha": 1.3743940725857091, + "beta": 13.897033158813263, + "ks_stat": 0.24926280981258125, + "ks_p": 0.5011849996034251, + "mean": 19.1, + "var": 265.43333333333334 + }, + "5": { + "alpha": 0.9361494863620262, + "beta": 16.557184750733136, + "ks_stat": 0.1536490420185963, + "ks_p": 0.9171105585780198, + "mean": 15.499999999999998, + "var": 256.63636363636357 + }, + "6": { + "alpha": 1.2187235998965547, + "beta": 11.86613475177305, + "ks_stat": 0.16066539364754212, + "ks_p": 0.8596565479062565, + "mean": 14.461538461538462, + "var": 171.6025641025641 + }, + "7": { + "alpha": 1.5101396667764861, + "beta": 7.656576576576577, + "ks_stat": 0.11948330000925744, + "ks_p": 0.9667158939885968, + "mean": 11.562500000000002, + "var": 88.52916666666667 + }, + "8": { + "alpha": 1.74663946231397, + "beta": 9.017316017316018, + "ks_stat": 0.13518747897227654, + "ks_p": 0.970649496493951, + "mean": 15.75, + "var": 142.02272727272728 + }, + "9": { + "alpha": 0.795743181337656, + "beta": 34.828178694158076, + "ks_stat": 0.2190668998726098, + "ks_p": 0.8438303293117618, + "mean": 27.714285714285715, + "var": 965.2380952380954 + }, + "10": { + "alpha": 1.1645512395272846, + "beta": 10.5333850931677, + "ks_stat": 0.12123312001282342, + "ks_p": 0.9713679143161565, + "mean": 12.266666666666667, + "var": 129.2095238095238 + }, + "11": { + "alpha": 1.4932182730081835, + "beta": 10.436071949440931, + "ks_stat": 0.17134204497605543, + "ks_p": 0.8373896613663442, + "mean": 15.583333333333336, + "var": 162.62878787878788 + }, + "12": { + "alpha": 2.172408494422078, + "beta": 6.656914893617022, + "ks_stat": 0.17601732519872904, + "ks_p": 0.7745730164440447, + "mean": 14.46153846153846, + "var": 96.26923076923077 + }, + "13": { + "alpha": 3.053838876355433, + "beta": 4.710338680926917, + "ks_stat": 0.1914315894272679, + "ks_p": 0.6793695410206004, + "mean": 14.384615384615387, + "var": 67.75641025641028 + }, + "14": { + "alpha": 1.212933614097307, + "beta": 15.746945898778357, + "ks_stat": 0.21036777714078211, + "ks_p": 0.7148892652300647, + "mean": 19.099999999999998, + "var": 300.7666666666666 + }, + "15": { + "alpha": 0.7061321221857875, + "beta": 16.374414414414417, + "ks_stat": 0.19067130499806645, + "ks_p": 0.5591658940903899, + "mean": 11.5625, + "var": 189.3291666666667 + }, + "16": { + "alpha": 1.0846632710809283, + "beta": 11.370656370656368, + "ks_stat": 0.1489433579862373, + "ks_p": 0.8656586821303156, + "mean": 12.333333333333332, + "var": 140.2380952380952 + } + }, + "sum_normal": { + "mu": 99.405, + "std": 20.78656734310761, + "ks_stat": 0.534817551304486, + "ks_p": 5.1357896407780965e-51 + } + }, + "phase3_params": { + "prior_weights": { + "w1_poisson": 0.7, + "w2_gamma": 0.1, + "w3_uniform": 0.20000000000000004 + }, + "likelihood_beta": 0.5, + "score_weights": { + "alpha1_posterior": 0.5, + "alpha2_poisson": 0.3, + "alpha3_miss": 0.2 + }, + "red_posterior": { + "1": 0.041284994966113174, + "2": 0.014125492274402759, + "3": 0.02789693931343785, + "4": 0.04031416510792008, + "5": 0.037374853230126695, + "6": 0.028100326438566883, + "7": 0.03451544873270375, + "8": 0.03802232040130101, + "9": 0.02092670148343668, + "10": 0.017939410939573668, + "11": 0.024225750376325517, + "12": 0.016720888834346623, + "13": 0.022164194192054218, + "14": 0.04125947539351494, + "15": 0.042648807559148884, + "16": 0.04250571023193705, + "17": 0.035709322257329146, + "18": 0.042368359117009716, + "19": 0.04031416510792008, + "20": 0.014961195937854561, + "21": 0.018345598868151767, + "22": 0.04004174939504707, + "23": 0.04290322634493454, + "24": 0.03049993673252673, + "25": 0.0314506691408355, + "26": 0.013728124497552232, + "27": 0.022560827885641874, + "28": 0.03687778758648243, + "29": 0.019293632283504742, + "30": 0.02015581062355759, + "31": 0.039203679941563985, + "32": 0.02962720081591567, + "33": 0.03193323398926242 + }, + "blue_posterior": { + "1": 0.06519587291923978, + "2": 0.06689796953021969, + "3": 0.06069987242644126, + "4": 0.043740599099595615, + "5": 0.06530858894343951, + "6": 0.07606334336014062, + "7": 0.06854514406446885, + "8": 0.06924317224112701, + "9": 0.020509988407735758, + "10": 0.06073494151727037, + "11": 0.07339930317356637, + "12": 0.0736340526151214, + "13": 0.0701273029123157, + "14": 0.051663892965531144, + "15": 0.0684089314249761, + "16": 0.06582702439881077 + }, + "red_scores": { + "1": 0.9226050194209983, + "2": 0.018118547860627606, + "3": 0.27961459602682937, + "4": 0.8424305853901556, + "5": 0.837087994456206, + "6": 0.29523743391775037, + "7": 0.7678084045952288, + "8": 0.8087619168080242, + "9": 0.24720848466330628, + "10": 0.02577533763155942, + "11": 0.48000049165292336, + "12": 0.0, + "13": 0.11266586434419372, + "14": 0.857504601209452, + "15": 0.9697191308343248, + "16": 0.9675399716715031, + "17": 0.5579442818147663, + "18": 0.9470676794412628, + "19": 0.8424305853901556, + "20": 0.012739875220838939, + "21": 0.32328876183841576, + "22": 0.7948377253007232, + "23": 1.0, + "24": 0.6139195983450473, + "25": 0.5525883398975033, + "26": 0.04990611633982708, + "27": 0.33163526581620967, + "28": 0.7950557786172284, + "29": 0.23257643950363227, + "30": 0.19006613891065824, + "31": 0.8498986998949459, + "32": 0.5422492378788306, + "33": 0.5896557422419143 + }, + "blue_scores": { + "1": 0.6756041975556122, + "2": 0.7551204973238194, + "3": 0.7329747273899282, + "4": 0.21156461180448582, + "5": 0.5071851642989382, + "6": 1.0, + "7": 0.8320710201031997, + "8": 0.850202514174632, + "9": 0.0, + "10": 0.6783962661928884, + "11": 0.8151907888837252, + "12": 0.8759921760203555, + "13": 0.6534303053941171, + "14": 0.666300114906581, + "15": 0.8257076180470206, + "16": 0.7050894981695546 + } + }, + "phase4_params": { + "red_k": 2, + "red_labels": { + "1": 1, + "2": 0, + "3": 0, + "4": 1, + "5": 1, + "6": 0, + "7": 1, + "8": 1, + "9": 1, + "10": 0, + "11": 1, + "12": 0, + "13": 0, + "14": 1, + "15": 1, + "16": 1, + "17": 0, + "18": 1, + "19": 1, + "20": 0, + "21": 1, + "22": 1, + "23": 1, + "24": 1, + "25": 1, + "26": 0, + "27": 1, + "28": 1, + "29": 1, + "30": 1, + "31": 1, + "32": 1, + "33": 1 + }, + "candidate_pool": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33 + ], + "eliminated": [], + "red_silhouette": 0.46781265861613386, + "red_inertia": 6.738683212042209 + }, + "phase5_params": { + "objective_weights": { + "w1_posterior": 0.5, + "w2_poisson": 0.3, + "w3_gamma": 0.2 + }, + "constraints": { + "odd_even": { + "desc": "奇偶数量约束", + "min_odd": 2, + "max_odd": 4 + }, + "big_small": { + "desc": "大小区间占比约束", + "min_big": 2, + "max_big": 4 + }, + "consecutive": { + "desc": "连号个数约束", + "min_consec": 0, + "max_consec": 2 + }, + "sum_range": { + "desc": "和值区间约束", + "min_sum": 70, + "max_sum": 130 + }, + "hot_cold": { + "desc": "冷热搭配比例", + "min_hot": 2, + "max_hot": 5 + }, + "zone_balance": { + "desc": "三区均衡约束", + "min_zone": 1, + "max_zone": 4 + }, + "exclude_historical": { + "desc": "排除历史已开出完整组合", + "enabled": true + } + }, + "best_combos": [ + { + "rank": 1, + "reds": [ + 1, + 14, + 17, + 18, + 22, + 33 + ], + "score": 0.23935138799201794, + "odd_even": "3:3", + "big_small": "4:2", + "sum": 105, + "consecutive": 1 + }, + { + "rank": 2, + "reds": [ + 1, + 14, + 15, + 17, + 22, + 33 + ], + "score": 0.23905210598985047, + "odd_even": "4:2", + "big_small": "3:3", + "sum": 102, + "consecutive": 1 + }, + { + "rank": 3, + "reds": [ + 6, + 14, + 17, + 18, + 22, + 33 + ], + "score": 0.23870467485298646, + "odd_even": "2:4", + "big_small": "4:2", + "sum": 110, + "consecutive": 1 + }, + { + "rank": 4, + "reds": [ + 6, + 14, + 15, + 17, + 22, + 33 + ], + "score": 0.238405392850819, + "odd_even": "3:3", + "big_small": "3:3", + "sum": 107, + "consecutive": 1 + }, + { + "rank": 5, + "reds": [ + 3, + 14, + 15, + 17, + 22, + 33 + ], + "score": 0.23830369928825446, + "odd_even": "4:2", + "big_small": "3:3", + "sum": 104, + "consecutive": 1 + }, + { + "rank": 6, + "reds": [ + 1, + 6, + 14, + 17, + 22, + 33 + ], + "score": 0.23820343411356779, + "odd_even": "3:3", + "big_small": "3:3", + "sum": 93, + "consecutive": 0 + }, + { + "rank": 7, + "reds": [ + 6, + 14, + 16, + 17, + 22, + 33 + ], + "score": 0.23679635930701645, + "odd_even": "2:4", + "big_small": "3:3", + "sum": 108, + "consecutive": 1 + }, + { + "rank": 8, + "reds": [ + 1, + 14, + 17, + 22, + 28, + 33 + ], + "score": 0.23641808807998085, + "odd_even": "3:3", + "big_small": "4:2", + "sum": 115, + "consecutive": 0 + }, + { + "rank": 9, + "reds": [ + 3, + 15, + 17, + 18, + 22, + 33 + ], + "score": 0.23587926563695064, + "odd_even": "4:2", + "big_small": "4:2", + "sum": 108, + "consecutive": 1 + }, + { + "rank": 10, + "reds": [ + 1, + 6, + 17, + 18, + 22, + 33 + ], + "score": 0.23577900046226394, + "odd_even": "3:3", + "big_small": "4:2", + "sum": 97, + "consecutive": 1 + }, + { + "rank": 11, + "reds": [ + 6, + 14, + 15, + 17, + 18, + 33 + ], + "score": 0.23569891141249064, + "odd_even": "3:3", + "big_small": "3:3", + "sum": 103, + "consecutive": 2 + }, + { + "rank": 12, + "reds": [ + 1, + 3, + 17, + 18, + 22, + 33 + ], + "score": 0.23567730689969943, + "odd_even": "4:2", + "big_small": "4:2", + "sum": 94, + "consecutive": 1 + }, + { + "rank": 13, + "reds": [ + 3, + 14, + 17, + 22, + 28, + 33 + ], + "score": 0.23566968137838487, + "odd_even": "3:3", + "big_small": "4:2", + "sum": 117, + "consecutive": 0 + }, + { + "rank": 14, + "reds": [ + 3, + 14, + 17, + 22, + 23, + 33 + ], + "score": 0.2356314811355857, + "odd_even": "4:2", + "big_small": "4:2", + "sum": 112, + "consecutive": 1 + }, + { + "rank": 15, + "reds": [ + 3, + 14, + 15, + 17, + 18, + 33 + ], + "score": 0.2355972178499261, + "odd_even": "4:2", + "big_small": "3:3", + "sum": 100, + "consecutive": 2 + } + ], + "blue_recommendations": [ + { + "rank": 1, + "blue": 6, + "score": 1.0 + }, + { + "rank": 2, + "blue": 12, + "score": 0.8759921760203555 + }, + { + "rank": 3, + "blue": 8, + "score": 0.850202514174632 + }, + { + "rank": 4, + "blue": 7, + "score": 0.8320710201031997 + }, + { + "rank": 5, + "blue": 15, + "score": 0.8257076180470206 + } + ] + } +} \ No newline at end of file diff --git a/skills/lottery_picker/skill.json b/skills/lottery_picker/skill.json new file mode 100644 index 0000000..ab013c6 --- /dev/null +++ b/skills/lottery_picker/skill.json @@ -0,0 +1,38 @@ +{ + "id": "lottery_picker", + "name": "彩票智能分析", + "description": "双色球(SSQ)和大乐透(DLT)的智能号码分析与机选推荐工具,基于多维度数学模型 + AI概率分析,提供数据采集、推理分析、机选推荐和回滚迭代完整流程。", + "version": "1.0.0", + "author": "QwenPaw彩票分析系统", + "keywords": [ + "双色球", + "大乐透", + "彩票", + "机选", + "AI分析" + ], + "enabled": true, + "main": "lottery_tool.py", + "triggers": [ + { + "type": "keyword", + "keywords": [ + "双色球", + "大乐透", + "彩票", + "今日彩票", + "机选", + "买彩票" + ] + } + ], + "requirements": [ + "requests", + "beautifulsoup4", + "markdown" + ], + "entrypoint": "lottery_tool.py", + "documentation": "SKILL.md", + "created_at": "2026-05-25T21:44:04.764539", + "updated_at": "2026-05-25T21:44:04.764539" +} \ No newline at end of file