From acc00ec702b5f405ec51539c2be0dbd32d5ebf48 Mon Sep 17 00:00:00 2001 From: oficcejo Date: Tue, 21 Oct 2025 17:27:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=BB=E5=8A=9B=E9=80=89?= =?UTF-8?q?=E8=82=A1-=E6=89=B9=E9=87=8F=E5=88=86=E6=9E=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 59 +++- docs/主力选股批量分析功能说明.md | 487 +++++++++++++++++++++++++++++++ main_force_ui.py | 467 +++++++++++++++++++++++++++++ stock_analysis.db | Bin 8224768 -> 8499200 bytes 修复说明-智策定时分析通知重复.md | 302 ------------------- 5 files changed, 1011 insertions(+), 304 deletions(-) create mode 100644 docs/主力选股批量分析功能说明.md delete mode 100644 修复说明-智策定时分析通知重复.md 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 06b5a809e3f7de279cc411bd6ce8be05d9c6030c..b0a063a25cac0cfb2ae440fc5c91cf4959b4df62 100644 GIT binary patch delta 95247 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 delta 433 zcmWm8%QF-J0Knm0yCSSzk2WPzDX-*5Z$zP&(0WuT*-EXRN-vMh>~#2@HQgF+O?R{Z zhqI#@CpRZ&x_+lGy=gd|-VH66XRibTIUB#KI)f`~$-mLuRAf8wUw#+F)-y#rvsszG zt<}8Dc1656_~$1Qlq?BJShD4SDu*O4howx8$WbYm zV^SfNa$Kt9gq)O9QZ1+DjMPZ2)JeTG$XRKWbJ8UHpJr*1^KwBh$|bohSLCX+N}IIH zHR+H}xh^-POS+{;ZptmWEqCOu^vXTylYSYH`;w4B8IoZckq7cn9?7VT$+$e0Co&;V zWm1yzOrFaNnUa_CN>VZ{ujP%rm3Q)9KFExGlut4%pXG~um2dK0=44(LWKn+nV=1u* DL;j5s 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 -