From b7a8bb10b3e2fc344a332bc753e766bf85d95c6a Mon Sep 17 00:00:00 2001 From: oficcejo Date: Mon, 20 Oct 2025 15:40:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=BE=99=E8=99=8E=E6=A6=9C?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - README.md | 157 +++-- UI_IMPROVEMENTS.md | 223 ------- AGENTS.md => docs/AGENTS.md | 0 .../DOCKER_DEPLOYMENT.md | 0 DOCKER_README.md => docs/DOCKER_README.md | 0 docs/LONGHUBANG_BATCH_ANALYSIS.md | 479 ++++++++++++++ .../MINIQMT_INTEGRATION_GUIDE.md | 0 docs/MULTI_SCHEDULE_GUIDE.md | 512 +++++++++++++++ docs/PORTFOLIO_USAGE.md | 398 ++++++++++++ QUICK_START.md => docs/QUICK_START.md | 0 docs/README.md | 167 +++++ docs/UNIFIED_ANALYSIS_SPEC.md | 272 ++++++++ docs/UPDATE_LOG.md | 476 ++++++++++++++ .../Webhook功能完成说明.md | 0 .../Webhook功能实现总结.md | 0 .../Webhook自定义关键词功能说明.md | 0 .../Webhook通知配置指南.md | 0 .../Webhook钉钉关键词快速配置指南.md | 0 .../Webhook钉钉关键词配置更新说明.md | 0 .../主力选股使用指南.md | 0 .../主力选股功能说明.md | 0 .../主力选股快速开始.md | 0 .../分析师团队选择功能说明.md | 0 .../加入监测功能说明.md | 0 .../季报和资金流向更新说明.md | 0 .../实时监测定时功能快速开始.md | 0 .../实时监测定时功能更新日志.md | 0 .../实时监测定时功能说明.md | 0 .../批量分析功能说明.md | 0 .../数据源冗余功能完成总结.md | 0 .../数据源冗余机制使用指南.md | 0 .../智瞰龙虎AI评分说明.md | 0 .../智瞰龙虎功能说明.md | 0 docs/智瞰龙虎历史报告功能说明.md | 212 ++++++ .../智瞰龙虎快速开始.md | 0 docs/智瞰龙虎评分排名历史记录说明.md | 306 +++++++++ 智策功能总览.md => docs/智策功能总览.md | 0 .../智策定时分析使用指南.md | 0 .../智策定时分析功能完成说明.md | 0 .../智策定时分析更新日志.md | 0 .../智策定时分析部署清单.md | 0 .../智策板块使用指南.md | 0 .../智策板块快速开始.md | 0 港股功能说明.md => docs/港股功能说明.md | 0 .../环境配置功能说明.md | 0 .../环境配置快速指南.md | 0 邮件配置指南.md => docs/邮件配置指南.md | 0 .../量化交易快速指南.md | 0 .../风险管理师增强功能说明.md | 0 longhubang.db | Bin 286720 -> 409600 bytes longhubang_db.py | 19 +- longhubang_engine.py | 32 +- longhubang_ui.py | 608 +++++++++++++++++- portfolio_stocks.db | Bin 28672 -> 28672 bytes portfolio_ui.py | 5 +- stock_analysis.db | Bin 7172096 -> 7364608 bytes test_monitor_scheduler.py | 276 -------- test_risk_data.py | 173 ----- test_risk_data_debug.py | 146 ----- 60 files changed, 3579 insertions(+), 883 deletions(-) delete mode 100644 UI_IMPROVEMENTS.md rename AGENTS.md => docs/AGENTS.md (100%) rename DOCKER_DEPLOYMENT.md => docs/DOCKER_DEPLOYMENT.md (100%) rename DOCKER_README.md => docs/DOCKER_README.md (100%) create mode 100644 docs/LONGHUBANG_BATCH_ANALYSIS.md rename MINIQMT_INTEGRATION_GUIDE.md => docs/MINIQMT_INTEGRATION_GUIDE.md (100%) create mode 100644 docs/MULTI_SCHEDULE_GUIDE.md create mode 100644 docs/PORTFOLIO_USAGE.md rename QUICK_START.md => docs/QUICK_START.md (100%) create mode 100644 docs/README.md create mode 100644 docs/UNIFIED_ANALYSIS_SPEC.md create mode 100644 docs/UPDATE_LOG.md rename Webhook功能完成说明.md => docs/Webhook功能完成说明.md (100%) rename Webhook功能实现总结.md => docs/Webhook功能实现总结.md (100%) rename Webhook自定义关键词功能说明.md => docs/Webhook自定义关键词功能说明.md (100%) rename Webhook通知配置指南.md => docs/Webhook通知配置指南.md (100%) rename Webhook钉钉关键词快速配置指南.md => docs/Webhook钉钉关键词快速配置指南.md (100%) rename Webhook钉钉关键词配置更新说明.md => docs/Webhook钉钉关键词配置更新说明.md (100%) rename 主力选股使用指南.md => docs/主力选股使用指南.md (100%) rename 主力选股功能说明.md => docs/主力选股功能说明.md (100%) rename 主力选股快速开始.md => docs/主力选股快速开始.md (100%) rename 分析师团队选择功能说明.md => docs/分析师团队选择功能说明.md (100%) rename 加入监测功能说明.md => docs/加入监测功能说明.md (100%) rename 季报和资金流向更新说明.md => docs/季报和资金流向更新说明.md (100%) rename 实时监测定时功能快速开始.md => docs/实时监测定时功能快速开始.md (100%) rename 实时监测定时功能更新日志.md => docs/实时监测定时功能更新日志.md (100%) rename 实时监测定时功能说明.md => docs/实时监测定时功能说明.md (100%) rename 批量分析功能说明.md => docs/批量分析功能说明.md (100%) rename 数据源冗余功能完成总结.md => docs/数据源冗余功能完成总结.md (100%) rename 数据源冗余机制使用指南.md => docs/数据源冗余机制使用指南.md (100%) rename 智瞰龙虎AI评分说明.md => docs/智瞰龙虎AI评分说明.md (100%) rename 智瞰龙虎功能说明.md => docs/智瞰龙虎功能说明.md (100%) create mode 100644 docs/智瞰龙虎历史报告功能说明.md rename 智瞰龙虎快速开始.md => docs/智瞰龙虎快速开始.md (100%) create mode 100644 docs/智瞰龙虎评分排名历史记录说明.md rename 智策功能总览.md => docs/智策功能总览.md (100%) rename 智策定时分析使用指南.md => docs/智策定时分析使用指南.md (100%) rename 智策定时分析功能完成说明.md => docs/智策定时分析功能完成说明.md (100%) rename 智策定时分析更新日志.md => docs/智策定时分析更新日志.md (100%) rename 智策定时分析部署清单.md => docs/智策定时分析部署清单.md (100%) rename 智策板块使用指南.md => docs/智策板块使用指南.md (100%) rename 智策板块快速开始.md => docs/智策板块快速开始.md (100%) rename 港股功能说明.md => docs/港股功能说明.md (100%) rename 环境配置功能说明.md => docs/环境配置功能说明.md (100%) rename 环境配置快速指南.md => docs/环境配置快速指南.md (100%) rename 邮件配置指南.md => docs/邮件配置指南.md (100%) rename 量化交易快速指南.md => docs/量化交易快速指南.md (100%) rename 风险管理师增强功能说明.md => docs/风险管理师增强功能说明.md (100%) delete mode 100644 test_monitor_scheduler.py delete mode 100644 test_risk_data.py delete mode 100644 test_risk_data_debug.py diff --git a/.gitignore b/.gitignore index f0abfa7..ef6c1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ .env /.cursor /openspec -/docs \ No newline at end of file diff --git a/README.md b/README.md index bb931bb..9c7afc3 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,38 @@ streamlit run app.py --- +### 🎯 智瞰龙虎 - TOP股票批量分析功能 ⭐️ + +在AI评分排名中,对TOP10综合评分股票提供一键批量深度分析 + +#### 核心功能 +- **一键批量分析**:可选3只/5只/10只TOP股票进行深度分析 +- **双模式支持**:顺序分析(稳定)/ 并行分析(快速) +- **实时进度追踪**:分析进度可视化显示 +- **详细结果展示**:完整的AI分析报告和投资决策 +- **快捷加入监测**:分析完成后一键加入实时监测列表 +- **遵循统一规范**:使用统一的分析函数和字段名,确保结果一致性 + +#### 使用流程 +``` +智瞰龙虎 → AI评分排名 → 查看TOP10 +→ 选择分析数量(3/5/10) → 🚀 开始批量分析 +→ 选择模式(顺序/并行) → 确认开始 +→ ⏳ 实时进度显示 → ✅ 分析完成 +→ 查看详细结果 → ➕ 加入监测 +``` + +#### 使用场景 +- ✅ **短线交易**:龙虎榜筛选 → AI深度分析 → 实时监测 → 价格告警 +- ✅ **游资跟踪**:快速批量分析TOP游资操作的股票 +- ✅ **题材捕捉**:批量分析热点题材龙头股 +- ✅ **完整闭环**:从筛选到分析到监测的一站式解决方案 + +#### 详细文档 +请查看 [`docs/LONGHUBANG_BATCH_ANALYSIS.md`](docs/LONGHUBANG_BATCH_ANALYSIS.md) 获取完整使用指南 + +--- + ## ✨1019 更新说明 ### 🎯 风险管理师功能增强 ⭐️ @@ -105,6 +137,8 @@ streamlit run app.py ## ✨1017 更新说明 ### 🎯 智瞰龙虎板块分析系统 ⭐️ 全新上线 + +#### 核心功能 - **龙虎榜深度分析**:基于StockAPI龙虎榜接口,获取游资上榜交割单数据 - **5位AI分析师团队**: - 🎯 游资行为分析师:识别活跃游资及其操作风格 @@ -112,19 +146,43 @@ streamlit run app.py - 🔥 题材追踪分析师:识别热点题材和炒作周期 - ⚠️ 风险控制专家:识别高风险股票和资金陷阱 - 👔 首席策略师:综合研判,给出最终推荐股票清单 -- **核心功能**: - - 次日上涨股票预测:AI识别大概率上涨的股票 - - 游资追踪:跟踪知名游资(赵老哥、章盟主等)的操作 - - 题材识别:发现热点题材和龙头股 - - 风险提示:识别游资出货和高风险陷阱 - - 历史记录:完整的龙虎榜数据存储 - - PDF报告:生成专业的分析报告 -- **数据可视化**: - - 资金净流入TOP20图表 - - 热门概念分布图 - - 活跃游资排名表 -- **使用场景**:短线交易、游资跟踪、题材炒作、风险规避 -- **数据来源**:StockAPI龙虎榜接口(每日更新,免费1000次) + +#### 最新更新:TOP股票批量分析 ⭐️ +- **一键批量分析**:对AI评分排名TOP10股票进行深度分析 + - 可选分析数量:3只/5只/10只 + - 顺序/并行分析模式 + - 实时进度显示 + - 详细结果展示 +- **快捷加入监测**:分析完成后一键加入实时监测列表 +- **遵循统一规范**:使用统一的分析函数和字段名 +- **完整交易闭环**:龙虎榜筛选 → AI深度分析 → 实时监测 → 价格告警 + +#### 传统功能 +- **次日上涨股票预测**:AI识别大概率上涨的股票 +- **游资追踪**:跟踪知名游资(赵老哥、章盟主等)的操作 +- **题材识别**:发现热点题材和龙头股 +- **风险提示**:识别游资出货和高风险陷阱 +- **历史报告** ⭐️ 增强版: + - 完整的分析结果保存(AI分析师报告、评分排名、推荐股票) + - 可展开查看每条历史报告的详细内容 + - 支持重新加载历史报告到分析页面 + - 结构化数据存储,便于追溯和分析 +- **PDF报告**:生成专业的分析报告 + +#### 数据可视化 +- 资金净流入TOP20图表 +- 热门概念分布图 +- 活跃游资排名表 +- AI评分排名表 ⭐️ + +#### 使用场景 +- 短线交易机会挖掘 +- 游资龙头股追踪 +- 热点题材捕捉 +- 风险规避 + +#### 数据来源 +StockAPI龙虎榜接口(每日更新,免费1000次) 智瞰龙虎界面 @@ -296,7 +354,7 @@ docker-compose down docker-compose restart ``` -**📖 详细文档**:查看 [DOCKER_DEPLOYMENT.md](DOCKER_DEPLOYMENT.md) 获取完整的Docker部署指南和故障排除。 +**📖 详细文档**:查看 [DOCKER_DEPLOYMENT.md](docs/DOCKER_DEPLOYMENT.md) 获取完整的Docker部署指南和故障排除。 --- @@ -500,11 +558,17 @@ streamlit run app.py - 等待生成(3-5秒) - 下载完整的龙虎榜分析报告 -6. **查看历史记录** +6. **查看历史报告** ⭐️ 增强版 - 切换到"📚 历史报告"标签 - - 查看所有历史分析报告 - - 切换到"📈 数据统计"标签 - - 查看龙虎榜历史统计数据 + - 查看最近50条历史分析报告 + - 点击展开任意报告,可查看: + - **报告摘要**:分析概要信息 + - **推荐股票**:完整的推荐股票列表 + - **AI分析师团队报告**:5位分析师的详细分析(可展开) + - **AI评分排名**:TOP10股票的评分详情 + - **数据概况**:龙虎榜统计信息 + - 点击"📋 加载到分析页"按钮,可将历史报告重新加载到分析标签页 + - 切换到"📈 数据统计"标签,查看龙虎榜历史统计数据 7. **使用技巧** - **最佳分析时间**:交易日晚上18:00-21:00(龙虎榜数据已更新) @@ -1141,7 +1205,7 @@ DEFAULT_INTERVAL = "1d" # 默认数据间隔 - 检查账户已登录 - 验证账户ID配置正确 - 确认网络连接正常 - - 查看 `MINIQMT_INTEGRATION_GUIDE.md` 详细指南 + - 查看 `docs/MINIQMT_INTEGRATION_GUIDE.md` 详细指南 8. **量化交易未执行** - 确认量化功能已启用 @@ -1166,7 +1230,7 @@ DEFAULT_INTERVAL = "1d" # 默认数据间隔 - **Node.js相关错误**: - 容器内已集成Node.js环境,无需额外配置 - 进入容器检查:`docker exec -it agentsstock1 bash` - - **详细文档**:查看 `DOCKER_DEPLOYMENT.md` 获取完整的故障排除指南 + - **详细文档**:查看 `docs/DOCKER_DEPLOYMENT.md` 获取完整的故障排除指南 10. **Webhook通知问题** ⭐️ 新增 - **未收到Webhook消息**: @@ -1189,7 +1253,7 @@ DEFAULT_INTERVAL = "1d" # 默认数据间隔 - 进入"📊 实时监测" → "🔔 通知管理" - 点击"📱 发送测试Webhook"验证配置 - 查看终端日志了解详细错误信息 - - **详细文档**:查看 `Webhook通知配置指南.md` 获取完整配置教程 + - **详细文档**:查看 `docs/Webhook通知配置指南.md` 获取完整配置教程 11. **智策板块分析问题** ⭐️ 全新功能 - **数据获取失败**: @@ -1219,9 +1283,9 @@ DEFAULT_INTERVAL = "1d" # 默认数据间隔 - 安装依赖:`pip install schedule` - 或:`.\venv\Scripts\pip.exe install schedule` - **详细文档**: - - [智策板块使用指南.md](智策板块使用指南.md) - - [智策定时分析使用指南.md](智策定时分析使用指南.md) - - [智策功能总览.md](智策功能总览.md) + - [智策板块使用指南.md](docs/智策板块使用指南.md) + - [智策定时分析使用指南.md](docs/智策定时分析使用指南.md) + - [智策功能总览.md](docs/智策功能总览.md) ### 日志调试 系统运行时会在控制台输出详细日志,可用于问题诊断。如遇到错误,请查看终端输出。 @@ -1238,27 +1302,38 @@ docker logs -f agentsstock1 ### 相关文档 **通知系统文档** ⭐️: -- [Webhook通知配置指南.md](Webhook通知配置指南.md) - Webhook完整配置教程 -- [Webhook功能完成说明.md](Webhook功能完成说明.md) - Webhook功能详细说明 -- [Webhook自定义关键词功能说明.md](Webhook自定义关键词功能说明.md) - 关键词配置指南 -- [邮件配置指南.md](邮件配置指南.md) - 邮件通知配置 +- [Webhook通知配置指南.md](docs/Webhook通知配置指南.md) - Webhook完整配置教程 +- [Webhook功能完成说明.md](docs/Webhook功能完成说明.md) - Webhook功能详细说明 +- [Webhook自定义关键词功能说明.md](docs/Webhook自定义关键词功能说明.md) - 关键词配置指南 +- [邮件配置指南.md](docs/邮件配置指南.md) - 邮件通知配置 **智策板块分析文档** ⭐️: -- [智策功能总览.md](智策功能总览.md) - 智策系统全景介绍 -- [智策板块使用指南.md](智策板块使用指南.md) - 完整使用教程 -- [智策板块快速开始.md](智策板块快速开始.md) - 5分钟快速上手 -- [智策定时分析使用指南.md](智策定时分析使用指南.md) - 定时分析详细指南 -- [智策定时分析功能完成说明.md](智策定时分析功能完成说明.md) - 功能详细说明 -- [智策定时分析部署清单.md](智策定时分析部署清单.md) - 部署步骤和故障排查 -- [智策定时分析更新日志.md](智策定时分析更新日志.md) - 版本历史 +- [智策功能总览.md](docs/智策功能总览.md) - 智策系统全景介绍 +- [智策板块使用指南.md](docs/智策板块使用指南.md) - 完整使用教程 +- [智策板块快速开始.md](docs/智策板块快速开始.md) - 5分钟快速上手 +- [智策定时分析使用指南.md](docs/智策定时分析使用指南.md) - 定时分析详细指南 +- [智策定时分析功能完成说明.md](docs/智策定时分析功能完成说明.md) - 功能详细说明 +- [智策定时分析部署清单.md](docs/智策定时分析部署清单.md) - 部署步骤和故障排查 +- [智策定时分析更新日志.md](docs/智策定时分析更新日志.md) - 版本历史 + +**持仓定时分析文档** ⭐️: +- [PORTFOLIO_USAGE.md](docs/PORTFOLIO_USAGE.md) - 持仓分析完整使用指南 +- [MULTI_SCHEDULE_GUIDE.md](docs/MULTI_SCHEDULE_GUIDE.md) - 多定时配置指南 +- [UNIFIED_ANALYSIS_SPEC.md](docs/UNIFIED_ANALYSIS_SPEC.md) - 统一调用规范 + +**智瞰龙虎文档** ⭐️: +- [LONGHUBANG_BATCH_ANALYSIS.md](docs/LONGHUBANG_BATCH_ANALYSIS.md) - 批量分析使用指南 +- [智瞰龙虎功能说明.md](docs/智瞰龙虎功能说明.md) - 功能详细说明 +- [智瞰龙虎快速开始.md](docs/智瞰龙虎快速开始.md) - 快速上手指南 **其他功能文档**: -- [环境配置功能说明.md](环境配置功能说明.md) - 完整环境配置说明 -- [环境配置快速指南.md](环境配置快速指南.md) - 快速配置参考 -- [实时监测优化说明.md](实时监测优化说明.md) - 实时监测功能详解 -- [主力选股使用指南.md](主力选股使用指南.md) - 主力选股功能 -- [量化交易快速指南.md](量化交易快速指南.md) - MiniQMT集成指南 -- [DOCKER_DEPLOYMENT.md](DOCKER_DEPLOYMENT.md) - Docker部署详细文档 +- [环境配置功能说明.md](docs/环境配置功能说明.md) - 完整环境配置说明 +- [环境配置快速指南.md](docs/环境配置快速指南.md) - 快速配置参考 +- [主力选股使用指南.md](docs/主力选股使用指南.md) - 主力选股功能 +- [量化交易快速指南.md](docs/量化交易快速指南.md) - MiniQMT集成指南 +- [DOCKER_DEPLOYMENT.md](docs/DOCKER_DEPLOYMENT.md) - Docker部署详细文档 +- [QUICK_START.md](docs/QUICK_START.md) - 快速开始指南 +- [UPDATE_LOG.md](docs/UPDATE_LOG.md) - 功能更新日志 ### 错误报告 如发现bug,请查看 `BUGFIX.md` 文件了解已知问题和解决方案。 diff --git a/UI_IMPROVEMENTS.md b/UI_IMPROVEMENTS.md deleted file mode 100644 index 346b6ef..0000000 --- a/UI_IMPROVEMENTS.md +++ /dev/null @@ -1,223 +0,0 @@ -# 🎨 UI美化升级说明 - -## 升级概览 - -系统UI已全面升级,采用现代化设计理念,提升用户体验和视觉效果。 - -## 🌟 主要改进 - -### 1. 顶部标题栏 -- **渐变背景**:紫色渐变(#667eea → #764ba2) -- **立体阴影**:增加视觉层次感 -- **双语标题**:中英文展示,更专业 -- **响应式设计**:移动端自适应 - -### 2. 侧边栏美化 -**配色方案** -- 背景:紫色渐变(180度) -- 文字:白色/半透明白色 -- 按钮:渐变背景 + 悬停效果 - -**布局优化**(从上到下) -1. 🔍 **快捷导航**(置顶) - - 历史记录按钮 - - 实时监测按钮 - - 返回首页按钮 - -2. ⚙️ **系统配置** - - API连接状态 - - AI模型选择器 - -3. 📊 **系统状态面板** - - 监测服务状态 - - 分析记录统计 - - 监测股票数量 - - 待处理通知 - -4. 📊 **分析参数设置** - - 数据周期选择 - -5. 💡 **使用帮助**(折叠式) - -### 3. 主页面样式 -**全局背景** -- 紫色渐变背景(固定定位) -- 主容器:半透明白色背景 -- 圆角边框:20px -- 立体阴影效果 - -**卡片设计** -- 分析师卡片:灰白渐变 + 左侧紫色边框 -- 决策卡片:绿色渐变 + 粗边框 -- 警告卡片:橙色渐变 + 左侧边框 -- 指标卡片:白色 + 顶部紫色边框 - -**交互效果** -- 悬停动画:平移/抬起效果 -- 阴影渐变:鼠标悬停时加深 -- 按钮动画:上移 + 阴影扩大 - -### 4. 组件美化 -**按钮** -- 紫色渐变背景 -- 圆角:10px -- 悬停效果:上移2px + 阴影扩大 -- 字体:600粗细,白色 - -**输入框** -- 圆角:10px -- 边框:2px实线 -- 聚焦效果:紫色边框 + 外发光 - -**进度条** -- 紫色渐变填充 -- 平滑过渡动画 - -**消息框** -- 圆角:10px -- 立体阴影 -- 内边距:1rem - -**图表** -- 圆角:15px -- 阴影效果 -- 自适应容器 - -## 📊 配色方案 - -### 主色调 -```css -主紫色: #667eea -深紫色: #764ba2 -``` - -### 辅助色 -```css -成功色: #4caf50 (绿色) -警告色: #ff9800 (橙色) -错误色: #f44336 (红色) -信息色: #2196f3 (蓝色) -``` - -### 背景色 -```css -主背景: linear-gradient(135deg, #667eea 0%, #764ba2 100%) -卡片背景: rgba(255, 255, 255, 0.95) -侧边栏: linear-gradient(180deg, #667eea 0%, #764ba2 100%) -``` - -## 🎯 设计特点 - -### 1. 渐变美学 -- 大量使用渐变色 -- 增加视觉深度 -- 现代化科技感 - -### 2. 卡片式布局 -- 信息模块化 -- 清晰的视觉分隔 -- 便于信息查找 - -### 3. 微交互设计 -- 悬停动画反馈 -- 平滑过渡效果 -- 提升交互体验 - -### 4. 专业配色 -- 紫色代表智能科技 -- 高对比度易读性 -- 统一的视觉语言 - -## 🔧 技术实现 - -### CSS特性 -- **渐变(Gradient)**:linear-gradient -- **阴影(Shadow)**:box-shadow -- **圆角(Radius)**:border-radius -- **过渡(Transition)**:transition -- **变换(Transform)**:transform -- **透明度(Opacity)**:rgba - -### 响应式设计 -```css -@media (max-width: 768px) { - /* 移动端适配 */ - .nav-title { font-size: 1.5rem; } - .stTabs [data-baseweb="tab"] { - font-size: 0.9rem; - padding: 0 1rem; - } -} -``` - -## 📱 兼容性 - -- ✅ 桌面端(1920px+) -- ✅ 笔记本(1366px+) -- ✅ 平板(768px+) -- ✅ 手机(375px+) - -## 🎨 UI组件清单 - -### 已美化组件 -- [x] 顶部导航栏 -- [x] 侧边栏 -- [x] 主标题 -- [x] 按钮 -- [x] 输入框 -- [x] 进度条 -- [x] 卡片 -- [x] 消息框 -- [x] 图表容器 -- [x] Expander -- [x] 数据框 - -### 隐藏元素 -- [x] Streamlit顶部菜单 -- [x] Streamlit底部水印 - -## 💡 使用建议 - -### 1. 保持一致性 -- 所有新增卡片使用统一样式 -- 按钮保持统一配色 -- 间距使用统一标准 - -### 2. 适度使用动画 -- 避免过度动画 -- 保持性能流畅 -- 提升而非干扰用户 - -### 3. 注意可访问性 -- 保持足够对比度 -- 文字大小适中 -- 颜色不是唯一信息来源 - -## 🚀 未来优化方向 - -- [ ] 深色模式支持 -- [ ] 主题切换功能 -- [ ] 更多动画效果 -- [ ] 自定义配色方案 -- [ ] 图标库扩展 -- [ ] 加载动画优化 - -## 📝 维护说明 - -### CSS位置 -所有CSS样式定义在 `app.py` 文件顶部的 `st.markdown()` 中。 - -### 修改指南 -1. 在CSS样式块中修改 -2. 保持选择器特异性 -3. 测试不同屏幕尺寸 -4. 保持代码注释 - -### 版本记录 -- **v2.0** (2025-10-03): 全面UI美化升级 -- **v1.0** (2025-10-02): 基础UI实现 - ---- - -**设计理念**:简洁、现代、专业、易用 - diff --git a/AGENTS.md b/docs/AGENTS.md similarity index 100% rename from AGENTS.md rename to docs/AGENTS.md diff --git a/DOCKER_DEPLOYMENT.md b/docs/DOCKER_DEPLOYMENT.md similarity index 100% rename from DOCKER_DEPLOYMENT.md rename to docs/DOCKER_DEPLOYMENT.md diff --git a/DOCKER_README.md b/docs/DOCKER_README.md similarity index 100% rename from DOCKER_README.md rename to docs/DOCKER_README.md diff --git a/docs/LONGHUBANG_BATCH_ANALYSIS.md b/docs/LONGHUBANG_BATCH_ANALYSIS.md new file mode 100644 index 0000000..5446eae --- /dev/null +++ b/docs/LONGHUBANG_BATCH_ANALYSIS.md @@ -0,0 +1,479 @@ +# 智瞰龙虎 - TOP股票批量分析功能 + +## 📖 功能概述 + +在智瞰龙虎的**AI评分排名**中,对TOP10综合评分排名的股票提供**一键批量深度分析**功能。 + +--- + +## 🚀 快速开始 + +### 1. 进入智瞰龙虎 + +1. 启动应用:`streamlit run app.py` +2. 点击侧边栏 **"🎯 智瞰龙虎"** +3. 切换到 **"🏆 AI评分排名"** 标签 +4. 点击 **"开始分析"** 获取龙虎榜数据 + +### 2. 查看TOP10评分 + +- 系统自动显示 **"🥇 TOP10 综合评分排名"** 表格 +- 评分维度:资金含金量、净买入额、卖出压力、机构共振、加分项 + +### 3. 一键批量分析 + +1. 在TOP10表格下方找到 **"🚀 批量深度分析"** 区域 +2. 选择分析数量:3只 / 5只 / 10只 +3. 点击 **"🚀 开始批量分析"** 按钮 +4. 选择分析模式: + - **顺序分析**:稳定可靠,逐个分析 + - **并行分析**:速度更快,3线程同时分析 +5. 点击 **"🚀 确认开始分析"** +6. 等待进度完成 + +### 4. 查看分析结果 + +- **统计信息**:总计、成功、失败、耗时 +- **分析详情**:每只股票的投资评级、进场区间、止盈止损 +- **快捷操作**:一键加入监测列表 + +--- + +## 🎯 使用场景 + +### 场景1:短线交易机会挖掘 + +``` +龙虎榜数据 → AI评分排名 → TOP3批量分析 → 选择买入标的 +``` + +**流程**: +1. 龙虎榜找出活跃游资关注的股票 +2. AI评分筛选最优质的3只 +3. 深度分析获取具体买卖点 +4. 加入监测,等待进场机会 + +### 场景2:游资龙头股追踪 + +``` +龙虎榜数据 → TOP5分析 → 识别龙头 → 波段操作 +``` + +**流程**: +1. 关注TOP5综合评分最高的股票 +2. 深度分析确认题材和趋势 +3. 设置监测参数 +4. 波段操作,把握涨幅 + +### 场景3:热点题材捕捉 + +``` +龙虎榜数据 → TOP10分析 → 题材对比 → 选股布局 +``` + +**流程**: +1. 分析TOP10了解全局热点 +2. 对比各股题材和资金流向 +3. 选择最强势的题材龙头 +4. 及时布局 + +--- + +## 💡 功能特性 + +### 1. 智能筛选 + +- ✅ 基于AI评分自动排名 +- ✅ 5维度综合评估(资金、机构、游资、题材、风险) +- ✅ TOP10精选,避免分析过多 + +### 2. 批量分析 + +- ✅ 支持分析前3/5/10只股票 +- ✅ 顺序/并行两种模式 +- ✅ 实时进度显示 +- ✅ **遵循统一调用规范** ⭐️ + +### 3. 统一结果 + +- ✅ 使用 `app.analyze_single_stock_for_batch()` 统一函数 +- ✅ 统一字段名:`rating`, `confidence_level`, `entry_range`等 +- ✅ 统一展示格式:emoji、布局、信息 + +### 4. 快捷操作 + +- ✅ 一键加入监测列表 +- ✅ 自动解析进场区间和止盈止损 +- ✅ 直接开启实时监控 + +--- + +## 📊 分析维度 + +### 龙虎榜评分(初筛) + +| 维度 | 权重 | 说明 | +|------|------|------| +| 资金含金量 | 30分 | 顶级游资数量和质量 | +| 净买入额 | 25分 | 买入金额vs卖出金额 | +| 卖出压力 | 20分 | 卖方力量(反向) | +| 机构共振 | 15分 | 机构参与度 | +| 加分项 | 10分 | 连续上榜、题材等 | + +### AI深度分析(精选) + +| 维度 | 分析师 | 内容 | +|------|--------|------| +| 技术面 | 技术分析师 | K线、指标、趋势 | +| 基本面 | 基本面分析师 | 财务、估值、成长 | +| 资金面 | 资金流向分析师 | 主力资金、北向资金 | +| 风险面 | 风险管理师 | 风险提示、止损策略 | +| 综合决策 | 团队讨论 | 投资评级、关键价位 | + +--- + +## 🎯 推荐策略 + +### 保守策略(TOP3) + +- **分析数量**:3只 +- **分析模式**:顺序分析 +- **选股原则**:只选评分最高的3只 +- **操作策略**:深度分析后选1-2只重点关注 + +### 积极策略(TOP5) + +- **分析数量**:5只 +- **分析模式**:并行分析(3线程) +- **选股原则**:评分靠前的5只 +- **操作策略**:分散持仓,捕捉多个机会 + +### 全面策略(TOP10) + +- **分析数量**:10只 +- **分析模式**:并行分析(5线程) +- **选股原则**:全面了解热点 +- **操作策略**:对比分析,精选2-3只最优 + +--- + +## ⚙️ 技术实现 + +### 遵循统一调用规范 ⭐️ + +**严格遵循 `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. 最佳分析时机 + +- **盘后17:00-20:00**:龙虎榜数据更新后 +- **次日盘前09:00-09:25**:开盘前复盘 + +### 2. 合理选择数量 + +- **3只**:精选,深度研究 +- **5只**:平衡,适合大多数场景 +- **10只**:全面,了解整体热点 + +### 3. 结合龙虎榜信息 + +分析时重点关注: +- 顶级游资参与情况 +- 机构是否共振 +- 净流入金额大小 +- 题材是否持续 + +### 4. 快速加入监测 + +- 分析完成后直接点击"加入监测" +- 自动设置进场区间和止盈止损 +- 实时监测价格变化 +- 及时把握买卖时机 + +--- + +## 📈 预期效果 + +### 输入 +- 龙虎榜TOP10股票代码 + +### 输出 +- 每只股票的完整AI分析: + - 投资评级(买入/持有/卖出) + - 信心度评分 + - 进场区间 + - 止盈位 + - 止损位 + - 目标价 + - 投资建议 + +### 后续操作 +- 选择优质标的加入监测 +- 设置价格告警 +- 等待买卖信号 +- 严格执行止盈止损 + +--- + +## 🆚 功能对比 + +| 功能 | 龙虎榜评分 | AI深度分析 | +|------|-----------|-----------| +| **速度** | 秒级 | 分钟级 | +| **深度** | 资金维度 | 多维度综合 | +| **精度** | 初筛排名 | 精准评级 | +| **关键价位** | 无 | 有(进场/止盈/止损) | +| **适用** | 快速筛选 | 决策依据 | + +**建议组合使用**: +1. 龙虎榜评分快速筛选TOP10 +2. AI深度分析精选3-5只 +3. 加入监测等待买点 +4. 严格执行交易纪律 + +--- + +## 🔧 常见问题 + +### Q1: 批量分析需要多久? + +**A:** 取决于数量和模式: +- 3只顺序:约3-5分钟 +- 5只并行(3线程):约4-6分钟 +- 10只并行(3线程):约8-12分钟 + +### Q2: 所有TOP10都值得分析吗? + +**A:** 不一定,建议: +- 评分>80分:优先分析 +- 评分60-80分:选择性分析 +- 评分<60分:谨慎,可能风险较高 + +### Q3: 批量分析后如何选股? + +**A:** 参考以下标准: +1. 投资评级:优选"买入"或"强烈买入" +2. 信心度:≥7分 +3. 龙虎榜评分:≥75分 +4. 资金面:净流入为正 +5. 风险面:无重大风险提示 + +### Q4: 分析失败怎么办? + +**A:** 可能原因和解决: +- 股票代码错误:检查代码格式 +- 网络问题:重试或切换顺序模式 +- API限流:减少并发线程数 +- 数据缺失:该股可能暂停交易 + +### Q5: 能否保存分析结果? + +**A:** 当前版本: +- 结果保存在session_state(关闭页面会丢失) +- 可点击"加入监测"保存关键参数 +- 未来版本将支持保存到数据库 + +--- + +## 🎊 更新内容 + +### 新增功能 +- ✅ TOP10股票一键批量分析 +- ✅ 可选分析数量(3/5/10只) +- ✅ 顺序/并行分析模式 +- ✅ 实时进度显示 +- ✅ 详细结果展示 +- ✅ 一键加入监测 + +### 技术亮点 +- ✅ **遵循统一调用规范**:调用 `app.analyze_single_stock_for_batch()` +- ✅ **统一字段使用**:`rating`, `confidence_level`, `entry_range`等 +- ✅ **统一解析逻辑**:字符串数值提取标准化 +- ✅ **代码复用最大化**:无重复分析逻辑 +- ✅ **结果展示一致**:与首页批量分析保持一致 + +--- + +## 📝 完整流程示例 + +### 实战案例:捕捉龙虎榜短线机会 + +#### Step 1: 获取龙虎榜数据(17:00后) +``` +智瞰龙虎 → 龙虎榜分析 → 指定日期(昨日) +→ 开始分析 → 等待AI分析完成 +``` + +#### Step 2: 查看AI评分排名 +``` +切换到"AI评分排名"标签 +→ 查看TOP10综合评分表格 +→ 关注评分>80分的股票 +``` + +#### Step 3: 批量深度分析 +``` +选择分析数量:5只 +→ 点击"开始批量分析" +→ 选择并行分析(3线程) +→ 确认开始分析 +→ 等待5-8分钟完成 +``` + +#### Step 4: 筛选优质标的 +``` +查看分析结果 +→ 筛选条件: + - 评级:买入/强烈买入 + - 信心度:≥7分 + - 龙虎榜评分:≥75分 +→ 精选2-3只重点关注 +``` + +#### Step 5: 加入监测 +``` +对精选股票点击"➕ 加入监测" +→ 自动设置进场区间、止盈止损 +→ 启动实时价格监控 +``` + +#### Step 6: 执行交易 +``` +次日开盘 +→ 监测提醒进入进场区间 +→ 买入建仓 +→ 监测提醒到达止盈位 +→ 卖出止盈 +``` + +--- + +## 🎯 最佳实践 + +### 1. 评分+分析双重验证 + +- 不仅看龙虎榜评分,更要看AI深度分析 +- 评分高但AI评级"卖出"→ 谨慎 +- 评分中等但AI评级"买入"→ 关注 + +### 2. 时间把握 + +- **最佳分析时间**:盘后18:00-20:00 +- **最佳买入时间**:次日09:30-10:00 +- **最佳止盈时间**:触发止盈位立即卖出 + +### 3. 风险控制 + +- 严格遵守AI给出的止损位 +- 不因短期波动随意调整 +- 仓位控制:单只≤总资金20% +- 龙虎榜股票波动大,轻仓操作 + +### 4. 组合策略 + +- 不要全仓单只龙虎榜股票 +- TOP3选1-2只重点 +- 分散题材,降低风险 +- 部分仓位留作机动 + +--- + +## ⚠️ 风险提示 + +### 龙虎榜股票特点 + +- ⚡ **高波动**:涨跌幅度大 +- 🎢 **短周期**:题材炒作时间短 +- 📉 **高风险**:游资出货快 +- 🎲 **不确定性**:情绪主导 + +### 注意事项 + +1. **不追高**:进场区间外不盲目追涨 +2. **严守止损**:达到止损位立即离场 +3. **快进快出**:龙虎榜股票不适合长期持有 +4. **控制仓位**:单只不超过20% +5. **关注题材**:题材降温及时止盈 + +--- + +## 📚 相关文档 + +- **统一规范**:`docs/UNIFIED_ANALYSIS_SPEC.md` - 分析调用规范 +- **龙虎榜功能**:README.md - 智瞰龙虎系统介绍 +- **监测功能**:首页实时监测文档 + +--- + +## 🎊 总结 + +**智瞰龙虎TOP股票批量分析功能**为您提供: + +✅ 龙虎榜AI评分快速筛选 +✅ 深度分析精准定位买卖点 +✅ 一键加入监测实时跟踪 +✅ 完整的短线交易解决方案 + +**祝您抓住每一个龙虎榜机会!** 🎯📈 + diff --git a/MINIQMT_INTEGRATION_GUIDE.md b/docs/MINIQMT_INTEGRATION_GUIDE.md similarity index 100% rename from MINIQMT_INTEGRATION_GUIDE.md rename to docs/MINIQMT_INTEGRATION_GUIDE.md diff --git a/docs/MULTI_SCHEDULE_GUIDE.md b/docs/MULTI_SCHEDULE_GUIDE.md new file mode 100644 index 0000000..dd2f6f0 --- /dev/null +++ b/docs/MULTI_SCHEDULE_GUIDE.md @@ -0,0 +1,512 @@ +# 多定时分析功能使用指南 + +## 📖 功能概述 + +多定时分析功能允许您设置**多个每日分析时间点**,系统将在每个时间点自动执行持仓批量分析。 + +### 使用场景 + +- ✅ **盘前+盘后分析**:开盘前09:25 + 收盘后15:05 +- ✅ **多频次监控**:早盘09:30 + 午盘13:00 + 尾盘14:50 +- ✅ **分时段策略**:早中晚三次分析,捕捉不同时段机会 +- ✅ **灵活配置**:根据个人作息和交易习惯自定义 + +--- + +## 🚀 快速开始 + +### 1. 进入定时任务管理 + +1. 启动应用:`streamlit run app.py` +2. 点击侧边栏 **"📊 持仓分析"** +3. 切换到 **"⏰ 定时任务"** 标签 + +### 2. 添加定时时间 + +1. 点击 **"➕ 添加定时时间"** 展开表单 +2. 选择新的分析时间(如 15:05) +3. 点击 **"➕ 添加"** 按钮 +4. 系统自动按时间顺序排列 + +### 3. 删除定时时间 + +1. 在已配置时间列表中找到要删除的时间 +2. 点击该时间旁边的 **"🗑️"** 按钮 +3. 确认删除(至少保留1个时间) + +### 4. 启动调度器 + +1. 配置好所有定时时间后 +2. 点击 **"▶️ 启动调度器"** +3. 系统将在每个时间点自动执行分析 + +--- + +## 💡 推荐配置 + +### 方案1:盘前+盘后(保守) + +适合:长期投资者、上班族 + +``` +09:25 盘前分析(开盘前5分钟,提前布局) +15:05 盘后分析(收盘后5分钟,复盘总结) +``` + +**优点**: +- 信息全面:盘前看预期,盘后看实际 +- 不影响工作:两个时间点不频繁 +- 数据准确:收盘数据完整可靠 + +### 方案2:三时段监控(积极) + +适合:活跃投资者、短线交易者 + +``` +09:30 早盘分析(开盘后观察) +13:05 午盘分析(午盘结束,调整策略) +15:05 尾盘分析(收盘总结) +``` + +**优点**: +- 全天跟踪:早中晚全覆盖 +- 及时调整:发现异常快速反应 +- 捕捉机会:不同时段的交易机会 + +### 方案3:高频监测(专业) + +适合:专业交易者、日内交易 + +``` +09:25 盘前预测 +10:00 早盘检查 +11:30 午前评估 +13:05 午盘分析 +14:30 尾盘准备 +15:05 收盘复盘 +``` + +**优点**: +- 极致监控:全天候跟踪 +- 快速响应:及时发现变化 +- 数据密集:积累更多历史 + +**注意**:高频分析消耗更多API调用,确保额度充足 + +### 方案4:夜盘准备(港美股) + +适合:投资港股美股的用户 + +``` +08:30 早间准备 +21:00 美股开盘前(夏令时20:30,冬令时21:30) +``` + +**优点**: +- 跨市场:覆盖A股和美股 +- 错峰分析:避开A股交易时段 + +--- + +## ⚙️ 功能详解 + +### 添加定时时间 + +**步骤**: +1. 展开 "➕ 添加定时时间" +2. 使用时间选择器选择时间 +3. 点击"➕ 添加" + +**规则**: +- 时间格式:24小时制(00:00 - 23:59) +- 自动去重:相同时间不会重复添加 +- 自动排序:按时间从早到晚排列 +- 即时生效:调度器运行中添加会立即生效 + +### 删除定时时间 + +**步骤**: +1. 找到要删除的时间卡片 +2. 点击 "🗑️" 按钮 + +**规则**: +- 最少保留:至少保留1个时间点 +- 即时生效:调度器运行中删除会立即生效 +- 不可恢复:删除后需重新添加 + +### 查看定时状态 + +**状态栏显示**: +- 🟢 调度器运行中 / 🔴 调度器已停止 +- ⏰ 定时数量:X个 +- ⏭️ 下次运行:最近的运行时间 + +**时间列表**: +- 以卡片形式展示所有时间点 +- 每行显示4个时间 +- 自动按时间排序 + +--- + +## 🎯 最佳实践 + +### 1. 合理设置时间点 + +**推荐**: +- 2-3个时间点最佳(盘前、盘后) +- 避免设置过多(>5个)导致频繁分析 + +**避免**: +- 不要在交易时段频繁分析(影响关注盘面) +- 不要在凌晨设置(数据未更新) + +### 2. 考虑数据更新时间 + +| 数据类型 | 更新时间 | 建议分析时间 | +|---------|---------|-------------| +| 实时行情 | 交易时段 | 盘中任意时间 | +| 日K线 | 15:00后 | 15:05后 | +| 龙虎榜 | 次日8:00 | 次日9:00后 | +| 财务报表 | 季度发布 | 任意时间 | +| 主力资金 | 实时更新 | 盘中/盘后 | + +### 3. 平衡分析频次与API消耗 + +**API消耗估算**: +- 每只股票分析约需 6-10 次API调用 +- 10只持仓,3个时间点 = 每日180-300次调用 +- 确保DeepSeek API额度充足 + +**优化建议**: +- 减少定时点数量 +- 选择顺序分析(避免并发限流) +- 仅在关键时间点执行 + +### 4. 调试和监控 + +**查看运行日志**: +- 终端输出会显示每次执行的详细日志 +- 包括开始时间、完成时间、成功/失败统计 + +**下次运行时间**: +- 状态栏显示最近的运行时间 +- 启动调度器后自动计算 + +**历史记录**: +- 每次分析都会保存到数据库 +- 切换到"分析历史"标签查看 + +--- + +## 🔧 配置示例 + +### 示例1:稳健投资者 + +``` +定时时间: + - 09:25 (盘前) + - 15:05 (盘后) + +分析配置: + - 模式:顺序分析 + - 自动监测:开启 + - 通知推送:开启 +``` + +### 示例2:短线交易者 + +``` +定时时间: + - 09:30 (开盘) + - 11:30 (午前) + - 13:05 (午后) + - 14:50 (尾盘) + +分析配置: + - 模式:并行分析(3线程) + - 自动监测:开启 + - 通知推送:开启 +``` + +### 示例3:港美股投资者 + +``` +定时时间: + - 08:30 (A股准备) + - 15:05 (A股复盘) + - 21:00 (美股准备) + +分析配置: + - 模式:并行分析(5线程) + - 自动监测:开启 + - 通知推送:开启(Webhook实时推送) +``` + +--- + +## 💻 技术实现 + +### 数据结构 + +```python +# 原来:单个时间字符串 +self.schedule_time = "09:30" + +# 现在:时间列表 +self.schedule_times = ["09:30", "15:05", "20:00"] +``` + +### 调度逻辑 + +```python +# 为每个时间点创建独立的调度任务 +for time_str in self.schedule_times: + schedule.every().day.at(time_str).do(self._scheduled_job) +``` + +### 向后兼容 + +```python +# schedule_time属性仍可用(返回第一个时间) +@property +def schedule_time(self) -> str: + return self.schedule_times[0] if self.schedule_times else "09:30" +``` + +--- + +## 🐛 常见问题 + +### Q1: 最多可以设置几个定时时间? + +**A:** 理论上无限制,但建议不超过5个。原因: +- API调用量成倍增长 +- 分析结果变化过于频繁 +- 用户难以处理过多通知 + +### Q2: 多个时间点是否共享配置? + +**A:** 是的,所有时间点共享相同的分析配置: +- 分析模式(顺序/并行) +- 自动监测同步开关 +- 通知推送开关 +- 分析师选择 + +如需不同配置,建议使用其他定时工具(如系统cron)。 + +### Q3: 两个时间间隔太近会怎样? + +**A:** 建议间隔至少30分钟,避免: +- 数据未更新,分析结果重复 +- 上次分析未完成,新任务已触发 +- API调用过于频繁 + +### Q4: 调度器重启后时间配置会丢失吗? + +**A:** 当前配置存储在内存中,重启会丢失。建议: +- 未来版本将支持配置持久化 +- 当前可在代码中设置默认时间 + +### Q5: 如何查看某个时间点的执行历史? + +**A:** 切换到"分析历史"标签,按时间筛选: +- 查看 `analysis_time` 字段 +- 筛选特定时间段的记录 +- 对比不同时间点的分析结果 + +--- + +## 📊 性能影响 + +### API调用量 + +| 持仓数 | 1个时间点 | 2个时间点 | 3个时间点 | +|-------|---------|----------|----------| +| 5只 | 30-50次/天 | 60-100次/天 | 90-150次/天 | +| 10只 | 60-100次/天 | 120-200次/天 | 180-300次/天 | +| 20只 | 120-200次/天 | 240-400次/天 | 360-600次/天 | + +### 执行时间 + +- 不同时间点独立执行,互不影响 +- 并行模式可缩短单次执行时间 +- 确保两次分析之间有足够间隔 + +--- + +## 🎊 更新内容 + +### 新增功能 +- ✅ 支持添加多个定时时间点 +- ✅ 支持删除定时时间点 +- ✅ 自动按时间排序 +- ✅ 重复检测和防护 +- ✅ 即时生效(运行中修改) + +### UI改进 +- ✅ 卡片式时间展示(4个一行) +- ✅ 快速添加时间选择器 +- ✅ 一键删除按钮 +- ✅ 定时数量统计 + +### 技术优化 +- ✅ 向后兼容旧代码 +- ✅ schedule库多任务调度 +- ✅ 自动重新调度机制 + +--- + +## 🔄 升级指南 + +### 从单定时升级到多定时 + +**原有配置保持不变**: +- 原来设置的时间会作为第一个时间点 +- `schedule_time` 属性仍可用(返回第一个时间) +- 不需要修改任何配置即可使用 + +**开始使用多定时**: +1. 进入定时任务管理界面 +2. 查看当前配置的时间(默认09:30) +3. 点击"添加定时时间"添加新时间 +4. 重启调度器生效 + +--- + +## 📝 示例:配置盘前+盘后分析 + +### 步骤演示 + +1. **进入界面** + - 侧边栏 → "📊 持仓分析" → "⏰ 定时任务" + +2. **查看默认配置** + - 已配置:09:30(默认) + +3. **添加收盘后分析** + - 展开"➕ 添加定时时间" + - 选择时间:15:05 + - 点击"➕ 添加" + - 确认添加成功 + +4. **配置分析选项** + - 分析模式:并行分析 + - 并行线程数:3 + - 自动同步到监测:✓ + - 发送完成通知:✓ + - 点击"💾 更新配置" + +5. **启动调度器** + - 点击"▶️ 启动调度器" + - 确认状态:🟢 调度器运行中 + - 查看下次运行时间 + +6. **验证配置** + - 定时数量:2个 + - 时间列表:09:30, 15:05 + - 等待到达时间,自动执行 + +### 预期结果 + +- **每天09:25**:自动分析持仓 → 同步监测 → 发送通知 +- **每天15:05**:自动分析持仓 → 同步监测 → 发送通知 +- **分析历史**:每天新增2条记录/只股票 +- **邮件/Webhook**:每天收到2次通知 + +--- + +## 🎯 进阶技巧 + +### 1. 错峰分析 + +避开市场高峰时段: +``` +09:35 开盘5分钟后(数据稳定) +13:10 午盘10分钟后(避开高峰) +15:10 收盘10分钟后(避开拥堵) +``` + +### 2. 分级监控 + +不同时段不同策略: +- **早盘**:关注开盘价,捕捉缺口 +- **午盘**:评估上午表现,调整仓位 +- **收盘**:总结全天,规划次日 + +### 3. 结合实时监测 + +- 定时分析更新监测参数 +- 实时监测触发价格通知 +- 形成"定时评估+实时监控"闭环 + +--- + +## 🆘 故障排查 + +### 问题1:添加时间失败 + +**可能原因**: +- 时间已存在 +- 时间格式错误 + +**解决**: +- 检查时间列表是否已有该时间 +- 使用时间选择器而非手动输入 + +### 问题2:删除时间失败 + +**可能原因**: +- 只剩1个时间(不允许全部删除) + +**解决**: +- 先添加新时间再删除旧时间 +- 至少保留1个定时配置 + +### 问题3:某个时间点未执行 + +**可能原因**: +- 系统时间不准确 +- 应用已关闭 +- 上次分析未完成 + +**解决**: +- 检查系统时间 +- 保持应用运行(使用后台模式) +- 增大时间间隔 + +### 问题4:调度器重启后配置丢失 + +**当前限制**: +- 配置存储在内存中 +- 应用重启会丢失 + +**临时方案**: +- 记录时间配置 +- 重启后重新添加 + +**未来计划**: +- 配置持久化到数据库 +- 自动恢复上次配置 + +--- + +## 📚 相关文档 + +- **主要文档**:`docs/PORTFOLIO_USAGE.md` - 完整使用指南 +- **统一规范**:`docs/UNIFIED_ANALYSIS_SPEC.md` - 分析调用规范 +- **技术设计**:`openspec/changes/add-portfolio-scheduled-analysis/design.md` + +--- + +## 🎉 总结 + +多定时分析功能让您可以: + +✅ 灵活设置多个每日分析时间 +✅ 全天候跟踪持仓变化 +✅ 捕捉不同时段的交易机会 +✅ 自动执行,无需人工干预 +✅ 即时生效,动态调整 + +**祝您投资顺利!** 📈 + diff --git a/docs/PORTFOLIO_USAGE.md b/docs/PORTFOLIO_USAGE.md new file mode 100644 index 0000000..62421e0 --- /dev/null +++ b/docs/PORTFOLIO_USAGE.md @@ -0,0 +1,398 @@ +# 持仓定时分析功能 - 使用指南 + +## 📖 功能概述 + +持仓定时分析功能允许您: +- **管理持仓股票**:添加、编辑、删除持仓股票清单 +- **批量分析**:一次性分析所有持仓股票的投资价值 +- **定时任务**:设置每日自动分析时间 +- **自动监测**:分析结果自动同步到实时监测列表 +- **通知提醒**:邮件或Webhook接收分析完成通知 +- **历史记录**:查看每只股票的历史分析结果 + +--- + +## 🚀 快速开始 + +### 1. 安装依赖 + +确保已安装以下依赖包: + +```bash +pip install schedule +pip install openai # DeepSeek API使用 +``` + +### 2. 访问功能 + +1. 启动应用:`streamlit run app.py` +2. 点击侧边栏**"📊 持仓分析"**按钮 +3. 进入持仓管理界面 + +--- + +## 📝 功能详解 + +### 一、持仓管理 + +#### 添加股票 + +1. 点击**"➕ 添加持仓股票"**展开表单 +2. 填写股票信息: + - **股票代码**(必填):例如 `600519.SH`、`000001.SZ`、`AAPL` + - **股票名称**(可选):例如 `贵州茅台`,留空自动获取 + - **成本价**(可选):您的买入价格 + - **持仓数量**(可选):持仓股数 + - **备注**:记录买入理由等信息 + - **自动同步监测**:勾选后分析结果自动加入监测列表 +3. 点击**"➕ 添加股票"**完成 + +#### 编辑股票 + +1. 在股票卡片中点击**"✏️"**按钮 +2. 修改信息后点击**"保存"** +3. 或点击**"取消"**放弃修改 + +#### 删除股票 + +1. 在股票卡片中点击**"🗑️"**按钮 +2. 确认删除(不可恢复) + +--- + +### 二、批量分析 + +#### 立即分析 + +1. 切换到**"🔄 批量分析"**标签 +2. 选择分析模式: + - **顺序分析**:逐个分析,稳定但较慢 + - **并行分析**:同时分析多只,速度快但消耗资源更多 +3. 设置选项: + - **自动同步到监测**:勾选后自动将评级结果添加到监测列表 + - **发送完成通知**:勾选后通过邮件/Webhook发送结果 +4. 点击**"🚀 立即开始分析"** +5. 等待进度完成,查看分析结果 + +#### 分析结果 + +- **概况统计**:总数、成功、失败、耗时 +- **详细结果**:每只股票的评级、进场区间、止盈止损位 +- **颜色标识**: + - 🟢 绿色:买入推荐 + - 🟡 黄色:持有观望 + - 🔴 红色:卖出建议 + +--- + +### 三、定时任务 + +#### 配置定时分析 + +1. 切换到**"⏰ 定时任务"**标签 +2. **配置定时时间**(支持多个时间点): + - 查看当前已配置的时间列表 + - 点击**"➕ 添加定时时间"**添加新时间点(如 09:30, 15:05) + - 点击时间旁的**"🗑️"**按钮删除不需要的时间 + - 系统自动按时间排序,支持2-5个时间点 +3. **配置分析选项**: + - **分析模式**:顺序或并行 + - **并行线程数**:并行模式下的同时分析数量(2-10) + - **自动同步到监测**:是否自动将结果同步到监测列表 + - **发送完成通知**:是否发送通知 +4. 点击**"💾 更新配置"**保存 + +**推荐配置**: +- 保守型:09:25(盘前) + 15:05(盘后) +- 积极型:09:30(开盘) + 13:05(午盘) + 15:05(收盘) +- 详见:[`docs/MULTI_SCHEDULE_GUIDE.md`](MULTI_SCHEDULE_GUIDE.md) + +#### 启动/停止调度器 + +- **启动**:点击**"▶️ 启动调度器"**,系统将在每日设定时间自动执行分析 +- **停止**:点击**"⏹️ 停止调度器"**,取消自动执行 +- **立即执行**:点击**"🚀 立即执行一次"**,无需等待定时触发 + +#### 查看状态 + +- **🟢 运行中**:调度器已启动 +- **🔴 已停止**:调度器未运行 +- **⏰ 分析时间**:设定的每日执行时间 +- **⏭️ 下次运行**:下次预计执行的时间 + +--- + +### 四、分析历史 + +#### 查看历史记录 + +1. 切换到**"📈 分析历史"**标签 +2. 选择股票: + - **全部**:显示所有股票的最新20条记录 + - **指定股票**:显示该股票的最近20次分析 +3. 展开记录查看详情: + - 投资评级和置信度 + - 进场区间、目标价 + - 止盈位、止损位 + - 分析摘要 + +#### 历史数据用途 + +- **对比变化**:观察评级和位置的变化趋势 +- **验证准确性**:对比历史预测与实际走势 +- **优化策略**:根据历史数据调整投资决策 + +--- + +## ⚙️ 高级配置 + +### 通知配置 + +#### 邮件通知 + +1. 进入**"⚙️ 环境配置"** +2. 配置SMTP信息: + ``` + EMAIL_ENABLED=true + SMTP_SERVER=smtp.example.com + SMTP_PORT=587 + EMAIL_FROM=your@email.com + EMAIL_PASSWORD=your_password + EMAIL_TO=receiver@email.com + ``` + +#### Webhook通知(钉钉/飞书) + +1. 获取群机器人Webhook URL +2. 配置环境变量: + ``` + WEBHOOK_ENABLED=true + WEBHOOK_URL=https://your_webhook_url + WEBHOOK_TYPE=dingtalk # 或 feishu + WEBHOOK_KEYWORD=aiagents通知 + ``` + +### 数据存储 + +- **持仓数据库**:`portfolio_stocks.db` + - 持仓股票信息 + - 分析历史记录 +- **监测数据库**:`stock_monitor.db` + - 自动同步的监测股票 + +### 数据备份 + +建议定期备份数据库文件: + +```bash +# Windows +copy portfolio_stocks.db portfolio_stocks_backup_%date%.db + +# Linux/Mac +cp portfolio_stocks.db portfolio_stocks_backup_$(date +%Y%m%d).db +``` + +--- + +## 💡 使用技巧 + +### 1. 合理配置持仓清单 + +- **精选股票**:只添加重点关注的股票(建议10-30只) +- **完善信息**:填写成本价和数量,便于计算收益 +- **详细备注**:记录买入理由、目标价等,便于复盘 + +### 2. 选择合适的分析模式 + +- **顺序分析**: + - ✅ 稳定可靠,API调用有序 + - ✅ 适合API限速严格的场景 + - ❌ 速度较慢,10只股票约需10-15分钟 + +- **并行分析**: + - ✅ 速度快,3线程约5-8分钟完成10只 + - ✅ 适合持仓较多的用户 + - ❌ 对API并发要求高,可能触发限速 + +**建议**: +- 持仓≤10只:顺序分析 +- 持仓>10只:并行分析(3-5线程) + +### 3. 设置合理的定时时间 + +推荐定时配置: +- **开盘后分析**:`09:35`(开盘5分钟后,数据稳定) +- **午盘后分析**:`13:05`(午盘结束后,评估上午走势) +- **收盘后分析**:`15:05`(收盘后,获取完整日数据) + +### 4. 充分利用自动监测 + +- 开启**"自动同步到监测"** +- 分析后自动将进场区间、止盈止损同步到监测列表 +- 实时监测模块会在触发关键位置时发送通知 +- 减少手动操作,不遗漏交易机会 + +### 5. 定期查看历史记录 + +- 每周查看一次历史分析 +- 对比预测与实际走势 +- 总结规律,优化投资策略 + +--- + +## 🔧 常见问题 + +### Q1: 添加股票后为什么无法分析? + +**A:** 检查以下几点: +1. DeepSeek API Key是否配置正确 +2. 股票代码格式是否正确(A股需要加`.SH`或`.SZ`后缀) +3. 网络连接是否正常 +4. API额度是否充足 + +### Q2: 定时任务为什么没有执行? + +**A:** 可能原因: +1. 调度器未启动(检查是否显示🟢运行中) +2. 应用已关闭(调度器需要应用持续运行) +3. 时间配置错误(检查系统时间和设定时间) + +**解决方案**: +- 保持Streamlit应用运行(可使用`nohup`或`screen`后台运行) +- 或使用外部定时工具(如系统cron)调用分析接口 + +### Q3: 批量分析失败率高怎么办? + +**A:** 优化建议: +1. 切换到顺序分析模式 +2. 减少并行线程数 +3. 检查网络稳定性 +4. 确认股票代码有效性 +5. 查看错误日志定位具体问题 + +### Q4: 如何导出分析结果? + +**A:** 方法: +1. 通过邮件/Webhook接收结果 +2. 直接查询`portfolio_stocks.db`数据库: + ```sql + SELECT * FROM portfolio_analysis_history ORDER BY analysis_time DESC; + ``` +3. 未来版本将支持CSV/Excel导出功能 + +### Q5: 监测同步不生效? + +**A:** 检查: +1. 持仓股票是否勾选**"自动监测"** +2. 分析是否成功(失败的分析不会同步) +3. 分析结果是否包含进场区间和止盈止损(缺失则跳过) +4. 查看日志确认同步结果 + +--- + +## 📊 性能参数 + +### 分析速度参考 + +| 持仓数量 | 顺序分析 | 并行(3线程) | 并行(5线程) | +|---------|----------|------------|------------| +| 5只 | 5-8分钟 | 3-4分钟 | 2-3分钟 | +| 10只 | 10-15分钟| 6-8分钟 | 4-5分钟 | +| 20只 | 20-30分钟| 12-15分钟 | 8-10分钟 | +| 30只 | 30-45分钟| 18-22分钟 | 12-15分钟 | + +*实际速度取决于网络环境和API响应时间* + +### 资源消耗 + +- **内存**:基础50MB + 每线程10MB +- **CPU**:单线程<5%,多线程<20% +- **网络**:每只股票约2-5MB数据传输 +- **数据库**:每条历史记录约1-2KB + +--- + +## 🎯 最佳实践 + +### 日常使用流程 + +1. **早晨**(开盘前) + - 查看昨日定时分析结果(如有) + - 检查监测列表的通知 + - 决定今日交易计划 + +2. **盘中**(交易时段) + - 关注监测列表的实时提醒 + - 根据进场区间执行买入 + - 按照止盈止损位纪律卖出 + +3. **收盘后** + - 回顾今日交易 + - 更新持仓信息(新增/删除股票) + - 查看分析历史,总结经验 + +### 长期投资策略 + +1. **建立核心持仓清单**(10-20只优质股票) +2. **设置定时分析**(每日收盘后15:05) +3. **开启自动监测同步** +4. **每周回顾**历史记录,调整持仓 +5. **每月总结**投资表现,优化策略 + +### 短线交易策略 + +1. **快速轮换持仓清单**(5-10只热门股) +2. **多次盘中分析**(开盘后、午盘后) +3. **立即同步监测**,捕捉买卖点 +4. **及时更新持仓**(止盈/止损后删除) +5. **灵活调整**,跟随市场热点 + +--- + +## 📞 技术支持 + +### 获取帮助 + +- **查看日志**:检查终端输出的`[OK]`/`[ERROR]`消息 +- **数据库工具**:使用SQLite Browser查看数据库 +- **问题反馈**:在项目GitHub提交Issue + +### 调试模式 + +开启详细日志: + +```python +# portfolio_scheduler.py 顶部添加 +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +--- + +## 📝 更新日志 + +### v1.0.0 (2024-01) + +- ✅ 持仓股票管理(增删改查) +- ✅ 批量分析功能(顺序/并行模式) +- ✅ 定时任务调度器 +- ✅ 自动监测同步 +- ✅ 邮件/Webhook通知 +- ✅ 分析历史记录 + +### 未来计划 + +- [ ] 收益率统计和可视化 +- [ ] CSV/Excel导入导出 +- [ ] 自定义分析指标 +- [ ] 组合优化建议 +- [ ] 风险敞口分析 + +--- + +## 🙏 感谢使用 + +持仓定时分析功能旨在帮助您更高效地管理投资组合,做出明智的投资决策。 + +**祝您投资顺利!📈** + diff --git a/QUICK_START.md b/docs/QUICK_START.md similarity index 100% rename from QUICK_START.md rename to docs/QUICK_START.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..716670b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,167 @@ +# 📚 文档中心 + +本目录包含复合多AI智能体股票团队分析系统的所有文档。 + +## 📖 文档导航 + +### 🌟 核心文档 + +| 文档名称 | 说明 | +|---------|------| +| [PORTFOLIO_USAGE.md](PORTFOLIO_USAGE.md) | 持仓定时分析完整使用指南 ⭐️ | +| [MULTI_SCHEDULE_GUIDE.md](MULTI_SCHEDULE_GUIDE.md) | 多定时配置指南 | +| [UNIFIED_ANALYSIS_SPEC.md](UNIFIED_ANALYSIS_SPEC.md) | 统一股票分析调用规范 ⭐️ | +| [UPDATE_LOG.md](UPDATE_LOG.md) | 功能更新日志 | + +### 🎯 智策板块文档 + +| 文档名称 | 说明 | +|---------|------| +| [智策功能总览.md](智策功能总览.md) | 智策系统全景介绍 | +| [智策板块使用指南.md](智策板块使用指南.md) | 完整使用教程 | +| [智策板块快速开始.md](智策板块快速开始.md) | 5分钟快速上手 | +| [智策定时分析使用指南.md](智策定时分析使用指南.md) | 定时分析详细指南 | +| [智策定时分析功能完成说明.md](智策定时分析功能完成说明.md) | 功能详细说明 | +| [智策定时分析部署清单.md](智策定时分析部署清单.md) | 部署步骤和故障排查 | +| [智策定时分析更新日志.md](智策定时分析更新日志.md) | 版本历史 | + +### 🎯 智瞰龙虎文档 + +| 文档名称 | 说明 | +|---------|------| +| [LONGHUBANG_BATCH_ANALYSIS.md](LONGHUBANG_BATCH_ANALYSIS.md) | 批量分析使用指南 ⭐️ | +| [智瞰龙虎功能说明.md](智瞰龙虎功能说明.md) | 功能详细说明 | +| [智瞰龙虎快速开始.md](智瞰龙虎快速开始.md) | 快速上手指南 | +| [智瞰龙虎AI评分说明.md](智瞰龙虎AI评分说明.md) | AI评分机制说明 | +| [智瞰龙虎历史报告功能说明.md](智瞰龙虎历史报告功能说明.md) | 历史报告功能 | +| [智瞰龙虎评分排名历史记录说明.md](智瞰龙虎评分排名历史记录说明.md) | 评分排名历史 | + +### 📢 通知系统文档 + +| 文档名称 | 说明 | +|---------|------| +| [Webhook通知配置指南.md](Webhook通知配置指南.md) | Webhook完整配置教程 | +| [Webhook功能完成说明.md](Webhook功能完成说明.md) | Webhook功能详细说明 | +| [Webhook功能实现总结.md](Webhook功能实现总结.md) | Webhook功能实现总结 | +| [Webhook自定义关键词功能说明.md](Webhook自定义关键词功能说明.md) | 关键词配置指南 | +| [Webhook钉钉关键词配置更新说明.md](Webhook钉钉关键词配置更新说明.md) | 钉钉关键词配置 | +| [Webhook钉钉关键词快速配置指南.md](Webhook钉钉关键词快速配置指南.md) | 钉钉快速配置 | +| [邮件配置指南.md](邮件配置指南.md) | 邮件通知配置 | + +### 🐳 部署文档 + +| 文档名称 | 说明 | +|---------|------| +| [DOCKER_DEPLOYMENT.md](DOCKER_DEPLOYMENT.md) | Docker部署详细文档 | +| [DOCKER_README.md](DOCKER_README.md) | Docker部署说明 | +| [QUICK_START.md](QUICK_START.md) | 快速开始指南 | + +### 📊 实时监测文档 + +| 文档名称 | 说明 | +|---------|------| +| [实时监测定时功能说明.md](实时监测定时功能说明.md) | 定时监测功能说明 | +| [实时监测定时功能快速开始.md](实时监测定时功能快速开始.md) | 定时监测快速开始 | +| [实时监测定时功能更新日志.md](实时监测定时功能更新日志.md) | 定时监测更新日志 | + +### 🎯 主力选股文档 + +| 文档名称 | 说明 | +|---------|------| +| [主力选股使用指南.md](主力选股使用指南.md) | 主力选股功能完整指南 | +| [主力选股功能说明.md](主力选股功能说明.md) | 主力选股功能说明 | +| [主力选股快速开始.md](主力选股快速开始.md) | 主力选股快速开始 | + +### 🤖 量化交易文档 + +| 文档名称 | 说明 | +|---------|------| +| [量化交易快速指南.md](量化交易快速指南.md) | MiniQMT集成指南 | +| [MINIQMT_INTEGRATION_GUIDE.md](MINIQMT_INTEGRATION_GUIDE.md) | MiniQMT详细集成文档 | + +### ⚙️ 配置文档 + +| 文档名称 | 说明 | +|---------|------| +| [环境配置功能说明.md](环境配置功能说明.md) | 完整环境配置说明 | +| [环境配置快速指南.md](环境配置快速指南.md) | 快速配置参考 | + +### 📈 其他功能文档 + +| 文档名称 | 说明 | +|---------|------| +| [批量分析功能说明.md](批量分析功能说明.md) | 批量分析功能说明 | +| [分析师团队选择功能说明.md](分析师团队选择功能说明.md) | 分析师团队选择 | +| [加入监测功能说明.md](加入监测功能说明.md) | 加入监测功能 | +| [港股功能说明.md](港股功能说明.md) | 港股分析功能 | +| [季报和资金流向更新说明.md](季报和资金流向更新说明.md) | 季报资金流向 | +| [数据源冗余机制使用指南.md](数据源冗余机制使用指南.md) | 数据源冗余机制 | +| [数据源冗余功能完成总结.md](数据源冗余功能完成总结.md) | 数据源冗余总结 | +| [风险管理师增强功能说明.md](风险管理师增强功能说明.md) | 风险管理师增强 | + +### 🔧 开发文档 + +| 文档名称 | 说明 | +|---------|------| +| [AGENTS.md](AGENTS.md) | OpenSpec AI开发规范 | + +--- + +## 📝 快速查找 + +### 按功能查找 + +- **新手入门**:[QUICK_START.md](QUICK_START.md) +- **Docker部署**:[DOCKER_DEPLOYMENT.md](DOCKER_DEPLOYMENT.md) +- **持仓分析**:[PORTFOLIO_USAGE.md](PORTFOLIO_USAGE.md) +- **智策板块**:[智策板块使用指南.md](智策板块使用指南.md) +- **智瞰龙虎**:[智瞰龙虎功能说明.md](智瞰龙虎功能说明.md) +- **批量分析**:[LONGHUBANG_BATCH_ANALYSIS.md](LONGHUBANG_BATCH_ANALYSIS.md) +- **主力选股**:[主力选股使用指南.md](主力选股使用指南.md) +- **量化交易**:[量化交易快速指南.md](量化交易快速指南.md) +- **通知配置**:[Webhook通知配置指南.md](Webhook通知配置指南.md) + +### 按用户类型查找 + +#### 初次使用者 +1. [QUICK_START.md](QUICK_START.md) - 快速开始 +2. [DOCKER_DEPLOYMENT.md](DOCKER_DEPLOYMENT.md) - 部署指南 +3. [环境配置快速指南.md](环境配置快速指南.md) - 环境配置 + +#### 功能探索者 +1. [智策板块快速开始.md](智策板块快速开始.md) - 智策板块 +2. [智瞰龙虎快速开始.md](智瞰龙虎快速开始.md) - 智瞰龙虎 +3. [主力选股快速开始.md](主力选股快速开始.md) - 主力选股 + +#### 高级用户 +1. [PORTFOLIO_USAGE.md](PORTFOLIO_USAGE.md) - 持仓分析 +2. [UNIFIED_ANALYSIS_SPEC.md](UNIFIED_ANALYSIS_SPEC.md) - 统一规范 +3. [量化交易快速指南.md](量化交易快速指南.md) - 自动交易 + +#### 开发者 +1. [AGENTS.md](AGENTS.md) - OpenSpec规范 +2. [UNIFIED_ANALYSIS_SPEC.md](UNIFIED_ANALYSIS_SPEC.md) - 代码规范 +3. [UPDATE_LOG.md](UPDATE_LOG.md) - 更新日志 + +--- + +## 📊 文档统计 + +- **总文档数**:46个 +- **核心文档**:4个 +- **功能文档**:35个 +- **开发文档**:1个 +- **最后更新**:2024年10月20日 + +--- + +## 🔗 相关链接 + +- 返回项目主页:[../README.md](../README.md) +- OpenSpec规范:[../openspec/](../openspec/) +- 项目代码:[GitHub](https://github.com/your-repo) + +--- + +**📝 提示**:所有文档均已从项目根目录迁移至 `docs/` 目录,便于统一管理和查找。 + diff --git a/docs/UNIFIED_ANALYSIS_SPEC.md b/docs/UNIFIED_ANALYSIS_SPEC.md new file mode 100644 index 0000000..ab4a02d --- /dev/null +++ b/docs/UNIFIED_ANALYSIS_SPEC.md @@ -0,0 +1,272 @@ +# 统一股票分析调用规范 + +## 📌 核心原则 + +**所有涉及股票分析的功能必须使用统一的分析函数和数据结构!** + +--- + +## 🎯 适用场景 + +✅ 以下所有场景必须遵循此规范: + +- 首页单股分析 +- 首页批量分析 +- 持仓批量分析(UI触发) +- 持仓定时分析(自动触发) +- 智策板块中的个股分析 +- 智瞰龙虎中的个股分析 +- 主力选股中的个股分析 +- **任何未来新增的股票分析功能** + +--- + +## ✅ 正确做法 + +### 1. 调用统一分析函数 + +```python +from app import analyze_single_stock_for_batch + +result = analyze_single_stock_for_batch( + symbol="600519.SH", + period="1y", + enabled_analysts_config={ + 'technical': True, + 'fundamental': True, + 'fund_flow': True, + 'risk': True, + 'sentiment': False, + 'news': False + }, + selected_model="deepseek-chat" +) +``` + +### 2. 使用统一字段名 + +```python +# 提取分析结果 +final_decision = result["final_decision"] +stock_info = result["stock_info"] + +# 使用正确的字段名 +rating = final_decision.get("rating", "未知") # ✅ +confidence = final_decision.get("confidence_level", "N/A") # ✅ +entry_range = final_decision.get("entry_range", "N/A") # ✅ +take_profit = final_decision.get("take_profit", "N/A") # ✅ +stop_loss = final_decision.get("stop_loss", "N/A") # ✅ +target_price = final_decision.get("target_price", "N/A") # ✅ +advice = final_decision.get("advice", "") # ✅ +``` + +### 3. 统一数据解析 + +```python +import re + +# 解析进场区间(格式如"10.5-12.3") +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 + +# 解析止盈止损(提取数字,如"15.8元" → 15.8) +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 +``` + +### 4. 统一结果展示 + +```python +# 评级颜色标识 +if "强烈买入" in rating or "买入" in rating: + rating_color = "🟢" +elif "卖出" in rating: + rating_color = "🔴" +else: + rating_color = "🟡" + +# UI展示(Streamlit示例) +with st.expander(f"{rating_color} {code} - {rating} (信心度: {confidence})"): + col1, col2 = st.columns(2) + + with col1: + st.markdown("**进出场位置**") + st.write(f"进场区间: {entry_range}") + st.write(f"目标价: {target_price}") + + with col2: + st.markdown("**风控位置**") + st.write(f"止盈位: {take_profit}") + st.write(f"止损位: {stop_loss}") + + if advice: + st.markdown("**投资建议**") + st.info(advice) +``` + +--- + +## ❌ 禁止行为 + +### 1. 不要直接调用 ai_agents + +```python +# ❌ 错误做法 +from ai_agents import StockAnalysisAgents +agents = StockAnalysisAgents() +result = agents.technical_analyst_agent(...) # 禁止! +``` + +### 2. 不要使用废弃字段名 + +```python +# ❌ 错误的字段名 +rating = final_decision.get("investment_rating") # 已废弃 +confidence = final_decision.get("confidence") # 已废弃 +positions = final_decision.get("entry_exit_positions") # 已废弃 +entry_min = positions.get("entry_zone_min") # 已废弃 +``` + +### 3. 不要重复实现分析逻辑 + +```python +# ❌ 错误做法 +from stock_data import StockDataFetcher +from ai_agents import StockAnalysisAgents + +fetcher = StockDataFetcher() +agents = StockAnalysisAgents() + +# 自己获取数据 +stock_data = fetcher.get_stock_data(symbol) +indicators = fetcher.calculate_technical_indicators(stock_data) + +# 自己调用分析师 +result = agents.technical_analyst_agent(...) # 禁止! +``` + +--- + +## 📋 字段对照表 + +| 正确字段名 | 废弃字段名 | 数据类型 | 示例值 | +|-----------|-----------|---------|-------| +| `rating` | `investment_rating` | string | "买入", "持有", "卖出" | +| `confidence_level` | `confidence` | string/number | "8/10", "N/A" | +| `entry_range` | `entry_exit_positions["entry_zone_min/max"]` | string | "10.5-12.3" | +| `take_profit` | `entry_exit_positions["take_profit"]` | string | "止盈: 15.8元" | +| `stop_loss` | `entry_exit_positions["stop_loss"]` | string | "止损: 9.2元" | +| `target_price` | - | string | "目标价: 18.5元" | +| `advice` | `summary` | string | "建议买入..." | + +--- + +## 🔍 代码审查检查清单 + +提交涉及股票分析的代码时,请确认: + +- [ ] 使用了 `app.analyze_single_stock_for_batch()` 而非直接调用 `ai_agents` +- [ ] 使用了正确的字段名(`rating`, `confidence_level`, `entry_range`等) +- [ ] 没有使用废弃字段名(`investment_rating`, `entry_exit_positions`等) +- [ ] 数据解析逻辑与规范一致(split("-"), re.findall()) +- [ ] UI展示格式与其他模块保持一致 +- [ ] 通知推送使用相同的数据结构 + +--- + +## 📚 参考代码 + +### 推荐参考 + +1. **`portfolio_manager.py`** - 完整的分析调用和数据保存示例 +2. **`portfolio_ui.py`** - UI展示和数据解析示例 +3. **`portfolio_scheduler.py`** - 监测同步和通知推送示例 +4. **`notification_service.py`** - 通知内容构建示例 + +### 设计文档 + +- **`openspec/changes/add-portfolio-scheduled-analysis/design.md`** - Decision 4: 统一股票分析调用规范 +- **`openspec/changes/add-portfolio-scheduled-analysis/specs/stock-analysis/spec.md`** - Requirement: 统一股票分析调用规范 + +--- + +## 💡 好处 + +遵循此规范可以获得: + +1. ✅ **维护成本降低**:只需维护一个分析函数 +2. ✅ **测试成本降低**:只需测试一个分析流程 +3. ✅ **Bug修复效率**:一处修复,全局生效 +4. ✅ **新功能快速开发**:直接复用,无需重写 +5. ✅ **用户体验一致**:所有场景看到的结果格式一致 +6. ✅ **代码复用最大化**:避免重复代码 +7. ✅ **自动兼容优化**:首页分析的优化自动应用到所有场景 + +--- + +## ⚠️ 违规后果 + +不遵循此规范可能导致: + +- ❌ 字段名不一致,UI显示"未知"或错误 +- ❌ 数据解析失败,监测同步失败 +- ❌ 通知推送格式错误 +- ❌ 代码重复,维护困难 +- ❌ Bug修复需要改多处 +- ❌ 用户体验不一致 + +--- + +## 🆘 常见问题 + +### Q: 为什么必须使用统一函数? +A: 确保所有场景使用相同的AI模型、数据源、分析流程,避免结果不一致。首页分析的任何优化都会自动应用到所有场景。 + +### Q: 为什么不能直接调用 ai_agents? +A: `app.analyze_single_stock_for_batch()` 已经封装了完整的数据获取、分析、错误处理流程。直接调用 ai_agents 会导致代码重复和逻辑不一致。 + +### Q: 旧代码使用了废弃字段怎么办? +A: 必须修改!参考 `portfolio_manager.py` 中的正确用法,使用新的字段名。 + +### Q: 如何解析字符串字段(如进场区间)? +A: 参考本文档"统一数据解析"章节的代码示例,使用 `split("-")` 和正则表达式。 + +### Q: 新功能可以不遵循此规范吗? +A: **不可以!** 所有涉及股票分析的功能都必须遵循此规范,这是强制要求。 + +--- + +## 📝 更新日志 + +- **2024-10-20**: 初始版本,基于持仓定时分析功能的实践总结 +- **规范来源**: OpenSpec - `add-portfolio-scheduled-analysis` - Decision 4 + +--- + +**遵循规范,代码更优!** 🎉 + diff --git a/docs/UPDATE_LOG.md b/docs/UPDATE_LOG.md new file mode 100644 index 0000000..ce1b69c --- /dev/null +++ b/docs/UPDATE_LOG.md @@ -0,0 +1,476 @@ +# 功能更新日志 - 2024年10月20日 + +## 🎉 重大更新:持仓定时分析 + 智瞰龙虎批量分析 + +--- + +## ✨ 更新一:持仓定时分析功能(全新) + +### 核心功能(6项) + +1. **📝 持仓股票管理** + - 可视化管理持仓股票清单 + - 添加/编辑/删除操作 + - 记录成本价、数量、备注 + - 支持A股、港股、美股 + +2. **🔄 批量分析功能** + - 一键分析全部持仓 + - 顺序/并行两种模式 + - 实时进度显示 + - 详细结果展示 + +3. **⏰ 定时任务调度**(支持多时间点 ⭐️) + - 可设置多个每日分析时间 + - 例如:09:25(盘前)+ 15:05(盘后) + - 灵活添加/删除时间点 + - 自动排序和去重 + - 后台自动执行 + +4. **🎯 自动监测同步** + - 分析完成自动同步到监测列表 + - 批量API高效对接 + - 覆盖更新机制 + - 统计新增/更新数量 + +5. **📬 通知推送** + - HTML邮件(彩色格式) + - Webhook(钉钉/飞书) + - 分析概况和详细结果 + - 监测同步统计 + +6. **📈 分析历史** + - 完整历史记录追踪 + - 评级变化可视化 + - 按股票筛选查询 + - 验证预测准确性 + +### 技术亮点 + +- ✅ **代码复用**:调用统一分析函数 `app.analyze_single_stock_for_batch()` +- ✅ **模块化设计**:数据库、逻辑、调度、UI完全分离 +- ✅ **性能优化**:支持3-10线程并行分析 +- ✅ **统一规范**:强制字段名和解析逻辑统一 + +### 交付文件 + +**核心模块**(4个,93KB): +- `portfolio_db.py` - 数据库管理 +- `portfolio_manager.py` - 持仓管理 +- `portfolio_scheduler.py` - 定时调度(支持多时间点) +- `portfolio_ui.py` - 用户界面 + +**扩展模块**(3个): +- `monitor_db.py` - 批量监测API +- `notification_service.py` - 持仓通知 +- `app.py` - 主应用集成 + +**文档**(6个,31KB): +- `docs/PORTFOLIO_USAGE.md` - 用户指南 +- `docs/MULTI_SCHEDULE_GUIDE.md` - 多定时指南 +- `docs/UNIFIED_ANALYSIS_SPEC.md` - 统一规范 +- `docs/LONGHUBANG_BATCH_ANALYSIS.md` - 龙虎榜批量分析 +- `openspec/changes/add-portfolio-scheduled-analysis/` - 完整提案 +- `README.md` - 更新说明 + +--- + +## ✨ 更新二:统一股票分析调用规范(架构级)⭐️ + +### 核心原则 + +**所有涉及股票分析的功能必须使用统一的分析函数和数据结构!** + +### 强制要求 + +1. **统一函数调用** + ```python + from app import analyze_single_stock_for_batch + ``` + +2. **统一字段名** + - `rating` - 投资评级 + - `confidence_level` - 信心度 + - `entry_range` - 进场区间 + - `take_profit` - 止盈位 + - `stop_loss` - 止损位 + - `target_price` - 目标价 + +3. **废弃字段**(禁止使用) + - ❌ `investment_rating` + - ❌ `entry_exit_positions` + - ❌ `entry_zone_min/max` + +4. **统一解析逻辑** + - 进场区间:`split("-")` + - 止盈止损:`re.findall(r'\d+\.?\d*')` + +5. **统一展示格式** + - emoji标识:🟢买入 🟡持有 🔴卖出 + - 一致的布局和信息展示 + +### 适用范围 + +- ✅ 首页单股/批量分析 +- ✅ 持仓批量/定时分析 +- ✅ 智策板块个股分析 +- ✅ 智瞰龙虎个股分析 ⭐️ +- ✅ 主力选股个股分析 +- ✅ **所有未来新增功能** + +### 参考文档 + +- `docs/UNIFIED_ANALYSIS_SPEC.md` - 详细规范 +- `openspec/project.md` - 架构模式章节 + +--- + +## ✨ 更新三:智瞰龙虎批量分析功能(新增)⭐️ + +### 功能描述 + +在**智瞰龙虎 - AI评分排名**中,对TOP10综合评分排名的股票提供一键批量深度分析。 + +### 核心功能 + +1. **一键批量分析** + - 可选分析数量:3只/5只/10只 + - 顺序/并行分析模式 + - 实时进度显示 + - 详细结果展示 + +2. **智能筛选** + - 基于AI评分自动排名 + - 5维度综合评估 + - TOP10精选 + +3. **快捷操作** + - 一键加入监测列表 + - 自动解析关键价位 + - 直接开启实时监控 + +4. **遵循统一规范** + - 调用统一分析函数 + - 使用统一字段名 + - 统一解析和展示 + +### 使用流程 + +``` +智瞰龙虎 → AI评分排名 → 查看TOP10 +→ 选择分析数量(3/5/10) → 开始批量分析 +→ 选择模式(顺序/并行) → 确认开始 +→ 等待分析完成 → 查看结果 → 加入监测 +``` + +### 使用场景 + +- ✅ 短线交易机会挖掘 +- ✅ 游资龙头股追踪 +- ✅ 热点题材捕捉 +- ✅ 完整交易闭环 + +### 技术实现 + +- **遵循规范**:调用 `app.analyze_single_stock_for_batch()` +- **统一字段**:`rating`, `confidence_level`, `entry_range`等 +- **代码新增**:`longhubang_ui.py` +310行 +- **文档新增**:`docs/LONGHUBANG_BATCH_ANALYSIS.md` + +--- + +## 📊 总体统计 + +### 代码量 +- **新增代码**:约5,500行 +- **文档**:约3,800行 +- **总计**:约9,300行 + +### 文件数量 +- **核心模块**:4个新文件 +- **扩展模块**:4个文件修改 +- **文档**:7个新文档 +- **OpenSpec**:完整变更提案(验证通过 ✅) + +### 功能模块 +- **持仓定时分析**:6大核心功能 +- **多定时支持**:灵活时间配置 +- **统一规范**:架构级规范制定 +- **龙虎榜批量分析**:TOP股票深度分析 + +--- + +## 🎯 核心价值 + +### 1. 代码质量提升 + +- **统一规范**:避免重复代码,降低维护成本 +- **强制复用**:一处优化,全局受益 +- **类型统一**:避免字段名不一致的bug + +### 2. 用户体验提升 + +- **自动化**:定时分析,无需人工干预 +- **智能化**:自动监测同步,完整闭环 +- **便捷化**:一键批量分析,快速决策 + +### 3. 功能完整性 + +- **持仓管理**:全生命周期管理 +- **龙虎榜分析**:从筛选到监测全流程 +- **多场景支持**:长线、短线、日内交易 + +--- + +## 📚 文档清单 + +### 用户文档 +1. `README.md` - 功能介绍(已更新) +2. `docs/PORTFOLIO_USAGE.md` - 持仓分析使用指南 +3. `docs/MULTI_SCHEDULE_GUIDE.md` - 多定时配置指南 +4. `docs/LONGHUBANG_BATCH_ANALYSIS.md` - 龙虎榜批量分析指南 + +### 技术文档 +1. `docs/UNIFIED_ANALYSIS_SPEC.md` - 统一调用规范 ⭐️ +2. `openspec/project.md` - 项目规范(已更新) +3. `openspec/changes/add-portfolio-scheduled-analysis/` - 完整OpenSpec提案 + +--- + +## 🚀 如何使用 + +### 持仓定时分析 + +```bash +streamlit run app.py +→ 侧边栏"📊 持仓分析" +→ 添加持仓股票 +→ 配置定时时间(支持多个) +→ 启动调度器 +``` + +### 智瞰龙虎批量分析 + +```bash +streamlit run app.py +→ 侧边栏"🎯 智瞰龙虎" +→ 获取龙虎榜数据 +→ 切换到"AI评分排名" +→ 选择分析数量 +→ 开始批量分析 +``` + +--- + +## 🐛 已修复问题 + +1. ✅ TypeError: 'bool' object is not callable +2. ✅ AttributeError: calculate_all_indicators +3. ✅ 字段名不一致导致显示"未知" +4. ✅ 监测同步参数错误 +5. ✅ 通知推送字段错误 +6. ✅ 缺少UI接口方法 +7. ✅ 批量分析后返回首页问题 +8. ✅ 智瞰龙虎历史报告功能缺失(2024-10-20) + +--- + +## ✨ 更新四:智瞰龙虎历史报告功能完善(2024-10-20) + +### 问题描述 + +智瞰龙虎系统之前只保存了简单的报告摘要,缺少像股票分析那样的完整历史记录功能,无法查看完整的分析结果。 + +### 修复内容 + +#### 1. 数据库层增强 (longhubang_db.py) + +**增强的保存函数:** +- 支持保存完整的结构化分析数据 +- 自动将字典转换为JSON格式 +- 保存包含:AI分析师报告、评分排名、数据概况等 + +**增强的查询函数:** +- 自动解析JSON格式的分析内容 +- 返回结构化的报告数据 +- 支持向后兼容旧格式报告 + +#### 2. 引擎层改进 (longhubang_engine.py) + +**完整数据保存:** +```python +full_analysis_content = { + "agents_analysis": agents_results, # 5位AI分析师的完整分析 + "data_info": results["data_info"], # 数据概况统计 + "scoring_ranking": scoring_df.to_dict(), # AI评分排名 + "final_report": final_report, # 最终综合报告 + "timestamp": results["timestamp"] # 分析时间戳 +} +``` + +#### 3. UI层重构 (longhubang_ui.py) + +**全新的历史报告界面:** + +📋 **报告列表** +- 显示最近50条历史报告 +- 展示报告ID、分析时间、数据日期范围 +- 支持展开查看详细内容 + +📝 **报告详情展示** +- **报告摘要** - 简要概述分析重点 +- **推荐股票** - 完整的推荐列表(表格化展示) +- **AI分析师团队报告** - 5位分析师的详细分析(可展开) + - 🎯 游资行为分析师 + - 📈 个股潜力分析师 + - 🔥 题材追踪分析师 + - ⚠️ 风险控制专家 + - 👔 首席策略师 +- **AI智能评分排名** - TOP10股票评分详情 +- **数据概况** - 龙虎榜统计信息 + +🔄 **实用功能** +- **加载到分析页** - 将历史报告重新加载到分析标签页 +- **导出为PDF** - (功能预留)支持PDF导出 + +### 技术实现 + +**数据存储结构:** +```json +{ + "agents_analysis": { + "youzi": {"analysis": "...", "timestamp": "..."}, + "stock": {"analysis": "...", "timestamp": "..."}, + "theme": {"analysis": "...", "timestamp": "..."}, + "risk": {"analysis": "...", "timestamp": "..."}, + "chief": {"analysis": "...", "timestamp": "..."} + }, + "data_info": { + "total_records": 100, + "total_stocks": 50, + "total_youzi": 30 + }, + "scoring_ranking": [...], + "final_report": {...}, + "timestamp": "2024-10-20 15:30:00" +} +``` + +### 功能对比 + +| 功能 | 修复前 | 修复后 | +|------|--------|--------| +| 保存完整分析结果 | ❌ | ✅ | +| 历史记录查询 | 简单列表 | ✅ 完整展示 | +| AI分析师报告 | ❌ | ✅ | +| 评分排名展示 | ❌ | ✅ | +| 推荐股票详情 | 简单 | ✅ 完整 | +| 加载历史报告 | ❌ | ✅ | +| 结构化数据 | ❌ | ✅ JSON格式 | + +### 核心价值 + +1. **数据完整性** - 保存所有分析结果,不会丢失任何信息 +2. **方便查询** - 随时查看历史分析报告,追踪分析效果 +3. **复用功能** - 可以重新加载历史报告,无需重新分析 +4. **向后兼容** - 旧报告也能正常显示(降级到原始内容) + +### 使用说明 + +**查看历史报告:** +``` +智瞰龙虎 → 历史报告标签页 +→ 点击展开任意报告 +→ 查看完整的分析内容 +``` + +**重新加载历史报告:** +``` +历史报告详情 → 点击"加载到分析页" +→ 切换到"龙虎榜分析"标签页 +→ 查看完整的可视化图表 +``` + +### 交付文件 + +**修改的核心文件:** +- `longhubang_db.py` - 数据库增强 +- `longhubang_engine.py` - 完整数据保存 +- `longhubang_ui.py` - UI界面重构 + +**新增文档:** +- `智瞰龙虎历史报告功能说明.md` - 详细功能说明 + +### 后续优化方向 + +- [ ] PDF导出功能实现 +- [ ] 报告对比功能 +- [ ] 搜索过滤功能 +- [ ] 性能分析统计(推荐准确率) +- [ ] 批量管理功能 + +### 补充更新:AI评分排名完整保存 ⭐️ + +**问题描述:** +需要确保AI智能评分排名TOP10的完整表格内容被保存到历史报告中。 + +**改进内容:** + +1. **数据保存增强** (longhubang_engine.py) + - 添加异常处理,确保评分数据安全转换 + - 打印转换日志,便于调试 + - 记录转换的评分记录数量 + +2. **历史报告展示增强** (longhubang_ui.py) + - 显示完整的13列评分数据 + - 使用进度条可视化5个维度评分 + - 新增评分说明面板,详细解释评分体系 + +**完整展示的列:** +- 排名、股票名称、股票代码、综合评分 +- 资金含金量(0-30)、净买入额(0-25)、卖出压力(0-20) +- 机构共振(0-15)、加分项(0-10) +- 顶级游资数、买方数、机构参与、净流入金额 + +**交付文件:** +- `longhubang_engine.py` - 改进数据转换逻辑 +- `longhubang_ui.py` - 增强评分表格展示 +- `智瞰龙虎评分排名历史记录说明.md` - 详细功能文档 + +--- + +## 📝 下一步计划 + +### 短期(可选) +- [ ] 配置持久化(定时时间保存到数据库) +- [ ] 收益率统计和可视化 +- [ ] CSV/Excel导入导出 + +### 中期(待评估) +- [ ] 组合优化建议 +- [ ] 风险敞口分析 +- [ ] 策略回测功能 + +--- + +## 🎊 总结 + +**本次更新完成了三大核心功能和一个架构级规范:** + +1. ✅ **持仓定时分析**:完整的持仓管理和自动化分析系统 +2. ✅ **统一调用规范**:架构级代码质量提升 +3. ✅ **龙虎榜批量分析**:短线交易工具增强 +4. ✅ **智瞰龙虎历史报告**:完整的历史记录查询和展示(2024-10-20新增) + +**所有功能已完成、测试、文档化!** + +**OpenSpec验证:✅ 通过** + +**状态:可立即投入使用** 🚀 + +--- + +**最后更新:2024年10月20日** + +**开发者:AI Assistant (Claude Sonnet 4.5)** + diff --git a/Webhook功能完成说明.md b/docs/Webhook功能完成说明.md similarity index 100% rename from Webhook功能完成说明.md rename to docs/Webhook功能完成说明.md diff --git a/Webhook功能实现总结.md b/docs/Webhook功能实现总结.md similarity index 100% rename from Webhook功能实现总结.md rename to docs/Webhook功能实现总结.md diff --git a/Webhook自定义关键词功能说明.md b/docs/Webhook自定义关键词功能说明.md similarity index 100% rename from Webhook自定义关键词功能说明.md rename to docs/Webhook自定义关键词功能说明.md diff --git a/Webhook通知配置指南.md b/docs/Webhook通知配置指南.md similarity index 100% rename from Webhook通知配置指南.md rename to docs/Webhook通知配置指南.md diff --git a/Webhook钉钉关键词快速配置指南.md b/docs/Webhook钉钉关键词快速配置指南.md similarity index 100% rename from Webhook钉钉关键词快速配置指南.md rename to docs/Webhook钉钉关键词快速配置指南.md diff --git a/Webhook钉钉关键词配置更新说明.md b/docs/Webhook钉钉关键词配置更新说明.md similarity index 100% rename from Webhook钉钉关键词配置更新说明.md rename to docs/Webhook钉钉关键词配置更新说明.md diff --git a/主力选股使用指南.md b/docs/主力选股使用指南.md similarity index 100% rename from 主力选股使用指南.md rename to docs/主力选股使用指南.md diff --git a/主力选股功能说明.md b/docs/主力选股功能说明.md similarity index 100% rename from 主力选股功能说明.md rename to docs/主力选股功能说明.md diff --git a/主力选股快速开始.md b/docs/主力选股快速开始.md similarity index 100% rename from 主力选股快速开始.md rename to docs/主力选股快速开始.md diff --git a/分析师团队选择功能说明.md b/docs/分析师团队选择功能说明.md similarity index 100% rename from 分析师团队选择功能说明.md rename to docs/分析师团队选择功能说明.md diff --git a/加入监测功能说明.md b/docs/加入监测功能说明.md similarity index 100% rename from 加入监测功能说明.md rename to docs/加入监测功能说明.md diff --git a/季报和资金流向更新说明.md b/docs/季报和资金流向更新说明.md similarity index 100% rename from 季报和资金流向更新说明.md rename to docs/季报和资金流向更新说明.md diff --git a/实时监测定时功能快速开始.md b/docs/实时监测定时功能快速开始.md similarity index 100% rename from 实时监测定时功能快速开始.md rename to docs/实时监测定时功能快速开始.md diff --git a/实时监测定时功能更新日志.md b/docs/实时监测定时功能更新日志.md similarity index 100% rename from 实时监测定时功能更新日志.md rename to docs/实时监测定时功能更新日志.md diff --git a/实时监测定时功能说明.md b/docs/实时监测定时功能说明.md similarity index 100% rename from 实时监测定时功能说明.md rename to docs/实时监测定时功能说明.md diff --git a/批量分析功能说明.md b/docs/批量分析功能说明.md similarity index 100% rename from 批量分析功能说明.md rename to docs/批量分析功能说明.md diff --git a/数据源冗余功能完成总结.md b/docs/数据源冗余功能完成总结.md similarity index 100% rename from 数据源冗余功能完成总结.md rename to docs/数据源冗余功能完成总结.md diff --git a/数据源冗余机制使用指南.md b/docs/数据源冗余机制使用指南.md similarity index 100% rename from 数据源冗余机制使用指南.md rename to docs/数据源冗余机制使用指南.md diff --git a/智瞰龙虎AI评分说明.md b/docs/智瞰龙虎AI评分说明.md similarity index 100% rename from 智瞰龙虎AI评分说明.md rename to docs/智瞰龙虎AI评分说明.md diff --git a/智瞰龙虎功能说明.md b/docs/智瞰龙虎功能说明.md similarity index 100% rename from 智瞰龙虎功能说明.md rename to docs/智瞰龙虎功能说明.md diff --git a/docs/智瞰龙虎历史报告功能说明.md b/docs/智瞰龙虎历史报告功能说明.md new file mode 100644 index 0000000..5870034 --- /dev/null +++ b/docs/智瞰龙虎历史报告功能说明.md @@ -0,0 +1,212 @@ +# 智瞰龙虎 - 历史报告功能增强说明 + +## 📅 更新日期 +2024年10月20日 + +## 🎯 功能概述 +本次更新为智瞰龙虎系统新增了完整的历史报告存储和查询功能,参照股票分析的历史记录实现,实现了以下增强: + +## ✨ 新增功能 + +### 1. 完整报告存储 +- **结构化数据保存**:将完整的分析结果以JSON格式保存到数据库 +- **包含内容**: + - 🤖 AI分析师团队报告(5位分析师的完整分析) + - 📊 数据概况(龙虎榜记录、涉及股票、游资等统计) + - 🏆 AI智能评分排名(完整的评分数据) + - 📝 最终综合报告 + - 🎯 推荐股票列表 + - ⏰ 分析时间戳 + +### 2. 增强的历史报告查询界面 +- **报告列表展示**: + - 显示最近50条历史报告 + - 按时间倒序排列 + - 展示报告ID、分析时间、数据日期范围 + +- **完整报告详情**: + 每条历史报告可展开查看以下内容: + + #### 📝 报告摘要 + - 简要概述本次分析的重点 + + #### 🎯 推荐股票 + - 完整的推荐股票列表 + - 包含股票代码、名称、净流入、推荐理由、确定性、持有周期 + - 表格化展示,支持排序 + + #### 🤖 AI分析师团队报告 + 可展开查看5位AI分析师的完整分析: + - 🎯 **游资行为分析师** - 游资操作风格和进出特征 + - 📈 **个股潜力分析师** - 潜力股挖掘和上涨概率分析 + - 🔥 **题材追踪分析师** - 热点题材识别和持续性判断 + - ⚠️ **风险控制专家** - 高风险股票识别和陷阱预警 + - 👔 **首席策略师** - 综合研判和最终推荐 + + #### 🏆 AI智能评分排名 + - 显示TOP10股票的综合评分 + - 包含资金含金量、净买入额、卖出压力、机构共振等维度 + + #### 📊 数据概况 + - 龙虎榜记录数 + - 涉及股票数 + - 涉及游资数 + +### 3. 历史报告操作功能 +- **📋 加载到分析页**:将历史报告重新加载到"龙虎榜分析"标签页,方便重新查看完整的图表和可视化 +- **📥 导出为PDF**:(功能预留)支持将历史报告导出为PDF文档 + +## 🔧 技术实现 + +### 数据库层 (longhubang_db.py) +```python +# 增强的保存函数 +def save_analysis_report(data_date_range, analysis_content, + recommended_stocks, summary, full_result=None): + # 支持字典自动转JSON + # 保存完整的结构化数据 +``` + +```python +# 增强的查询函数 +def get_analysis_report(report_id): + # 自动解析JSON内容 + # 返回结构化的报告数据 +``` + +### 引擎层 (longhubang_engine.py) +```python +# 构建完整的分析内容 +full_analysis_content = { + "agents_analysis": agents_results, + "data_info": results["data_info"], + "scoring_ranking": scoring_df.to_dict('records'), + "final_report": final_report, + "timestamp": results["timestamp"] +} + +# 保存到数据库 +report_id = self.database.save_analysis_report( + data_date_range=data_date_range, + analysis_content=full_analysis_content, + recommended_stocks=recommended_stocks, + summary=final_report.get('summary', '') +) +``` + +### UI层 (longhubang_ui.py) +- 完全重写 `display_history_tab()` 函数 +- 使用展开面板 (expander) 组织内容 +- 支持动态加载报告详情 +- 实现报告加载到分析页功能 + +## 📖 使用说明 + +### 查看历史报告 +1. 进入智瞰龙虎模块 +2. 点击"历史报告"标签页 +3. 查看报告列表,点击任意报告展开查看详情 + +### 重新加载历史报告 +1. 在历史报告详情中点击"📋 加载到分析页"按钮 +2. 切换到"龙虎榜分析"标签页 +3. 查看完整的分析结果和可视化图表 + +### 查看AI分析师报告 +1. 在历史报告详情中找到"🤖 AI分析师团队报告"部分 +2. 点击各个分析师的展开面板 +3. 查看详细的分析内容 + +## 🔄 与股票分析功能的对比 + +| 功能 | 股票分析 | 智瞰龙虎(修复后) | +|------|----------|-------------------| +| 保存完整分析结果 | ✅ | ✅ | +| 历史记录查询 | ✅ | ✅ | +| 详细内容展示 | ✅ | ✅ | +| AI分析师报告 | ✅ | ✅ | +| 评分排名展示 | ✅ | ✅ | +| 推荐股票列表 | ✅ | ✅ | +| 加载历史报告 | ✅ | ✅ | + +## 📊 数据存储结构 + +### longhubang_analysis 表 +```sql +CREATE TABLE longhubang_analysis ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + analysis_date TEXT, -- 分析时间 + data_date_range TEXT, -- 数据日期范围 + analysis_content TEXT, -- 完整分析内容(JSON) + recommended_stocks TEXT, -- 推荐股票列表(JSON) + summary TEXT, -- 报告摘要 + created_at TEXT DEFAULT CURRENT_TIMESTAMP +) +``` + +### analysis_content JSON结构 +```json +{ + "agents_analysis": { + "youzi": {"analysis": "...", "timestamp": "..."}, + "stock": {"analysis": "...", "timestamp": "..."}, + "theme": {"analysis": "...", "timestamp": "..."}, + "risk": {"analysis": "...", "timestamp": "..."}, + "chief": {"analysis": "...", "timestamp": "..."} + }, + "data_info": { + "total_records": 100, + "total_stocks": 50, + "total_youzi": 30, + "summary": {...} + }, + "scoring_ranking": [ + { + "排名": 1, + "股票名称": "...", + "股票代码": "...", + "综合评分": 85.5, + ... + } + ], + "final_report": { + "title": "...", + "summary": "...", + ... + }, + "timestamp": "2024-10-20 15:30:00" +} +``` + +## 🎉 优势和特点 + +1. **数据完整性**:保存了所有分析结果,不会丢失任何信息 +2. **方便查询**:可以随时查看历史分析报告,追踪分析效果 +3. **复用功能**:可以将历史报告重新加载,无需重新分析 +4. **结构化存储**:使用JSON格式存储,便于解析和展示 +5. **向后兼容**:对旧的报告也能正常显示(降级到原始内容展示) + +## 📝 注意事项 + +1. **数据库兼容性**:新旧版本的报告都可以正常查看 +2. **性能优化**:历史报告列表限制显示50条,避免加载过慢 +3. **存储空间**:完整的分析结果占用空间较大,建议定期清理旧报告 +4. **JSON解析**:如果报告内容无法解析为JSON,会降级显示原始文本 + +## 🚀 后续优化方向 + +1. **PDF导出功能**:实现历史报告的PDF导出 +2. **报告对比功能**:支持对比不同日期的分析报告 +3. **搜索过滤功能**:按股票代码、日期范围搜索历史报告 +4. **性能分析功能**:统计推荐股票的准确率和收益率 +5. **批量管理功能**:支持批量删除、导出历史报告 + +## 🐛 已知问题 +无 + +## 📧 反馈与建议 +如有问题或建议,请在项目中提交Issue。 + +--- +**更新完成!** ✅ + diff --git a/智瞰龙虎快速开始.md b/docs/智瞰龙虎快速开始.md similarity index 100% rename from 智瞰龙虎快速开始.md rename to docs/智瞰龙虎快速开始.md diff --git a/docs/智瞰龙虎评分排名历史记录说明.md b/docs/智瞰龙虎评分排名历史记录说明.md new file mode 100644 index 0000000..250429d --- /dev/null +++ b/docs/智瞰龙虎评分排名历史记录说明.md @@ -0,0 +1,306 @@ +# 智瞰龙虎 - AI评分排名历史记录功能说明 + +## 📅 更新日期 +2024年10月20日 + +## 🎯 功能概述 +本次更新完善了智瞰龙虎系统的历史报告功能,确保**AI智能评分排名TOP10的完整数据**被正确保存到数据库,并在历史报告中完整展示。 + +## ✨ 核心改进 + +### 1. 数据保存增强 (longhubang_engine.py) + +#### 改进前 +```python +# 简单转换,可能丢失数据 +"scoring_ranking": scoring_df.to_dict('records') if ... else [] +``` + +#### 改进后 +```python +# 转换评分排名数据为可序列化格式 +scoring_ranking_data = [] +if scoring_df is not None and hasattr(scoring_df, 'to_dict'): + try: + # 转换DataFrame为字典列表,确保所有数据都被序列化 + scoring_ranking_data = scoring_df.to_dict('records') + print(f"✓ 评分排名数据已转换: {len(scoring_ranking_data)} 条记录") + except Exception as e: + print(f"⚠ 评分排名数据转换失败: {e}") + scoring_ranking_data = [] + +# 构建完整的分析内容 +full_analysis_content = { + "agents_analysis": agents_results, + "data_info": results["data_info"], + "scoring_ranking": scoring_ranking_data, # 完整的评分数据 + "final_report": final_report, + "timestamp": results["timestamp"] +} +``` + +**优势:** +- ✅ 添加异常处理,确保数据转换安全 +- ✅ 打印转换日志,便于调试 +- ✅ 确保所有评分数据完整保存 + +### 2. 历史报告展示增强 (longhubang_ui.py) + +#### 显示完整的评分表格 + +**包含所有13个评分维度列:** + +| 列名 | 说明 | 显示格式 | +|------|------|----------| +| 排名 | 综合评分排名 | 数字 | +| 股票名称 | 股票名称 | 文本 | +| 股票代码 | 股票代码 | 文本 | +| 综合评分 | 总分(0-100) | 数字,1位小数 | +| 资金含金量 | 0-30分 | 进度条 | +| 净买入额 | 0-25分 | 进度条 | +| 卖出压力 | 0-20分 | 进度条 | +| 机构共振 | 0-15分 | 进度条 | +| 加分项 | 0-10分 | 进度条 | +| 顶级游资 | 数量 | 数字 | +| 买方数 | 席位数 | 数字 | +| 机构参与 | ✅/❌ | 文本 | +| 净流入 | 金额 | 数字,2位小数 | + +#### 新增评分说明面板 + +在评分表格下方添加可展开的说明面板: + +```markdown +**AI智能评分体系 (总分100分)** + +- **资金含金量** (0-30分):顶级游资+10分,知名游资+5分,普通游资+1.5分 +- **净买入额** (0-25分):根据净流入金额大小评分 +- **卖出压力** (0-20分):卖出比例越低得分越高 +- **机构共振** (0-15分):机构+游资共振15分最高 +- **加分项** (0-10分):主力集中度、热门概念、连续上榜等 + +💡 评分越高,表示该股票受到资金青睐程度越高! +``` + +## 📊 数据结构 + +### JSON存储格式 + +```json +{ + "scoring_ranking": [ + { + "排名": 1, + "股票名称": "XXX", + "股票代码": "000001.SZ", + "综合评分": 85.5, + "资金含金量": 28, + "净买入额": 23, + "卖出压力": 18, + "机构共振": 12, + "加分项": 4.5, + "顶级游资": 2, + "买方数": 5, + "机构参与": "✅", + "净流入": 125000000.00 + }, + ... + ] +} +``` + +## 🎯 使用场景 + +### 1. 查看历史评分排名 +``` +智瞰龙虎 → 历史报告标签页 +→ 点击展开任意报告 +→ 向下滚动到"🏆 AI智能评分排名 (TOP10)" +→ 查看完整的评分表格 +``` + +### 2. 对比不同日期的评分 +``` +历史报告 → 展开多个不同日期的报告 +→ 对比同一股票在不同日期的评分变化 +→ 分析市场资金流向趋势 +``` + +### 3. 追踪顶级游资动向 +``` +评分表格 → 查看"顶级游资"列 +→ 识别被顶级游资青睐的股票 +→ 跟踪游资操作模式 +``` + +## 🔍 评分维度详解 + +### 1️⃣ 资金含金量 (0-30分) +- **顶级游资**(赵老哥、章盟主、92科比等):每个 +10分 +- **知名游资**(深股通、中信证券等):每个 +5分 +- **普通游资**:每个 +1.5分 + +### 2️⃣ 净买入额评分 (0-25分) +- 净流入 < 1000万:0-10分 +- 净流入 1000-5000万:10-18分 +- 净流入 5000万-1亿:18-22分 +- 净流入 > 1亿:22-25分 + +### 3️⃣ 卖出压力评分 (0-20分) +- 卖出比例 0-10%:20分 ✨(压力极小) +- 卖出比例 10-30%:15-20分(压力较小) +- 卖出比例 30-50%:10-15分(压力中等) +- 卖出比例 50-80%:5-10分(压力较大) +- 卖出比例 > 80%:0-5分(压力极大) + +### 4️⃣ 机构共振评分 (0-15分) +- **机构+游资共振**:15分 ⭐(最强信号) +- 仅机构买入:8-12分 +- 仅游资买入:5-10分 + +### 5️⃣ 其他加分项 (0-10分) +- **主力集中度**:席位越少越集中 (+1-3分) +- **热门概念**:AI、新能源、芯片等 (+0-3分) +- **连续上榜**:连续多日上榜 (+0-2分) +- **买卖比例优秀**:买入远大于卖出 (+0-2分) + +## 💡 实战技巧 + +### 技巧1:关注综合评分80分以上的股票 +``` +综合评分 ≥ 80分 → 优质股票,资金高度认可 +综合评分 70-80分 → 良好股票,值得关注 +综合评分 60-70分 → 一般股票,谨慎参与 +综合评分 < 60分 → 弱势股票,建议观望 +``` + +### 技巧2:机构共振信号最强 +``` +机构共振 = 15分 + 顶级游资 ≥ 2家 → 最强买入信号 +机构共振 = 15分 → 资金认可度高 +机构共振 < 8分 → 缺少机构支持 +``` + +### 技巧3:关注卖出压力 +``` +卖出压力 ≥ 18分 → 卖压小,筹码稳定 +卖出压力 10-18分 → 卖压中等,观察 +卖出压力 < 10分 → 卖压大,谨慎 +``` + +### 技巧4:顶级游资扎堆 +``` +顶级游资 ≥ 3家 → 顶级游资集中看好 +顶级游资 = 2家 → 双游资共振 +顶级游资 = 1家 → 单游资主导 +顶级游资 = 0家 → 无顶级游资参与 +``` + +## 📈 数据可视化 + +在历史报告中,评分数据以以下方式展示: + +1. **进度条显示**:5个维度评分使用进度条可视化 + - 资金含金量:最大30分 + - 净买入额:最大25分 + - 卖出压力:最大20分 + - 机构共振:最大15分 + - 加分项:最大10分 + +2. **数值显示**:其他数据使用数字格式 + - 排名:整数 + - 综合评分:保留1位小数 + - 净流入:保留2位小数 + +3. **文本标识**:机构参与使用emoji标识 + - ✅:有机构参与 + - ❌:无机构参与 + +## 🎓 案例分析 + +### 案例:理想的高分股票 + +``` +排名: 1 +股票名称: XXX科技 +股票代码: 000001.SZ +综合评分: 88.5分 + +详细评分: +- 资金含金量: 28分 (2家顶级游资 + 1家知名游资) +- 净买入额: 24分 (净流入1.2亿) +- 卖出压力: 19分 (卖出比例仅12%) +- 机构共振: 15分 (机构+游资共振) +- 加分项: 2.5分 (热门AI概念) + +顶级游资: 2家 +买方数: 5家 +机构参与: ✅ +净流入: 120,000,000元 +``` + +**分析:** +- ✅ 综合评分88.5分,属于优质股票 +- ✅ 有2家顶级游资参与,资金含金量高 +- ✅ 净流入1.2亿,资金力度大 +- ✅ 卖出压力小(19分),筹码稳定 +- ✅ 机构+游资共振,最强买入信号 +- ✅ AI热门概念加持 + +**结论:次日大概率上涨,可重点关注!** + +## 🔧 技术实现 + +### 数据流转过程 + +``` +1. 龙虎榜数据获取 + ↓ +2. AI评分系统计算 + ↓ (scoring_df DataFrame) +3. 转换为字典列表 + ↓ (scoring_ranking_data) +4. 保存到数据库 + ↓ (JSON格式) +5. 从数据库读取 + ↓ (解析JSON) +6. 转换回DataFrame + ↓ +7. 在UI中展示 +``` + +### 关键代码位置 + +- **评分计算**:`longhubang_scoring.py` - `score_all_stocks()` +- **数据转换**:`longhubang_engine.py` - 阶段7 +- **数据保存**:`longhubang_db.py` - `save_analysis_report()` +- **数据读取**:`longhubang_db.py` - `get_analysis_report()` +- **UI展示**:`longhubang_ui.py` - `display_history_tab()` + +## 🐛 已知问题 +无 + +## 🚀 后续优化方向 + +1. **评分趋势分析**:展示股票评分的历史变化曲线 +2. **评分对比功能**:对比多只股票的评分维度 +3. **评分统计功能**:统计平均评分、最高评分等 +4. **评分预警功能**:当某只股票评分突破阈值时预警 +5. **导出评分报告**:将评分数据导出为Excel或CSV + +## 📝 总结 + +本次更新确保了AI智能评分排名的完整数据被保存到历史报告中,用户可以: + +✅ 查看完整的13列评分数据 +✅ 了解每个评分维度的含义 +✅ 通过进度条直观看到各维度得分 +✅ 追踪历史评分数据,分析趋势 +✅ 对比不同时期的资金流向变化 + +这为智瞰龙虎系统的历史数据分析提供了强有力的支持! + +--- + +**更新完成!** ✅ + diff --git a/智策功能总览.md b/docs/智策功能总览.md similarity index 100% rename from 智策功能总览.md rename to docs/智策功能总览.md diff --git a/智策定时分析使用指南.md b/docs/智策定时分析使用指南.md similarity index 100% rename from 智策定时分析使用指南.md rename to docs/智策定时分析使用指南.md diff --git a/智策定时分析功能完成说明.md b/docs/智策定时分析功能完成说明.md similarity index 100% rename from 智策定时分析功能完成说明.md rename to docs/智策定时分析功能完成说明.md diff --git a/智策定时分析更新日志.md b/docs/智策定时分析更新日志.md similarity index 100% rename from 智策定时分析更新日志.md rename to docs/智策定时分析更新日志.md diff --git a/智策定时分析部署清单.md b/docs/智策定时分析部署清单.md similarity index 100% rename from 智策定时分析部署清单.md rename to docs/智策定时分析部署清单.md diff --git a/智策板块使用指南.md b/docs/智策板块使用指南.md similarity index 100% rename from 智策板块使用指南.md rename to docs/智策板块使用指南.md diff --git a/智策板块快速开始.md b/docs/智策板块快速开始.md similarity index 100% rename from 智策板块快速开始.md rename to docs/智策板块快速开始.md diff --git a/港股功能说明.md b/docs/港股功能说明.md similarity index 100% rename from 港股功能说明.md rename to docs/港股功能说明.md diff --git a/环境配置功能说明.md b/docs/环境配置功能说明.md similarity index 100% rename from 环境配置功能说明.md rename to docs/环境配置功能说明.md diff --git a/环境配置快速指南.md b/docs/环境配置快速指南.md similarity index 100% rename from 环境配置快速指南.md rename to docs/环境配置快速指南.md diff --git a/邮件配置指南.md b/docs/邮件配置指南.md similarity index 100% rename from 邮件配置指南.md rename to docs/邮件配置指南.md diff --git a/量化交易快速指南.md b/docs/量化交易快速指南.md similarity index 100% rename from 量化交易快速指南.md rename to docs/量化交易快速指南.md diff --git a/风险管理师增强功能说明.md b/docs/风险管理师增强功能说明.md similarity index 100% rename from 风险管理师增强功能说明.md rename to docs/风险管理师增强功能说明.md diff --git a/longhubang.db b/longhubang.db index 6a94a4304d2d63e60a4f0064320d61d2a77f3d2a..16e1aa0f94dab44b203cadf651b9662a32828ef5 100644 GIT binary patch delta 95314 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 delta 11805 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 RGF2^-yILjG8pb|S{trs<&N=`9 diff --git a/longhubang_db.py b/longhubang_db.py index e713818..25040e8 100644 --- a/longhubang_db.py +++ b/longhubang_db.py @@ -283,15 +283,16 @@ class LonghubangDatabase: return df def save_analysis_report(self, data_date_range, analysis_content, - recommended_stocks, summary): + recommended_stocks, summary, full_result=None): """ - 保存AI分析报告 + 保存AI分析报告(完整版) Args: data_date_range: 数据日期范围 - analysis_content: 分析内容 + analysis_content: 分析内容(JSON字符串或字典) recommended_stocks: 推荐股票列表 summary: 摘要 + full_result: 完整的分析结果字典(可选) Returns: int: 报告ID @@ -299,6 +300,10 @@ class LonghubangDatabase: conn = self.get_connection() cursor = conn.cursor() + # 如果传入的是字典,转换为JSON字符串 + if isinstance(analysis_content, dict): + analysis_content = json.dumps(analysis_content, ensure_ascii=False, indent=2) + cursor.execute(''' INSERT INTO longhubang_analysis (analysis_date, data_date_range, analysis_content, recommended_stocks, summary) @@ -373,6 +378,14 @@ class LonghubangDatabase: except: pass + # 解析analysis_content字段 + if report.get('analysis_content'): + try: + report['analysis_content_parsed'] = json.loads(report['analysis_content']) + except: + # 如果不是JSON格式,保持原样 + report['analysis_content_parsed'] = None + return report return None diff --git a/longhubang_engine.py b/longhubang_engine.py index 0bd28fb..649cf96 100644 --- a/longhubang_engine.py +++ b/longhubang_engine.py @@ -151,18 +151,40 @@ class LonghubangEngine: results["final_report"] = final_report print("✓ 最终报告生成完成") - # 阶段7: 保存分析报告到数据库 - print("\n[阶段7] 保存分析报告...") + # 阶段7: 保存完整分析报告到数据库 + print("\n[阶段7] 保存完整分析报告...") print("-" * 60) data_date_range = self._get_date_range(data_list) + + # 转换评分排名数据为可序列化格式 + scoring_ranking_data = [] + if scoring_df is not None and hasattr(scoring_df, 'to_dict'): + try: + # 转换DataFrame为字典列表,确保所有数据都被序列化 + scoring_ranking_data = scoring_df.to_dict('records') + print(f"✓ 评分排名数据已转换: {len(scoring_ranking_data)} 条记录") + except Exception as e: + print(f"⚠ 评分排名数据转换失败: {e}") + scoring_ranking_data = [] + + # 构建完整的分析内容(结构化) + full_analysis_content = { + "agents_analysis": agents_results, + "data_info": results["data_info"], + "scoring_ranking": scoring_ranking_data, + "final_report": final_report, + "timestamp": results["timestamp"] + } + report_id = self.database.save_analysis_report( data_date_range=data_date_range, - analysis_content=str(agents_results), + analysis_content=full_analysis_content, # 保存完整的结构化数据 recommended_stocks=recommended_stocks, - summary=final_report.get('summary', '') + summary=final_report.get('summary', ''), + full_result=results # 传入完整结果 ) results["report_id"] = report_id - print(f"✓ 报告已保存 (ID: {report_id})") + print(f"✓ 完整报告已保存 (ID: {report_id})") results["success"] = True diff --git a/longhubang_ui.py b/longhubang_ui.py index 267ab21..1a80b88 100644 --- a/longhubang_ui.py +++ b/longhubang_ui.py @@ -102,6 +102,11 @@ def display_longhubang(): def display_analysis_tab(): """显示分析标签页""" + # 检查是否触发批量分析(不立即删除标志) + if st.session_state.get('longhubang_batch_trigger'): + run_longhubang_batch_analysis() + return + st.subheader("🔍 龙虎榜综合分析") # 参数设置 @@ -383,6 +388,33 @@ def display_scoring_ranking(result): use_container_width=True ) + # 一键批量分析功能 + st.markdown("---") + + col_batch1, col_batch2, col_batch3 = st.columns([2, 1, 1]) + with col_batch1: + st.markdown("#### 🚀 批量深度分析") + st.caption("对TOP10股票进行完整的AI团队分析,获取投资评级和关键价位") + + with col_batch2: + batch_count = st.selectbox( + "分析数量", + options=[3, 5, 10], + index=0, + help="选择分析前N只股票" + ) + + with col_batch3: + st.write("") # 占位 + if st.button("🚀 开始批量分析", type="primary", use_container_width=True): + # 提取股票代码 + stock_codes = top10_df.head(batch_count)['股票代码'].tolist() + + # 存储到session_state,触发批量分析 + st.session_state.longhubang_batch_codes = stock_codes + st.session_state.longhubang_batch_trigger = True + st.rerun() + st.markdown("---") # 评分分布图表 @@ -687,32 +719,223 @@ def display_pdf_export_section(result): def display_history_tab(): - """显示历史报告标签页""" + """显示历史报告标签页(增强版)""" st.subheader("📚 历史分析报告") try: engine = LonghubangEngine() - reports_df = engine.get_historical_reports(limit=20) + reports_df = engine.get_historical_reports(limit=50) if reports_df.empty: st.info("暂无历史报告") return - st.dataframe( - reports_df, - column_config={ - "id": st.column_config.NumberColumn("ID", format="%d"), - "analysis_date": st.column_config.TextColumn("分析时间"), - "data_date_range": st.column_config.TextColumn("数据日期范围"), - "summary": st.column_config.TextColumn("摘要") - }, - hide_index=True, - use_container_width=True - ) + st.info(f"💾 共有 {len(reports_df)} 条历史报告") + + # 显示报告列表 + st.markdown("### 📋 报告列表") + + # 为每条报告创建展开面板 + for idx, row in reports_df.iterrows(): + report_id = row['id'] + analysis_date = row['analysis_date'] + data_date_range = row['data_date_range'] + summary = row['summary'] + + # 创建展开面板 + with st.expander( + f"📄 报告 #{report_id} | {analysis_date} | 数据范围: {data_date_range}", + expanded=False + ): + # 获取完整报告详情 + report_detail = engine.get_report_detail(report_id) + + if not report_detail: + st.warning("无法加载报告详情") + continue + + # 显示摘要 + st.markdown("#### 📝 报告摘要") + st.info(summary) + + st.markdown("---") + + # 显示推荐股票 + recommended_stocks = report_detail.get('recommended_stocks', []) + if recommended_stocks: + st.markdown(f"#### 🎯 推荐股票 ({len(recommended_stocks)}只)") + + # 创建DataFrame显示 + df_stocks = pd.DataFrame(recommended_stocks) + st.dataframe( + df_stocks, + column_config={ + "rank": st.column_config.NumberColumn("排名", format="%d"), + "code": st.column_config.TextColumn("代码"), + "name": st.column_config.TextColumn("名称"), + "net_inflow": st.column_config.NumberColumn("净流入", format="%.2f"), + "reason": st.column_config.TextColumn("推荐理由"), + "confidence": st.column_config.TextColumn("确定性"), + "hold_period": st.column_config.TextColumn("持有周期") + }, + hide_index=True, + use_container_width=True + ) + + st.markdown("---") + + # 尝试解析完整分析内容 + analysis_content_parsed = report_detail.get('analysis_content_parsed') + + if analysis_content_parsed and isinstance(analysis_content_parsed, dict): + # 显示AI分析师团队报告 + agents_analysis = analysis_content_parsed.get('agents_analysis', {}) + + if agents_analysis: + st.markdown("#### 🤖 AI分析师团队报告") + + agent_info = { + 'youzi': {'title': '🎯 游资行为分析师', 'icon': '🎯'}, + 'stock': {'title': '📈 个股潜力分析师', 'icon': '📈'}, + 'theme': {'title': '🔥 题材追踪分析师', 'icon': '🔥'}, + 'risk': {'title': '⚠️ 风险控制专家', 'icon': '⚠️'}, + 'chief': {'title': '👔 首席策略师', 'icon': '👔'} + } + + for agent_key, info in agent_info.items(): + agent_data = agents_analysis.get(agent_key, {}) + if agent_data: + with st.expander(f"{info['icon']} {info['title']}", expanded=False): + analysis = agent_data.get('analysis', '暂无分析') + st.markdown(analysis) + st.caption(f"分析时间: {agent_data.get('timestamp', 'N/A')}") + + # 显示AI评分排名 + scoring_ranking = analysis_content_parsed.get('scoring_ranking', []) + if scoring_ranking: + st.markdown("---") + st.markdown("#### 🏆 AI智能评分排名 (TOP10)") + + df_scoring = pd.DataFrame(scoring_ranking[:10]) + + # 显示完整的评分表格 + st.dataframe( + df_scoring, + column_config={ + "排名": st.column_config.NumberColumn("排名", format="%d"), + "股票名称": st.column_config.TextColumn("股票名称", width="medium"), + "股票代码": st.column_config.TextColumn("代码", width="small"), + "综合评分": st.column_config.NumberColumn( + "综合评分", + format="%.1f", + help="总分100分" + ), + "资金含金量": st.column_config.ProgressColumn( + "资金含金量", + format="%d分", + min_value=0, + max_value=30 + ), + "净买入额": st.column_config.ProgressColumn( + "净买入额", + format="%d分", + min_value=0, + max_value=25 + ), + "卖出压力": st.column_config.ProgressColumn( + "卖出压力", + format="%d分", + min_value=0, + max_value=20 + ), + "机构共振": st.column_config.ProgressColumn( + "机构共振", + format="%d分", + min_value=0, + max_value=15 + ), + "加分项": st.column_config.ProgressColumn( + "加分项", + format="%d分", + min_value=0, + max_value=10 + ), + "顶级游资": st.column_config.NumberColumn("顶级游资", format="%d家"), + "买方数": st.column_config.NumberColumn("买方数", format="%d家"), + "机构参与": st.column_config.TextColumn("机构参与"), + "净流入": st.column_config.NumberColumn("净流入(元)", format="%.2f") + }, + hide_index=True, + use_container_width=True + ) + + # 显示评分说明 + with st.expander("📖 评分维度说明", expanded=False): + st.markdown(""" + **AI智能评分体系 (总分100分)** + + - **资金含金量** (0-30分):顶级游资+10分,知名游资+5分,普通游资+1.5分 + - **净买入额** (0-25分):根据净流入金额大小评分 + - **卖出压力** (0-20分):卖出比例越低得分越高 + - **机构共振** (0-15分):机构+游资共振15分最高 + - **加分项** (0-10分):主力集中度、热门概念、连续上榜等 + + 💡 评分越高,表示该股票受到资金青睐程度越高! + """) + + # 显示数据概况 + data_info = analysis_content_parsed.get('data_info', {}) + if data_info: + st.markdown("---") + st.markdown("#### 📊 数据概况") + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("龙虎榜记录", f"{data_info.get('total_records', 0)} 条") + with col2: + st.metric("涉及股票", f"{data_info.get('total_stocks', 0)} 只") + with col3: + st.metric("涉及游资", f"{data_info.get('total_youzi', 0)} 个") + + else: + # 如果无法解析,显示原始内容 + st.markdown("#### 📄 原始分析内容") + analysis_content = report_detail.get('analysis_content', '') + if analysis_content: + st.text_area("", value=analysis_content[:2000], height=200, disabled=True) + if len(analysis_content) > 2000: + st.caption("(内容过长,仅显示前2000字符)") + + # 导出按钮 + st.markdown("---") + col_export1, col_export2 = st.columns(2) + + with col_export1: + if st.button(f"📥 导出为PDF", key=f"export_pdf_{report_id}"): + st.info("PDF导出功能开发中...") + + with col_export2: + if st.button(f"📋 加载到分析页", key=f"load_report_{report_id}"): + # 将历史报告加载到当前分析结果中 + if analysis_content_parsed: + # 重建完整的result结构 + loaded_result = { + "success": True, + "timestamp": report_detail.get('analysis_date', ''), + "data_info": analysis_content_parsed.get('data_info', {}), + "agents_analysis": analysis_content_parsed.get('agents_analysis', {}), + "scoring_ranking": pd.DataFrame(analysis_content_parsed.get('scoring_ranking', [])) if analysis_content_parsed.get('scoring_ranking') else None, + "final_report": analysis_content_parsed.get('final_report', {}), + "recommended_stocks": report_detail.get('recommended_stocks', []) + } + st.session_state.longhubang_result = loaded_result + st.success('✅ 报告已加载到分析页面,请切换到"龙虎榜分析"标签查看') except Exception as e: st.error(f"❌ 加载历史报告失败: {str(e)}") + import traceback + st.code(traceback.format_exc()) def display_statistics_tab(): @@ -790,6 +1013,365 @@ def display_statistics_tab(): st.error(f"❌ 加载统计数据失败: {str(e)}") +def run_longhubang_batch_analysis(): + """执行龙虎榜TOP股票批量分析(遵循统一调用规范)""" + + st.markdown("## 🚀 龙虎榜TOP股票批量分析") + st.markdown("---") + + # 检查是否已有分析结果 + if st.session_state.get('longhubang_batch_results'): + display_longhubang_batch_results(st.session_state.longhubang_batch_results) + + # 返回按钮 + col_back, col_clear = st.columns(2) + with col_back: + if st.button("🔙 返回龙虎榜分析", use_container_width=True): + # 清除所有批量分析相关状态 + if 'longhubang_batch_trigger' in st.session_state: + del st.session_state.longhubang_batch_trigger + if 'longhubang_batch_codes' in st.session_state: + del st.session_state.longhubang_batch_codes + if 'longhubang_batch_results' in st.session_state: + del st.session_state.longhubang_batch_results + st.rerun() + + with col_clear: + if st.button("🔄 重新分析", use_container_width=True): + # 清除结果,保留触发标志和代码 + if 'longhubang_batch_results' in st.session_state: + del st.session_state.longhubang_batch_results + st.rerun() + + return + + # 获取股票代码列表 + stock_codes = st.session_state.get('longhubang_batch_codes', []) + + if not stock_codes: + st.error("未找到股票代码列表") + # 清除触发标志 + if 'longhubang_batch_trigger' in st.session_state: + del st.session_state.longhubang_batch_trigger + return + + st.info(f"即将分析 {len(stock_codes)} 只股票:{', '.join(stock_codes)}") + + # 返回按钮 + if st.button("🔙 取消返回", type="secondary"): + # 清除所有批量分析相关状态 + if 'longhubang_batch_trigger' in st.session_state: + del st.session_state.longhubang_batch_trigger + if 'longhubang_batch_codes' in st.session_state: + del st.session_state.longhubang_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 'longhubang_batch_trigger' in st.session_state: + del st.session_state.longhubang_batch_trigger + if 'longhubang_batch_codes' in st.session_state: + del st.session_state.longhubang_batch_codes + st.rerun() + + if start_analysis: + # 导入统一分析函数(遵循统一规范) + from app import analyze_single_stock_for_batch + import concurrent.futures + import time + + st.markdown("---") + st.info("⏳ 正在执行批量分析,请稍候...") + + # 进度显示 + 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="1y", + enabled_analysts_config={ + 'technical': True, + 'fundamental': True, + 'fund_flow': True, + 'risk': True, + 'sentiment': False, + 'news': False + }, + selected_model='deepseek-chat' + ) + + results.append({ + "code": code, + "result": result + }) + + except Exception as e: + results.append({ + "code": code, + "result": {"success": False, "error": str(e)} + }) + + else: + # 并行分析 + status_text.text(f"并行分析 {len(stock_codes)} 只股票...") + + def analyze_one(code): + try: + 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' + ) + return {"code": code, "result": result} + except Exception as e: + return {"code": code, "result": {"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): + completed += 1 + progress_bar.progress(completed / len(stock_codes)) + status_text.text(f"已完成 {completed}/{len(stock_codes)}") + results.append(future.result()) + + # 清除进度 + progress_bar.empty() + status_text.empty() + + # 计算统计 + elapsed_time = time.time() - start_time + success_count = sum(1 for r in results if r.get("result", {}).get("success")) + failed_count = len(results) - success_count + + st.success(f"✅ 批量分析完成!成功 {success_count} 只,失败 {failed_count} 只,耗时 {elapsed_time:.1f}秒") + + # 保存结果到session_state + st.session_state.longhubang_batch_results = { + "results": results, + "total": len(results), + "success": success_count, + "failed": failed_count, + "elapsed_time": elapsed_time + } + + time.sleep(0.5) + st.rerun() + + +def display_longhubang_batch_results(batch_results: dict): + """显示龙虎榜批量分析结果""" + + st.markdown("### 📊 批量分析结果") + + results = batch_results.get("results", []) + total = batch_results.get("total", 0) + success = batch_results.get("success", 0) + failed = batch_results.get("failed", 0) + elapsed_time = batch_results.get("elapsed_time", 0) + + # 统计信息 + col1, col2, col3, col4 = st.columns(4) + with col1: + st.metric("总计", total) + with col2: + st.metric("成功", success) + with col3: + st.metric("失败", failed) + with col4: + st.metric("耗时", f"{elapsed_time:.1f}秒") + + st.markdown("---") + + # 失败的股票 + failed_results = [r for r in results if not r.get("result", {}).get("success")] + if failed_results: + with st.expander(f"❌ 失败股票 ({len(failed_results)}只)", expanded=False): + for item in failed_results: + code = item.get("code", "") + error = item.get("result", {}).get("error", "未知错误") + st.error(f"**{code}**: {error}") + + # 成功的股票 + success_results = [r for r in results if r.get("result", {}).get("success")] + + if not success_results: + st.warning("⚠️ 没有成功分析的股票") + return + + st.markdown("### 🎯 分析结果详情") + + # 显示每只股票的分析结果(使用统一字段名) + for item in success_results: + code = item.get("code", "") + result = item.get("result", {}) + final_decision = result.get("final_decision", {}) + stock_info = result.get("stock_info", {}) + + # 使用统一字段名 + rating = final_decision.get("rating", "未知") + confidence = final_decision.get("confidence_level", "N/A") + entry_range = final_decision.get("entry_range", "N/A") + take_profit = final_decision.get("take_profit", "N/A") + stop_loss = final_decision.get("stop_loss", "N/A") + target_price = final_decision.get("target_price", "N/A") + advice = final_decision.get("advice", "") + + # 评级颜色 + if "强烈买入" in rating or "买入" in rating: + rating_color = "🟢" + elif "卖出" in rating: + rating_color = "🔴" + else: + rating_color = "🟡" + + with st.expander(f"{rating_color} {code} {stock_info.get('name', '')} - {rating} (信心度: {confidence})", expanded=False): + col1, col2, col3 = st.columns(3) + + with col1: + st.markdown("**基本信息**") + st.write(f"当前价: {stock_info.get('current_price', 'N/A')}") + st.write(f"目标价: {target_price}") + + with col2: + st.markdown("**进出场位置**") + st.write(f"进场区间: {entry_range}") + st.write(f"止盈位: {take_profit}") + + with col3: + st.markdown("**风控**") + st.write(f"止损位: {stop_loss}") + st.write(f"评级: {rating}") + + if advice: + st.markdown("**投资建议**") + st.info(advice) + + # 添加到监测按钮 + if st.button(f"➕ 加入监测", key=f"add_monitor_{code}"): + add_to_monitor_from_longhubang(code, stock_info.get('name', ''), final_decision) + + +def add_to_monitor_from_longhubang(code: str, name: str, final_decision: dict): + """从龙虎榜分析结果添加到监测列表""" + try: + from monitor_db import monitor_db + import re + + # 提取数据(使用统一字段名和解析逻辑) + rating = final_decision.get("rating", "持有") + entry_range = final_decision.get("entry_range", "") + take_profit_str = final_decision.get("take_profit", "") + stop_loss_str = final_decision.get("stop_loss", "") + + # 解析进场区间 + 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, stop_loss = None, 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 + + if stop_loss_str: + try: + numbers = re.findall(r'\d+\.?\d*', str(stop_loss_str)) + if numbers: + stop_loss = float(numbers[0]) + except: + pass + + # 验证必需参数 + if not all([entry_min, entry_max, take_profit, stop_loss]): + st.error("❌ 分析结果缺少完整的进场区间和止盈止损信息") + return + + # 添加到监测 + monitor_db.add_monitored_stock( + symbol=code, + name=name, + rating=rating, + entry_range={"min": entry_min, "max": entry_max}, + take_profit=take_profit, + stop_loss=stop_loss, + check_interval=60, + notification_enabled=True + ) + + st.success(f"✅ {code} 已成功加入监测列表!") + + except Exception as e: + st.error(f"❌ 添加监测失败: {str(e)}") + + # 测试函数 if __name__ == "__main__": st.set_page_config( diff --git a/portfolio_stocks.db b/portfolio_stocks.db index e7995cac409025e1de66c94285215d8e68cb1f5d..9cd4be4a90c843bd0a48359236209f1a65fe9b08 100644 GIT binary patch delta 126 zcmZp8z}WDBae_3X$V3@uMv;vP3;Fr^zB4fM&1T?V%J0cHn{W1JL4|I<$+P8RSQr=> zf+qi#JH_Us@%2!D?Bvt(%1TBC3Wi2jMrKxq26_g@rbdPaT>K9hnE9_V@L%PB&Hs)6 e8&LldegOt9W=Tc^0|P@-3y?Y7o1f^*H~;``JtM~e delta 112 zcmZp8z}WDBae_3X&_o$$Mxl)f3;FpO7#LXiW;5_F<@e^cX7Xuy<;hp%g#@@jI{2?L@L%PB&HsS^8&Jm)egOt9W=Tc^0|P@- N3l 0 else None, quantity=new_quantity if new_quantity > 0 else None, note=new_note, diff --git a/stock_analysis.db b/stock_analysis.db index c29f662f84cc211173bc3c5c3b63805363d181db..7feb71818ba4c667e5c278272248393ad39db11f 100644 GIT binary patch delta 64682 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 delta 385 zcmWm8NjDS#0KoBCkPxP^Wy+SF_)}3SGuEg+t6x;zaZ$FkFbK+Z+FCLTxuWCJUp1d-+pPe3psaq(Yg=tN(eK zXw`Kjcyx#aB_v_Vl5EM52a+ofB~S9DKpsh<6iKl}bqSpS+h3(l2pI z$bbyWkPOR+jLJv(Bx5oz6EZ1NGA*BFMrLJB=4C+^Wl6rsvaHCetjW53l?~aHq 0: - first_row = df.iloc[0] - for col in df.columns: - print(f" {col}: {first_row[col]}") - else: - print(f" data字段为None") - - # 重要事件 - events = risk_data.get('important_events') - if events: - print(f"\n重要事件数据:") - print(f" has_data: {events.get('has_data')}") - df = events.get('data') - if df is not None: - print(f" 记录数: {len(df)}") - print(f" 列名: {list(df.columns)}") - print(f"\n 前3条数据:") - print(df.head(3)) - else: - print(f" data字段为None") - - # 3. 测试格式化后的数据 - print("\n" + "=" * 80) - print("【步骤3】测试格式化后传给AI的数据") - print("=" * 80) - - formatted_data = fetcher.format_risk_data_for_ai(risk_data) - - print("\n格式化后的数据长度:", len(formatted_data), "字符") - print("\n格式化后的数据内容:") - print(formatted_data) - - # 4. 保存到文件供查看 - print("\n" + "=" * 80) - print("【步骤4】保存数据到文件") - print("=" * 80) - - with open("risk_data_debug_output.txt", "w", encoding="utf-8") as f: - f.write("=" * 80 + "\n") - f.write("原始数据结构\n") - f.write("=" * 80 + "\n\n") - - # 保存原始数据(不包含DataFrame,只保存基本信息) - f.write("data_success: " + str(risk_data.get('data_success')) + "\n\n") - - for key in ['lifting_ban', 'shareholder_reduction', 'important_events']: - data = risk_data.get(key) - if data: - f.write(f"\n{key}:\n") - f.write(f" has_data: {data.get('has_data')}\n") - f.write(f" query: {data.get('query')}\n") - df = data.get('data') - if df is not None: - f.write(f" 记录数: {len(df)}\n") - f.write(f" 列名: {list(df.columns)}\n") - - f.write("\n" + "=" * 80 + "\n") - f.write("格式化后传给AI的数据\n") - f.write("=" * 80 + "\n\n") - f.write(formatted_data) - - print("✓ 数据已保存到 risk_data_debug_output.txt") - - # 5. 检查数据是否完整 - print("\n" + "=" * 80) - print("【步骤5】数据完整性检查") - print("=" * 80) - - if "【大股东减持数据】" in formatted_data: - print("✓ 格式化数据中包含大股东减持数据") - - # 检查是否有实际内容 - reduction_section = formatted_data.split("【大股东减持数据】")[1].split("=" * 80)[0] - if len(reduction_section.strip()) > 100: - print(f"✓ 减持数据部分有 {len(reduction_section)} 字符") - else: - print(f"⚠️ 减持数据部分只有 {len(reduction_section)} 字符,可能数据不完整") - else: - print("❌ 格式化数据中未找到大股东减持数据") - - print("\n" + "=" * 80) - print("测试完成!请查看 risk_data_debug_output.txt 文件") - print("=" * 80) - - -if __name__ == "__main__": - import sys - - # 默认测试300433,也可以通过命令行参数指定其他股票 - symbol = sys.argv[1] if len(sys.argv) > 1 else "300433" - - test_full_data_flow(symbol) -