智能选号功能丰富,加入多种模式

This commit is contained in:
Mars 2025-06-23 15:43:02 +08:00
parent a093b50d5a
commit 3e07f72b98
13 changed files with 2473 additions and 207 deletions

212
README.md
View File

@ -8,8 +8,11 @@
- Python 3.8+
- FastAPI
- SQLAlchemy
- PostgreSQL
- MySQL/PostgreSQL
- Pydantic
- Scikit-learn (机器学习)
- NumPy (数值计算)
- Pandas (数据处理)
### 前端
- Vue 3
@ -20,23 +23,42 @@
- Vue Router
## 系统功能
1. 数据管理
- 双色球和大乐透历史数据导入
- 数据查询和筛选
- 数据导出
- 手动数据录入
2. 统计分析
- 号码出现频率统计
- 热门号码分析
- 冷门号码分析
- 数据可视化展示
### 1. 数据管理
- 双色球和大乐透历史数据导入
- 数据查询和筛选
- 数据导出
- 手动数据录入
- 自动数据更新
3. 智能选号
- 随机选号
- 频率选号
- 热门号码选号
- 冷门号码选号
### 2. 基础统计分析
- 号码出现频率统计
- 热门号码分析
- 冷门号码分析
- 数据可视化展示
### 3. 高级数据分析 ⭐ 新功能
- **遗漏值分析**: 分析各号码的遗漏期数
- **和值分析**: 统计红球和值的分布规律
- **AC值分析**: 邻号差值分析
- **质合比分析**: 质数与合数的比例分析
- **012路分析**: 除3余数分析
- **跨度分析**: 最大最小号码差值分析
- **综合分析**: 多维度数据整合分析
### 4. 智能预测系统 ⭐ 新功能
- **机器学习预测**: 基于历史数据的AI预测
- **模式预测**: 基于统计模式的预测
- **集成预测**: 多方法综合预测
- **预测模型训练**: 可自定义训练参数
- **预测结果评估**: 预测准确率统计
### 5. 智能选号
- 随机选号
- 频率选号
- 热门号码选号
- 冷门号码选号
- 自定义选号策略
## 项目结构
```
@ -44,10 +66,15 @@ lottery/
├── backend/ # 后端代码
│ ├── app/
│ │ ├── api/ # API 路由
│ │ │ ├── endpoints/ # 基础API端点
│ │ │ └── v1/ # API版本1
│ │ ├── core/ # 核心配置
│ │ ├── models/ # 数据模型
│ │ ├── schemas/ # 数据验证
│ │ └── services/ # 业务逻辑
│ │ ├── analysis_service.py # 基础分析服务
│ │ ├── advanced_analysis.py # 高级分析服务 ⭐
│ │ └── prediction_service.py # 预测服务 ⭐
│ ├── requirements.txt # 依赖包
│ └── main.py # 入口文件
├── frontend/ # 前端代码
@ -57,6 +84,8 @@ lottery/
│ │ ├── components/ # 组件
│ │ ├── router/ # 路由配置
│ │ ├── views/ # 页面
│ │ │ ├── AdvancedAnalysis.vue # 高级分析页面 ⭐
│ │ │ └── Prediction.vue # 预测页面 ⭐
│ │ ├── App.vue # 根组件
│ │ └── main.js # 入口文件
│ ├── package.json # 依赖配置
@ -67,7 +96,7 @@ lottery/
## 开发环境要求
- Python 3.8+
- Node.js 16+
- PostgreSQL 12+
- MySQL 8.0+ 或 PostgreSQL 12+
- npm 或 yarn
## 安装和运行
@ -87,7 +116,7 @@ pip install -r requirements.txt
```
3. 配置数据库
- 创建 PostgreSQL 数据库
- 创建 MySQL/PostgreSQL 数据库
- 修改 `backend/app/core/database.py` 中的数据库连接配置
4. 启动服务
@ -142,9 +171,30 @@ python schedule_update.py
```
系统会在每天凌晨2点自动检查并更新数据。
#### 其他说明
- 首页、API、前端等所有"最新开奖"展示均以 `open_time` 最大值为准,保证数据准确。
- 数据库不会因期号异常导致遗漏或重复,所有唯一性、顺序均以开奖日期为核心。
### 高级分析功能 ⭐
1. 进入"高级分析"页面
2. 选择彩票类型(双色球/大乐透)
3. 选择分析类型:
- **遗漏值分析**: 查看各号码的遗漏期数
- **和值分析**: 分析红球和值的分布规律
- **AC值分析**: 邻号差值分析
- **质合比分析**: 质数与合数比例
- **012路分析**: 除3余数分布
- **跨度分析**: 号码跨度统计
- **综合分析**: 多维度整合分析
4. 设置分析期数10-500期
5. 点击"分析"按钮查看结果
### 智能预测功能 ⭐
1. 进入"智能预测"页面
2. 选择彩票类型
3. 设置训练期数建议100-500期
4. 点击"训练模型"按钮
5. 训练完成后,可选择以下预测方法:
- **机器学习预测**: 基于AI算法的预测
- **模式预测**: 基于统计模式的预测
- **集成预测**: 多方法综合预测
6. 查看预测结果和置信度
### 数据查询
1. 在查询表单中输入查询条件
@ -167,6 +217,23 @@ python schedule_update.py
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
### 新增API端点 ⭐
#### 高级分析API
- `GET /api/v1/advanced-analysis/missing-value/{lottery_type}` - 遗漏值分析
- `GET /api/v1/advanced-analysis/sum-value/{lottery_type}` - 和值分析
- `GET /api/v1/advanced-analysis/ac-value/{lottery_type}` - AC值分析
- `GET /api/v1/advanced-analysis/prime-composite/{lottery_type}` - 质合比分析
- `GET /api/v1/advanced-analysis/road-012/{lottery_type}` - 012路分析
- `GET /api/v1/advanced-analysis/span/{lottery_type}` - 跨度分析
- `GET /api/v1/advanced-analysis/comprehensive/{lottery_type}` - 综合分析
#### 预测API
- `POST /api/v1/prediction/train/{lottery_type}` - 训练预测模型
- `GET /api/v1/prediction/predict/{lottery_type}` - 机器学习预测
- `GET /api/v1/prediction/pattern/{lottery_type}` - 模式预测
- `GET /api/v1/prediction/ensemble/{lottery_type}` - 集成预测
## 常见问题
1. 数据库连接失败
- 检查数据库服务是否启动
@ -180,12 +247,21 @@ python schedule_update.py
- 检查 JSON 文件格式是否正确
- 确认数据库表结构是否完整
4. 机器学习预测失败
- 确保历史数据充足建议至少100期
- 检查训练参数设置是否合理
## 开发计划
- [x] 添加高级数据分析功能
- [x] 实现机器学习预测系统
- [x] 优化数据可视化展示
- [ ] 添加用户认证功能
- [ ] 实现数据备份和恢复
- [ ] 优化数据导入性能
- [ ] 添加更多统计分析功能
- [ ] 实现自定义选号策略
- [ ] 添加移动端适配
- [ ] 实现数据导出功能增强
## 贡献指南
1. Fork 项目
@ -197,84 +273,20 @@ python schedule_update.py
## 许可证
MIT License
## 数据更新功能
## 更新日志
系统支持自动从聚合数据API获取最新的开奖数据并更新到本地数据库。更新功能包括
### v2.0.0 (2024-01-XX)
- ✨ 新增高级数据分析功能
- ✨ 新增机器学习预测系统
- ✨ 新增遗漏值、和值、AC值等分析
- ✨ 新增质合比、012路、跨度分析
- ✨ 优化数据可视化展示
- 🔧 更新依赖包版本
- 📝 完善API文档
1. 手动更新
```bash
cd backend
python update_lottery.py
```
2. 自动更新
```bash
cd backend
python schedule_update.py
```
系统会在每天凌晨2点自动检查并更新数据。
### 数据更新说明
- 系统会自动检查本地数据库中最新的开奖日期
- 只获取并更新比本地数据更新的开奖记录
- 更新过程会记录日志到 `lottery_update.log` 文件
- 支持双色球和大乐透两种彩票的数据更新
## 分析功能说明
### 基础分析策略
#### 1. 冷热号码分析
- 热号统计近50期出现频率最高的号码
- 冷号:统计超过平均遗漏期数的号码
- API: GET `/api/analysis/hot-cold/{lottery_type}?periods=50`
#### 2. 号码分布分析
- 分区统计:将号码划分为多个区间,分析各区出号比例
- 奇偶比:分析奇偶数的分布规律
- 大小比:统计大数和小数的分布规律
- API: GET `/api/analysis/distribution/{lottery_type}?periods=100`
#### 3. 连号与重复号分析
- 连号追踪:统计连号出现的频率和位置
- 重复号观察:分析相邻期号码重复情况
- API: GET `/api/analysis/consecutive/{lottery_type}?periods=100`
### 数学理论应用
#### 1. 数学统计特征
- 计算红球总和的历史平均值和标准差
- 分析号码组合的数学特征
- API: GET `/api/analysis/math-stats/{lottery_type}?periods=100`
#### 2. 遗漏值分析
- 计算每个号码的当前遗漏值
- 分析历史最大遗漏
- API: GET `/api/analysis/missing/{lottery_type}`
### 智能选号策略
系统提供多种智能选号策略:
- 均衡策略:综合考虑号码分布特征
- 热号策略:偏好选择近期高频号码
- 冷号策略:偏好选择遗漏值较大的号码
- 遗漏值策略:基于遗漏值分析选号
- API: GET `/api/analysis/smart-numbers/{lottery_type}?strategy=balanced`
### 使用示例
1. 获取双色球热号冷号分析:
```bash
curl http://localhost:8000/api/analysis/hot-cold/ssq?periods=50
```
2. 获取大乐透号码分布分析:
```bash
curl http://localhost:8000/api/analysis/distribution/dlt?periods=100
```
3. 使用均衡策略生成双色球号码:
```bash
curl http://localhost:8000/api/analysis/smart-numbers/ssq?strategy=balanced
```
### v1.0.0 (2024-01-XX)
- 🎉 初始版本发布
- ✨ 基础数据管理功能
- ✨ 基础统计分析
- ✨ 智能选号功能
- ✨ 前后端分离架构

View File

@ -0,0 +1,105 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import Dict
from ...core.database import get_db
from ...services.advanced_analysis import AdvancedAnalysisService
router = APIRouter()
@router.get("/missing-value/{lottery_type}")
def get_missing_value_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取遗漏值分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_missing_value_analysis(lottery_type, periods)
@router.get("/sum-value/{lottery_type}")
def get_sum_value_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取和值分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_sum_value_analysis(lottery_type, periods)
@router.get("/ac-value/{lottery_type}")
def get_ac_value_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取AC值分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_ac_value_analysis(lottery_type, periods)
@router.get("/prime-composite/{lottery_type}")
def get_prime_composite_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取质合比分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_prime_composite_analysis(lottery_type, periods)
@router.get("/road-012/{lottery_type}")
def get_road_012_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取012路分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_road_012_analysis(lottery_type, periods)
@router.get("/span/{lottery_type}")
def get_span_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取跨度分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_span_analysis(lottery_type, periods)
@router.get("/comprehensive/{lottery_type}")
def get_comprehensive_analysis(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""获取综合分析"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = AdvancedAnalysisService(db)
return service.get_comprehensive_analysis(lottery_type, periods)

View File

@ -0,0 +1,63 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import Dict
from ...core.database import get_db
from ...services.prediction_service import PredictionService
router = APIRouter()
@router.post("/train/{lottery_type}")
def train_prediction_model(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""训练预测模型"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = PredictionService(db)
return service.train_model(lottery_type, periods)
@router.get("/predict/{lottery_type}")
def predict_next_numbers(
lottery_type: str,
periods: int = 10,
db: Session = Depends(get_db)
):
"""预测下一期号码"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = PredictionService(db)
return service.predict_next_numbers(lottery_type, periods)
@router.get("/pattern/{lottery_type}")
def get_pattern_prediction(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""基于模式的预测"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = PredictionService(db)
return service.get_pattern_based_prediction(lottery_type, periods)
@router.get("/ensemble/{lottery_type}")
def get_ensemble_prediction(
lottery_type: str,
periods: int = 100,
db: Session = Depends(get_db)
):
"""集成预测"""
if lottery_type not in ['ssq', 'dlt']:
raise HTTPException(status_code=400, detail="Invalid lottery type")
service = PredictionService(db)
return service.get_ensemble_prediction(lottery_type, periods)

View File

@ -3,6 +3,8 @@ from fastapi.middleware.cors import CORSMiddleware
from app.core.config import settings
from app.api.v1.lottery import router as lottery_router
from app.api.endpoints.analysis import router as analysis_router
from app.api.endpoints.advanced_analysis import router as advanced_analysis_router
from app.api.endpoints.prediction import router as prediction_router
from app.core.database import Base, engine
# 创建数据库表
@ -27,6 +29,10 @@ app.include_router(
lottery_router, prefix=f"{settings.API_V1_STR}/lottery", tags=["lottery"])
app.include_router(
analysis_router, prefix=f"{settings.API_V1_STR}/analysis", tags=["analysis"])
app.include_router(
advanced_analysis_router, prefix=f"{settings.API_V1_STR}/advanced-analysis", tags=["advanced-analysis"])
app.include_router(
prediction_router, prefix=f"{settings.API_V1_STR}/prediction", tags=["prediction"])
if __name__ == "__main__":
import uvicorn

View File

@ -0,0 +1,201 @@
from typing import Dict
import numpy as np
from collections import defaultdict
from sqlalchemy.orm import Session
from ..models.lottery import SSQLottery, DLTLottery
class AdvancedAnalysisService:
def __init__(self, db: Session):
self.db = db
def get_missing_value_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
if lottery_type == 'ssq':
red_range = 33
blue_range = 16
else:
red_range = 35
blue_range = 12
red_missing = {i: 0 for i in range(1, red_range + 1)}
blue_missing = {i: 0 for i in range(1, blue_range + 1)}
for draw in reversed(recent_draws):
if lottery_type == 'ssq':
red_numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
blue_numbers = [draw.blue_ball]
else:
red_numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
blue_numbers = [draw.back_ball_1, draw.back_ball_2]
for num in range(1, red_range + 1):
if num not in red_numbers:
red_missing[num] += 1
for num in range(1, blue_range + 1):
if num not in blue_numbers:
blue_missing[num] += 1
return {
'red_missing': red_missing,
'blue_missing': blue_missing,
'max_red_missing': max(red_missing.values()),
'max_blue_missing': max(blue_missing.values()),
'avg_red_missing': sum(red_missing.values()) / len(red_missing),
'avg_blue_missing': sum(blue_missing.values()) / len(blue_missing)
}
def get_sum_value_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
sums = []
for draw in recent_draws:
if lottery_type == 'ssq':
red_numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
else:
red_numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
sums.append(sum(red_numbers))
sum_distribution = defaultdict(int)
for s in sums:
sum_distribution[s] += 1
return {
'sums': sums,
'sum_distribution': dict(sum_distribution),
'min_sum': min(sums),
'max_sum': max(sums),
'avg_sum': float(np.mean(sums)),
'median_sum': float(np.median(sums)),
'std_sum': float(np.std(sums)),
'most_common_sums': sorted(sum_distribution.items(), key=lambda x: x[1], reverse=True)[:10]
}
def get_ac_value_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
ac_values = []
for draw in recent_draws:
if lottery_type == 'ssq':
red_numbers = sorted([draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6])
else:
red_numbers = sorted([draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5])
ac = sum(abs(red_numbers[i+1] - red_numbers[i])
for i in range(len(red_numbers)-1))
ac_values.append(ac)
ac_distribution = defaultdict(int)
for ac in ac_values:
ac_distribution[ac] += 1
return {
'ac_values': ac_values,
'ac_distribution': dict(ac_distribution),
'min_ac': min(ac_values),
'max_ac': max(ac_values),
'avg_ac': float(np.mean(ac_values)),
'median_ac': float(np.median(ac_values)),
'std_ac': float(np.std(ac_values)),
'most_common_ac': sorted(ac_distribution.items(), key=lambda x: x[1], reverse=True)[:10]
}
def get_prime_composite_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
prime_ratios = []
for draw in recent_draws:
if lottery_type == 'ssq':
red_numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
else:
red_numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
prime_count = sum(1 for num in red_numbers if is_prime(num))
composite_count = len(red_numbers) - prime_count
prime_ratios.append(f"{prime_count}:{composite_count}")
ratio_distribution = defaultdict(int)
for ratio in prime_ratios:
ratio_distribution[ratio] += 1
return {
'prime_ratios': prime_ratios,
'ratio_distribution': dict(ratio_distribution),
'most_common_ratios': sorted(ratio_distribution.items(), key=lambda x: x[1], reverse=True)[:10]
}
def get_road_012_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
road_012_counts = []
for draw in recent_draws:
if lottery_type == 'ssq':
red_numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
else:
red_numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
road_0 = sum(1 for num in red_numbers if num % 3 == 0)
road_1 = sum(1 for num in red_numbers if num % 3 == 1)
road_2 = sum(1 for num in red_numbers if num % 3 == 2)
road_012_counts.append({
'road_0': road_0,
'road_1': road_1,
'road_2': road_2,
'pattern': f"{road_0}:{road_1}:{road_2}"
})
pattern_distribution = defaultdict(int)
for count in road_012_counts:
pattern_distribution[count['pattern']] += 1
return {
'road_012_counts': road_012_counts,
'pattern_distribution': dict(pattern_distribution),
'most_common_patterns': sorted(pattern_distribution.items(), key=lambda x: x[1], reverse=True)[:10]
}
def get_span_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
spans = []
for draw in recent_draws:
if lottery_type == 'ssq':
red_numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
else:
red_numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
span = max(red_numbers) - min(red_numbers)
spans.append(span)
span_distribution = defaultdict(int)
for span in spans:
span_distribution[span] += 1
return {
'spans': spans,
'span_distribution': dict(span_distribution),
'min_span': min(spans),
'max_span': max(spans),
'avg_span': float(np.mean(spans)),
'median_span': float(np.median(spans)),
'std_span': float(np.std(spans)),
'most_common_spans': sorted(span_distribution.items(), key=lambda x: x[1], reverse=True)[:10]
}
def get_comprehensive_analysis(self, lottery_type: str, periods: int = 100) -> Dict:
return {
'missing_value': self.get_missing_value_analysis(lottery_type, periods),
'sum_value': self.get_sum_value_analysis(lottery_type, periods),
'ac_value': self.get_ac_value_analysis(lottery_type, periods),
'prime_composite': self.get_prime_composite_analysis(lottery_type, periods),
'road_012': self.get_road_012_analysis(lottery_type, periods),
'span': self.get_span_analysis(lottery_type, periods)
}

View File

@ -0,0 +1,535 @@
from typing import List, Dict, Tuple, Optional
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from collections import defaultdict
from sqlalchemy.orm import Session
from ..models.lottery import SSQLottery, DLTLottery
class PredictionService:
# 类级别的字典来存储所有模型
_models = {}
_scalers = {}
def __init__(self, db: Session):
self.db = db
def prepare_features(self, lottery_type: str, periods: int = 100) -> Tuple[np.ndarray, np.ndarray]:
"""准备机器学习特征
Args:
lottery_type: 彩票类型 ('ssq' 'dlt')
periods: 使用期数
Returns:
Tuple: (特征矩阵, 标签矩阵)
"""
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
features = []
labels = []
for i in range(len(recent_draws) - 10): # 使用前10期预测下一期
# 特征前10期的号码
feature_row = []
for j in range(10):
draw = recent_draws[i + j]
if lottery_type == 'ssq':
numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
feature_row.extend(sorted(numbers))
feature_row.append(draw.blue_ball) # 添加蓝球
else:
numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
feature_row.extend(sorted(numbers))
feature_row.extend(
[draw.back_ball_1, draw.back_ball_2]) # 添加后区号码
# 标签:下一期的号码
next_draw = recent_draws[i + 10]
if lottery_type == 'ssq':
label_numbers = [next_draw.red_ball_1, next_draw.red_ball_2, next_draw.red_ball_3,
next_draw.red_ball_4, next_draw.red_ball_5, next_draw.red_ball_6]
label_numbers.append(next_draw.blue_ball) # 添加蓝球
else:
label_numbers = [next_draw.front_ball_1, next_draw.front_ball_2, next_draw.front_ball_3,
next_draw.front_ball_4, next_draw.front_ball_5]
label_numbers.extend(
[next_draw.back_ball_1, next_draw.back_ball_2]) # 添加后区号码
features.append(feature_row)
# 保持红球排序,蓝球位置不变
labels.append(sorted(label_numbers[:-1]) + [label_numbers[-1]])
return np.array(features), np.array(labels)
def train_model(self, lottery_type: str, periods: int = 100) -> Dict:
"""训练预测模型
Args:
lottery_type: 彩票类型 ('ssq' 'dlt')
periods: 使用期数
Returns:
Dict: 训练结果
"""
try:
features, labels = self.prepare_features(lottery_type, periods)
if len(features) < 20: # 数据不足
return {"success": False, "message": "数据不足,无法训练模型"}
# 标准化特征
scaler = StandardScaler()
features_scaled = scaler.fit_transform(features)
self._scalers[lottery_type] = scaler
# 为每个号码位置训练一个模型
models = {}
accuracies = []
for pos in range(labels.shape[1]):
model = RandomForestRegressor(
n_estimators=100, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
features_scaled, labels[:, pos], test_size=0.2, random_state=42
)
model.fit(X_train, y_train)
accuracy = model.score(X_test, y_test)
models[f"pos_{pos}"] = model
accuracies.append(accuracy)
self._models[lottery_type] = models
return {
"success": True,
"message": "模型训练成功",
"avg_accuracy": np.mean(accuracies),
"accuracies": accuracies,
"training_samples": len(features)
}
except Exception as e:
return {"success": False, "message": f"训练失败: {str(e)}"}
def predict_next_numbers(self, lottery_type: str, periods: int = 10) -> Dict:
"""预测下一期号码
Args:
lottery_type: 彩票类型 ('ssq' 'dlt')
periods: 使用期数
Returns:
Dict: 预测结果
"""
if lottery_type not in self._models:
return {"success": False, "message": "模型未训练,请先训练模型"}
try:
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(10).all() # 只需要最近10期
if len(recent_draws) < 10:
return {"success": False, "message": "历史数据不足"}
# 准备特征
feature_row = []
for draw in recent_draws:
if lottery_type == 'ssq':
numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
feature_row.extend(sorted(numbers))
feature_row.append(draw.blue_ball) # 添加蓝球
else:
numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
feature_row.extend(sorted(numbers))
feature_row.extend(
[draw.back_ball_1, draw.back_ball_2]) # 添加后区号码
# 标准化特征
scaler = self._scalers.get(lottery_type)
if not scaler:
return {"success": False, "message": "模型未训练,请先训练模型"}
feature_scaled = scaler.transform([feature_row])
# 预测每个位置的号码
predictions = []
models = self._models[lottery_type]
for pos in range(len(models)):
model = models[f"pos_{pos}"]
pred = model.predict(feature_scaled)[0]
predictions.append(round(pred))
# 确保预测的号码在有效范围内
if lottery_type == 'ssq':
max_red = 33
max_blue = 16
red_count = 6
else:
max_red = 35
max_blue = 12
red_count = 5
# 分离红球和蓝球预测
if lottery_type == 'ssq':
red_predictions = predictions[:red_count]
blue_prediction = predictions[-1]
else:
red_predictions = predictions[:red_count]
blue_predictions = predictions[red_count:]
# 处理红球
red_predictions = [max(1, min(max_red, p))
for p in red_predictions]
red_predictions = sorted(list(set(red_predictions)))
# 如果红球不够,补充随机号码
while len(red_predictions) < red_count:
import random
new_num = random.randint(1, max_red)
if new_num not in red_predictions:
red_predictions.append(new_num)
red_predictions = sorted(red_predictions[:red_count])
# 处理蓝球
if lottery_type == 'ssq':
blue_prediction = max(1, min(max_blue, blue_prediction))
else:
blue_predictions = [max(1, min(max_blue, p))
for p in blue_predictions]
blue_predictions = sorted(list(set(blue_predictions)))
while len(blue_predictions) < 2:
new_num = random.randint(1, max_blue)
if new_num not in blue_predictions:
blue_predictions.append(new_num)
blue_predictions = sorted(blue_predictions)
return {
"success": True,
"predicted_numbers": red_predictions,
"predicted_blue": blue_prediction if lottery_type == 'ssq' else None,
"predicted_blues": blue_predictions if lottery_type == 'dlt' else None,
"confidence": "基于历史数据的机器学习预测"
}
except Exception as e:
return {"success": False, "message": f"预测失败: {str(e)}"}
def get_pattern_based_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
"""基于模式的预测
Args:
lottery_type: 彩票类型 ('ssq' 'dlt')
periods: 分析期数
Returns:
Dict: 预测结果
"""
model = SSQLottery if lottery_type == 'ssq' else DLTLottery
recent_draws = self.db.query(model).order_by(
model.open_time.desc()).limit(periods).all()
# 分析最近的开奖模式
patterns = {
'sum_range': [],
'odd_even_ratio': [],
'zone_distribution': [],
'consecutive_count': []
}
for draw in recent_draws:
if lottery_type == 'ssq':
numbers = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
else:
numbers = [draw.front_ball_1, draw.front_ball_2, draw.front_ball_3,
draw.front_ball_4, draw.front_ball_5]
# 和值
patterns['sum_range'].append(sum(numbers))
# 奇偶比
odd_count = sum(1 for n in numbers if n % 2 == 1)
patterns['odd_even_ratio'].append(
f"{odd_count}:{len(numbers)-odd_count}")
# 分区分布
zones = [(n-1)//5 + 1 for n in numbers]
zone_count = len(set(zones))
patterns['zone_distribution'].append(zone_count)
# 连号数量
sorted_nums = sorted(numbers)
consecutive = sum(1 for i in range(len(sorted_nums)-1)
if sorted_nums[i+1] - sorted_nums[i] == 1)
patterns['consecutive_count'].append(consecutive)
# 计算最常见的模式
most_common_patterns = {}
for key, values in patterns.items():
if key == 'sum_range':
# 和值范围
avg_sum = np.mean(values)
std_sum = np.std(values)
most_common_patterns[key] = {
'avg': avg_sum,
'std': std_sum,
'range': [int(avg_sum - std_sum), int(avg_sum + std_sum)]
}
else:
# 其他模式
from collections import Counter
counter = Counter(values)
most_common_patterns[key] = counter.most_common(3)
# 根据模式生成推荐号码
max_num = 33 if lottery_type == 'ssq' else 35
target_count = 6 if lottery_type == 'ssq' else 5
# 获取推荐的模式
target_sum_range = most_common_patterns['sum_range']['range']
target_odd_ratio = most_common_patterns['odd_even_ratio'][0][0].split(
':')[0] if most_common_patterns['odd_even_ratio'] else "3"
target_zones = most_common_patterns['zone_distribution'][0][
0] if most_common_patterns['zone_distribution'] else 4
target_consecutive = most_common_patterns['consecutive_count'][
0][0] if most_common_patterns['consecutive_count'] else 1
# 生成符合模式的号码
import random
best_numbers = None
best_score = -1
# 尝试100次生成最符合模式的号码
for _ in range(100):
# 初始化号码集
numbers = set()
# 确保有连号
if target_consecutive > 0:
start = random.randint(1, max_num - target_consecutive)
for i in range(target_consecutive + 1):
if len(numbers) < target_count:
numbers.add(start + i)
# 根据奇偶比例添加号码
target_odd = int(target_odd_ratio)
current_odd = sum(1 for n in numbers if n % 2 == 1)
while len(numbers) < target_count:
n = random.randint(1, max_num)
if n not in numbers:
if (n % 2 == 1 and current_odd < target_odd) or \
(n % 2 == 0 and (len(numbers) - current_odd) < (target_count - target_odd)):
numbers.add(n)
if n % 2 == 1:
current_odd += 1
numbers = sorted(list(numbers))
# 计算当前号码组合的得分
score = 0
# 和值得分
current_sum = sum(numbers)
if target_sum_range[0] <= current_sum <= target_sum_range[1]:
score += 1
# 奇偶比得分
current_odd = sum(1 for n in numbers if n % 2 == 1)
if current_odd == int(target_odd_ratio):
score += 1
# 分区得分
current_zones = len(set((n-1)//5 + 1 for n in numbers))
if current_zones == target_zones:
score += 1
# 连号得分
current_consecutive = sum(1 for i in range(
len(numbers)-1) if numbers[i+1] - numbers[i] == 1)
if current_consecutive == target_consecutive:
score += 1
if score > best_score:
best_score = score
best_numbers = numbers
# 如果是双色球,还需要生成蓝球
predicted_blue = None
if lottery_type == 'ssq':
# 分析蓝球规律
blue_numbers = [draw.blue_ball for draw in recent_draws]
blue_counter = Counter(blue_numbers)
# 选择最近出现频率适中的蓝球
common_blues = [num for num, _ in blue_counter.most_common(
)[len(blue_counter)//3:(len(blue_counter)*2)//3]]
if common_blues:
predicted_blue = random.choice(common_blues)
else:
predicted_blue = random.randint(1, 16)
return {
"success": True,
"patterns": most_common_patterns,
"suggested_criteria": {
"sum_range": most_common_patterns['sum_range']['range'],
"odd_even_ratio": most_common_patterns['odd_even_ratio'][0][0] if most_common_patterns['odd_even_ratio'] else "3:3",
"zone_distribution": most_common_patterns['zone_distribution'][0][0] if most_common_patterns['zone_distribution'] else 4,
"consecutive_count": most_common_patterns['consecutive_count'][0][0] if most_common_patterns['consecutive_count'] else 1
},
"predicted_numbers": best_numbers,
"predicted_blue": predicted_blue if lottery_type == 'ssq' else None
}
def get_ensemble_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
"""集成预测(结合多种方法)
Args:
lottery_type: 彩票类型 ('ssq' 'dlt')
periods: 分析期数
Returns:
Dict: 预测结果
"""
# 机器学习预测
ml_result = self.predict_next_numbers(lottery_type, periods)
# 模式预测
pattern_result = self.get_pattern_based_prediction(
lottery_type, periods)
# 频率预测(基于现有服务)
from .analysis_service import LotteryAnalysisService
analysis_service = LotteryAnalysisService(self.db)
freq_result = analysis_service.get_hot_cold_numbers(
lottery_type, periods)
# 综合推荐
recommendations = []
if ml_result.get('success'):
recommendations.append({
'method': '机器学习',
'numbers': ml_result['predicted_numbers'],
'blue': ml_result['predicted_blue'] if lottery_type == 'ssq' else None,
'blues': ml_result['predicted_blues'] if lottery_type == 'dlt' else None,
'confidence': ''
})
if freq_result:
max_red = 33 if lottery_type == 'ssq' else 35
max_blue = 16 if lottery_type == 'ssq' else 12
red_count = 6 if lottery_type == 'ssq' else 5
# 获取热号和冷号
hot_reds = freq_result['hot_reds']
cold_reds = freq_result['cold_reds']
# 初始化号码集
selected_numbers = set()
# 从热号中选择2-3个号码
hot_count = min(3, len(hot_reds))
for num in hot_reds[:hot_count]:
selected_numbers.add(num)
# 从冷号中选择1-2个号码
cold_count = min(2, len(cold_reds))
for num in cold_reds[:cold_count]:
if not any(abs(num - x) == 1 for x in selected_numbers): # 避免连号
selected_numbers.add(num)
# 计算还需要多少个号码
remaining = red_count - len(selected_numbers)
# 获取温号(既不是热号也不是冷号的号码)
all_numbers = set(range(1, max_red + 1))
warm_numbers = list(all_numbers - set(hot_reds) - set(cold_reds))
import random
random.shuffle(warm_numbers)
# 从温号中补充号码
for num in warm_numbers:
if len(selected_numbers) >= red_count:
break
# 检查是否会形成连号
consecutive_count = sum(
1 for x in selected_numbers if abs(num - x) == 1)
if consecutive_count <= 1: # 最多允许两个连号
selected_numbers.add(num)
# 如果还不够,从剩余号码中随机选择
remaining_numbers = list(all_numbers - selected_numbers)
while len(selected_numbers) < red_count:
num = random.choice(remaining_numbers)
consecutive_count = sum(
1 for x in selected_numbers if abs(num - x) == 1)
if consecutive_count <= 1:
selected_numbers.add(num)
remaining_numbers.remove(num)
# 生成蓝球
if lottery_type == 'ssq':
if 'hot_blues' in freq_result and freq_result['hot_blues']:
# 从热门蓝球中随机选择
blue_prediction = random.choice(
freq_result['hot_blues'][:3])
else:
blue_prediction = random.randint(1, max_blue)
recommendations.append({
'method': '热冷号分析',
'numbers': sorted(list(selected_numbers)),
'blue': blue_prediction,
'confidence': ''
})
else:
# 大乐透后区号码选择
blue_predictions = []
if 'hot_blues' in freq_result and freq_result['hot_blues']:
# 从热门后区号码中选择
available_blues = freq_result['hot_blues'][:4] # 取前4个热门号码
while len(blue_predictions) < 2 and available_blues:
num = random.choice(available_blues)
blue_predictions.append(num)
available_blues.remove(num)
# 如果还不够2个随机补充
while len(blue_predictions) < 2:
num = random.randint(1, max_blue)
if num not in blue_predictions:
blue_predictions.append(num)
recommendations.append({
'method': '热冷号分析',
'numbers': sorted(list(selected_numbers)),
'blues': sorted(blue_predictions),
'confidence': ''
})
if pattern_result and pattern_result.get('success'):
recommendations.append({
'method': '模式分析',
'numbers': pattern_result['predicted_numbers'],
'blue': pattern_result['predicted_blue'] if lottery_type == 'ssq' else None,
'blues': pattern_result['predicted_blues'] if lottery_type == 'dlt' else None,
'confidence': ''
})
return {
"success": True,
"recommendations": recommendations,
"pattern_analysis": pattern_result.get('suggested_criteria', {}) if pattern_result else {},
"frequency_analysis": freq_result or {}
}

View File

@ -12,4 +12,8 @@ python-dotenv==1.0.0
pandas==2.1.3
aiofiles==23.2.1
requests==2.31.0
schedule==1.2.1
schedule==1.2.1
numpy>=1.21.0,<2.0.0
scikit-learn>=1.0.0,<2.0.0
matplotlib>=3.5.0,<4.0.0
seaborn>=0.11.0,<1.0.0

View File

@ -1,119 +1,144 @@
<template>
<el-container class="app-container">
<el-container class="layout-container">
<el-header>
<div class="header-left">
<h1>彩票数据分析系统</h1>
</div>
<div class="header-right">
<div class="header-container">
<div class="logo">
<h1>彩票数据分析系统</h1>
</div>
<el-menu
:default-active="activeIndex"
class="nav-menu"
mode="horizontal"
router
:default-active="$route.path"
background-color="#409EFF"
text-color="#fff"
active-text-color="#fff"
@select="handleSelect"
>
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/ssq">双色球</el-menu-item>
<el-menu-item index="/dlt">大乐透</el-menu-item>
<el-menu-item index="/statistics">统计分析</el-menu-item>
<el-menu-item index="/advanced-analysis">高级分析</el-menu-item>
<el-menu-item index="/prediction">智能预测</el-menu-item>
<el-menu-item index="/number-generator">智能选号</el-menu-item>
</el-menu>
</div>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>
<div class="footer-content">
<p>彩票数据分析系统 &copy; 2024</p>
</div>
</el-footer>
</el-container>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElContainer, ElHeader, ElMain, ElMenu, ElMenuItem, ElDrawer, ElButton } from 'element-plus'
import { Menu } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const drawer = ref(false)
const isMobile = ref(false)
const $route = useRoute()
const router = useRouter()
const activeIndex = ref('/')
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
const handleSelect = (key) => {
activeIndex.value = key
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
</script>
<style scoped>
.app-container {
<style>
.layout-container {
min-height: 100vh;
}
.el-header {
background-color: #409EFF;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
flex-direction: column;
}
.header-container {
height: 60px;
box-shadow: 0 2px 8px 0 rgba(64,158,255,0.08);
}
.header-left h1 {
margin: 0;
font-size: 22px;
font-weight: bold;
letter-spacing: 2px;
}
.header-right {
display: flex;
align-items: center;
padding: 0 20px;
}
.menu-btn {
color: white;
border: none;
background: transparent;
font-size: 22px;
.logo {
margin-right: 40px;
}
.el-menu {
background: transparent;
border-bottom: none;
overflow-x: auto;
white-space: nowrap;
flex-wrap: nowrap;
.logo h1 {
color: #fff;
margin: 0;
font-size: 20px;
font-weight: bold;
}
.el-menu-item {
.nav-menu {
flex: 1;
border: none !important;
}
.el-menu--horizontal {
border-bottom: none !important;
}
.el-menu--horizontal > .el-menu-item {
height: 60px;
line-height: 60px;
font-size: 16px;
font-weight: 500;
padding: 0 20px;
border-bottom: none !important;
}
@media screen and (max-width: 1024px) {
.el-header {
flex-direction: row;
height: 50px;
.el-header {
padding: 0;
background-color: #409EFF;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.el-menu--horizontal > .el-menu-item:hover,
.el-menu--horizontal > .el-menu-item:focus,
.el-menu--horizontal > .el-menu-item.is-active {
background-color: rgba(255, 255, 255, 0.1) !important;
border-bottom: 2px solid #fff !important;
}
.el-main {
padding: 20px;
flex: 1;
background-color: #f5f7fa;
}
.el-footer {
padding: 20px;
background-color: #fff;
border-top: 1px solid #e4e7ed;
}
.footer-content {
text-align: center;
color: #909399;
}
/* 响应式布局 */
@media screen and (max-width: 768px) {
.header-container {
padding: 0 10px;
}
.header-left h1 {
.logo {
margin-right: 20px;
}
.logo h1 {
font-size: 16px;
}
/* 不再隐藏el-menu */
}
.el-header .el-menu-item.is-active {
background-color: #fff !important;
color: #409EFF !important;
border-radius: 8px 8px 0 0;
font-weight: bold;
}
.el-header .el-menu-item {
color: #fff !important;
transition: background 0.2s, color 0.2s;
}
.el-header .el-menu-item:not(.is-active):hover {
background: rgba(255,255,255,0.15) !important;
color: #fff !important;
}
.el-header .el-menu::-webkit-scrollbar {
height: 4px;
.el-menu--horizontal > .el-menu-item {
padding: 0 10px;
font-size: 14px;
}
}
</style>

View File

@ -0,0 +1,83 @@
import request from '@/utils/request'
// 获取遗漏值分析
export function getMissingValueAnalysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/missing-value/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 获取和值分析
export function getSumValueAnalysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/sum-value/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 获取AC值分析
export function getAcValueAnalysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/ac-value/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 获取质合比分析
export function getPrimeCompositeAnalysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/prime-composite/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 获取012路分析
export function getRoad012Analysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/road-012/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 获取跨度分析
export function getSpanAnalysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/span/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 获取综合分析
export function getComprehensiveAnalysis(lotteryType, periods = 100) {
return request({
url: `/api/v1/advanced-analysis/comprehensive/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 通用高级分析接口
export function getAdvancedAnalysis(lotteryType, analysisType, periods = 100) {
const urlMap = {
'missing': `/api/v1/advanced-analysis/missing-value/${lotteryType}`,
'sum': `/api/v1/advanced-analysis/sum-value/${lotteryType}`,
'ac': `/api/v1/advanced-analysis/ac-value/${lotteryType}`,
'prime': `/api/v1/advanced-analysis/prime-composite/${lotteryType}`,
'road': `/api/v1/advanced-analysis/road-012/${lotteryType}`,
'span': `/api/v1/advanced-analysis/span/${lotteryType}`,
'comprehensive': `/api/v1/advanced-analysis/comprehensive/${lotteryType}`
}
return request({
url: urlMap[analysisType],
method: 'get',
params: { periods }
})
}

View File

@ -0,0 +1,37 @@
import request from '@/utils/request'
// 训练预测模型
export function trainPredictionModel(lotteryType, periods = 100) {
return request({
url: `/api/v1/prediction/train/${lotteryType}`,
method: 'post',
params: { periods }
})
}
// 预测下一期号码
export function predictNextNumbers(lotteryType, periods = 10) {
return request({
url: `/api/v1/prediction/predict/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 基于模式的预测
export function getPatternPrediction(lotteryType, periods = 100) {
return request({
url: `/api/v1/prediction/pattern/${lotteryType}`,
method: 'get',
params: { periods }
})
}
// 集成预测
export function getEnsemblePrediction(lotteryType, periods = 100) {
return request({
url: `/api/v1/prediction/ensemble/${lotteryType}`,
method: 'get',
params: { periods }
})
}

View File

@ -1,36 +1,51 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/ssq',
name: 'SSQ',
component: () => import('../views/SSQ.vue')
},
{
path: '/dlt',
name: 'DLT',
component: () => import('../views/DLT.vue')
},
{
path: '/statistics',
name: 'Statistics',
component: () => import('../views/Statistics.vue')
},
{
path: '/number-generator',
name: 'NumberGenerator',
component: () => import('../views/NumberGenerator.vue')
}
]
import Home from '../views/Home.vue'
import SSQ from '../views/SSQ.vue'
import DLT from '../views/DLT.vue'
import Statistics from '../views/Statistics.vue'
import AdvancedAnalysis from '../views/AdvancedAnalysis.vue'
import Prediction from '../views/Prediction.vue'
import NumberGenerator from '../views/NumberGenerator.vue'
const router = createRouter({
history: createWebHistory(),
routes
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/ssq',
name: 'SSQ',
component: SSQ
},
{
path: '/dlt',
name: 'DLT',
component: DLT
},
{
path: '/statistics',
name: 'Statistics',
component: Statistics
},
{
path: '/advanced-analysis',
name: 'AdvancedAnalysis',
component: AdvancedAnalysis
},
{
path: '/prediction',
name: 'Prediction',
component: Prediction
},
{
path: '/number-generator',
name: 'NumberGenerator',
component: NumberGenerator
}
]
})
export default router

View File

@ -0,0 +1,576 @@
<template>
<el-card class="advanced-analysis">
<template #header>
<el-form :inline="true" class="analysis-form" label-width="80px">
<el-form-item label="彩票类型">
<el-select v-model="lotteryType" placeholder="请选择" @change="loadAnalysis" style="width: 120px">
<el-option label="双色球" value="ssq"></el-option>
<el-option label="大乐透" value="dlt"></el-option>
</el-select>
</el-form-item>
<el-form-item label="分析类型">
<el-select v-model="analysisType" placeholder="请选择" @change="loadAnalysis" style="width: 140px">
<el-option label="遗漏值分析" value="missing"></el-option>
<el-option label="和值分析" value="sum"></el-option>
<el-option label="AC值分析" value="ac"></el-option>
<el-option label="质合比分析" value="prime"></el-option>
<el-option label="012路分析" value="road"></el-option>
<el-option label="跨度分析" value="span"></el-option>
<el-option label="综合分析" value="comprehensive"></el-option>
</el-select>
</el-form-item>
<el-form-item label="分析期数">
<el-input-number
v-model="periods"
:min="10"
:max="500"
style="width: 120px"
@change="loadAnalysis"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadAnalysis" :loading="loading">
分析
</el-button>
</el-form-item>
</el-form>
</template>
<div class="chart-area">
<div v-if="loading" class="loading">
<el-skeleton :rows="10" animated />
</div>
<div v-else-if="analysisData" class="analysis-content">
<div class="chart-title">
<h3>{{ analysisTypeLabelMap[analysisType] || '分析结果' }}</h3>
</div>
<div v-if="analysisType !== 'comprehensive'" class="chart-container">
<div ref="missingChart" v-show="analysisType === 'missing'" style="width: 100%; height: 400px;"></div>
<div ref="sumChart" v-show="analysisType === 'sum'" style="width: 100%; height: 400px;"></div>
<div ref="acChart" v-show="analysisType === 'ac'" style="width: 100%; height: 400px;"></div>
<div ref="primeChart" v-show="analysisType === 'prime'" style="width: 100%; height: 400px;"></div>
<div ref="roadChart" v-show="analysisType === 'road'" style="width: 100%; height: 400px;"></div>
<div ref="spanChart" v-show="analysisType === 'span'" style="width: 100%; height: 400px;"></div>
<el-empty v-if="!hasChart(analysisType)" description="暂无数据" />
</div>
<el-row :gutter="16" class="stat-cards" v-if="statItems.length">
<el-col v-for="item in statItems" :key="item.label" :span="6">
<el-card shadow="hover" class="stat-card">
<el-statistic :title="item.label" :value="item.value" />
</el-card>
</el-col>
</el-row>
</div>
</div>
</el-card>
</template>
<script>
import { ref, reactive, onMounted, nextTick, computed, watch } from 'vue'
import * as echarts from 'echarts'
import { getAdvancedAnalysis } from '@/api/advancedAnalysis'
export default {
name: 'AdvancedAnalysis',
setup() {
const lotteryType = ref('ssq')
const analysisType = ref('missing')
const periods = ref(100)
const loading = ref(false)
const analysisData = ref(null)
const analysisTypeLabelMap = {
missing: '遗漏值分析',
sum: '和值分析',
ac: 'AC值分析',
prime: '质合比分析',
road: '012路分析',
span: '跨度分析',
comprehensive: '综合分析'
}
//
const missingChart = ref(null)
const sumChart = ref(null)
const acChart = ref(null)
const primeChart = ref(null)
const roadChart = ref(null)
const spanChart = ref(null)
const compMissingChart = ref(null)
const compSumChart = ref(null)
const compAcChart = ref(null)
const compSpanChart = ref(null)
//
let chartInstances = {}
const statItems = computed(() => {
if (!analysisData.value) return []
if (analysisType.value === 'missing') {
return [
{ label: '最大遗漏值', value: analysisData.value.max_red_missing },
{ label: '平均遗漏值', value: Math.round(analysisData.value.avg_red_missing) },
{ label: '蓝球最大遗漏', value: analysisData.value.max_blue_missing },
{ label: '蓝球平均遗漏', value: Math.round(analysisData.value.avg_blue_missing) }
]
}
if (analysisType.value === 'sum') {
return [
{ label: '最小和值', value: analysisData.value.min_sum },
{ label: '最大和值', value: analysisData.value.max_sum },
{ label: '平均和值', value: Math.round(analysisData.value.avg_sum) },
{ label: '中位数', value: Math.round(analysisData.value.median_sum) },
{ label: '标准差', value: Math.round(analysisData.value.std_sum) },
{ label: '分析期数', value: analysisData.value.sums.length }
]
}
if (analysisType.value === 'ac') {
return [
{ label: '最小AC值', value: analysisData.value.min_ac },
{ label: '最大AC值', value: analysisData.value.max_ac },
{ label: '平均AC值', value: Math.round(analysisData.value.avg_ac) },
{ label: '中位数', value: Math.round(analysisData.value.median_ac) },
{ label: '标准差', value: Math.round(analysisData.value.std_ac) },
{ label: '分析期数', value: analysisData.value.ac_values.length }
]
}
if (analysisType.value === 'span') {
return [
{ label: '最小跨度', value: analysisData.value.min_span },
{ label: '最大跨度', value: analysisData.value.max_span },
{ label: '平均跨度', value: Math.round(analysisData.value.avg_span) },
{ label: '中位数', value: Math.round(analysisData.value.median_span) },
{ label: '标准差', value: Math.round(analysisData.value.std_span) },
{ label: '分析期数', value: analysisData.value.spans.length }
]
}
//
if (analysisType.value === 'comprehensive' && analysisData.value) {
const d = analysisData.value
return [
//
{ label: '最大遗漏值', value: d.missing_value.max_red_missing },
{ label: '平均遗漏值', value: Math.round(d.missing_value.avg_red_missing) },
{ label: '蓝球最大遗漏', value: d.missing_value.max_blue_missing },
{ label: '蓝球平均遗漏', value: Math.round(d.missing_value.avg_blue_missing) },
//
{ label: '最小和值', value: d.sum_value.min_sum },
{ label: '最大和值', value: d.sum_value.max_sum },
{ label: '平均和值', value: Math.round(d.sum_value.avg_sum) },
// AC
{ label: '最小AC值', value: d.ac_value.min_ac },
{ label: '最大AC值', value: d.ac_value.max_ac },
{ label: '平均AC值', value: Math.round(d.ac_value.avg_ac) },
//
{ label: '最小跨度', value: d.span.min_span },
{ label: '最大跨度', value: d.span.max_span },
{ label: '平均跨度', value: Math.round(d.span.avg_span) }
]
}
return []
})
const hasChart = (type) => {
return ['missing', 'sum', 'ac', 'prime', 'road', 'span'].includes(type)
}
const getChartRef = (type) => {
switch (type) {
case 'missing': return 'missingChart'
case 'sum': return 'sumChart'
case 'ac': return 'acChart'
case 'prime': return 'primeChart'
case 'road': return 'roadChart'
case 'span': return 'spanChart'
default: return null
}
}
const loadAnalysis = async () => {
if (!lotteryType.value || !analysisType.value) return
loading.value = true
try {
const response = await getAdvancedAnalysis(
lotteryType.value,
analysisType.value,
periods.value
)
analysisData.value = response.data
await nextTick()
renderChart()
} catch (error) {
console.error('加载分析数据失败:', error)
ElMessage.error('加载分析数据失败')
} finally {
loading.value = false
}
}
const renderChart = () => {
if (!analysisData.value) return
nextTick(() => {
switch (analysisType.value) {
case 'missing': renderMissingChart(); break
case 'sum': renderSumChart(); break
case 'ac': renderAcChart(); break
case 'prime': renderPrimeChart(); break
case 'road': renderRoadChart(); break
case 'span': renderSpanChart(); break
}
})
}
const renderMissingChart = () => {
if (!missingChart.value || !analysisData.value) {
return
}
if (chartInstances.missing) {
chartInstances.missing.dispose()
}
const chart = echarts.init(missingChart.value)
chartInstances.missing = chart
const redData = Object.entries(analysisData.value.red_missing || {})
.map(([num, missing]) => ({ value: missing, name: num }))
.sort((a, b) => Number(a.name) - Number(b.name))
const blueData = Object.entries(analysisData.value.blue_missing || {})
.map(([num, missing]) => ({ value: missing, name: num }))
.sort((a, b) => Number(a.name) - Number(b.name))
const option = {
title: {
text: '红球/蓝球遗漏值分布',
left: 'center',
top: 10,
textStyle: { fontSize: 16 }
},
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['红球遗漏值', '蓝球遗漏值'], top: 40 },
grid: { left: '3%', right: '4%', bottom: '8%', containLabel: true },
xAxis: [{ type: 'category', data: redData.map(item => item.name), name: '号码', axisLabel: { rotate: 0 } }],
yAxis: [{ type: 'value', name: '遗漏期数' }],
series: [
{ name: '红球遗漏值', type: 'bar', data: redData.map(item => item.value), itemStyle: { color: '#ff4757' }, barWidth: '40%' },
{ name: '蓝球遗漏值', type: 'bar', data: blueData.map(item => item.value), itemStyle: { color: '#3742fa' }, barWidth: '40%' }
]
}
chart.setOption(option)
setTimeout(() => { chart.resize() }, 100)
}
const renderSumChart = () => {
if (!sumChart.value) return
const chart = echarts.init(sumChart.value)
chartInstances.sum = chart
const sumDistribution = analysisData.value.sum_distribution
const data = Object.entries(sumDistribution)
.map(([sum, count]) => ({ value: count, name: sum }))
.sort((a, b) => parseInt(a.name) - parseInt(b.name))
const option = {
title: {
text: '和值分布分析',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
name: '和值'
},
yAxis: {
type: 'value',
name: '出现次数'
},
series: [
{
type: 'line',
data: data.map(item => item.value),
smooth: true,
itemStyle: {
color: '#2ed573'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(46, 213, 115, 0.3)' },
{ offset: 1, color: 'rgba(46, 213, 115, 0.1)' }
]
}
}
}
]
}
chart.setOption(option)
}
const renderAcChart = () => {
if (!acChart.value) return
const chart = echarts.init(acChart.value)
chartInstances.ac = chart
const acDistribution = analysisData.value.ac_distribution
const data = Object.entries(acDistribution)
.map(([ac, count]) => ({ value: count, name: ac }))
.sort((a, b) => parseInt(a.name) - parseInt(b.name))
const option = {
title: {
text: 'AC值分布分析',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
name: 'AC值'
},
yAxis: {
type: 'value',
name: '出现次数'
},
series: [
{
type: 'bar',
data: data.map(item => item.value),
itemStyle: {
color: '#ffa502'
}
}
]
}
chart.setOption(option)
}
const renderPrimeChart = () => {
if (!primeChart.value) return
const chart = echarts.init(primeChart.value)
chartInstances.prime = chart
const data = analysisData.value.most_common_ratios.map(item => ({
value: item[1],
name: item[0]
}))
const option = {
title: {
text: '质合比分布',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: '质合比',
type: 'pie',
radius: '50%',
data: data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
chart.setOption(option)
}
const renderRoadChart = () => {
if (!roadChart.value) return
const chart = echarts.init(roadChart.value)
chartInstances.road = chart
const data = analysisData.value.most_common_patterns.map(item => ({
value: item[1],
name: item[0]
}))
const option = {
title: {
text: '012路分布',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: '012路',
type: 'pie',
radius: '50%',
data: data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
chart.setOption(option)
}
const renderSpanChart = () => {
if (!spanChart.value) return
const chart = echarts.init(spanChart.value)
chartInstances.span = chart
const spanDistribution = analysisData.value.span_distribution
const data = Object.entries(spanDistribution)
.map(([span, count]) => ({ value: count, name: span }))
.sort((a, b) => parseInt(a.name) - parseInt(b.name))
const option = {
title: {
text: '跨度分布分析',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
name: '跨度'
},
yAxis: {
type: 'value',
name: '出现次数'
},
series: [
{
type: 'bar',
data: data.map(item => item.value),
itemStyle: {
color: '#ff6348'
}
}
]
}
chart.setOption(option)
}
onMounted(() => {
window.addEventListener('resize', () => {
Object.values(chartInstances).forEach(chart => {
if (chart && typeof chart.resize === 'function') {
chart.resize()
}
})
})
// onMounted
nextTick(() => {
renderChart()
})
loadAnalysis()
})
// watchDOM
watch([analysisData, analysisType], async () => {
await nextTick()
renderChart()
})
return {
lotteryType,
analysisType,
periods,
loading,
analysisData,
analysisTypeLabelMap,
missingChart,
sumChart,
acChart,
primeChart,
roadChart,
spanChart,
compMissingChart,
compSumChart,
compAcChart,
compSpanChart,
loadAnalysis,
hasChart,
getChartRef,
statItems
}
}
}
</script>
<style scoped>
.advanced-analysis {
padding: 24px;
}
.analysis-form {
margin-bottom: 0;
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
}
.chart-area {
margin-top: 16px;
min-height: 480px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px 0 rgba(64,158,255,0.04);
padding: 24px 16px 8px 16px;
}
.chart-title {
margin-bottom: 12px;
font-size: 18px;
font-weight: bold;
color: #409EFF;
}
.chart-container {
min-height: 400px;
background: #fafbfc;
border-radius: 6px;
border: 1px solid #ebeef5;
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-cards {
margin-top: 8px;
}
.stat-card {
text-align: center;
border-radius: 8px;
background: #f8f9fa;
box-shadow: 0 1px 4px 0 rgba(64,158,255,0.04);
}
.loading {
padding: 40px;
text-align: center;
}
.no-data {
padding: 60px;
text-align: center;
}
</style>

View File

@ -0,0 +1,604 @@
<template>
<div class="prediction">
<el-card class="prediction-card">
<template #header>
<el-form :inline="true" class="prediction-form" label-width="80px">
<el-form-item label="彩票类型">
<el-select v-model="lotteryType" placeholder="请选择" @change="resetPrediction" style="width: 120px">
<el-option label="双色球" value="ssq"></el-option>
<el-option label="大乐透" value="dlt"></el-option>
</el-select>
</el-form-item>
<el-form-item label="训练期数">
<el-input-number
v-model="trainingPeriods"
:min="50"
:max="500"
style="width: 120px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="trainModel" :loading="training">
训练模型
</el-button>
</el-form-item>
</el-form>
</template>
<div class="prediction-content">
<!-- 预测结果优先展示 -->
<div v-if="predictionResults.length > 0" class="prediction-results">
<h3>预测结果</h3>
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="机器学习预测" name="ml">
<div v-if="mlPrediction" class="prediction-item">
<div class="prediction-header">
<h4>机器学习预测</h4>
<el-tag type="success">置信度: </el-tag>
</div>
<div class="prediction-numbers">
<div class="red-balls">
<span v-for="num in mlPrediction.predicted_numbers" :key="num" class="ball red-ball">
{{ num.toString().padStart(2, '0') }}
</span>
<span v-if="mlPrediction.predicted_blue" class="ball blue-ball">
{{ mlPrediction.predicted_blue.toString().padStart(2, '0') }}
</span>
</div>
</div>
<div class="prediction-info">
<p>{{ mlPrediction.confidence }}</p>
</div>
</div>
<div v-else class="no-prediction">暂无机器学习预测结果</div>
</el-tab-pane>
<el-tab-pane label="模式预测" name="pattern">
<div v-if="patternPrediction" class="prediction-item">
<div class="prediction-header">
<h4>模式分析预测</h4>
<el-tag type="warning">置信度: </el-tag>
</div>
<div v-if="patternPrediction.predicted_numbers" class="prediction-numbers">
<div class="red-balls">
<span v-for="num in patternPrediction.predicted_numbers" :key="num" class="ball red-ball">
{{ num.toString().padStart(2, '0') }}
</span>
<span v-if="patternPrediction.predicted_blue" class="ball blue-ball">
{{ patternPrediction.predicted_blue.toString().padStart(2, '0') }}
</span>
</div>
</div>
<div class="pattern-criteria">
<el-descriptions title="推荐选号标准" :column="2" border>
<el-descriptions-item label="和值范围">
{{ patternPrediction.suggested_criteria.sum_range.join(' - ') }}
</el-descriptions-item>
<el-descriptions-item label="奇偶比">
{{ patternPrediction.suggested_criteria.odd_even_ratio }}
</el-descriptions-item>
<el-descriptions-item label="分区数量">
{{ patternPrediction.suggested_criteria.zone_distribution }}
</el-descriptions-item>
<el-descriptions-item label="连号数量">
{{ patternPrediction.suggested_criteria.consecutive_count }}
</el-descriptions-item>
</el-descriptions>
</div>
</div>
<div v-else class="no-prediction">暂无模式预测结果</div>
</el-tab-pane>
<el-tab-pane label="集成预测" name="ensemble">
<div v-if="ensemblePrediction" class="prediction-item">
<div class="prediction-header">
<h4>集成预测结果</h4>
<el-tag type="info">多方法综合</el-tag>
</div>
<div class="ensemble-recommendations">
<div v-for="(rec, index) in ensemblePrediction.recommendations" :key="index" class="recommendation">
<div class="recommendation-header">
<h5>{{ rec.method }}</h5>
<el-tag :type="rec.confidence === '高' ? 'success' : rec.confidence === '中' ? 'warning' : 'info'">
置信度: {{ rec.confidence }}
</el-tag>
</div>
<div class="recommendation-numbers">
<div class="red-balls">
<span v-for="num in rec.numbers" :key="num" class="ball red-ball">
{{ num.toString().padStart(2, '0') }}
</span>
<span v-if="rec.blue" class="ball blue-ball">
{{ rec.blue.toString().padStart(2, '0') }}
</span>
</div>
</div>
</div>
</div>
<div class="pattern-analysis">
<h5>模式分析</h5>
<el-descriptions :column="2" border>
<el-descriptions-item label="和值范围">
{{ ensemblePrediction.pattern_analysis.sum_range.join(' - ') }}
</el-descriptions-item>
<el-descriptions-item label="奇偶比">
{{ ensemblePrediction.pattern_analysis.odd_even_ratio }}
</el-descriptions-item>
<el-descriptions-item label="分区数量">
{{ ensemblePrediction.pattern_analysis.zone_distribution }}
</el-descriptions-item>
<el-descriptions-item label="连号数量">
{{ ensemblePrediction.pattern_analysis.consecutive_count }}
</el-descriptions-item>
</el-descriptions>
</div>
</div>
<div v-else class="no-prediction">暂无集成预测结果</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 模型训练状态 -->
<el-alert
v-if="modelStatus"
:title="modelStatus.title"
:type="modelStatus.type"
:description="modelStatus.description"
show-icon
:closable="false"
class="model-status"
/>
<!-- 预测操作按钮区域 -->
<div class="prediction-actions">
<el-row :gutter="20" justify="center" align="middle">
<el-col :span="6">
<el-button
type="primary"
@click="predictML"
:loading="predicting"
block
>
{{ modelStatus && modelStatus.type !== 'error' ? '机器学习预测' : '训练并预测' }}
</el-button>
</el-col>
<el-col :span="6">
<el-button
type="warning"
@click="predictPattern"
:loading="predicting"
block
>
模式预测
</el-button>
</el-col>
<el-col :span="6">
<el-button
type="info"
@click="predictEnsemble"
:loading="predicting"
block
>
{{ modelStatus && modelStatus.type !== 'error' ? '集成预测' : '训练并预测' }}
</el-button>
</el-col>
</el-row>
</div>
<!-- 历史预测记录 -->
<div v-if="predictionHistory.length > 0" class="prediction-history">
<h3>历史预测记录</h3>
<el-table :data="predictionHistory" stripe>
<el-table-column prop="timestamp" label="预测时间" width="180" />
<el-table-column prop="lotteryType" label="彩票类型" width="100" />
<el-table-column prop="method" label="预测方法" width="120" />
<el-table-column prop="numbers" label="预测号码">
<template #default="scope">
<div class="prediction-numbers">
<div class="red-balls">
<span v-for="(num, index) in scope.row.numbers.split(' ')"
:key="index"
class="ball red-ball"
>
{{ num.padStart(2, '0') }}
</span>
<span v-if="scope.row.blue" class="ball blue-ball">
{{ scope.row.blue.toString().padStart(2, '0') }}
</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="confidence" label="置信度" width="100">
<template #default="scope">
<el-tag :type="scope.row.confidence === '高' ? 'success' : scope.row.confidence === '中' ? 'warning' : 'info'">
{{ scope.row.confidence }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-card>
</div>
</template>
<script>
import { ref, watch, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import {
trainPredictionModel,
predictNextNumbers,
getPatternPrediction,
getEnsemblePrediction
} from '@/api/prediction'
export default {
name: 'Prediction',
setup() {
const lotteryType = ref('ssq')
const trainingPeriods = ref(100)
const training = ref(false)
const predicting = ref(false)
const activeTab = ref('ml')
const modelStatus = ref(null)
const mlPrediction = ref(null)
const patternPrediction = ref(null)
const ensemblePrediction = ref(null)
const predictionResults = ref([])
const predictionHistory = ref([])
const trainModel = async () => {
training.value = true
try {
const response = await trainPredictionModel(lotteryType.value, trainingPeriods.value)
if (response.data.success) {
modelStatus.value = {
type: 'success',
title: '模型训练成功',
description: `平均准确率: ${(response.data.avg_accuracy * 100).toFixed(2)}%, 训练样本: ${response.data.training_samples}`
}
ElMessage.success('模型训练成功')
} else {
modelStatus.value = {
type: 'error',
title: '模型训练失败',
description: response.data.message
}
ElMessage.error(response.data.message)
}
} catch (error) {
console.error('训练模型失败:', error)
modelStatus.value = {
type: 'error',
title: '模型训练失败',
description: '网络错误或服务器异常'
}
ElMessage.error('训练模型失败')
} finally {
training.value = false
}
}
const predictML = async () => {
predicting.value = true
try {
//
if (!modelStatus.value || modelStatus.value.type === 'error') {
const trainResult = await trainPredictionModel(lotteryType.value, trainingPeriods.value)
if (!trainResult.data.success) {
ElMessage.error('模型训练失败:' + trainResult.data.message)
return
}
modelStatus.value = {
type: 'success',
title: '模型训练成功',
description: `平均准确率: ${(trainResult.data.avg_accuracy * 100).toFixed(2)}%, 训练样本: ${trainResult.data.training_samples}`
}
}
//
const response = await predictNextNumbers(lotteryType.value, 10)
if (response.data.success) {
mlPrediction.value = response.data
activeTab.value = 'ml'
predictionResults.value = [response.data]
predictionHistory.value.unshift({
timestamp: new Date().toLocaleString(),
lotteryType: lotteryType.value === 'ssq' ? '双色球' : '大乐透',
method: '机器学习',
numbers: response.data.predicted_numbers.join(' '),
blue: response.data.predicted_blue,
confidence: '高'
})
ElMessage.success('机器学习预测完成')
} else {
mlPrediction.value = null
ElMessage.error(response.data.message)
}
} catch (error) {
mlPrediction.value = null
console.error('机器学习预测失败:', error)
ElMessage.error('机器学习预测失败')
} finally {
predicting.value = false
}
}
const predictPattern = async () => {
predicting.value = true
try {
const response = await getPatternPrediction(lotteryType.value, trainingPeriods.value)
if (response.data.success) {
patternPrediction.value = response.data
activeTab.value = 'pattern'
predictionResults.value = [response.data]
//
predictionHistory.value.unshift({
timestamp: new Date().toLocaleString(),
lotteryType: lotteryType.value === 'ssq' ? '双色球' : '大乐透',
method: '模式预测',
numbers: response.data.predicted_numbers.join(' '),
blue: response.data.predicted_blue,
confidence: '中'
})
ElMessage.success('模式预测完成')
} else {
patternPrediction.value = null
ElMessage.error(response.data.message)
}
} catch (error) {
patternPrediction.value = null
console.error('模式预测失败:', error)
ElMessage.error('模式预测失败')
} finally {
predicting.value = false
}
}
const predictEnsemble = async () => {
predicting.value = true
try {
//
if (!modelStatus.value || modelStatus.value.type === 'error') {
const trainResult = await trainPredictionModel(lotteryType.value, trainingPeriods.value)
if (!trainResult.data.success) {
ElMessage.error('模型训练失败:' + trainResult.data.message)
return
}
modelStatus.value = {
type: 'success',
title: '模型训练成功',
description: `平均准确率: ${(trainResult.data.avg_accuracy * 100).toFixed(2)}%, 训练样本: ${trainResult.data.training_samples}`
}
}
const response = await getEnsemblePrediction(lotteryType.value, trainingPeriods.value)
if (response.data.success) {
ensemblePrediction.value = response.data
activeTab.value = 'ensemble'
predictionResults.value = [response.data]
//
response.data.recommendations.forEach(rec => {
predictionHistory.value.unshift({
timestamp: new Date().toLocaleString(),
lotteryType: lotteryType.value === 'ssq' ? '双色球' : '大乐透',
method: `集成预测-${rec.method}`,
numbers: rec.numbers.join(' '),
blue: rec.blue,
confidence: rec.confidence
})
})
ElMessage.success('集成预测完成')
} else {
ensemblePrediction.value = null
ElMessage.error(response.data.message)
}
} catch (error) {
ensemblePrediction.value = null
console.error('集成预测失败:', error)
ElMessage.error('集成预测失败')
} finally {
predicting.value = false
}
}
const resetPrediction = () => {
modelStatus.value = null
mlPrediction.value = null
patternPrediction.value = null
ensemblePrediction.value = null
predictionResults.value = []
//
trainModel()
}
//
watch(lotteryType, (newType) => {
if (newType) {
resetPrediction()
}
})
//
onMounted(() => {
trainModel()
})
return {
lotteryType,
trainingPeriods,
training,
predicting,
activeTab,
modelStatus,
mlPrediction,
patternPrediction,
ensemblePrediction,
predictionResults,
predictionHistory,
trainModel,
predictML,
predictPattern,
predictEnsemble,
resetPrediction
}
}
}
</script>
<style scoped>
.prediction {
padding: 20px;
}
.prediction-card {
margin-bottom: 20px;
}
.prediction-form {
margin-bottom: 0;
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
}
.prediction-content {
margin-top: 20px;
}
.model-status {
margin-bottom: 20px;
}
.prediction-results {
margin: 20px 0;
}
.prediction-item {
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
margin-bottom: 20px;
}
.prediction-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.prediction-header h4 {
margin: 0;
color: #303133;
}
.prediction-numbers {
margin: 15px 0;
}
.red-balls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.ball {
display: inline-block;
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 50%;
font-weight: bold;
color: white;
}
.red-ball {
background-color: #ff4757;
}
.blue-ball {
background-color: #3742fa;
}
.prediction-info {
color: #606266;
font-size: 14px;
}
.pattern-criteria {
margin: 15px 0;
}
.ensemble-recommendations {
margin: 15px 0;
}
.recommendation {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
}
.recommendation-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.recommendation-header h5 {
margin: 0;
color: #303133;
}
.recommendation-numbers {
margin: 10px 0;
}
.pattern-analysis {
margin-top: 20px;
}
.pattern-analysis h5 {
color: #303133;
margin-bottom: 10px;
}
.prediction-actions {
margin: 20px 0 0 0;
padding: 10px 0 0 0;
background: none;
border-radius: 0;
}
.prediction-actions .el-row {
justify-content: center;
}
.prediction-history {
margin-top: 30px;
}
.no-prediction {
color: #bbb;
text-align: center;
padding: 30px 0;
}
h3 {
color: #303133;
margin-bottom: 20px;
border-bottom: 2px solid #409eff;
padding-bottom: 10px;
}
</style>