Files
2026-01-25 16:53:55 +08:00

1230 lines
47 KiB
Python

"""
新闻流量监测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
def display_news_flow_monitor():
"""显示新闻流量监测主界面"""
st.title("📰 新闻流量监测")
# 功能说明
with st.expander("💡 功能说明 - 流量为王理念", expanded=False):
st.markdown("""
### 核心公式
**接盘总量 = 流量 × 转化率 × 客单价**
### 核心理念
- **流量高潮 = 价格高潮 = 逃命时刻**
- 当热搜、媒体报道、KOL转发同时达到高潮时,就是出货时机
- 短线操作:快进快出,紧跟龙头
### 流量阶段
1. **启动** - 刚开始发酵,可关注
2. **加速** - 增速加快,可参与
3. **分歧** - 多空分歧,需谨慎
4. **一致** - 流量高潮,危险!准备跑路
5. **退潮** - 热度下降,及时止盈止损
### 流量类型
- **存量流量型**:出生自带顶流(政策/大事件),时间窗口短(2-3天)
- **增量流量型**:逐步发酵(话题传播),时间窗口长(5-10天)
""")
# 标签页
tabs = st.tabs([
"📊 仪表盘",
"🔥 实时监测",
"⚠️ 预警中心",
"📈 趋势分析",
"📚 历史记录",
"⚙️ 设置"
])
with tabs[0]:
display_dashboard()
with tabs[1]:
display_realtime_monitor()
with tabs[2]:
display_alert_center()
with tabs[3]:
display_trend_analysis()
with tabs[4]:
display_history_records()
with tabs[5]:
display_settings()
def display_dashboard():
"""显示仪表盘"""
st.subheader("📊 流量仪表盘")
try:
from news_flow_engine import news_flow_engine
dashboard_data = news_flow_engine.get_dashboard_data()
except Exception as e:
st.error(f"获取仪表盘数据失败: {e}")
return
# 核心指标卡片
col1, col2, col3, col4 = st.columns(4)
latest = dashboard_data.get('latest_snapshot', {})
sentiment = dashboard_data.get('latest_sentiment', {})
ai = dashboard_data.get('latest_ai_analysis', {})
with col1:
score = latest.get('total_score', 0) if latest else 0
level = latest.get('flow_level', '无数据') if latest else '无数据'
st.metric("流量得分", f"{score}", delta=level)
with col2:
sent_idx = sentiment.get('sentiment_index', 50) if sentiment else 50
sent_class = sentiment.get('sentiment_class', '中性') if sentiment else '中性'
st.metric("情绪指数", f"{sent_idx}", delta=sent_class)
with col3:
stage = sentiment.get('flow_stage', '未知') if sentiment else '未知'
k_val = sentiment.get('viral_k', 1.0) if sentiment else 1.0
st.metric("流量阶段", stage, delta=f"K值:{k_val}")
with col4:
advice = ai.get('advice', '观望') if ai else '观望'
confidence = ai.get('confidence', 50) if ai else 50
st.metric("AI建议", advice, delta=f"置信度:{confidence}%")
st.divider()
# 两列布局
left_col, right_col = st.columns([2, 1])
with left_col:
# 流量趋势图
st.markdown("#### 流量趋势(7天)")
trend = dashboard_data.get('flow_trend', {})
if trend.get('dates'):
fig = go.Figure()
fig.add_trace(go.Scatter(
x=trend['dates'],
y=trend['avg_scores'],
mode='lines+markers',
name='平均得分',
line=dict(color='#1f77b4', width=2)
))
fig.add_trace(go.Scatter(
x=trend['dates'],
y=trend['max_scores'],
mode='lines',
name='最高得分',
line=dict(color='#ff7f0e', dash='dash')
))
fig.update_layout(
height=250,
margin=dict(l=0, r=0, t=20, b=0),
legend=dict(orientation="h", yanchor="bottom", y=1.02)
)
st.plotly_chart(fig, width='stretch')
st.caption(f"趋势: {trend.get('trend', '无数据')} - {trend.get('analysis', '')[:50]}...")
else:
st.info("暂无趋势数据,请先执行监测")
with right_col:
# 最近预警
st.markdown("#### 最近预警")
alerts = dashboard_data.get('recent_alerts', [])
if alerts:
for alert in alerts[:5]:
level = alert.get('alert_level', 'info')
icon = {'danger': '🔴', 'warning': '🟠', 'info': '🔵'}.get(level, '⚪')
st.markdown(f"{icon} **{alert.get('title', '')[:20]}...**")
st.caption(alert.get('created_at', '')[:16])
else:
st.info("暂无预警")
st.divider()
# 热点词云和TOP100新闻
display_wordcloud_and_top_news()
st.divider()
# 调度器状态
scheduler = dashboard_data.get('scheduler_status', {})
if scheduler:
st.markdown("#### ⏰ 定时任务状态")
cols = st.columns(4)
cols[0].metric("运行状态", "运行中" if scheduler.get('running') else "已停止")
next_runs = scheduler.get('next_run_times', {})
cols[1].caption(f"热点同步: {next_runs.get('sync_hotspots', 'N/A')[:16] if next_runs.get('sync_hotspots') else 'N/A'}")
cols[2].caption(f"预警生成: {next_runs.get('generate_alerts', 'N/A')[:16] if next_runs.get('generate_alerts') else 'N/A'}")
cols[3].caption(f"深度分析: {next_runs.get('deep_analysis', 'N/A')[:16] if next_runs.get('deep_analysis') else 'N/A'}")
def display_wordcloud_and_top_news():
"""显示热点词云和跨平台TOP100新闻"""
# 尝试获取最新数据
try:
from news_flow_db import news_flow_db
from news_flow_data import NewsFlowDataFetcher
# 获取最近的快照
recent_snapshots = news_flow_db.get_recent_snapshots(limit=1)
if not recent_snapshots:
st.info("暂无数据,请先执行实时监测")
return
snapshot_id = recent_snapshots[0]['id']
detail = news_flow_db.get_snapshot_detail(snapshot_id)
hot_topics = detail.get('hot_topics', [])
except Exception as e:
st.warning(f"获取数据失败: {e}")
return
# 两列布局
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("#### ☁️ 热点词云")
if hot_topics:
# 使用Plotly生成词云效果(气泡图模拟)
import random
# 准备数据
words_data = []
for i, topic in enumerate(hot_topics[:30]):
words_data.append({
'word': topic.get('topic', '')[:10],
'heat': topic.get('heat', 10),
'x': random.uniform(0, 100),
'y': random.uniform(0, 100),
})
if words_data:
df_words = pd.DataFrame(words_data)
# 创建气泡图模拟词云
fig = px.scatter(
df_words,
x='x',
y='y',
size='heat',
text='word',
color='heat',
color_continuous_scale='YlOrRd',
size_max=60
)
fig.update_traces(
textposition='middle center',
textfont=dict(size=12),
marker=dict(opacity=0.7)
)
fig.update_layout(
height=350,
showlegend=False,
xaxis=dict(visible=False),
yaxis=dict(visible=False),
margin=dict(l=0, r=0, t=0, b=0),
coloraxis_showscale=False
)
st.plotly_chart(fig, width='stretch', key="wordcloud_chart")
else:
st.info("暂无热点词云数据")
with col2:
st.markdown("#### 📊 热点话题TOP20")
if hot_topics:
for i, topic in enumerate(hot_topics[:20], 1):
heat = topic.get('heat', 0)
cross = topic.get('cross_platform', 0)
# 热度条
heat_pct = min(heat, 100)
st.markdown(f"**{i}. {topic.get('topic', '')[:15]}**")
st.progress(heat_pct / 100)
st.caption(f"热度: {heat} | 跨{cross}个平台")
else:
st.info("暂无热点话题")
st.divider()
# 跨平台热点新闻TOP100
st.markdown("#### 📰 跨平台热点新闻TOP100")
try:
# 获取所有平台新闻
fetcher = NewsFlowDataFetcher()
multi_result = fetcher.get_multi_platform_news()
if multi_result.get('success'):
# 汇总所有新闻
all_news = []
for platform_data in multi_result.get('platforms_data', []):
if platform_data.get('success'):
platform_name = platform_data.get('platform_name', '')
category = platform_data.get('category', '')
weight = platform_data.get('weight', 5)
for news in platform_data.get('data', []):
rank = news.get('rank', 99)
# 计算综合得分
score = (100 - rank) * weight
all_news.append({
'排名': len(all_news) + 1,
'平台': platform_name,
'类别': {'finance': '财经', 'social': '社交', 'news': '新闻', 'tech': '科技'}.get(category, '其他'),
'标题': (news.get('title') or '')[:40],
'平台排名': rank,
'综合分': score,
})
# 按综合分排序
all_news.sort(key=lambda x: x['综合分'], reverse=True)
# 取TOP100
top_100 = all_news[:100]
# 重新编排名
for i, news in enumerate(top_100, 1):
news['排名'] = i
if top_100:
df_news = pd.DataFrame(top_100)
# 添加筛选
filter_col1, filter_col2 = st.columns([1, 3])
with filter_col1:
category_filter = st.selectbox(
"按类别筛选",
["全部", "财经", "社交", "新闻", "科技"],
key="news_category_filter"
)
if category_filter != "全部":
df_news = df_news[df_news['类别'] == category_filter]
st.dataframe(
df_news[['排名', '平台', '类别', '标题', '综合分']],
width='stretch',
hide_index=True,
height=400
)
st.caption(f"共 {len(df_news)} 条新闻 | 数据来源: {multi_result.get('success_count', 0)} 个平台")
else:
st.info("暂无新闻数据")
else:
st.warning("获取新闻数据失败")
except Exception as e:
st.error(f"加载新闻失败: {e}")
def display_realtime_monitor():
"""显示实时监测"""
st.subheader("🔥 实时监测")
# 监测参数
col1, col2, col3 = st.columns([1, 1, 1])
with col1:
category = st.selectbox(
"平台类别",
["全部平台", "财经平台", "社交媒体", "新闻媒体", "科技媒体"],
key="monitor_category"
)
category_map = {
"全部平台": None,
"财经平台": "finance",
"社交媒体": "social",
"新闻媒体": "news",
"科技媒体": "tech",
}
with col2:
st.write("")
st.write("")
run_btn = st.button("🚀 开始AI智能分析", type="primary", width='stretch')
with col3:
st.write("")
st.write("")
# 空占位
if run_btn:
with st.spinner("🤖 AI正在分析全网热点新闻..."):
try:
from news_flow_engine import news_flow_engine
cat = category_map.get(category)
# 显示分析进度
progress_bar = st.progress(0)
status_text = st.empty()
status_text.text("📊 获取多平台新闻数据...")
progress_bar.progress(10)
result = news_flow_engine.run_full_analysis(category=cat, include_ai=True)
progress_bar.progress(100)
status_text.empty()
progress_bar.empty()
if result['success']:
st.session_state['news_flow_result'] = result
# 清除之前的PDF缓存
if 'news_flow_pdf_data' in st.session_state:
del st.session_state['news_flow_pdf_data']
st.success(f"✅ AI分析完成!耗时 {result.get('duration', 0):.1f} 秒")
else:
st.error(f"❌ 分析失败: {result.get('error')}")
except Exception as e:
st.error(f"❌ 分析异常: {e}")
# 显示结果
if 'news_flow_result' in st.session_state:
display_analysis_results(st.session_state['news_flow_result'])
def display_analysis_results(result: dict):
"""显示分析结果"""
st.divider()
flow_data = result.get('flow_data', {})
model_data = result.get('model_data', {})
sentiment_data = result.get('sentiment_data', {})
ai_analysis = result.get('ai_analysis')
trading_signals = result.get('trading_signals', {})
# 核心指标
st.markdown("### 📊 核心指标")
cols = st.columns(5)
with cols[0]:
score = flow_data.get('total_score', 0)
level = flow_data.get('level', '中')
level_color = {'极高': '🔴', '高': '🟠', '中': '🟡', '低': '🔵'}.get(level, '⚪')
st.metric("流量得分", f"{score}", delta=f"{level_color} {level}")
with cols[1]:
if sentiment_data:
sent = sentiment_data.get('sentiment', {})
st.metric("情绪指数", f"{sent.get('sentiment_index', 50)}",
delta=sent.get('sentiment_class', '中性'))
with cols[2]:
if sentiment_data:
stage = sentiment_data.get('flow_stage', {})
signal = stage.get('signal', '观察')
st.metric("流量阶段", stage.get('stage_name', '未知'), delta=signal)
with cols[3]:
if model_data:
viral = model_data.get('viral_k', {})
st.metric("K值", f"{viral.get('k_value', 1.0)}", delta=viral.get('trend', ''))
with cols[4]:
signal = trading_signals.get('overall_signal', '观望')
conf = trading_signals.get('confidence', 50)
signal_color = {'卖出': '🔴', '观望': '🟡', '买入': '🟢', '关注': '🔵'}.get(signal, '⚪')
st.metric("交易信号", f"{signal_color} {signal}", delta=f"置信度:{conf}%")
# 关键提示
key_msg = trading_signals.get('key_message', '')
if key_msg:
if '危险' in key_msg or '逃命' in key_msg:
st.error(f"⚠️ {key_msg}")
elif '加速' in key_msg or '买入' in key_msg:
st.success(f"💡 {key_msg}")
else:
st.info(f"💡 {key_msg}")
st.divider()
# 详细分析(两列)
left_col, right_col = st.columns(2)
with left_col:
# 流量模型
st.markdown("#### 🔬 流量模型分析")
if model_data:
# 潜力计算
potential = model_data.get('potential', {})
conversion = model_data.get('conversion', {})
st.markdown(f"""
- **接盘潜力**: {potential.get('potential_volume', 0):.1f}亿元 ({potential.get('potential_level', '未知')})
- **转化率**: {conversion.get('conversion_rate', 0):.4%}
- **流量类型**: {model_data.get('flow_type', {}).get('flow_type', '未知')}
""")
# 流量类型详情
flow_type = model_data.get('flow_type', {})
if flow_type.get('characteristics'):
st.caption(f"特征: {', '.join(flow_type.get('characteristics', [])[:2])}")
st.caption(f"时间窗口: {flow_type.get('time_window', 'N/A')}")
# 情绪分析
st.markdown("#### 💭 情绪分析")
if sentiment_data:
sentiment = sentiment_data.get('sentiment', {})
flow_stage = sentiment_data.get('flow_stage', {})
momentum = sentiment_data.get('momentum', {})
st.markdown(f"""
- **情绪分类**: {sentiment.get('sentiment_class', '中性')} ({sentiment.get('sentiment_index', 50)}分)
- **流量阶段**: {flow_stage.get('stage_name', '未知')} - {flow_stage.get('signal', '观察')}
- **情绪动量**: {momentum.get('momentum', 1.0)} ({momentum.get('trend', '稳定')})
- **风险等级**: {sentiment_data.get('risk_level', '中等')}
""")
st.caption(sentiment_data.get('advice', ''))
with right_col:
# AI分析(如果有)
if ai_analysis:
st.markdown("#### 🤖 AI智能分析")
# 投资建议
advice = ai_analysis.get('investment_advice', {})
if advice:
advice_emoji = {'买入': '🟢', '持有': '🔵', '观望': '🟡', '回避': '🔴'}.get(advice.get('advice', '观望'), '⚪')
st.markdown(f"**{advice_emoji} AI建议**: {advice.get('advice', '观望')} (置信度: {advice.get('confidence', 50)}%)")
key_msg = advice.get('key_message', '')
if key_msg:
st.info(f"💡 {key_msg}")
if advice.get('action_plan'):
with st.expander("📋 行动计划"):
for i, plan in enumerate(advice.get('action_plan', [])[:5], 1):
st.write(f"{i}. {plan}")
# 风险评估
risk = ai_analysis.get('risk_assess', {})
if risk:
risk_color = {'低': '🟢', '中': '🟡', '高': '🔴'}.get(risk.get('risk_level', '中'), '🟡')
st.markdown(f"**{risk_color} 风险等级**: {risk.get('risk_level', '未知')} (分数: {risk.get('risk_score', 50)}/100)")
else:
st.info("未运行AI分析。选择'完整分析(含AI)'模式获取AI智能分析。")
st.divider()
# AI详细分析区域(如果有AI分析)
if ai_analysis:
st.markdown("### 📊 AI深度分析报告")
# 热门题材
sector_analysis = ai_analysis.get('sector_analysis', {})
hot_themes = sector_analysis.get('hot_themes', [])
if hot_themes:
st.markdown("#### 🔥 今日热门题材")
theme_cols = st.columns(min(len(hot_themes), 4))
for i, theme in enumerate(hot_themes[:4]):
with theme_cols[i]:
heat_emoji = {'极高': '🔴', '高': '🟠', '中': '🟡'}.get(theme.get('heat_level', '中'), '🟡')
st.metric(
theme.get('theme', '未知'),
f"{heat_emoji} {theme.get('heat_level', '中')}",
delta=theme.get('sustainability', '')
)
st.caption(f"题材来源: {', '.join([t.get('source', '')[:15] for t in hot_themes[:3]])}")
# 受益板块详细分析
benefited_sectors = sector_analysis.get('benefited_sectors', [])
if benefited_sectors:
st.markdown("#### 📈 受益板块分析")
for sector in benefited_sectors[:5]:
with st.expander(f"**{sector.get('name', '')}** - 置信度 {sector.get('confidence', 0)}%"):
st.write(f"**分析**: {sector.get('reason', '')}")
if sector.get('related_concepts'):
st.write(f"**相关概念**: {', '.join(sector.get('related_concepts', []))}")
if sector.get('leader_characteristics'):
st.write(f"**龙头特征**: {sector.get('leader_characteristics', '')}")
# 多板块深度分析(多次AI调用结果)
multi_sector = ai_analysis.get('multi_sector', {})
sector_analyses = multi_sector.get('sector_analyses', [])
if sector_analyses:
st.markdown("#### 🎯 板块深度分析(多Agent分析)")
st.caption(f"共分析 {len(sector_analyses)} 个热门板块,耗时 {multi_sector.get('analysis_time', 0)} 秒")
# 板块核心指标卡片
sector_cols = st.columns(min(len(sector_analyses), 4))
for i, sector in enumerate(sector_analyses[:4]):
with sector_cols[i]:
heat_emoji = {'极高': '🔴', '高': '🟠', '中': '🟡', '低': '🔵'}.get(
sector.get('heat_level', '中'), '🟡'
)
outlook_emoji = {'看涨': '📈', '震荡': '📊', '看跌': '📉'}.get(
sector.get('short_term_outlook', '震荡'), '📊'
)
st.metric(
sector.get('sector_name', '未知'),
f"{heat_emoji} 热度{sector.get('heat_score', 50)}",
delta=f"{outlook_emoji} {sector.get('short_term_outlook', '震荡')}"
)
# 显示关键指标
indicators = sector.get('key_indicators', {})
if indicators:
indicator_text = ' | '.join([f"{k}:{v}" for k, v in list(indicators.items())[:3]])
st.caption(indicator_text)
# 详细分析展开
for sector in sector_analyses:
with st.expander(f"📊 {sector.get('sector_name', '')} - 详细分析"):
# 驱动因素
drivers = sector.get('drivers', [])
if drivers:
st.markdown("**驱动因素**:")
for driver in drivers[:3]:
impact_emoji = '✅' if driver.get('impact') == '正面' else '❌'
st.write(f"- {impact_emoji} [{driver.get('type', '')}] {driver.get('content', '')}")
# 预判理由
st.markdown(f"**短期预判**: {sector.get('short_term_outlook', '震荡')} - {sector.get('outlook_reason', '')}")
# 龙头股
leaders = sector.get('leader_stocks', [])
if leaders:
st.markdown("**板块龙头股**:")
leader_data = []
for stock in leaders[:5]:
leader_data.append({
'代码': stock.get('code', ''),
'名称': stock.get('name', ''),
'理由': stock.get('reason', '')[:30],
'策略': stock.get('strategy', '')[:30],
})
if leader_data:
st.dataframe(pd.DataFrame(leader_data), width='stretch', hide_index=True)
# 投资建议
st.info(f"💡 {sector.get('investment_advice', '')}")
st.warning(f"⚠️ {sector.get('risk_warning', '')}")
# 股票推荐
stock_recommend = ai_analysis.get('stock_recommend', {})
recommended_stocks = stock_recommend.get('recommended_stocks', [])
if recommended_stocks:
st.markdown("#### 💰 AI选股推荐")
st.warning("⚠️ 以下为AI分析结果,仅供参考,不构成投资建议。股市有风险,投资需谨慎!")
# 用表格展示推荐股票
stock_data = []
for stock in recommended_stocks[:8]:
stock_data.append({
'代码': stock.get('code', ''),
'名称': stock.get('name', ''),
'板块': stock.get('sector', ''),
'风险': stock.get('risk_level', '中'),
'目标空间': stock.get('target_space', '-'),
'推荐理由': stock.get('reason', '')[:30] + '...' if len(stock.get('reason', '')) > 30 else stock.get('reason', ''),
})
if stock_data:
df_stocks = pd.DataFrame(stock_data)
st.dataframe(df_stocks, width='stretch', hide_index=True)
# 展开查看详情
with st.expander("📋 查看详细推荐理由"):
for stock in recommended_stocks[:8]:
st.markdown(f"**{stock.get('code', '')} {stock.get('name', '')}**")
st.write(f"- 板块: {stock.get('sector', '')}")
st.write(f"- 理由: {stock.get('reason', '')}")
st.write(f"- 催化剂: {stock.get('catalyst', '')}")
st.write(f"- 策略: {stock.get('strategy', '')}")
if stock.get('attention_points'):
st.write(f"- 注意: {', '.join(stock.get('attention_points', []))}")
st.divider()
# 整体策略
if stock_recommend.get('overall_strategy'):
st.info(f"📊 **整体策略**: {stock_recommend.get('overall_strategy', '')}")
if stock_recommend.get('timing_advice'):
st.caption(f"⏰ 时机建议: {stock_recommend.get('timing_advice', '')}")
if stock_recommend.get('risk_warning'):
st.error(f"⚠️ {stock_recommend.get('risk_warning', '')}")
# 机会评估
opportunity = sector_analysis.get('opportunity_assessment', '')
trading_suggestion = sector_analysis.get('trading_suggestion', '')
if opportunity or trading_suggestion:
st.markdown("#### 💡 综合评估")
if opportunity:
st.write(opportunity)
if trading_suggestion:
st.success(f"📌 {trading_suggestion}")
st.divider()
# 热门话题和新闻
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 🔥 热门话题TOP10")
hot_topics = result.get('hot_topics', [])[:10]
if hot_topics:
df = pd.DataFrame([{
'话题': t['topic'],
'热度': t['heat'],
'跨平台': t.get('cross_platform', 0),
} for t in hot_topics])
fig = px.bar(df, x='热度', y='话题', orientation='h',
color='热度', color_continuous_scale='Oranges')
fig.update_layout(height=300, margin=dict(l=0, r=0, t=0, b=0))
st.plotly_chart(fig, width='stretch')
with col2:
st.markdown("#### 📰 股票相关新闻TOP5")
stock_news = result.get('stock_news', [])[:5]
if stock_news:
for news in stock_news:
st.markdown(f"**[{news.get('platform_name', '')}]** {news.get('title', '')[:40]}...")
st.caption(f"关键词: {', '.join(news.get('matched_keywords', [])[:3])}")
else:
st.info("暂无股票相关新闻")
# PDF导出区域
display_pdf_export_section(result)
def display_pdf_export_section(result: dict):
"""显示PDF导出区域"""
st.divider()
st.markdown("### 📄 导出报告")
col1, col2, col3 = st.columns([3, 1, 1])
with col1:
st.write("将分析报告导出为PDF文件,方便保存和分享")
with col2:
if st.button("📥 生成PDF报告", type="primary", key="gen_pdf_btn"):
with st.spinner("正在生成PDF报告..."):
try:
from news_flow_pdf import NewsFlowPDFGenerator
generator = NewsFlowPDFGenerator()
pdf_path = generator.generate_report(result)
if pdf_path:
with open(pdf_path, "rb") as f:
pdf_bytes = f.read()
# 保存到session_state
st.session_state.news_flow_pdf_data = pdf_bytes
st.session_state.news_flow_pdf_filename = f"新闻流量分析报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
st.success("✅ PDF报告生成成功!")
st.rerun()
else:
st.error("❌ PDF生成失败")
except Exception as e:
st.error(f"❌ PDF生成失败: {str(e)}")
with col3:
# 如果已经生成了PDF,显示下载按钮
if 'news_flow_pdf_data' in st.session_state:
st.download_button(
label="💾 下载PDF",
data=st.session_state.news_flow_pdf_data,
file_name=st.session_state.news_flow_pdf_filename,
mime="application/pdf",
key="download_pdf_btn"
)
def display_alert_center():
"""显示预警中心"""
st.subheader("⚠️ 预警中心")
try:
from news_flow_alert import news_flow_alert_system
from news_flow_db import news_flow_db
except Exception as e:
st.error(f"模块加载失败: {e}")
return
# 预警统计
summary = news_flow_alert_system.get_alert_summary(days=7)
cols = st.columns(4)
cols[0].metric("总预警数", summary.get('total_count', 0))
cols[1].metric("危险预警", summary.get('danger_count', 0))
cols[2].metric("警告预警", summary.get('warning_count', 0))
cols[3].metric("提示预警", summary.get('info_count', 0))
st.divider()
# 预警列表
col1, col2 = st.columns([3, 1])
with col2:
days = st.selectbox("时间范围", [1, 3, 7, 30], index=2, key="alert_days")
alert_type = st.selectbox("预警类型", ["全部", "热度飙升", "流量高潮", "情绪极值", "病毒传播", "流量退潮"], key="alert_type_filter")
type_map = {
"热度飙升": "heat_surge",
"流量高潮": "flow_peak",
"情绪极值": "sentiment_extreme",
"病毒传播": "viral_spread",
"流量退潮": "flow_decline",
}
filter_type = type_map.get(alert_type) if alert_type != "全部" else None
alerts = news_flow_alert_system.get_alert_history(days=days, alert_type=filter_type)
with col1:
if alerts:
for alert in alerts[:20]:
level = alert.get('alert_level', 'info')
icon = {'danger': '🔴', 'warning': '🟠', 'info': '🔵'}.get(level, '⚪')
level_name = {'danger': '危险', 'warning': '警告', 'info': '提示'}.get(level, '未知')
with st.expander(f"{icon} [{level_name}] {alert.get('title', '')[:40]}..."):
st.markdown(alert.get('content', ''))
st.caption(f"时间: {alert.get('created_at', '')} | 类型: {alert.get('alert_type', '')}")
else:
st.info("暂无预警记录")
st.divider()
# 预警配置
st.markdown("#### ⚙️ 预警阈值配置")
config = news_flow_alert_system.get_threshold_config()
col1, col2, col3 = st.columns(3)
with col1:
heat_th = st.number_input("热度阈值", value=int(config.get('heat_threshold', 800)),
min_value=100, max_value=1000, key="heat_th")
k_th = st.number_input("K值阈值", value=float(config.get('viral_k_threshold', 1.5)),
min_value=1.0, max_value=3.0, step=0.1, key="k_th")
with col2:
sent_high = st.number_input("情绪高位阈值", value=int(config.get('sentiment_high_threshold', 90)),
min_value=70, max_value=100, key="sent_high")
sent_low = st.number_input("情绪低位阈值", value=int(config.get('sentiment_low_threshold', 20)),
min_value=0, max_value=30, key="sent_low")
with col3:
rank_th = st.number_input("排名变化阈值", value=int(config.get('rank_change_threshold', 10)),
min_value=5, max_value=50, key="rank_th")
if st.button("保存配置", key="save_alert_config"):
news_flow_alert_system.set_threshold('heat_threshold', heat_th)
news_flow_alert_system.set_threshold('viral_k_threshold', k_th)
news_flow_alert_system.set_threshold('sentiment_high_threshold', sent_high)
news_flow_alert_system.set_threshold('sentiment_low_threshold', sent_low)
news_flow_alert_system.set_threshold('rank_change_threshold', rank_th)
st.success("配置已保存")
def display_trend_analysis():
"""显示趋势分析"""
st.subheader("📈 趋势分析")
try:
from news_flow_engine import news_flow_engine
from news_flow_db import news_flow_db
except Exception as e:
st.error(f"模块加载失败: {e}")
return
# 时间范围选择
days = st.slider("分析天数", min_value=3, max_value=30, value=7, key="trend_days")
# 流量趋势
trend = news_flow_engine.get_flow_trend(days=days)
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("#### 流量趋势图")
if trend.get('dates'):
fig = go.Figure()
# 平均得分
fig.add_trace(go.Scatter(
x=trend['dates'],
y=trend['avg_scores'],
mode='lines+markers',
name='平均得分',
line=dict(color='#1f77b4', width=2),
fill='tozeroy',
fillcolor='rgba(31, 119, 180, 0.1)'
))
# 最高得分
fig.add_trace(go.Scatter(
x=trend['dates'],
y=trend['max_scores'],
mode='lines',
name='最高得分',
line=dict(color='#ff7f0e', dash='dash')
))
# 最低得分
fig.add_trace(go.Scatter(
x=trend['dates'],
y=trend.get('min_scores', []),
mode='lines',
name='最低得分',
line=dict(color='#2ca02c', dash='dot')
))
fig.update_layout(
height=350,
margin=dict(l=0, r=0, t=20, b=0),
legend=dict(orientation="h", yanchor="bottom", y=1.02),
yaxis_title="流量得分"
)
st.plotly_chart(fig, width='stretch')
else:
st.info("暂无趋势数据")
with col2:
st.markdown("#### 趋势分析")
st.markdown(f"**趋势方向**: {trend.get('trend', '无数据')}")
st.markdown(trend.get('analysis', '暂无分析'))
st.divider()
# 情绪趋势
st.markdown("#### 情绪趋势")
sentiments = news_flow_db.get_sentiment_history(limit=days * 3)
if sentiments:
df = pd.DataFrame([{
'时间': s.get('fetch_time', s.get('created_at', ''))[:16],
'情绪指数': s.get('sentiment_index', 50),
'K值': s.get('viral_k', 1.0),
'阶段': s.get('flow_stage', '未知'),
} for s in sentiments])
if not df.empty:
df = df.sort_values('时间')
fig = go.Figure()
fig.add_trace(go.Scatter(
x=df['时间'],
y=df['情绪指数'],
mode='lines+markers',
name='情绪指数',
line=dict(color='#9467bd', width=2)
))
# 添加阈值线
fig.add_hline(y=80, line_dash="dash", line_color="red",
annotation_text="乐观区", annotation_position="right")
fig.add_hline(y=20, line_dash="dash", line_color="green",
annotation_text="悲观区", annotation_position="right")
fig.update_layout(
height=250,
margin=dict(l=0, r=0, t=20, b=0),
yaxis_title="情绪指数"
)
st.plotly_chart(fig, width='stretch')
else:
st.info("暂无情绪数据")
st.divider()
# 每日统计表格
st.markdown("#### 每日统计")
stats = news_flow_db.get_daily_statistics(days)
if stats:
df = pd.DataFrame([{
'日期': s['date'],
'平均得分': s['avg_score'],
'最高得分': s['max_score'],
'最低得分': s['min_score'],
'采集次数': s['snapshot_count'],
'热门话题': ', '.join(s.get('top_topics', [])[:3]),
} for s in stats])
st.dataframe(df, width='stretch', hide_index=True)
else:
st.info("暂无统计数据")
def display_history_records():
"""显示历史记录"""
st.subheader("📚 历史记录")
try:
from news_flow_db import news_flow_db
except Exception as e:
st.error(f"模块加载失败: {e}")
return
# 获取历史快照
snapshots = news_flow_db.get_history_snapshots(limit=50)
if not snapshots:
st.info("暂无历史记录,请先执行监测")
return
# 快照列表
st.markdown("#### 分析报告列表")
for snapshot in snapshots[:20]:
score = snapshot.get('total_score', 0)
level = snapshot.get('flow_level', '中')
level_icon = {'极高': '🔴', '高': '🟠', '中': '🟡', '低': '🔵'}.get(level, '⚪')
with st.expander(f"{level_icon} {snapshot.get('fetch_time', '')} - 流量得分: {score} ({level})"):
# 获取详情
detail = news_flow_db.get_snapshot_detail(snapshot['id'])
if detail:
col1, col2 = st.columns(2)
with col1:
st.markdown("**基本信息**")
st.markdown(f"- 流量得分: {score}")
st.markdown(f"- 流量等级: {level}")
st.markdown(f"- 平台数: {snapshot.get('total_platforms', 0)}")
st.markdown(f"- 成功数: {snapshot.get('success_count', 0)}")
# 情绪信息
sentiment = detail.get('sentiment')
if sentiment:
st.markdown("**情绪分析**")
st.markdown(f"- 情绪指数: {sentiment.get('sentiment_index', 'N/A')}")
st.markdown(f"- 情绪分类: {sentiment.get('sentiment_class', 'N/A')}")
st.markdown(f"- 流量阶段: {sentiment.get('flow_stage', 'N/A')}")
with col2:
# AI分析
ai = detail.get('ai_analysis')
if ai:
st.markdown("**AI分析结果**")
st.markdown(f"- 投资建议: {ai.get('advice', 'N/A')}")
st.markdown(f"- 置信度: {ai.get('confidence', 'N/A')}%")
st.markdown(f"- 风险等级: {ai.get('risk_level', 'N/A')}")
if ai.get('summary'):
st.caption(ai['summary'][:100] + "...")
# 热门话题
topics = detail.get('hot_topics', [])[:5]
if topics:
st.markdown("**热门话题**")
for t in topics:
st.markdown(f"- {t['topic']} (热度:{t['heat']})")
st.divider()
# AI分析历史
st.markdown("#### AI分析历史")
ai_history = news_flow_db.get_ai_analysis_history(limit=10)
if ai_history:
df = pd.DataFrame([{
'时间': a.get('created_at', '')[:16],
'建议': a.get('advice', 'N/A'),
'置信度': f"{a.get('confidence', 0)}%",
'风险': a.get('risk_level', 'N/A'),
'摘要': (a.get('summary', '') or '')[:30] + "...",
} for a in ai_history])
st.dataframe(df, width='stretch', hide_index=True)
else:
st.info("暂无AI分析记录")
def display_settings():
"""显示设置"""
st.subheader("⚙️ 设置")
# 平台配置
st.markdown("#### 📡 支持的平台")
try:
from news_flow_data import NewsFlowDataFetcher
fetcher = NewsFlowDataFetcher()
platforms = fetcher.get_platform_list()
# 按类别分组
categories = {}
for p in platforms:
cat = p['category']
if cat not in categories:
categories[cat] = []
categories[cat].append(p)
cat_names = {
'social': '社交媒体',
'news': '新闻媒体',
'finance': '财经平台',
'tech': '科技媒体',
}
cols = st.columns(len(categories))
for i, (cat, items) in enumerate(categories.items()):
with cols[i]:
st.markdown(f"**{cat_names.get(cat, cat)}** ({len(items)}个)")
for p in items:
st.caption(f"- {p['name']} (权重:{p['weight']})")
except:
st.warning("无法加载平台列表")
st.divider()
# 定时任务配置
st.markdown("#### ⏰ 定时任务管理")
try:
from news_flow_scheduler import news_flow_scheduler
status = news_flow_scheduler.get_status()
col1, col2 = st.columns([1, 2])
with col1:
st.markdown(f"**运行状态**: {'✅ 运行中' if status.get('running') else '⏸️ 已停止'}")
if status.get('running'):
if st.button("停止调度器", key="stop_scheduler"):
news_flow_scheduler.stop()
st.success("调度器已停止")
st.rerun()
else:
if st.button("启动调度器", key="start_scheduler"):
news_flow_scheduler.start()
st.success("调度器已启动")
st.rerun()
with col2:
st.markdown("**任务配置**")
intervals = status.get('task_intervals', {})
enabled = status.get('task_enabled', {})
tasks = [
('sync_hotspots', '热点同步', 30),
('generate_alerts', '预警生成', 60),
('deep_analysis', '深度分析', 120),
]
for task_id, task_name, default_interval in tasks:
col_a, col_b, col_c = st.columns([2, 2, 1])
with col_a:
is_enabled = st.checkbox(
task_name,
value=enabled.get(task_id, True),
key=f"task_enabled_{task_id}"
)
with col_b:
interval = st.number_input(
"间隔(分钟)",
value=intervals.get(task_id, default_interval),
min_value=5,
max_value=240,
key=f"task_interval_{task_id}",
label_visibility="collapsed"
)
with col_c:
if st.button("立即执行", key=f"run_{task_id}"):
if task_id == 'sync_hotspots':
news_flow_scheduler.run_sync_now()
elif task_id == 'generate_alerts':
news_flow_scheduler.run_alerts_now()
else:
news_flow_scheduler.run_analysis_now()
st.success(f"{task_name} 已触发")
if st.button("保存任务配置", key="save_task_config"):
for task_id, _, _ in tasks:
news_flow_scheduler.set_task_enabled(
task_id,
st.session_state.get(f"task_enabled_{task_id}", True)
)
news_flow_scheduler.set_task_interval(
task_id,
st.session_state.get(f"task_interval_{task_id}", 30)
)
st.success("任务配置已保存")
except Exception as e:
st.warning(f"定时任务模块加载失败: {e}")
st.divider()
# 使用建议
st.markdown("#### 💡 使用建议")
st.markdown("""
1. **盘前分析** (09:00前)
- 运行快速分析,了解当日热点
- 关注流量阶段和情绪指数
2. **盘中监测** (交易时间)
- 开启定时任务自动监测
- 关注预警通知
3. **盘后复盘** (15:00后)
- 运行完整分析(含AI)
- 分析当日流量变化趋势
4. **风险提示**
- 当流量阶段进入"一致"时,立即警惕
- 当情绪指数>85或K值>1.5时,注意风险
- 本工具仅供参考,投资决策需谨慎
""")
# 入口
if __name__ == "__main__":
display_news_flow_monitor()