增加主力选股-批量分析功能

This commit is contained in:
oficcejo
2025-10-21 17:27:29 +08:00
parent 04de9c28bb
commit acc00ec702
5 changed files with 1011 additions and 304 deletions
+57 -2
View File
@@ -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` - 龙虎榜批量分析
- **监测功能**:首页实时监测文档
---
## 🎊 总结
**主力选股批量分析功能**为您提供:
✅ 主力资金筛选快速定位候选
✅ 深度分析精准评估投资价值
✅ 一键加入监测实时跟踪
✅ 完整的主力资金跟踪解决方案
**祝您抓住每一个主力资金驱动的投资机会!** 🎯📈
+467
View File
@@ -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)
BIN
View File
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