""" 智瞰龙虎UI界面模块 展示龙虎榜分析结果和推荐股票 """ import streamlit as st import plotly.graph_objects as go import plotly.express as px import pandas as pd from datetime import datetime, timedelta import time import base64 from longhubang_engine import LonghubangEngine from longhubang_pdf import LonghubangPDFGenerator import config def display_longhubang(): """显示智瞰龙虎主界面""" st.markdown("""

🎯 智瞰龙虎 - AI驱动的龙虎榜分析

""", unsafe_allow_html=True) st.markdown("---") # 功能说明 with st.expander("💡 智瞰龙虎系统介绍", expanded=False): st.markdown(""" ### 🌟 系统特色 **智瞰龙虎**是基于多AI智能体的龙虎榜深度分析系统,通过5位专业分析师的协同工作, 为您挖掘次日大概率上涨的潜力股票。 ### 🤖 AI分析师团队 1. **🎯 游资行为分析师** - 识别活跃游资及其操作风格 - 分析游资席位的进出特征 - 研判游资对个股的态度 2. **📈 个股潜力分析师** - 从龙虎榜数据挖掘潜力股 - 识别次日大概率上涨的股票 - 分析资金动向和技术形态 3. **🔥 题材追踪分析师** - 识别当前热点题材和概念 - 分析题材的炒作周期 - 预判题材的持续性 4. **⚠️ 风险控制专家** - 识别高风险股票和陷阱 - 分析游资出货信号 - 提供风险管理建议 5. **👔 首席策略师** - 综合所有分析师意见 - 给出最终推荐股票清单 - 提供具体操作策略 ### 📊 数据来源 数据来自**StockAPI龙虎榜接口**,包括: - 游资上榜交割单历史数据 - 股票买卖金额和净流入 - 热门概念和题材 - 更新时间:交易日下午5点40 ### 🎯 核心功能 - ✅ **潜力股挖掘** - AI识别次日大概率上涨股票 - ✅ **游资追踪** - 跟踪活跃游资的操作 - ✅ **题材识别** - 发现热点题材和龙头股 - ✅ **风险提示** - 识别高风险股票和陷阱 - ✅ **历史记录** - 存储所有龙虎榜数据 - ✅ **PDF报告** - 生成专业分析报告 """) st.markdown("---") # 创建标签页 tab1, tab2, tab3 = st.tabs([ "📊 龙虎榜分析", "📚 历史报告", "📈 数据统计" ]) with tab1: display_analysis_tab() with tab2: display_history_tab() with tab3: display_statistics_tab() def display_analysis_tab(): """显示分析标签页""" # 检查是否触发批量分析(不立即删除标志) if st.session_state.get('longhubang_batch_trigger'): run_longhubang_batch_analysis() return st.subheader("🔍 龙虎榜综合分析") # 参数设置 col1, col2 = st.columns([2, 2]) with col1: analysis_mode = st.selectbox( "分析模式", ["指定日期", "最近N天"], help="选择分析特定日期还是最近几天的数据" ) with col2: if analysis_mode == "指定日期": selected_date = st.date_input( "选择日期", value=datetime.now() - timedelta(days=1), help="选择要分析的龙虎榜日期" ) else: days = st.number_input( "最近天数", min_value=1, max_value=10, value=1, help="分析最近N天的龙虎榜数据" ) # 分析按钮 col1, col2 = st.columns([2, 2]) with col1: analyze_button = st.button("🚀 开始分析", type="primary", width='stretch') with col2: if st.button("🔄 清除结果", width='stretch'): if 'longhubang_result' in st.session_state: del st.session_state.longhubang_result st.success("已清除分析结果") st.rerun() st.markdown("---") # 开始分析 if analyze_button: # 清除之前的结果 if 'longhubang_result' in st.session_state: del st.session_state.longhubang_result # 准备参数(使用.env中配置的默认模型) if analysis_mode == "指定日期": date_str = selected_date.strftime('%Y-%m-%d') run_longhubang_analysis(date=date_str) else: run_longhubang_analysis(days=days) # 显示分析结果 if 'longhubang_result' in st.session_state: result = st.session_state.longhubang_result if result.get("success"): display_analysis_results(result) else: st.error(f"❌ 分析失败: {result.get('error', '未知错误')}") def run_longhubang_analysis(model=None, date=None, days=1): """运行龙虎榜分析""" import config model = model or config.DEFAULT_MODEL_NAME # 进度显示 progress_bar = st.progress(0) status_text = st.empty() try: status_text.text("🚀 初始化分析引擎...") progress_bar.progress(5) engine = LonghubangEngine(model=model) status_text.text("📊 正在获取龙虎榜数据...") progress_bar.progress(15) # 运行分析 result = engine.run_comprehensive_analysis(date=date, days=days) progress_bar.progress(90) if result.get("success"): # 保存结果 st.session_state.longhubang_result = result progress_bar.progress(100) status_text.text("✅ 分析完成!") time.sleep(1) status_text.empty() progress_bar.empty() # 自动刷新显示结果 st.rerun() else: st.error(f"❌ 分析失败: {result.get('error', '未知错误')}") except Exception as e: st.error(f"❌ 分析过程出错: {str(e)}") import traceback st.code(traceback.format_exc()) finally: progress_bar.empty() status_text.empty() def display_analysis_results(result): """显示分析结果""" st.success("✅ 龙虎榜分析完成!") st.info(f"📅 分析时间: {result.get('timestamp', 'N/A')}") # 数据概况 data_info = result.get('data_info', {}) col1, col2, col3, col4 = st.columns(4) 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)} 个") with col4: recommended = result.get('recommended_stocks', []) st.metric("推荐股票", f"{len(recommended)} 只", delta="AI筛选") # PDF导出功能 display_pdf_export_section(result) st.markdown("---") # 创建子标签页 tab1, tab2, tab3, tab4, tab5 = st.tabs([ "🏆 AI评分排名", "🎯 推荐股票", "🤖 AI分析师报告", "📊 数据详情", "📈 可视化图表" ]) with tab1: display_scoring_ranking(result) with tab2: display_recommended_stocks(result) with tab3: display_agents_reports(result) with tab4: display_data_details(result) with tab5: display_visualizations(result) def display_scoring_ranking(result): """显示AI智能评分排名""" st.subheader("🏆 AI智能评分排名") scoring_df = result.get('scoring_ranking') if scoring_df is None or (hasattr(scoring_df, 'empty') and scoring_df.empty): st.warning("暂无评分数据") return # 评分说明 with st.expander("📖 评分维度说明", expanded=False): st.markdown(""" ### 📊 AI智能评分体系 (总分100分) #### 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分) --- 💡 **评分越高,表示该股票受到资金青睐程度越高!** ⚠️ **但仍需结合市场环境、技术面等因素综合判断!** """) st.markdown("---") # 显示TOP10评分表格 st.markdown("### 🥇 TOP10 综合评分排名") # 兼容历史数据与类型统一,避免 Arrow 序列化错误 if isinstance(scoring_df, list): scoring_df = pd.DataFrame(scoring_df) numeric_cols = ['排名','综合评分','资金含金量','净买入额','卖出压力','机构共振','加分项','顶级游资','买方数','净流入'] for col in numeric_cols: if col in scoring_df.columns: scoring_df[col] = pd.to_numeric(scoring_df[col], errors='coerce') text_cols = ['股票名称','股票代码','机构参与'] for col in text_cols: if col in scoring_df.columns: scoring_df[col] = scoring_df[col].astype(str) top10_df = scoring_df.head(10).copy() if '排名' in top10_df.columns: top10_df['排名'] = pd.to_numeric(top10_df['排名'], errors='coerce').fillna(0).astype(int) # 格式化显示 st.dataframe( top10_df, column_config={ "排名": st.column_config.NumberColumn("排名", format="%d", width="small"), "股票名称": 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, width='stretch' ) # 一键批量分析功能 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只股票", key="batch_count_selector" ) # 同步更新session_state中的batch_count st.session_state.batch_count = batch_count with col_batch3: st.write("") # 占位 if st.button("🚀 开始批量分析", type="primary", width='stretch'): # 提取股票代码 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("---") # 评分分布图表 st.markdown("### 📊 评分分布可视化") col1, col2 = st.columns(2) with col1: # 综合评分柱状图 fig1 = px.bar( top10_df, x='股票名称', y='综合评分', title='TOP10 综合评分对比', text='综合评分', color='综合评分', color_continuous_scale='RdYlGn' ) fig1.update_traces(texttemplate='%{text:.1f}分', textposition='outside') fig1.update_layout( xaxis_tickangle=-45, showlegend=False, height=400 ) st.plotly_chart(fig1, config={'displayModeBar': False}, use_container_width=True) with col2: # 五维评分雷达图(显示批量分析数量的股票) if len(top10_df) > 0: display_count = min(5, len(top10_df)) fig2 = go.Figure() # 为每只股票添加雷达图 colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'] for i in range(display_count): stock = top10_df.iloc[i] fig2.add_trace(go.Scatterpolar( r=[ stock['资金含金量'] / 30 * 100, stock['净买入额'] / 25 * 100, stock['卖出压力'] / 20 * 100, stock['机构共振'] / 15 * 100, stock['加分项'] / 10 * 100 ], theta=['资金含金量', '净买入额', '卖出压力', '机构共振', '加分项'], fill='toself', name=f"{stock['股票名称']}", line_color=colors[i % len(colors)], fillcolor=colors[i % len(colors)], opacity=0.6 )) fig2.update_layout( polar=dict( radialaxis=dict( visible=True, range=[0, 100] ) ), showlegend=True, title=f"🏆 TOP{display_count} 五维评分对比", height=400, legend=dict( orientation="h", yanchor="auto", y=-0.2, xanchor="center", x=0.5 ) ) st.plotly_chart(fig2, config={'displayModeBar': False}, use_container_width=True) st.markdown("---") # 完整排名表格 st.markdown("### 📋 完整评分排名") st.dataframe( scoring_df, column_config={ "排名": st.column_config.NumberColumn("排名", format="%d", width="small"), "股票名称": st.column_config.TextColumn("股票名称"), "股票代码": st.column_config.TextColumn("代码"), "综合评分": st.column_config.NumberColumn("综合评分", format="%.1f"), "顶级游资": 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, width='stretch' ) def display_recommended_stocks(result): """显示推荐股票""" st.subheader("🎯 AI推荐股票") recommended = result.get('recommended_stocks', []) if not recommended: st.warning("暂无推荐股票") return st.info(f"💡 基于5位AI分析师的综合分析,系统识别出以下 **{len(recommended)}** 只潜力股票") # 创建DataFrame df_recommended = pd.DataFrame(recommended) # 显示表格 st.dataframe( df_recommended, 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"), "confidence": st.column_config.TextColumn("确定性"), "hold_period": st.column_config.TextColumn("持有周期"), "reason": st.column_config.TextColumn("推荐理由") }, hide_index=True, width='stretch' ) # 详细推荐理由 st.markdown("### 📝 详细推荐理由") for stock in recommended[:5]: # 只显示前5只 with st.expander(f"**{stock.get('rank', '-')}. {stock.get('name', '-')} ({stock.get('code', '-')})**"): col1, col2 = st.columns([2, 1]) with col1: st.markdown(f"**推荐理由:** {stock.get('reason', '暂无')}") st.markdown(f"**净流入:** {stock.get('net_inflow', 0):,.2f} 元") with col2: st.markdown(f"**确定性:** {stock.get('confidence', '-')}") st.markdown(f"**持有周期:** {stock.get('hold_period', '-')}") def display_agents_reports(result): """显示AI分析师报告""" st.subheader("🤖 AI分析师团队报告") agents_analysis = result.get('agents_analysis', {}) if not agents_analysis: st.warning("暂无分析报告") return # 各分析师报告 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=(agent_key == 'chief')): analysis = agent_data.get('analysis', '暂无分析') st.markdown(analysis) st.markdown(f"*{agent_data.get('agent_role', '')}*") st.caption(f"分析时间: {agent_data.get('timestamp', 'N/A')}") def display_data_details(result): """显示数据详情""" st.subheader("📊 龙虎榜数据详情") data_info = result.get('data_info', {}) summary = data_info.get('summary', {}) # TOP游资 if summary.get('top_youzi'): st.markdown("### 🏆 活跃游资 TOP10") youzi_data = [ {'排名': idx, '游资名称': name, '净流入金额': amount} for idx, (name, amount) in enumerate(list(summary['top_youzi'].items())[:10], 1) ] df_youzi = pd.DataFrame(youzi_data) st.dataframe( df_youzi, column_config={ "排名": st.column_config.NumberColumn("排名", format="%d"), "游资名称": st.column_config.TextColumn("游资名称"), "净流入金额": st.column_config.NumberColumn("净流入金额(元)", format="%.2f") }, hide_index=True, width='stretch' ) # TOP股票 if summary.get('top_stocks'): st.markdown("### 📈 资金净流入 TOP20 股票") df_stocks = pd.DataFrame(summary['top_stocks'][:20]) st.dataframe( df_stocks, column_config={ "code": st.column_config.TextColumn("股票代码"), "name": st.column_config.TextColumn("股票名称"), "net_inflow": st.column_config.NumberColumn("净流入金额(元)", format="%.2f") }, hide_index=True, width='stretch' ) # 热门概念 if summary.get('hot_concepts'): st.markdown("### 🔥 热门概念 TOP20") concepts_data = [ {'排名': idx, '概念名称': concept, '出现次数': count} for idx, (concept, count) in enumerate(list(summary['hot_concepts'].items())[:20], 1) ] df_concepts = pd.DataFrame(concepts_data) st.dataframe( df_concepts, column_config={ "排名": st.column_config.NumberColumn("排名", format="%d"), "概念名称": st.column_config.TextColumn("概念名称"), "出现次数": st.column_config.NumberColumn("出现次数", format="%d") }, hide_index=True, width='stretch' ) def display_visualizations(result): """显示可视化图表""" st.subheader("📈 数据可视化") data_info = result.get('data_info', {}) summary = data_info.get('summary', {}) # 资金流向图表 if summary.get('top_stocks'): st.markdown("### 💰 TOP20 股票资金净流入") stocks = summary['top_stocks'][:20] df_chart = pd.DataFrame(stocks) fig = px.bar( df_chart, x='name', y='net_inflow', title='TOP20 股票资金净流入金额', labels={'name': '股票名称', 'net_inflow': '净流入金额(元)'} ) fig.update_layout(xaxis_tickangle=-45) st.plotly_chart(fig, config={'displayModeBar': False}, use_container_width=True) # 热门概念图表 if summary.get('hot_concepts'): st.markdown("### 🔥 热门概念分布") concepts = list(summary['hot_concepts'].items())[:15] df_concepts = pd.DataFrame(concepts, columns=['概念', '次数']) fig = px.pie( df_concepts, values='次数', names='概念', title='热门概念出现次数分布' ) st.plotly_chart(fig, config={'displayModeBar': False}, use_container_width=True) def display_pdf_export_section(result): """显示PDF导出功能""" st.markdown("### 📄 导出报告") col1, col2, col3 = st.columns([2, 1, 1]) with col1: st.info("💡 点击按钮生成并下载专业分析报告") with col2: if st.button("📥 生成PDF", type="primary", width='stretch'): with st.spinner("正在生成PDF报告..."): try: generator = LonghubangPDFGenerator() pdf_path = generator.generate_pdf(result) # 读取PDF文件 with open(pdf_path, "rb") as f: pdf_bytes = f.read() # 提供下载 st.download_button( label="📥 下载PDF报告", data=pdf_bytes, file_name=f"智瞰龙虎报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf", mime="application/pdf", width='stretch' ) st.success("✅ PDF报告生成成功!") except Exception as e: st.error(f"❌ PDF生成失败: {str(e)}") with col3: if st.button("📝 生成Markdown", type="secondary", width='stretch'): with st.spinner("正在生成Markdown报告..."): try: # 生成Markdown内容 markdown_content = generate_markdown_report(result) # 提供下载 st.download_button( label="📥 下载Markdown报告", data=markdown_content, file_name=f"智瞰龙虎报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md", mime="text/markdown", width='stretch' ) st.success("✅ Markdown报告生成成功!") except Exception as e: st.error(f"❌ Markdown生成失败: {str(e)}") def generate_markdown_report(result_data: dict) -> str: """生成龙虎榜分析Markdown报告""" # 获取当前时间 current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S") # 标题页 markdown_content = f"""# 智瞰龙虎榜分析报告 **AI驱动的龙虎榜多维度分析系统** --- ## 📊 报告概览 - **生成时间**: {current_time} - **数据记录**: {result_data.get('data_info', {}).get('total_records', 0)} 条 - **涉及股票**: {result_data.get('data_info', {}).get('total_stocks', 0)} 只 - **涉及游资**: {result_data.get('data_info', {}).get('total_youzi', 0)} 个 - **AI分析师**: 5位专业分析师团队 - **分析模型**: DeepSeek AI Multi-Agent System > ⚠️ 本报告由AI系统基于龙虎榜公开数据自动生成,仅供参考,不构成投资建议。市场有风险,投资需谨慎。 --- ## 📈 数据概况 本次分析共涵盖 **{result_data.get('data_info', {}).get('total_records', 0)}** 条龙虎榜记录, 涉及 **{result_data.get('data_info', {}).get('total_stocks', 0)}** 只股票和 **{result_data.get('data_info', {}).get('total_youzi', 0)}** 个游资席位。 """ # 资金概况 summary = result_data.get('data_info', {}).get('summary', {}) markdown_content += f""" ### 💰 资金概况 - **总买入金额**: {summary.get('total_buy_amount', 0):,.2f} 元 - **总卖出金额**: {summary.get('total_sell_amount', 0):,.2f} 元 - **净流入金额**: {summary.get('total_net_inflow', 0):,.2f} 元 """ # TOP游资 if summary.get('top_youzi'): markdown_content += "### 🏆 活跃游资 TOP10\n\n| 排名 | 游资名称 | 净流入金额(元) |\n|------|----------|---------------|\n" for idx, (name, amount) in enumerate(list(summary['top_youzi'].items())[:10], 1): markdown_content += f"| {idx} | {name} | {amount:,.2f} |\n" markdown_content += "\n" # TOP股票 if summary.get('top_stocks'): markdown_content += "### 📈 资金净流入 TOP20 股票\n\n| 排名 | 股票代码 | 股票名称 | 净流入金额(元) |\n|------|----------|----------|---------------|\n" for idx, stock in enumerate(summary['top_stocks'][:20], 1): markdown_content += f"| {idx} | {stock['code']} | {stock['name']} | {stock['net_inflow']:,.2f} |\n" markdown_content += "\n" # 热门概念 if summary.get('hot_concepts'): markdown_content += "### 🔥 热门概念 TOP15\n\n" for idx, (concept, count) in enumerate(list(summary['hot_concepts'].items())[:15], 1): markdown_content += f"{idx}. {concept} ({count}次) \n" markdown_content += "\n" # 推荐股票 recommended = result_data.get('recommended_stocks', []) if recommended: markdown_content += f""" ## 🎯 AI推荐股票 基于5位AI分析师的综合分析,系统识别出以下 **{len(recommended)}** 只潜力股票, 这些股票在资金流向、游资关注度、题材热度等多个维度表现突出。 ### 推荐股票清单 | 排名 | 股票代码 | 股票名称 | 净流入金额 | 确定性 | 持有周期 | |------|----------|----------|------------|--------|----------| """ for stock in recommended[:10]: markdown_content += f"| {stock.get('rank', '-')} | {stock.get('code', '-')} | {stock.get('name', '-')} | {stock.get('net_inflow', 0):,.0f} | {stock.get('confidence', '-')} | {stock.get('hold_period', '-')} |\n" markdown_content += "\n### 推荐理由详解\n\n" for stock in recommended[:5]: # 只详细展示前5只 markdown_content += f"**{stock.get('rank', '-')}. {stock.get('name', '-')} ({stock.get('code', '-')})**\n\n" markdown_content += f"- 推荐理由: {stock.get('reason', '暂无')}\n" markdown_content += f"- 确定性: {stock.get('confidence', '-')}\n" markdown_content += f"- 持有周期: {stock.get('hold_period', '-')}\n\n" # AI分析师报告 agents_analysis = result_data.get('agents_analysis', {}) if agents_analysis: markdown_content += "## 🤖 AI分析师报告\n\n" markdown_content += "本报告由5位AI专业分析师从不同维度进行分析,综合形成投资建议:\n\n" markdown_content += "- **游资行为分析师** - 分析游资操作特征和意图\n" markdown_content += "- **个股潜力分析师** - 挖掘次日大概率上涨的股票\n" markdown_content += "- **题材追踪分析师** - 识别热点题材和轮动机会\n" markdown_content += "- **风险控制专家** - 识别高风险股票和市场陷阱\n" markdown_content += "- **首席策略师** - 综合研判并给出最终建议\n\n" agent_titles = { 'youzi': '游资行为分析师', 'stock': '个股潜力分析师', 'theme': '题材追踪分析师', 'risk': '风险控制专家', 'chief': '首席策略师综合研判' } for agent_key, agent_title in agent_titles.items(): agent_data = agents_analysis.get(agent_key, {}) if agent_data: markdown_content += f"### {agent_title}\n\n" analysis_text = agent_data.get('analysis', '暂无分析') # 处理文本中的换行 analysis_text = analysis_text.replace('\n', '\n\n') markdown_content += f"{analysis_text}\n\n" markdown_content += """ --- *报告由智瞰龙虎AI系统自动生成* """ return markdown_content def display_history_tab(): """显示历史报告标签页(增强版)""" st.subheader("📚 历史分析报告") try: engine = LonghubangEngine() reports_df = engine.get_historical_reports(limit=50) if reports_df.empty: st.info("暂无历史报告") return 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, width='stretch' ) 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]) # 类型统一,避免Arrow序列化错误 numeric_cols = ['排名','综合评分','资金含金量','净买入额','卖出压力','机构共振','加分项','顶级游资','买方数','净流入'] for col in numeric_cols: if col in df_scoring.columns: df_scoring[col] = pd.to_numeric(df_scoring[col], errors='coerce') text_cols = ['股票名称','股票代码','机构参与'] for col in text_cols: if col in df_scoring.columns: df_scoring[col] = df_scoring[col].astype(str) if '排名' in df_scoring.columns: df_scoring['排名'] = pd.to_numeric(df_scoring['排名'], errors='coerce').fillna(0).astype(int) # 显示完整的评分表格 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, width='stretch' ) # 显示评分说明 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, col_export3 = st.columns(3) with col_export1: if st.button(f"📥 导出为PDF", key=f"export_pdf_{report_id}"): st.info("PDF导出功能开发中...") with col_export2: # 使用session_state来管理按钮状态,避免需要点击两次的问题 load_key = f"load_report_{report_id}" if st.button(f"📋 加载到分析页", key=load_key): # 将历史报告加载到当前分析结果中 if analysis_content_parsed: # 重建完整的result结构 scoring_data = analysis_content_parsed.get('scoring_ranking', []) if scoring_data: df_scoring = pd.DataFrame(scoring_data) # 类型统一,避免Arrow序列化错误 numeric_cols = ['排名','综合评分','资金含金量','净买入额','卖出压力','机构共振','加分项','顶级游资','买方数','净流入'] for col in numeric_cols: if col in df_scoring.columns: df_scoring[col] = pd.to_numeric(df_scoring[col], errors='coerce') text_cols = ['股票名称','股票代码','机构参与'] for col in text_cols: if col in df_scoring.columns: df_scoring[col] = df_scoring[col].astype(str) if '排名' in df_scoring.columns: df_scoring['排名'] = pd.to_numeric(df_scoring['排名'], errors='coerce').fillna(0).astype(int) else: df_scoring = None 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": df_scoring, "final_report": analysis_content_parsed.get('final_report', {}), "recommended_stocks": report_detail.get('recommended_stocks', []) } st.session_state.longhubang_result = loaded_result # 使用rerun来立即刷新页面状态 st.success('✅ 报告已加载到分析页面,请切换到"龙虎榜分析"标签查看') st.rerun() with col_export3: # 删除按钮 delete_key = f"delete_report_{report_id}" if st.button(f"🗑️ 删除报告", key=delete_key, type="secondary"): # 使用session_state来管理删除确认状态 st.session_state[f"confirm_delete_{report_id}"] = True st.rerun() # 删除确认对话框 if st.session_state.get(f"confirm_delete_{report_id}", False): st.warning(f"⚠️ 确认删除报告 #{report_id}?此操作不可撤销!") col_confirm1, col_confirm2 = st.columns(2) with col_confirm1: if st.button(f"✅ 确认删除", key=f"confirm_delete_yes_{report_id}", type="primary"): try: # 调用数据库删除方法 - 修复属性名 engine.database.delete_analysis_report(report_id) st.success(f"✅ 报告 #{report_id} 已成功删除") # 清除确认状态并刷新页面 if f"confirm_delete_{report_id}" in st.session_state: del st.session_state[f"confirm_delete_{report_id}"] st.rerun() except Exception as e: st.error(f"❌ 删除失败: {str(e)}") with col_confirm2: if st.button(f"❌ 取消", key=f"confirm_delete_no_{report_id}"): # 清除确认状态 if f"confirm_delete_{report_id}" in st.session_state: del st.session_state[f"confirm_delete_{report_id}"] st.rerun() except Exception as e: st.error(f"❌ 加载历史报告失败: {str(e)}") import traceback st.code(traceback.format_exc()) def display_statistics_tab(): """显示数据统计标签页""" st.subheader("📈 数据统计") try: engine = LonghubangEngine() stats = engine.get_statistics() # 基本统计 col1, col2, col3, col4 = st.columns(4) with col1: st.metric("总记录数", f"{stats.get('total_records', 0):,}") with col2: st.metric("股票总数", f"{stats.get('total_stocks', 0):,}") with col3: st.metric("游资总数", f"{stats.get('total_youzi', 0):,}") with col4: st.metric("分析报告", f"{stats.get('total_reports', 0):,}") # 日期范围 date_range = stats.get('date_range', {}) if date_range: st.info(f"📅 数据日期范围: {date_range.get('start', 'N/A')} 至 {date_range.get('end', 'N/A')}") st.markdown("---") # 活跃游资排名 st.markdown("### 🏆 历史活跃游资排名 (近30天)") end_date = datetime.now().strftime('%Y-%m-%d') start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d') top_youzi_df = engine.get_top_youzi(start_date, end_date, limit=20) if not top_youzi_df.empty: st.dataframe( top_youzi_df, column_config={ "youzi_name": st.column_config.TextColumn("游资名称"), "trade_count": st.column_config.NumberColumn("交易次数", format="%d"), "total_net_inflow": st.column_config.NumberColumn("总净流入(元)", format="%.2f") }, hide_index=True, width='stretch' ) st.markdown("---") # 热门股票排名 st.markdown("### 📈 历史热门股票排名 (近30天)") top_stocks_df = engine.get_top_stocks(start_date, end_date, limit=20) if not top_stocks_df.empty: st.dataframe( top_stocks_df, column_config={ "stock_code": st.column_config.TextColumn("股票代码"), "stock_name": st.column_config.TextColumn("股票名称"), "youzi_count": st.column_config.NumberColumn("游资数量", format="%d"), "total_net_inflow": st.column_config.NumberColumn("总净流入(元)", format="%.2f") }, hide_index=True, width='stretch' ) except Exception as e: 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("🔙 返回龙虎榜分析", width='stretch'): # 清除所有批量分析相关状态 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("🔄 重新分析", width='stretch'): # 清除结果,保留触发标志和代码 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", width='stretch'): start_analysis = True with col_cancel: if st.button("❌ 取消", type="secondary", width='stretch'): # 清除所有批量分析相关状态 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=config.DEFAULT_MODEL_NAME ) 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=config.DEFAULT_MODEL_NAME ) 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( page_title="智瞰龙虎", page_icon="🎯", layout="wide" ) display_longhubang()