增加主力选股-批量分析功能
This commit is contained in:
@@ -12,9 +12,31 @@
|
|||||||
<img width="1910" height="923" alt="image" src="https://github.com/user-attachments/assets/ff80584a-0599-4891-9b8d-47485fa3678a" />
|
<img width="1910" height="923" alt="image" src="https://github.com/user-attachments/assets/ff80584a-0599-4891-9b8d-47485fa3678a" />
|
||||||
|
|
||||||
|
|
||||||
## ✨最新更新 - 持仓定时分析功能 ⭐️
|
## ✨1021最新更新 - 主力选股批量分析功能 ⭐️
|
||||||
|
|
||||||
### 📊 持仓定时分析系统全新上线
|
### 💰 主力选股 - TOP股票批量分析
|
||||||
|
在主力选股的候选股票列表中,对主力资金净流入TOP股票提供一键批量深度分析
|
||||||
|
|
||||||
|
#### 核心亮点
|
||||||
|
- **一键批量分析**:可选10/20/30/50只主力资金TOP股票进行深度AI分析
|
||||||
|
- **双模式支持**:顺序分析(稳定)/ 并行分析(快速)
|
||||||
|
- **实时进度追踪**:分析进度可视化显示,状态实时更新
|
||||||
|
- **快捷加入监测**:分析完成后一键加入实时监测列表
|
||||||
|
- **完整交易闭环**:主力资金筛选 → AI深度分析 → 实时监测 → 价格告警
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
- ✅ **主力资金跟踪**:批量分析主力重仓股,跟随聪明资金
|
||||||
|
- ✅ **资金流向研究**:批量分析TOP50了解主力资金整体流向
|
||||||
|
- ✅ **精选优质股票**:深度分析TOP10主力最青睐的股票
|
||||||
|
|
||||||
|
#### 详细文档
|
||||||
|
请查看 [`docs/主力选股批量分析功能说明.md`](docs/主力选股批量分析功能说明.md) 获取完整使用指南
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨1020持仓定时分析功能 ⭐️
|
||||||
|
|
||||||
|
### 📊 持仓定时分析系统
|
||||||
专为长期投资者和组合管理打造的智能持仓管理与自动化分析系统
|
专为长期投资者和组合管理打造的智能持仓管理与自动化分析系统
|
||||||
|
|
||||||
#### 核心功能
|
#### 核心功能
|
||||||
@@ -113,6 +135,38 @@ streamlit run app.py
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 💰 主力选股 - TOP股票批量分析功能 ⭐️新增
|
||||||
|
|
||||||
|
在主力选股的候选股票列表中,对主力资金净流入TOP股票提供一键批量深度分析
|
||||||
|
|
||||||
|
#### 核心功能
|
||||||
|
- **一键批量分析**:可选10只/20只/30只/50只主力资金TOP股票进行深度分析
|
||||||
|
- **双模式支持**:顺序分析(稳定)/ 并行分析(快速)
|
||||||
|
- **实时进度追踪**:分析进度可视化显示
|
||||||
|
- **详细结果展示**:完整的AI分析报告和投资决策
|
||||||
|
- **快捷加入监测**:分析完成后一键加入实时监测列表
|
||||||
|
- **遵循统一规范**:使用统一的分析函数和字段名,确保结果一致性
|
||||||
|
|
||||||
|
#### 使用流程
|
||||||
|
```
|
||||||
|
主力选股 → 候选股票列表(按主力资金排序)
|
||||||
|
→ 选择分析数量(10/20/30/50) → 🚀 开始批量分析
|
||||||
|
→ 选择模式(顺序/并行) → 确认开始
|
||||||
|
→ ⏳ 实时进度显示 → ✅ 分析完成
|
||||||
|
→ 查看详细结果 → ➕ 加入监测
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
- ✅ **主力资金跟踪**:主力选股筛选 → 批量深度分析 → 实时监测 → 价格告警
|
||||||
|
- ✅ **资金流向研究**:批量分析TOP50了解主力资金整体流向
|
||||||
|
- ✅ **精选优质股票**:深度分析TOP10主力最青睐的股票
|
||||||
|
- ✅ **完整闭环**:从主力资金筛选到分析到监测的一站式解决方案
|
||||||
|
|
||||||
|
#### 详细文档
|
||||||
|
请查看 [`docs/主力选股批量分析功能说明.md`](docs/主力选股批量分析功能说明.md) 获取完整使用指南
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ✨1019 更新说明
|
## ✨1019 更新说明
|
||||||
|
|
||||||
### 🎯 风险管理师功能增强 ⭐️
|
### 🎯 风险管理师功能增强 ⭐️
|
||||||
@@ -261,6 +315,7 @@ StockAPI龙虎榜接口(每日更新,免费1000次)
|
|||||||
- **智能选股**:基于主力资金流向,AI团队精选3-5只优质标的
|
- **智能选股**:基于主力资金流向,AI团队精选3-5只优质标的
|
||||||
- **数据来源**:问财前100名主力资金净流入股票
|
- **数据来源**:问财前100名主力资金净流入股票
|
||||||
- **多维筛选**:市值范围、涨跌幅限制、财务指标
|
- **多维筛选**:市值范围、涨跌幅限制、财务指标
|
||||||
|
- **批量分析**:对主力资金净流入TOP 10/20/30/50只股票进行深度AI分析 ⭐️新增
|
||||||
|
|
||||||
#### 🔥 批量分析(1008新增)
|
#### 🔥 批量分析(1008新增)
|
||||||
- **双模式**:顺序分析 / 多线程并行
|
- **双模式**:顺序分析 / 多线程并行
|
||||||
|
|||||||
@@ -0,0 +1,487 @@
|
|||||||
|
# 主力选股 - TOP股票批量分析功能
|
||||||
|
|
||||||
|
## 📖 功能概述
|
||||||
|
|
||||||
|
在主力选股的**候选股票列表**中,对主力资金净流入排名前N的股票提供**一键批量深度分析**功能,参照智瞰龙虎板块的批量分析方式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 进入主力选股
|
||||||
|
|
||||||
|
1. 启动应用:`streamlit run app.py`
|
||||||
|
2. 点击侧边栏 **"🎯 主力选股"**
|
||||||
|
3. 设置筛选参数,点击 **"🚀 开始主力选股"**
|
||||||
|
4. 等待系统完成主力资金筛选和整体分析
|
||||||
|
|
||||||
|
### 2. 查看候选股票列表
|
||||||
|
|
||||||
|
- 系统显示 **"📋 候选股票列表(筛选后)"**
|
||||||
|
- 列表按主力资金净流入从高到低排序
|
||||||
|
- 展示股票代码、名称、行业、主力资金、涨跌幅等信息
|
||||||
|
|
||||||
|
### 3. 一键批量分析
|
||||||
|
|
||||||
|
1. 在候选列表下方找到 **"🚀 批量深度分析"** 功能区
|
||||||
|
2. 选择分析数量:10只 / 20只 / 30只 / 50只(默认20只)
|
||||||
|
3. 点击 **"🚀 开始批量分析"** 按钮
|
||||||
|
4. 选择分析模式:
|
||||||
|
- **顺序分析(稳定)**:逐个分析,适合数量较少
|
||||||
|
- **并行分析(快速)**:多线程同时分析,适合数量较多
|
||||||
|
5. 如选择并行分析,设置并行线程数(2-5个,默认3个)
|
||||||
|
6. 点击 **"🚀 确认开始分析"**
|
||||||
|
7. 等待进度完成(实时显示进度和状态)
|
||||||
|
|
||||||
|
### 4. 查看分析结果
|
||||||
|
|
||||||
|
- **统计信息**:总计、成功、失败、耗时
|
||||||
|
- **成功列表**:每只股票的投资评级、信心度、进场区间、止盈止损
|
||||||
|
- **详细报告**:点击展开查看完整AI分析报告
|
||||||
|
- **快捷操作**:一键加入监测列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 使用场景
|
||||||
|
|
||||||
|
### 场景1:主力资金跟踪
|
||||||
|
|
||||||
|
```
|
||||||
|
主力选股筛选 → 候选列表 → TOP20批量分析 → 选择买入标的
|
||||||
|
```
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
1. 主力选股筛选出主力资金净流入前100名股票
|
||||||
|
2. 批量分析前20只主力资金最多的股票
|
||||||
|
3. 深度分析获取具体买卖点
|
||||||
|
4. 加入监测,等待进场机会
|
||||||
|
|
||||||
|
### 场景2:资金流向研究
|
||||||
|
|
||||||
|
```
|
||||||
|
主力选股 → 批量分析TOP50 → 对比分析 → 识别强势标的
|
||||||
|
```
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
1. 筛选主力资金流入股票
|
||||||
|
2. 批量分析前50只,全面了解资金流向
|
||||||
|
3. 对比各股评级和信心度
|
||||||
|
4. 精选2-3只最优标的重点关注
|
||||||
|
|
||||||
|
### 场景3:精选优质股票
|
||||||
|
|
||||||
|
```
|
||||||
|
主力选股 → TOP10深度分析 → 投资组合构建
|
||||||
|
```
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
1. 主力选股筛选候选
|
||||||
|
2. 深度分析前10只主力最青睐的股票
|
||||||
|
3. 根据评级和关键价位构建投资组合
|
||||||
|
4. 分散投资,控制风险
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 功能特性
|
||||||
|
|
||||||
|
### 1. 智能筛选
|
||||||
|
|
||||||
|
- ✅ 基于主力资金净流入自动排名
|
||||||
|
- ✅ 支持选择分析前10/20/30/50只
|
||||||
|
- ✅ 自动按主力资金降序排序
|
||||||
|
|
||||||
|
### 2. 批量分析
|
||||||
|
|
||||||
|
- ✅ 支持分析前10/20/30/50只股票
|
||||||
|
- ✅ 顺序/并行两种模式
|
||||||
|
- ✅ 实时进度显示
|
||||||
|
- ✅ **遵循统一调用规范** ⭐️
|
||||||
|
|
||||||
|
### 3. 统一结果
|
||||||
|
|
||||||
|
- ✅ 使用 `app.analyze_single_stock_for_batch()` 统一函数
|
||||||
|
- ✅ 统一字段名:`rating`, `confidence_level`, `entry_range`等
|
||||||
|
- ✅ 统一展示格式:emoji、布局、信息
|
||||||
|
- ✅ 与首页批量分析、龙虎榜批量分析保持一致
|
||||||
|
|
||||||
|
### 4. 快捷操作
|
||||||
|
|
||||||
|
- ✅ 一键加入监测列表
|
||||||
|
- ✅ 自动解析进场区间和止盈止损
|
||||||
|
- ✅ 直接开启实时监控
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 分析维度
|
||||||
|
|
||||||
|
### 主力选股筛选(初筛)
|
||||||
|
|
||||||
|
| 维度 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 主力资金净流入 | 问财数据,主力资金排名前100 |
|
||||||
|
| 市值范围 | 默认50-5000亿,可调整 |
|
||||||
|
| 涨跌幅控制 | 默认<30%,避免追高 |
|
||||||
|
| 财务基本面 | 去除ST股票、数据不完整的股票 |
|
||||||
|
|
||||||
|
### AI深度分析(精选)
|
||||||
|
|
||||||
|
| 维度 | 分析师 | 内容 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 技术面 | 技术分析师 | K线、指标、趋势 |
|
||||||
|
| 基本面 | 基本面分析师 | 财务、估值、成长 |
|
||||||
|
| 资金面 | 资金流向分析师 | 主力资金、北向资金 |
|
||||||
|
| 风险面 | 风险管理师 | 风险提示、止损策略 |
|
||||||
|
| 综合决策 | 团队讨论 | 投资评级、关键价位 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 推荐策略
|
||||||
|
|
||||||
|
### 保守策略(TOP10)
|
||||||
|
|
||||||
|
- **分析数量**:10只
|
||||||
|
- **分析模式**:顺序分析
|
||||||
|
- **选股原则**:只选主力资金最多的10只
|
||||||
|
- **操作策略**:深度分析后选1-2只重点关注
|
||||||
|
|
||||||
|
### 积极策略(TOP20)
|
||||||
|
|
||||||
|
- **分析数量**:20只
|
||||||
|
- **分析模式**:并行分析(3线程)
|
||||||
|
- **选股原则**:主力资金前20只
|
||||||
|
- **操作策略**:分散持仓,捕捉多个机会
|
||||||
|
|
||||||
|
### 全面策略(TOP50)
|
||||||
|
|
||||||
|
- **分析数量**:50只
|
||||||
|
- **分析模式**:并行分析(5线程)
|
||||||
|
- **选股原则**:全面了解主力资金流向
|
||||||
|
- **操作策略**:对比分析,精选3-5只最优
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 技术实现
|
||||||
|
|
||||||
|
### 遵循统一调用规范 ⭐️
|
||||||
|
|
||||||
|
**严格遵循 `docs/UNIFIED_ANALYSIS_SPEC.md` 规范:**
|
||||||
|
|
||||||
|
1. ✅ 调用统一函数:`app.analyze_single_stock_for_batch()`
|
||||||
|
2. ✅ 使用统一字段:`rating`, `confidence_level`, `entry_range`, `take_profit`, `stop_loss`
|
||||||
|
3. ✅ 统一解析逻辑:`split("-")`, `re.findall(r'\d+\.?\d*')`
|
||||||
|
4. ✅ 统一展示格式:emoji标识、一致布局
|
||||||
|
|
||||||
|
### 代码示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 调用统一分析函数
|
||||||
|
from app import analyze_single_stock_for_batch
|
||||||
|
|
||||||
|
result = analyze_single_stock_for_batch(
|
||||||
|
symbol=code,
|
||||||
|
period="1y",
|
||||||
|
enabled_analysts_config={
|
||||||
|
'technical': True,
|
||||||
|
'fundamental': True,
|
||||||
|
'fund_flow': True,
|
||||||
|
'risk': True,
|
||||||
|
'sentiment': False,
|
||||||
|
'news': False
|
||||||
|
},
|
||||||
|
selected_model='deepseek-chat'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 使用统一字段名
|
||||||
|
final_decision = result["final_decision"]
|
||||||
|
rating = final_decision.get("rating", "未知")
|
||||||
|
confidence = final_decision.get("confidence_level", "N/A")
|
||||||
|
entry_range = final_decision.get("entry_range", "N/A")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量分析流程
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击"开始批量分析"
|
||||||
|
↓
|
||||||
|
提取TOP N股票代码(按主力资金降序)
|
||||||
|
↓
|
||||||
|
存储到session_state
|
||||||
|
↓
|
||||||
|
重新渲染页面
|
||||||
|
↓
|
||||||
|
检测到批量分析触发标志
|
||||||
|
↓
|
||||||
|
显示分析配置界面
|
||||||
|
↓
|
||||||
|
用户确认开始
|
||||||
|
↓
|
||||||
|
顺序/并行调用统一分析函数
|
||||||
|
↓
|
||||||
|
显示进度和结果
|
||||||
|
↓
|
||||||
|
保存结果到session_state
|
||||||
|
↓
|
||||||
|
展示分析详情
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 使用技巧
|
||||||
|
|
||||||
|
### 1. 最佳分析时机
|
||||||
|
|
||||||
|
- **盘后**:主力资金数据更新后
|
||||||
|
- **早盘前**:开盘前复盘和选股
|
||||||
|
|
||||||
|
### 2. 合理选择数量
|
||||||
|
|
||||||
|
- **10只**:精选,深度研究
|
||||||
|
- **20只**:平衡,适合大多数场景(推荐)
|
||||||
|
- **50只**:全面,了解整体主力资金流向
|
||||||
|
|
||||||
|
### 3. 结合主力选股信息
|
||||||
|
|
||||||
|
分析时重点关注:
|
||||||
|
- 主力资金净流入金额
|
||||||
|
- 区间涨跌幅是否适中
|
||||||
|
- 所属行业是否景气
|
||||||
|
- 财务基本面是否健康
|
||||||
|
|
||||||
|
### 4. 快速加入监测
|
||||||
|
|
||||||
|
- 分析完成后直接点击"加入监测"
|
||||||
|
- 自动设置进场区间和止盈止损
|
||||||
|
- 实时监测价格变化
|
||||||
|
- 及时把握买卖时机
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 预期效果
|
||||||
|
|
||||||
|
### 输入
|
||||||
|
- 主力选股候选股票列表
|
||||||
|
- 主力资金净流入排名TOP N
|
||||||
|
|
||||||
|
### 输出
|
||||||
|
- 每只股票的完整AI分析:
|
||||||
|
- 投资评级(买入/持有/卖出)
|
||||||
|
- 信心度评分(0-10分)
|
||||||
|
- 进场区间
|
||||||
|
- 止盈位
|
||||||
|
- 止损位
|
||||||
|
- 目标价
|
||||||
|
- 投资建议
|
||||||
|
|
||||||
|
### 后续操作
|
||||||
|
- 选择优质标的加入监测
|
||||||
|
- 设置价格告警
|
||||||
|
- 等待买卖信号
|
||||||
|
- 严格执行止盈止损
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆚 功能对比
|
||||||
|
|
||||||
|
| 功能 | 主力选股整体分析 | 批量深度分析 |
|
||||||
|
|------|----------------|------------|
|
||||||
|
| **目标** | 精选3-5只推荐 | 深度分析TOP N只 |
|
||||||
|
| **分析方式** | 整体分析+资深研究员决策 | 逐个AI团队分析 |
|
||||||
|
| **结果** | 推荐理由+投资建议 | 评级+关键价位 |
|
||||||
|
| **关键价位** | 无 | 有(进场/止盈/止损) |
|
||||||
|
| **适用** | 快速选股 | 详细决策依据 |
|
||||||
|
|
||||||
|
**建议组合使用**:
|
||||||
|
1. 主力选股整体分析快速获取推荐
|
||||||
|
2. 批量深度分析TOP20详细评估
|
||||||
|
3. 对比两种结果,精选最优标的
|
||||||
|
4. 加入监测等待买点
|
||||||
|
5. 严格执行交易纪律
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 常见问题
|
||||||
|
|
||||||
|
### Q1: 批量分析需要多久?
|
||||||
|
|
||||||
|
**A:** 取决于数量和模式:
|
||||||
|
- 10只顺序:约10-15分钟
|
||||||
|
- 20只并行(3线程):约15-20分钟
|
||||||
|
- 50只并行(5线程):约25-35分钟
|
||||||
|
|
||||||
|
### Q2: 分析多少只合适?
|
||||||
|
|
||||||
|
**A:** 建议:
|
||||||
|
- **新手**:分析10只,重点研究
|
||||||
|
- **进阶**:分析20只,平衡效率和深度
|
||||||
|
- **资深**:分析50只,全面了解主力资金流向
|
||||||
|
|
||||||
|
### Q3: 批量分析后如何选股?
|
||||||
|
|
||||||
|
**A:** 参考以下标准:
|
||||||
|
1. 投资评级:优选"买入"或"强烈买入"
|
||||||
|
2. 信心度:≥7分
|
||||||
|
3. 主力资金:净流入金额大
|
||||||
|
4. 涨跌幅:适中,避免追高
|
||||||
|
5. 风险面:无重大风险提示
|
||||||
|
|
||||||
|
### Q4: 分析失败怎么办?
|
||||||
|
|
||||||
|
**A:** 可能原因和解决:
|
||||||
|
- 股票代码错误:检查代码格式
|
||||||
|
- 网络问题:重试或切换顺序模式
|
||||||
|
- API限流:减少并发线程数
|
||||||
|
- 数据缺失:该股可能暂停交易或数据不完整
|
||||||
|
|
||||||
|
### Q5: 能否保存分析结果?
|
||||||
|
|
||||||
|
**A:** 当前版本:
|
||||||
|
- 结果保存在session_state(关闭页面会丢失)
|
||||||
|
- 可点击"加入监测"保存关键参数
|
||||||
|
- 建议截图或导出重要信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎊 更新内容
|
||||||
|
|
||||||
|
### 新增功能
|
||||||
|
- ✅ TOP股票一键批量分析
|
||||||
|
- ✅ 可选分析数量(10/20/30/50只)
|
||||||
|
- ✅ 顺序/并行分析模式
|
||||||
|
- ✅ 实时进度显示
|
||||||
|
- ✅ 详细结果展示
|
||||||
|
- ✅ 一键加入监测
|
||||||
|
|
||||||
|
### 技术亮点
|
||||||
|
- ✅ **遵循统一调用规范**:调用 `app.analyze_single_stock_for_batch()`
|
||||||
|
- ✅ **统一字段使用**:`rating`, `confidence_level`, `entry_range`等
|
||||||
|
- ✅ **统一解析逻辑**:字符串数值提取标准化
|
||||||
|
- ✅ **代码复用最大化**:无重复分析逻辑
|
||||||
|
- ✅ **结果展示一致**:与首页批量分析、龙虎榜批量分析保持一致
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 完整流程示例
|
||||||
|
|
||||||
|
### 实战案例:主力资金跟踪选股
|
||||||
|
|
||||||
|
#### Step 1: 主力选股筛选(获取候选)
|
||||||
|
```
|
||||||
|
主力选股 → 设置参数(最近3个月)
|
||||||
|
→ 开始分析 → 获取主力资金前100名
|
||||||
|
→ 系统筛选后剩余X只候选股票
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: 查看候选列表
|
||||||
|
```
|
||||||
|
候选股票列表
|
||||||
|
→ 按主力资金净流入降序排列
|
||||||
|
→ 查看前20只的主力资金金额和涨跌幅
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: 批量深度分析
|
||||||
|
```
|
||||||
|
选择分析数量:20只
|
||||||
|
→ 点击"开始批量分析"
|
||||||
|
→ 选择并行分析(3线程)
|
||||||
|
→ 确认开始分析
|
||||||
|
→ 等待15-20分钟完成
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 4: 筛选优质标的
|
||||||
|
```
|
||||||
|
查看分析结果
|
||||||
|
→ 筛选条件:
|
||||||
|
- 评级:买入/强烈买入
|
||||||
|
- 信心度:≥7分
|
||||||
|
- 主力资金:排名靠前
|
||||||
|
- 涨幅:<20%(避免追高)
|
||||||
|
→ 精选3-5只重点关注
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 5: 加入监测
|
||||||
|
```
|
||||||
|
对精选股票点击"➕ 加入监测"
|
||||||
|
→ 自动设置进场区间、止盈止损
|
||||||
|
→ 启动实时价格监控
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 6: 执行交易
|
||||||
|
```
|
||||||
|
次日开盘
|
||||||
|
→ 监测提醒进入进场区间
|
||||||
|
→ 买入建仓
|
||||||
|
→ 监测提醒到达止盈位
|
||||||
|
→ 卖出止盈
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 最佳实践
|
||||||
|
|
||||||
|
### 1. 主力资金+AI双重验证
|
||||||
|
|
||||||
|
- 不仅看主力资金排名,更要看AI深度分析
|
||||||
|
- 主力资金大但AI评级"卖出"→ 谨慎
|
||||||
|
- 主力资金中等但AI评级"买入"→ 关注
|
||||||
|
|
||||||
|
### 2. 时间把握
|
||||||
|
|
||||||
|
- **最佳分析时间**:盘后或早盘前
|
||||||
|
- **最佳买入时间**:进入进场区间时
|
||||||
|
- **最佳止盈时间**:触发止盈位立即卖出
|
||||||
|
|
||||||
|
### 3. 风险控制
|
||||||
|
|
||||||
|
- 严格遵守AI给出的止损位
|
||||||
|
- 不因短期波动随意调整
|
||||||
|
- 仓位控制:单只≤总资金20%
|
||||||
|
- 主力资金股票有波动,轻仓操作
|
||||||
|
|
||||||
|
### 4. 组合策略
|
||||||
|
|
||||||
|
- 不要全仓单只股票
|
||||||
|
- TOP10选2-3只重点
|
||||||
|
- 分散行业,降低风险
|
||||||
|
- 部分仓位留作机动
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 风险提示
|
||||||
|
|
||||||
|
### 主力资金股票特点
|
||||||
|
|
||||||
|
- 💰 **资金驱动**:主力资金是重要推动力
|
||||||
|
- 📈 **波动性**:资金流入流出会导致价格波动
|
||||||
|
- ⏱️ **时效性**:主力资金流向可能快速变化
|
||||||
|
- 🎯 **题材性**:可能受市场热点影响
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
1. **主力资金流入不代表一定上涨**
|
||||||
|
2. **注意市场整体趋势和环境**
|
||||||
|
3. **设置合理的止损位(建议5-10%)**
|
||||||
|
4. **定期跟踪主力资金变化**
|
||||||
|
5. **及时调整持仓和策略**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- **统一规范**:`docs/UNIFIED_ANALYSIS_SPEC.md` - 分析调用规范
|
||||||
|
- **主力选股**:`docs/主力选股使用指南.md` - 主力选股系统介绍
|
||||||
|
- **龙虎榜批量**:`docs/LONGHUBANG_BATCH_ANALYSIS.md` - 龙虎榜批量分析
|
||||||
|
- **监测功能**:首页实时监测文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎊 总结
|
||||||
|
|
||||||
|
**主力选股批量分析功能**为您提供:
|
||||||
|
|
||||||
|
✅ 主力资金筛选快速定位候选
|
||||||
|
✅ 深度分析精准评估投资价值
|
||||||
|
✅ 一键加入监测实时跟踪
|
||||||
|
✅ 完整的主力资金跟踪解决方案
|
||||||
|
|
||||||
|
**祝您抓住每一个主力资金驱动的投资机会!** 🎯📈
|
||||||
|
|
||||||
@@ -13,6 +13,11 @@ import pandas as pd
|
|||||||
def display_main_force_selector():
|
def display_main_force_selector():
|
||||||
"""显示主力选股界面"""
|
"""显示主力选股界面"""
|
||||||
|
|
||||||
|
# 检查是否触发批量分析(不立即删除标志)
|
||||||
|
if st.session_state.get('main_force_batch_trigger'):
|
||||||
|
run_main_force_batch_analysis()
|
||||||
|
return
|
||||||
|
|
||||||
st.markdown("## 🎯 主力选股 - 智能筛选优质标的")
|
st.markdown("## 🎯 主力选股 - 智能筛选优质标的")
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
@@ -270,6 +275,50 @@ def display_analysis_results(result: dict, analyzer):
|
|||||||
file_name=f"main_force_stocks_{datetime.now().strftime('%Y%m%d')}.csv",
|
file_name=f"main_force_stocks_{datetime.now().strftime('%Y%m%d')}.csv",
|
||||||
mime="text/csv"
|
mime="text/csv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 批量分析功能区
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
col_batch1, col_batch2, col_batch3 = st.columns([2, 1, 1])
|
||||||
|
with col_batch1:
|
||||||
|
st.markdown("#### 🚀 批量深度分析")
|
||||||
|
st.caption("对主力资金净流入TOP股票进行完整的AI团队分析,获取投资评级和关键价位")
|
||||||
|
|
||||||
|
with col_batch2:
|
||||||
|
batch_count = st.selectbox(
|
||||||
|
"分析数量",
|
||||||
|
options=[10, 20, 30, 50],
|
||||||
|
index=1, # 默认20只
|
||||||
|
help="选择分析主力资金净流入前N只股票"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col_batch3:
|
||||||
|
st.write("") # 占位
|
||||||
|
if st.button("🚀 开始批量分析", type="primary", use_container_width=True):
|
||||||
|
# 准备数据:按主力资金净流入排序
|
||||||
|
df_sorted = analyzer.raw_stocks.copy()
|
||||||
|
|
||||||
|
# 确保主力资金列是数值类型并排序
|
||||||
|
if main_fund_col:
|
||||||
|
df_sorted[main_fund_col] = pd.to_numeric(df_sorted[main_fund_col], errors='coerce')
|
||||||
|
df_sorted = df_sorted.sort_values(by=main_fund_col, ascending=False)
|
||||||
|
|
||||||
|
# 提取股票代码并去掉市场后缀(.SH, .SZ等)
|
||||||
|
raw_codes = df_sorted.head(batch_count)['股票代码'].tolist()
|
||||||
|
stock_codes = []
|
||||||
|
for code in raw_codes:
|
||||||
|
# 去掉后缀(如果有的话)
|
||||||
|
if isinstance(code, str):
|
||||||
|
# 去掉 .SH, .SZ, .BJ 等后缀
|
||||||
|
clean_code = code.split('.')[0] if '.' in code else code
|
||||||
|
stock_codes.append(clean_code)
|
||||||
|
else:
|
||||||
|
stock_codes.append(str(code))
|
||||||
|
|
||||||
|
# 存储到session_state,触发批量分析
|
||||||
|
st.session_state.main_force_batch_codes = stock_codes
|
||||||
|
st.session_state.main_force_batch_trigger = True
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
# 显示PDF报告下载区域
|
# 显示PDF报告下载区域
|
||||||
if analyzer and result:
|
if analyzer and result:
|
||||||
@@ -417,3 +466,421 @@ def format_number(value, unit='', suffix=''):
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def run_main_force_batch_analysis():
|
||||||
|
"""执行主力选股TOP股票批量分析(遵循统一调用规范)"""
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
|
st.markdown("## 🚀 主力选股TOP股票批量分析")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# 检查是否已有分析结果
|
||||||
|
if st.session_state.get('main_force_batch_results'):
|
||||||
|
display_main_force_batch_results(st.session_state.main_force_batch_results)
|
||||||
|
|
||||||
|
# 返回按钮
|
||||||
|
col_back, col_clear = st.columns(2)
|
||||||
|
with col_back:
|
||||||
|
if st.button("🔙 返回主力选股", use_container_width=True):
|
||||||
|
# 清除所有批量分析相关状态
|
||||||
|
if 'main_force_batch_trigger' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_trigger
|
||||||
|
if 'main_force_batch_codes' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_codes
|
||||||
|
if 'main_force_batch_results' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_results
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col_clear:
|
||||||
|
if st.button("🔄 重新分析", use_container_width=True):
|
||||||
|
# 清除结果,保留触发标志和代码
|
||||||
|
if 'main_force_batch_results' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_results
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取股票代码列表
|
||||||
|
stock_codes = st.session_state.get('main_force_batch_codes', [])
|
||||||
|
|
||||||
|
if not stock_codes:
|
||||||
|
st.error("未找到股票代码列表")
|
||||||
|
# 清除触发标志
|
||||||
|
if 'main_force_batch_trigger' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_trigger
|
||||||
|
return
|
||||||
|
|
||||||
|
st.info(f"即将分析 {len(stock_codes)} 只股票:{', '.join(stock_codes[:10])}{'...' if len(stock_codes) > 10 else ''}")
|
||||||
|
|
||||||
|
# 返回按钮
|
||||||
|
if st.button("🔙 取消返回", type="secondary"):
|
||||||
|
# 清除所有批量分析相关状态
|
||||||
|
if 'main_force_batch_trigger' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_trigger
|
||||||
|
if 'main_force_batch_codes' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_codes
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# 分析选项
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
analysis_mode = st.selectbox(
|
||||||
|
"分析模式",
|
||||||
|
options=["sequential", "parallel"],
|
||||||
|
format_func=lambda x: "顺序分析(稳定)" if x == "sequential" else "并行分析(快速)",
|
||||||
|
help="顺序分析较慢但稳定,并行分析更快但消耗更多资源"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if analysis_mode == "parallel":
|
||||||
|
max_workers = st.number_input(
|
||||||
|
"并行线程数",
|
||||||
|
min_value=2,
|
||||||
|
max_value=5,
|
||||||
|
value=3,
|
||||||
|
help="同时分析的股票数量"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
max_workers = 1
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# 开始分析按钮
|
||||||
|
col_confirm, col_cancel = st.columns(2)
|
||||||
|
|
||||||
|
start_analysis = False
|
||||||
|
with col_confirm:
|
||||||
|
if st.button("🚀 确认开始分析", type="primary", use_container_width=True):
|
||||||
|
start_analysis = True
|
||||||
|
|
||||||
|
with col_cancel:
|
||||||
|
if st.button("❌ 取消", type="secondary", use_container_width=True):
|
||||||
|
# 清除所有批量分析相关状态
|
||||||
|
if 'main_force_batch_trigger' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_trigger
|
||||||
|
if 'main_force_batch_codes' in st.session_state:
|
||||||
|
del st.session_state.main_force_batch_codes
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
if start_analysis:
|
||||||
|
# 导入统一分析函数(遵循统一规范)
|
||||||
|
from app import analyze_single_stock_for_batch
|
||||||
|
import concurrent.futures
|
||||||
|
import time
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
st.info("⏳ 正在执行批量分析,请稍候...")
|
||||||
|
|
||||||
|
# 显示即将分析的股票代码(调试用)
|
||||||
|
with st.expander("🔍 调试信息", expanded=True):
|
||||||
|
st.write(f"**股票代码数量**: {len(stock_codes)} 只")
|
||||||
|
st.write(f"**股票代码列表**: {stock_codes}")
|
||||||
|
st.write(f"**代码格式检查**: {'✅ 无后缀,格式正确' if all('.' not in str(c) for c in stock_codes) else '❌ 包含后缀,可能有问题'}")
|
||||||
|
st.write(f"**分析模式**: {analysis_mode}")
|
||||||
|
st.write(f"**线程数**: {max_workers if analysis_mode == 'parallel' else 1}")
|
||||||
|
|
||||||
|
# 配置分析师参数
|
||||||
|
enabled_analysts_config = {
|
||||||
|
'technical': True,
|
||||||
|
'fundamental': True,
|
||||||
|
'fund_flow': True,
|
||||||
|
'risk': True,
|
||||||
|
'sentiment': False, # 禁用以提升速度
|
||||||
|
'news': False # 禁用以提升速度
|
||||||
|
}
|
||||||
|
selected_model = 'deepseek-chat'
|
||||||
|
period = '1y'
|
||||||
|
|
||||||
|
# 创建进度显示
|
||||||
|
progress_bar = st.progress(0)
|
||||||
|
status_text = st.empty()
|
||||||
|
|
||||||
|
# 存储结果
|
||||||
|
results = []
|
||||||
|
|
||||||
|
# 记录开始时间
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
if analysis_mode == "sequential":
|
||||||
|
# 顺序分析
|
||||||
|
for i, code in enumerate(stock_codes):
|
||||||
|
status_text.text(f"正在分析 {code} ({i+1}/{len(stock_codes)})")
|
||||||
|
progress_bar.progress((i + 1) / len(stock_codes))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 调用统一分析函数
|
||||||
|
result = analyze_single_stock_for_batch(
|
||||||
|
symbol=code,
|
||||||
|
period=period,
|
||||||
|
enabled_analysts_config=enabled_analysts_config,
|
||||||
|
selected_model=selected_model
|
||||||
|
)
|
||||||
|
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results.append({
|
||||||
|
"symbol": code,
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 并行分析
|
||||||
|
status_text.text(f"并行分析 {len(stock_codes)} 只股票({max_workers}线程)...")
|
||||||
|
|
||||||
|
def analyze_one(code):
|
||||||
|
try:
|
||||||
|
result = analyze_single_stock_for_batch(
|
||||||
|
symbol=code,
|
||||||
|
period=period,
|
||||||
|
enabled_analysts_config=enabled_analysts_config,
|
||||||
|
selected_model=selected_model
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
return {"symbol": code, "success": False, "error": str(e)}
|
||||||
|
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
|
futures = {executor.submit(analyze_one, code): code for code in stock_codes}
|
||||||
|
|
||||||
|
completed = 0
|
||||||
|
for future in concurrent.futures.as_completed(futures):
|
||||||
|
code = futures[future] # 获取对应的股票代码
|
||||||
|
completed += 1
|
||||||
|
progress_bar.progress(completed / len(stock_codes))
|
||||||
|
status_text.text(f"已完成 {completed}/{len(stock_codes)} ({code})")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = future.result()
|
||||||
|
results.append(result)
|
||||||
|
except Exception as e:
|
||||||
|
results.append({"symbol": code, "success": False, "error": str(e)})
|
||||||
|
|
||||||
|
# 清除进度
|
||||||
|
progress_bar.empty()
|
||||||
|
status_text.empty()
|
||||||
|
|
||||||
|
# 计算统计
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
success_count = sum(1 for r in results if r.get("success", False))
|
||||||
|
failed_count = len(results) - success_count
|
||||||
|
|
||||||
|
# 显示完成信息
|
||||||
|
if success_count > 0:
|
||||||
|
st.success(f"✅ 批量分析完成!成功 {success_count} 只,失败 {failed_count} 只,耗时 {elapsed_time/60:.1f} 分钟")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ 批量分析完成,但所有 {failed_count} 只股票都分析失败!")
|
||||||
|
|
||||||
|
# 显示失败原因(调试用)
|
||||||
|
with st.expander("❌ 查看失败原因", expanded=True):
|
||||||
|
for r in results:
|
||||||
|
if not r.get("success", False):
|
||||||
|
st.error(f"**{r.get('symbol', 'N/A')}**: {r.get('error', '未知错误')}")
|
||||||
|
|
||||||
|
# 保存结果到session_state
|
||||||
|
st.session_state.main_force_batch_results = {
|
||||||
|
"results": results,
|
||||||
|
"total": len(results),
|
||||||
|
"success": success_count,
|
||||||
|
"failed": failed_count,
|
||||||
|
"elapsed_time": elapsed_time,
|
||||||
|
"analysis_mode": analysis_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 重新渲染以显示结果
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
|
||||||
|
def display_main_force_batch_results(batch_results):
|
||||||
|
"""显示主力选股批量分析结果"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
results = batch_results['results']
|
||||||
|
total = batch_results['total']
|
||||||
|
success = batch_results['success']
|
||||||
|
failed = batch_results['failed']
|
||||||
|
elapsed_time = batch_results['elapsed_time']
|
||||||
|
|
||||||
|
st.markdown("## 📊 批量分析结果")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# 统计信息
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("总计分析", f"{total} 只")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.metric("成功分析", f"{success} 只", delta=f"{success/total*100:.1f}%")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
st.metric("失败分析", f"{failed} 只")
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
st.metric("总耗时", f"{elapsed_time/60:.1f} 分钟")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# 成功分析的股票
|
||||||
|
successful_results = [r for r in results if r['success']]
|
||||||
|
|
||||||
|
if successful_results:
|
||||||
|
st.markdown(f"### ✅ 成功分析的股票 ({len(successful_results)}只)")
|
||||||
|
|
||||||
|
# 创建DataFrame展示
|
||||||
|
display_data = []
|
||||||
|
for result in successful_results:
|
||||||
|
stock_info = result.get('stock_info', {})
|
||||||
|
final_decision = result.get('final_decision', {})
|
||||||
|
|
||||||
|
# 提取评级emoji
|
||||||
|
rating = final_decision.get('rating', '未知')
|
||||||
|
rating_emoji = {
|
||||||
|
'强烈买入': '🔥',
|
||||||
|
'买入': '✅',
|
||||||
|
'持有': '⏸️',
|
||||||
|
'卖出': '⚠️',
|
||||||
|
'强烈卖出': '🚫'
|
||||||
|
}.get(rating, '❓')
|
||||||
|
|
||||||
|
display_data.append({
|
||||||
|
'股票代码': stock_info.get('symbol', ''),
|
||||||
|
'股票名称': stock_info.get('name', ''),
|
||||||
|
'评级': f"{rating_emoji} {rating}",
|
||||||
|
'信心度': final_decision.get('confidence_level', 'N/A'),
|
||||||
|
'进场区间': final_decision.get('entry_range', 'N/A'),
|
||||||
|
'止盈位': final_decision.get('take_profit', 'N/A'),
|
||||||
|
'止损位': final_decision.get('stop_loss', 'N/A'),
|
||||||
|
'目标价': final_decision.get('target_price', 'N/A')
|
||||||
|
})
|
||||||
|
|
||||||
|
df_display = pd.DataFrame(display_data)
|
||||||
|
st.dataframe(df_display, use_container_width=True, height=400)
|
||||||
|
|
||||||
|
# 详细分析结果(可展开)
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown("### 📋 详细分析报告")
|
||||||
|
|
||||||
|
for result in successful_results:
|
||||||
|
stock_info = result.get('stock_info', {})
|
||||||
|
final_decision = result.get('final_decision', {})
|
||||||
|
|
||||||
|
symbol = stock_info.get('symbol', '')
|
||||||
|
name = stock_info.get('name', '')
|
||||||
|
rating = final_decision.get('rating', '未知')
|
||||||
|
rating_emoji = {
|
||||||
|
'强烈买入': '🔥',
|
||||||
|
'买入': '✅',
|
||||||
|
'持有': '⏸️',
|
||||||
|
'卖出': '⚠️',
|
||||||
|
'强烈卖出': '🚫'
|
||||||
|
}.get(rating, '❓')
|
||||||
|
|
||||||
|
with st.expander(f"{rating_emoji} {symbol} - {name} | {rating}"):
|
||||||
|
# 关键信息
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("信心度", final_decision.get('confidence_level', 'N/A'))
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.metric("进场区间", final_decision.get('entry_range', 'N/A'))
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
st.metric("目标价", final_decision.get('target_price', 'N/A'))
|
||||||
|
|
||||||
|
# 止盈止损
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("止盈位", final_decision.get('take_profit', 'N/A'))
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.metric("止损位", final_decision.get('stop_loss', 'N/A'))
|
||||||
|
|
||||||
|
# 投资建议
|
||||||
|
st.markdown("#### 💡 投资建议")
|
||||||
|
advice = final_decision.get('advice', '暂无建议')
|
||||||
|
st.info(advice)
|
||||||
|
|
||||||
|
# 加入监测按钮
|
||||||
|
if st.button(f"➕ 加入监测列表", key=f"monitor_{symbol}"):
|
||||||
|
# 解析进场区间
|
||||||
|
entry_range = final_decision.get('entry_range', '')
|
||||||
|
entry_min, entry_max = None, None
|
||||||
|
if entry_range and isinstance(entry_range, str) and "-" in entry_range:
|
||||||
|
try:
|
||||||
|
parts = entry_range.split("-")
|
||||||
|
entry_min = float(parts[0].strip())
|
||||||
|
entry_max = float(parts[1].strip())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 解析止盈止损
|
||||||
|
take_profit_str = final_decision.get('take_profit', '')
|
||||||
|
take_profit = None
|
||||||
|
if take_profit_str:
|
||||||
|
try:
|
||||||
|
numbers = re.findall(r'\d+\.?\d*', str(take_profit_str))
|
||||||
|
if numbers:
|
||||||
|
take_profit = float(numbers[0])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
stop_loss_str = final_decision.get('stop_loss', '')
|
||||||
|
stop_loss = None
|
||||||
|
if stop_loss_str:
|
||||||
|
try:
|
||||||
|
numbers = re.findall(r'\d+\.?\d*', str(stop_loss_str))
|
||||||
|
if numbers:
|
||||||
|
stop_loss = float(numbers[0])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 调用监测管理器添加
|
||||||
|
from monitor_db import monitor_db
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 准备进场区间数据
|
||||||
|
entry_range_dict = {}
|
||||||
|
if entry_min and entry_max:
|
||||||
|
entry_range_dict = {"min": entry_min, "max": entry_max}
|
||||||
|
|
||||||
|
# 添加到监测列表
|
||||||
|
monitor_db.add_monitored_stock(
|
||||||
|
symbol=symbol,
|
||||||
|
name=name,
|
||||||
|
rating=rating,
|
||||||
|
entry_range=entry_range_dict if entry_range_dict else None,
|
||||||
|
take_profit=take_profit,
|
||||||
|
stop_loss=stop_loss,
|
||||||
|
notes=f"主力选股批量分析 | {rating}"
|
||||||
|
)
|
||||||
|
st.success(f"✅ {symbol} - {name} 已加入监测列表")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"❌ 添加失败: {str(e)}")
|
||||||
|
|
||||||
|
# 失败的股票
|
||||||
|
failed_results = [r for r in results if not r['success']]
|
||||||
|
|
||||||
|
if failed_results:
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown(f"### ❌ 分析失败的股票 ({len(failed_results)}只)")
|
||||||
|
|
||||||
|
failed_data = []
|
||||||
|
for result in failed_results:
|
||||||
|
failed_data.append({
|
||||||
|
'股票代码': result.get('symbol', ''),
|
||||||
|
'失败原因': result.get('error', '未知错误')
|
||||||
|
})
|
||||||
|
|
||||||
|
df_failed = pd.DataFrame(failed_data)
|
||||||
|
st.dataframe(df_failed, use_container_width=True)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,302 +0,0 @@
|
|||||||
# 智策定时分析通知重复问题 - 修复说明
|
|
||||||
|
|
||||||
## 📋 问题描述
|
|
||||||
|
|
||||||
**症状**:智策定时分析完成后
|
|
||||||
- ✉️ 邮箱收到 **3 封相同邮件**
|
|
||||||
- 📱 钉钉收到 **2 次相同通知**
|
|
||||||
- 所有通知内容完全一样,同时收到
|
|
||||||
|
|
||||||
**定时设置**:
|
|
||||||
- 定时时间: 08:15
|
|
||||||
- 任务状态: 运行中
|
|
||||||
- 上次运行: 2025-10-21 08:21:55
|
|
||||||
|
|
||||||
## 🔍 根本原因分析
|
|
||||||
|
|
||||||
### 原因1:schedule库任务重复添加 ⭐ 主要原因
|
|
||||||
**问题**:`schedule.clear('sector_strategy')` 在某些情况下无法正确清除所有旧任务,导致任务累积。
|
|
||||||
|
|
||||||
**证据**:
|
|
||||||
- 3封邮件表示 `_send_analysis_notification()` 被调用了3次
|
|
||||||
- 说明同一时刻有3个定时任务被触发
|
|
||||||
|
|
||||||
### 原因2:持仓定时分析的干扰
|
|
||||||
**问题**:`portfolio_scheduler.py` 中使用 `schedule.clear()` 会清除**所有**定时任务,包括智策的!
|
|
||||||
|
|
||||||
**代码位置**:
|
|
||||||
- `portfolio_scheduler.py` 第540行:`schedule.clear()`
|
|
||||||
- `portfolio_scheduler.py` 第492行:`schedule.clear()`
|
|
||||||
- `portfolio_scheduler.py` 第573行:`schedule.clear()`
|
|
||||||
|
|
||||||
### 原因3:缺少并发保护
|
|
||||||
**问题**:如果分析还没完成,定时器再次触发,会导致多个分析任务并发执行。
|
|
||||||
|
|
||||||
## ✅ 修复方案
|
|
||||||
|
|
||||||
### 修复1:智策调度器改进(sector_strategy_scheduler.py)
|
|
||||||
|
|
||||||
#### 1.1 添加并发保护锁
|
|
||||||
```python
|
|
||||||
def __init__(self):
|
|
||||||
# ...
|
|
||||||
self._analysis_lock = threading.Lock() # 防止并发执行
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.2 安全的任务执行包装
|
|
||||||
```python
|
|
||||||
def _run_analysis_safe(self):
|
|
||||||
"""运行智策分析(带锁保护,防止并发执行)"""
|
|
||||||
if not self._analysis_lock.acquire(blocking=False):
|
|
||||||
print("[智策定时] ⚠️ 上一次分析还未完成,跳过本次执行")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._run_analysis()
|
|
||||||
finally:
|
|
||||||
self._analysis_lock.release()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.3 改进任务清除逻辑
|
|
||||||
**修复前**:
|
|
||||||
```python
|
|
||||||
schedule.clear('sector_strategy') # 可能无法正确清除
|
|
||||||
```
|
|
||||||
|
|
||||||
**修复后**:
|
|
||||||
```python
|
|
||||||
# 手动遍历并清除所有带sector_strategy标签的任务
|
|
||||||
jobs_to_remove = [job for job in schedule.jobs if 'sector_strategy' in job.tags]
|
|
||||||
for job in jobs_to_remove:
|
|
||||||
schedule.cancel_job(job)
|
|
||||||
print(f"[智策定时] 清除了 {len(jobs_to_remove)} 个旧任务")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.4 添加通知去重机制 ⭐ 核心防护
|
|
||||||
```python
|
|
||||||
def _send_analysis_notification(self, result):
|
|
||||||
# 去重检查:如果5分钟内已发送过通知,则跳过
|
|
||||||
current_time = datetime.now()
|
|
||||||
if self.last_notification_time:
|
|
||||||
time_diff = (current_time - self.last_notification_time).total_seconds()
|
|
||||||
if time_diff < 300: # 5分钟 = 300秒
|
|
||||||
print(f"[智策定时] ⚠️ 距离上次通知仅{time_diff:.0f}秒,跳过重复发送")
|
|
||||||
return
|
|
||||||
|
|
||||||
# ... 发送通知 ...
|
|
||||||
|
|
||||||
# 更新最后通知时间
|
|
||||||
if sent_count > 0:
|
|
||||||
self.last_notification_time = current_time
|
|
||||||
print(f"[智策定时] 📝 已记录通知时间: {current_time.strftime('%H:%M:%S')}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 修复2:持仓调度器改进(portfolio_scheduler.py)
|
|
||||||
|
|
||||||
#### 2.1 使用标签化任务管理
|
|
||||||
**修复前**:
|
|
||||||
```python
|
|
||||||
schedule.clear() # 危险!会清除所有任务
|
|
||||||
```
|
|
||||||
|
|
||||||
**修复后**:
|
|
||||||
```python
|
|
||||||
# 只清除持仓定时分析的任务,不影响智策和监测
|
|
||||||
jobs_to_remove = [job for job in schedule.jobs if 'portfolio_analysis' in job.tags]
|
|
||||||
for job in jobs_to_remove:
|
|
||||||
schedule.cancel_job(job)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2 添加任务标签
|
|
||||||
```python
|
|
||||||
for time_str in self.schedule_times:
|
|
||||||
job = schedule.every().day.at(time_str).do(self._scheduled_job)
|
|
||||||
job.tag('portfolio_analysis') # 打标签,方便管理
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 修复效果
|
|
||||||
|
|
||||||
### 修复前
|
|
||||||
```
|
|
||||||
08:15:00 → 触发任务1 → 发送邮件 ✉️
|
|
||||||
08:15:00 → 触发任务2 → 发送邮件 ✉️ (重复!)
|
|
||||||
08:15:00 → 触发任务3 → 发送邮件 ✉️ (重复!)
|
|
||||||
```
|
|
||||||
**结果**:收到3封邮件
|
|
||||||
|
|
||||||
### 修复后
|
|
||||||
```
|
|
||||||
08:15:00 → 触发任务1 → 获取锁 → 发送邮件 ✉️
|
|
||||||
08:15:00 → 触发任务2 → 无法获取锁 → 跳过
|
|
||||||
08:15:00 → 触发任务3 → 无法获取锁 → 跳过
|
|
||||||
```
|
|
||||||
**结果**:只收到1封邮件
|
|
||||||
|
|
||||||
### 多层防护机制
|
|
||||||
1. **任务去重**:改进的清除逻辑防止任务累积
|
|
||||||
2. **并发锁**:防止多个任务同时执行
|
|
||||||
3. **时间去重**:5分钟内重复通知直接跳过
|
|
||||||
4. **模块隔离**:各模块任务独立管理,互不影响
|
|
||||||
|
|
||||||
## 🧪 测试方法
|
|
||||||
|
|
||||||
### 测试1:验证任务唯一性
|
|
||||||
```python
|
|
||||||
# 在智策定时分析UI中
|
|
||||||
1. 启动定时任务
|
|
||||||
2. 运行以下代码检查任务数量:
|
|
||||||
import schedule
|
|
||||||
sector_jobs = [job for job in schedule.jobs if 'sector_strategy' in job.tags]
|
|
||||||
print(f"智策任务数量: {len(sector_jobs)}") # 应该只有1个
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试2:验证去重机制
|
|
||||||
```python
|
|
||||||
# 手动触发两次分析
|
|
||||||
1. 点击"立即运行"
|
|
||||||
2. 等待1分钟
|
|
||||||
3. 再次点击"立即运行"
|
|
||||||
# 第二次应该显示"距离上次通知仅XX秒,跳过重复发送"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试3:验证模块隔离
|
|
||||||
```python
|
|
||||||
# 同时运行智策和持仓定时分析
|
|
||||||
1. 启动智策定时任务
|
|
||||||
2. 启动持仓定时任务
|
|
||||||
3. 检查两个任务是否都正常运行
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试4:验证实际效果(最重要)
|
|
||||||
```python
|
|
||||||
# 等待下一次定时触发(明天08:15)
|
|
||||||
1. 确认只收到1封邮件
|
|
||||||
2. 确认只收到1次钉钉通知
|
|
||||||
3. 查看日志输出,确认只有1次发送记录
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 日志输出示例
|
|
||||||
|
|
||||||
### 正常情况(修复后)
|
|
||||||
```
|
|
||||||
[智策定时] 开始定时分析 - 2025-10-22 08:15:00
|
|
||||||
[智策定时] [1/3] 获取市场数据...
|
|
||||||
[智策定时] ✓ 数据获取成功
|
|
||||||
[智策定时] [2/3] AI智能体分析中...
|
|
||||||
[智策定时] ✓ 分析完成
|
|
||||||
[智策定时] [3/3] 发送邮件通知...
|
|
||||||
[智策定时] [Webhook] 准备发送...
|
|
||||||
[智策定时] ✓ Webhook发送成功
|
|
||||||
[智策定时] [邮件] 准备发送...
|
|
||||||
[智策定时] ✓ 邮件发送成功
|
|
||||||
[智策定时] 📝 已记录通知时间: 08:15:23
|
|
||||||
[智策定时] ✓ 定时分析完成!
|
|
||||||
```
|
|
||||||
|
|
||||||
### 重复触发时(修复后)
|
|
||||||
```
|
|
||||||
[智策定时] ⚠️ 上一次分析还未完成,跳过本次执行
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5分钟内重复通知时(修复后)
|
|
||||||
```
|
|
||||||
[智策定时] ⚠️ 距离上次通知仅123秒,跳过重复发送
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 如何使用修复后的版本
|
|
||||||
|
|
||||||
### 步骤1:重启系统
|
|
||||||
```bash
|
|
||||||
# 停止当前运行的streamlit
|
|
||||||
Ctrl+C
|
|
||||||
|
|
||||||
# 重新启动
|
|
||||||
python run.py
|
|
||||||
# 或
|
|
||||||
streamlit run app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤2:重新配置定时任务
|
|
||||||
1. 进入"智策板块分析"页面
|
|
||||||
2. 找到"定时分析"标签页
|
|
||||||
3. 点击"停止"(如果正在运行)
|
|
||||||
4. 重新设置定时时间为 08:15
|
|
||||||
5. 点击"启动"
|
|
||||||
|
|
||||||
### 步骤3:验证状态
|
|
||||||
查看页面显示:
|
|
||||||
```
|
|
||||||
✅ 定时任务运行中
|
|
||||||
⏰ 定时时间: 08:15
|
|
||||||
📅 下次运行: 2025-10-22 08:15:00
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤4:等待下次执行
|
|
||||||
明天08:15自动执行后,检查:
|
|
||||||
- ✅ 邮箱只收到1封邮件
|
|
||||||
- ✅ 钉钉只收到1次通知
|
|
||||||
- ✅ UI显示正常的执行时间
|
|
||||||
|
|
||||||
## 🛡️ 预防措施
|
|
||||||
|
|
||||||
### 1. 避免重复启动
|
|
||||||
- 在启动新任务前,先检查是否已有任务在运行
|
|
||||||
- 如果要修改时间,先"停止"再重新"启动"
|
|
||||||
|
|
||||||
### 2. 避免频繁手动触发
|
|
||||||
- "立即运行"功能会立即执行分析并发送通知
|
|
||||||
- 避免在5分钟内多次点击
|
|
||||||
|
|
||||||
### 3. 查看日志
|
|
||||||
运行时在控制台查看日志输出,确认:
|
|
||||||
- 任务数量是否正确
|
|
||||||
- 是否有重复执行的警告
|
|
||||||
- 通知发送次数是否正确
|
|
||||||
|
|
||||||
## 📚 技术要点
|
|
||||||
|
|
||||||
### 任务标签体系
|
|
||||||
- `sector_strategy` - 智策定时分析
|
|
||||||
- `portfolio_analysis` - 持仓定时分析
|
|
||||||
- `monitor` - 实时监测定时功能
|
|
||||||
|
|
||||||
### 锁机制
|
|
||||||
使用 `threading.Lock()` 的非阻塞获取:
|
|
||||||
- `acquire(blocking=False)` - 如果锁已被占用,立即返回False
|
|
||||||
- 保证同一时刻只有一个分析任务在执行
|
|
||||||
|
|
||||||
### 时间去重
|
|
||||||
- 记录 `last_notification_time`
|
|
||||||
- 每次发送前检查时间差
|
|
||||||
- 5分钟(300秒)内的重复请求直接跳过
|
|
||||||
|
|
||||||
## ✅ 修复清单
|
|
||||||
|
|
||||||
- [x] 智策调度器添加并发保护锁
|
|
||||||
- [x] 智策调度器添加通知去重机制(5分钟)
|
|
||||||
- [x] 智策调度器改进任务清除逻辑
|
|
||||||
- [x] 持仓调度器使用标签化任务管理
|
|
||||||
- [x] 持仓调度器避免清除其他模块任务
|
|
||||||
- [x] 添加详细日志输出
|
|
||||||
- [x] 创建测试方案
|
|
||||||
- [x] 编写修复说明文档
|
|
||||||
|
|
||||||
## 🎯 预期效果
|
|
||||||
|
|
||||||
修复后,智策定时分析应该:
|
|
||||||
1. ✅ 每次只发送 **1封** 邮件
|
|
||||||
2. ✅ 每次只发送 **1次** 钉钉通知
|
|
||||||
3. ✅ 不会受到持仓定时分析的影响
|
|
||||||
4. ✅ 即使误操作多次启动,也有去重保护
|
|
||||||
5. ✅ 日志清晰,便于排查问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**修复日期**:2025-10-21
|
|
||||||
**修复版本**:v1.1.0
|
|
||||||
**修复文件**:
|
|
||||||
- `sector_strategy_scheduler.py` ✅
|
|
||||||
- `portfolio_scheduler.py` ✅
|
|
||||||
|
|
||||||
**下次执行验证**:2025-10-22 08:15:00
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user