diff --git a/README.md b/README.md index 9c7afc3..49b920e 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,31 @@ image -## ✨最新更新 - 持仓定时分析功能 ⭐️ +## ✨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 更新说明 ### 🎯 风险管理师功能增强 ⭐️ @@ -261,6 +315,7 @@ StockAPI龙虎榜接口(每日更新,免费1000次) - **智能选股**:基于主力资金流向,AI团队精选3-5只优质标的 - **数据来源**:问财前100名主力资金净流入股票 - **多维筛选**:市值范围、涨跌幅限制、财务指标 +- **批量分析**:对主力资金净流入TOP 10/20/30/50只股票进行深度AI分析 ⭐️新增 #### 🔥 批量分析(1008新增) - **双模式**:顺序分析 / 多线程并行 diff --git a/docs/主力选股批量分析功能说明.md b/docs/主力选股批量分析功能说明.md new file mode 100644 index 0000000..6ecebbd --- /dev/null +++ b/docs/主力选股批量分析功能说明.md @@ -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` - 龙虎榜批量分析 +- **监测功能**:首页实时监测文档 + +--- + +## 🎊 总结 + +**主力选股批量分析功能**为您提供: + +✅ 主力资金筛选快速定位候选 +✅ 深度分析精准评估投资价值 +✅ 一键加入监测实时跟踪 +✅ 完整的主力资金跟踪解决方案 + +**祝您抓住每一个主力资金驱动的投资机会!** 🎯📈 + diff --git a/main_force_ui.py b/main_force_ui.py index 8f37ca3..b26fb44 100644 --- a/main_force_ui.py +++ b/main_force_ui.py @@ -13,6 +13,11 @@ import pandas as pd def display_main_force_selector(): """显示主力选股界面""" + # 检查是否触发批量分析(不立即删除标志) + if st.session_state.get('main_force_batch_trigger'): + run_main_force_batch_analysis() + return + 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", 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报告下载区域 if analyzer and result: @@ -417,3 +466,421 @@ def format_number(value, unit='', suffix=''): except (ValueError, TypeError): 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) + diff --git a/stock_analysis.db b/stock_analysis.db index 06b5a80..b0a063a 100644 Binary files a/stock_analysis.db and b/stock_analysis.db differ diff --git a/修复说明-智策定时分析通知重复.md b/修复说明-智策定时分析通知重复.md deleted file mode 100644 index ef2ad56..0000000 --- a/修复说明-智策定时分析通知重复.md +++ /dev/null @@ -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 -