diff --git a/README.md b/README.md
index 756dc13..ec93969 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
- 初心:在股市摸爬滚打多年,自学自编各种指标,花冤柉钱学习了各种战法各种策略,也曾入各种小班,总是赚少赔多,逐渐失去在股市玩的信心。自从去年deepseek上市,一直探索用ai辅助分析,且近日受tradingagents项目启发(感谢原作),多agent结合跟踪主力资金战法(某指每年收费6000rmb),用各种ai辅助编程,拼凑了这么个小程序,根据软件提供的辅助信息,实盘测试盈率还是挺高的,并且逐步形成了自己的交易系统,近一个月来,账户也慢慢在扰亏为盈。开源此软件的目的,就是为了使像我一样的小散,不再迷范。也许这个软件不能让你发大财,但是他能给你足够的信心。最后提醒:股市有风险,入市需谨慎!
+## ⭐20261.25第一更 - 新闻流量监测 📈
+
+实时监测百度、微博、东财、财联社、抖音、B站等20个平台热点新闻,调用ai分析对A股板块及股票的影响,生成分析报告
+
## ⭐ 1214更新 - 净利增长策略 📈
新增“净利增长策略”选股板块,专注稳健成长股票:
diff --git a/app.py b/app.py
index f030097..8636077 100644
--- a/app.py
+++ b/app.py
@@ -22,6 +22,7 @@ from main_force_ui import display_main_force_selector
from sector_strategy_ui import display_sector_strategy
from longhubang_ui import display_longhubang
from smart_monitor_ui import smart_monitor_ui
+from news_flow_ui import display_news_flow_monitor
# 页面配置
st.set_page_config(
@@ -294,7 +295,7 @@ def main():
if st.button("🏠 股票分析", width='stretch', key="nav_home", help="返回首页,进行单只股票的深度分析"):
# 清除所有功能页面标志
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull']:
+ 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
@@ -307,28 +308,28 @@ def main():
if st.button("💰 主力选股", width='stretch', key="nav_main_force", help="基于主力资金流向的选股策略"):
st.session_state.show_main_force = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
- 'show_longhubang', 'show_portfolio', 'show_low_price_bull']:
+ 'show_longhubang', 'show_portfolio', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
if st.button("🐂 低价擒牛", width='stretch', key="nav_low_price_bull", help="低价高成长股票筛选策略"):
st.session_state.show_low_price_bull = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
- 'show_longhubang', 'show_portfolio', 'show_main_force', 'show_small_cap', 'show_profit_growth']:
+ 'show_longhubang', 'show_portfolio', 'show_main_force', 'show_small_cap', 'show_profit_growth', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
if st.button("📊 小市值策略", width='stretch', key="nav_small_cap", help="小盘高成长股票筛选策略"):
st.session_state.show_small_cap = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
- 'show_longhubang', 'show_portfolio', 'show_main_force', 'show_low_price_bull', 'show_profit_growth']:
+ 'show_longhubang', 'show_portfolio', 'show_main_force', 'show_low_price_bull', 'show_profit_growth', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
if st.button("📈 净利增长", width='stretch', key="nav_profit_growth", help="净利润增长稳健股票筛选策略"):
st.session_state.show_profit_growth = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
- 'show_longhubang', 'show_portfolio', 'show_main_force', 'show_low_price_bull', 'show_small_cap']:
+ 'show_longhubang', 'show_portfolio', 'show_main_force', 'show_low_price_bull', 'show_small_cap', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
@@ -339,14 +340,21 @@ def main():
if st.button("🎯 智策板块", width='stretch', key="nav_sector_strategy", help="AI板块策略分析"):
st.session_state.show_sector_strategy = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_longhubang', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull']:
+ 'show_longhubang', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
if st.button("🐉 智瞰龙虎", width='stretch', key="nav_longhubang", help="龙虎榜深度分析"):
st.session_state.show_longhubang = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull']:
+ 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull', 'show_news_flow']:
+ if key in st.session_state:
+ del st.session_state[key]
+
+ if st.button("📰 新闻流量", width='stretch', key="nav_news_flow", help="新闻流量监测与短线指导"):
+ st.session_state.show_news_flow = True
+ for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
+ 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull', 'show_longhubang']:
if key in st.session_state:
del st.session_state[key]
@@ -357,21 +365,21 @@ def main():
if st.button("📊 持仓分析", width='stretch', key="nav_portfolio", help="投资组合分析与定时跟踪"):
st.session_state.show_portfolio = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_sector_strategy', 'show_longhubang', 'show_smart_monitor', 'show_low_price_bull']:
+ 'show_sector_strategy', 'show_longhubang', 'show_smart_monitor', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
if st.button("🤖 AI盯盘", width='stretch', key="nav_smart_monitor", help="DeepSeek AI自动盯盘决策交易(支持A股T+1)"):
st.session_state.show_smart_monitor = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull']:
+ 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
if st.button("📡 实时监测", width='stretch', key="nav_monitor", help="价格监控与预警提醒"):
st.session_state.show_monitor = True
for key in ['show_history', 'show_main_force', 'show_longhubang', 'show_portfolio',
- 'show_config', 'show_sector_strategy', 'show_smart_monitor', 'show_low_price_bull']:
+ 'show_config', 'show_sector_strategy', 'show_smart_monitor', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
@@ -381,7 +389,7 @@ def main():
if st.button("📖 历史记录", width='stretch', key="nav_history", help="查看历史分析记录"):
st.session_state.show_history = True
for key in ['show_monitor', 'show_longhubang', 'show_portfolio', 'show_config',
- 'show_main_force', 'show_sector_strategy', 'show_low_price_bull']:
+ 'show_main_force', 'show_sector_strategy', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
@@ -389,7 +397,7 @@ def main():
if st.button("⚙️ 环境配置", width='stretch', key="nav_config", help="系统设置与API配置"):
st.session_state.show_config = True
for key in ['show_history', 'show_monitor', 'show_main_force', 'show_sector_strategy',
- 'show_longhubang', 'show_portfolio', 'show_low_price_bull']:
+ 'show_longhubang', 'show_portfolio', 'show_low_price_bull', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
@@ -521,6 +529,11 @@ def main():
display_portfolio_manager()
return
+ # 检查是否显示新闻流量监测
+ if 'show_news_flow' in st.session_state and st.session_state.show_news_flow:
+ display_news_flow_monitor()
+ return
+
# 检查是否显示环境配置
if 'show_config' in st.session_state and st.session_state.show_config:
display_config_manager()
diff --git a/docs/新闻流量监测功能说明.md b/docs/新闻流量监测功能说明.md
new file mode 100644
index 0000000..abcb2ef
--- /dev/null
+++ b/docs/新闻流量监测功能说明.md
@@ -0,0 +1,400 @@
+# 新闻流量监测功能说明
+
+## 📰 功能概述
+
+**新闻流量监测**是基于"流量为王"炒股理念为本系统新增的短线交易指导功能模块。通过实时监测20+主流平台的新闻热点和流量数据,帮助短线投资者把握题材炒作的节奏,识别流量高潮(卖出信号)和流量爆发(买入机会)。
+
+---
+
+## 🎯 核心理念
+
+### "流量为王"投机市场规律
+
+```
+接盘总量 = 流量 × 转化率 × 客单价
+```
+
+**关键理论:**
+1. **流量高潮 = 价格高潮 = 逃命时刻**
+2. **流量爆发 = 题材启动 = 进场机会**
+3. **流量衰减 = 资金离场 = 减仓信号**
+
+### 两种流量类型
+
+**1. 存量流量型**(快速爆发)
+- 特征:顶级流量,快速到位,不需要发酵
+- 案例:政策事件、突发新闻、知名人物发声
+- 时间窗口:短(几小时到几天)
+- 策略:手快吃肉,手慢接盘
+
+**2. 增量流量型**(裂变传播)
+- 特征:初始流量小,有病毒传播能力,指数增长
+- 判断指标:K值(病毒系数)>1.5为指数型爆发
+- 时间窗口:长(几天到几周)
+- 策略:埋伏早期,分批进出
+
+---
+
+## 🚀 功能特性
+
+### 1. 多平台数据聚合
+
+**支持20+主流平台:**
+
+| 类别 | 平台 | 权重 | 说明 |
+|------|------|------|------|
+| 社交媒体 | 微博热搜 | 10 | 核心流量指标 |
+| 社交媒体 | 抖音热点 | 9 | 短视频流量 |
+| 社交媒体 | 知乎热榜 | 7 | 深度内容流量 |
+| 财经平台 | 新浪财经 | 9 | 股市相关 |
+| 财经平台 | 东方财富 | 9 | 散户关注度 |
+| 财经平台 | 雪球 | 8 | 投资者讨论 |
+| 财经平台 | 财联社 | 8 | 专业资讯 |
+| 新闻媒体 | 百度热搜 | 8 | 全民热点 |
+| 新闻媒体 | 今日头条 | 7 | 算法推荐 |
+
+### 2. 智能流量分析
+
+- **流量得分计算**:综合权重×新闻数量,归一化到0-1000分
+- **流量等级判断**:
+ - 极高(≥800分):流量爆发,警惕见顶
+ - 高(500-800分):流量活跃,关注机会
+ - 中(200-500分):流量正常,保持观望
+ - 低(<200分):流量低迷,空仓为主
+- **历史对比**:计算当前得分的百分位,了解历史水平
+
+### 3. 股票相关新闻提取
+
+- 自动识别40+股票关键词(涨停、跌停、概念股、龙头股等)
+- 按平台权重和关键词匹配数排序
+- 提取高频提及的个股代码
+- 生成潜力个股TOP10
+
+### 4. 热门话题挖掘
+
+- 基于jieba分词的话题提取
+- 过滤停用词,保留有效话题
+- 统计话题热度(0-100分)
+- 识别热门板块(AI、新能源、半导体等)
+
+### 5. 交易信号生成
+
+**基于流量等级的交易信号:**
+
+| 流量等级 | 得分范围 | 信号 | 信心度 | 操作建议 |
+|----------|----------|------|--------|----------|
+| 极高(顶部) | ≥900 | 卖出 | 9/10 | 立即减仓或清仓,锁定利润 |
+| 极高(高位) | 800-900 | 观望 | 7/10 | 持仓观望,逢高减仓 |
+| 高 | 500-800 | 买入 | 8/10 | 关注龙头股,轻仓试探 |
+| 中 | 200-500 | 观望 | 5/10 | 保持观望,等待信号明确 |
+| 低 | <200 | 观望 | 3/10 | 空仓观望,等待市场转暖 |
+
+**输出内容:**
+- 整体信号(买入/观望/卖出)
+- 信心度(0-10分)
+- 热门板块TOP3
+- 潜力个股TOP10
+- 风险提示
+- 操作建议
+
+### 6. 流量趋势分析
+
+- 7-30天流量趋势图
+- 平均/最高/最低流量得分
+- 趋势判断(上升/下降/平稳)
+- 每日统计汇总
+
+### 7. 历史记录管理
+
+- 完整的监测历史保存
+- 快照详情查看(股票新闻、热门话题)
+- 历史数据对比分析
+- 支持按关键词搜索
+
+---
+
+## 💻 技术架构
+
+### 模块组成
+
+```
+news_flow_data.py # 数据获取模块
+├── NewsFlowDataFetcher # 新闻数据获取器
+ ├── get_platform_news() # 单平台数据
+ ├── get_multi_platform_news() # 多平台聚合
+ ├── extract_stock_related_news() # 股票新闻提取
+ ├── calculate_flow_score() # 流量得分计算
+ └── get_hot_topics() # 热门话题挖掘
+
+news_flow_db.py # 数据库模块
+├── NewsFlowDatabase # 数据库管理类
+ ├── flow_snapshots # 流量快照表
+ ├── platform_news # 平台新闻表
+ ├── stock_related_news # 股票相关新闻表
+ ├── hot_topics # 热门话题表
+ └── flow_statistics # 每日统计表
+
+news_flow_engine.py # 分析引擎
+├── NewsFlowEngine # 流量分析引擎
+ ├── run_full_analysis() # 完整分析流程
+ ├── _generate_trading_signals() # 生成交易信号
+ ├── get_flow_trend() # 流量趋势分析
+ └── compare_with_history() # 历史对比
+
+news_flow_ui.py # UI界面
+└── display_news_flow_monitor() # 主界面
+ ├── display_realtime_monitor() # 实时监测
+ ├── display_flow_trend() # 流量趋势
+ ├── display_history_records() # 历史记录
+ └── display_settings() # 设置
+```
+
+### 数据库表结构
+
+**1. flow_snapshots(流量快照表)**
+- 记录每次监测的整体情况
+- 字段:total_score, flow_level, social_score, finance_score, analysis等
+
+**2. platform_news(平台新闻表)**
+- 存储各平台的新闻数据
+- 字段:platform, title, content, url, publish_time等
+
+**3. stock_related_news(股票相关新闻表)**
+- 股票相关新闻筛选结果
+- 字段:title, matched_keywords, keyword_count等
+
+**4. hot_topics(热门话题表)**
+- 热门话题词频统计
+- 字段:topic, count, heat
+
+**5. flow_statistics(每日统计表)**
+- 按天汇总的流量统计
+- 字段:date, avg_score, max_score, snapshot_count等
+
+---
+
+## 📊 使用指南
+
+### 快速开始
+
+1. **进入新闻流量监测**
+ - 点击侧边栏"📊 策略分析" → "📰 新闻流量"
+
+2. **选择监测模式**
+ - 全平台监测:监测所有20+平台
+ - 社交媒体:仅监测微博、抖音、知乎等
+ - 财经平台:仅监测新浪财经、东财、雪球等
+ - 新闻媒体:仅监测百度、头条、腾讯网等
+ - 自定义:手动选择平台
+
+3. **开始监测**
+ - 点击"🚀 开始监测"按钮
+ - 等待数据获取和分析(约30-60秒)
+
+4. **查看结果**
+ - 流量得分和等级
+ - 交易信号和操作建议
+ - 热门板块TOP3
+ - 潜力个股TOP10
+ - 股票相关新闻
+ - 热门话题词云
+
+### 监测频率建议
+
+**盘前监测(9:00-9:25)**
+- 目的:了解当日市场热点
+- 重点:关注新增热点题材
+- 操作:制定当日交易计划
+
+**盘后监测(15:30-16:00)**
+- 目的:复盘当日市场情绪
+- 重点:分析流量变化趋势
+- 操作:调整持仓和策略
+
+**晚间监测(20:00-21:00)**
+- 目的:准备次日策略
+- 重点:深度研究热点板块
+- 操作:精选次日目标股
+
+### 信号解读原则
+
+**1. 流量极高(≥900分)**
+- **理论**:流量高潮=价格高潮,后面没有更傻的接盘侠
+- **特征**:主流媒体报道、全民讨论、短视频霸屏
+- **操作**:立即减仓,不要贪恋最后一跌
+- **案例**:深度求索概念(2024年)
+
+**2. 流量高(500-800分)**
+- **理论**:流量快速上升,题材正在发酵
+- **特征**:热搜上榜、财经媒体关注、散户开始涌入
+- **操作**:关注龙头股,轻仓试探,设好止损
+- **策略**:先涨为王,名字为王
+
+**3. 流量中(200-500分)**
+- **理论**:流量正常波动,方向不明确
+- **特征**:常规新闻,没有突出热点
+- **操作**:保持观望,可关注潜力题材
+- **策略**:等待流量信号明确
+
+**4. 流量低(<200分)**
+- **理论**:流量低迷,市场情绪冷淡
+- **特征**:成交量萎缩、缺乏热点、媒体冷清
+- **操作**:空仓观望,等待市场转暖
+- **策略**:可布局低估值个股
+
+### 配合使用建议
+
+**与其他功能协同:**
+
+1. **新闻流量 + 智瞰龙虎**
+ - 流量识别热点 → 龙虎榜确认游资动向
+ - 双重验证,提高成功率
+
+2. **新闻流量 + 智策板块**
+ - 流量发现题材 → 智策分析板块轮动
+ - 把握进场和退出时机
+
+3. **新闻流量 + 主力选股**
+ - 流量筛选热点 → 主力资金确认标的
+ - 跟随聪明钱,提高胜率
+
+4. **新闻流量 + AI盯盘**
+ - 流量判断大盘情绪 → AI盯盘执行个股交易
+ - 自动化交易,解放双手
+
+---
+
+## ⚠️ 风险提示
+
+### 使用注意事项
+
+1. **流量≠价格**
+ - 流量高只代表关注度高,不代表股价一定上涨
+ - 必须结合基本面、技术面、资金面综合判断
+
+2. **反向指标**
+ - 当你的反向指标(如擦鞋童、小区大妈)都在讨论某个股票时,往往是见顶信号
+ - 记住:连擦鞋童都在炒股了 → 肯尼迪立即清仓
+
+3. **流量高潮识别**
+ - 短视频热搜排名停止上升
+ - 评论增速明显放缓
+ - 主流媒体不再报道
+ - 新用户涌入停滞
+
+4. **数据时效性**
+ - 新闻API数据有一定延迟(约10-30分钟)
+ - 不适用于超高频交易
+ - 建议配合实时盘口观察
+
+5. **市场风险**
+ - 题材炒作具有高风险,可能快速反转
+ - 严格执行止损,不要死扛
+ - 仓位控制:单只股票不超过30%
+ - 本功能仅供参考,不构成投资建议
+
+### 免责声明
+
+本功能基于公开的新闻数据和"流量为王"理论进行分析,所有分析结果仅供参考。股市有风险,投资需谨慎。使用本功能进行投资决策的风险由用户自行承担。
+
+---
+
+## 🔧 技术依赖
+
+### Python包
+```bash
+pip install requests # HTTP请求
+pip install jieba # 中文分词
+pip install plotly # 数据可视化
+pip install pandas # 数据处理
+```
+
+### 外部API
+- **新闻API**: https://newsapi.ws4.cn/api/v1/dailynews/
+- **免费使用**:无需API Key
+- **速率限制**:建议每次请求间隔0.5秒以上
+
+---
+
+## 📈 实战案例
+
+### 案例1:流量爆发 - 深度求索概念(2024年)
+
+**背景**:
+- 东大自研AI大模型技术突破
+- 主流媒体报道,短视频热搜霸榜
+- 带有民族情绪加成
+
+**流量特征**:
+- 存量流量型(自带顶流)
+- 流量得分:950+(极高)
+- 全平台关注度爆发
+
+**交易信号**:
+- 第1天:流量500分,信号"买入",龙头涨停
+- 第2天:流量800分,信号"观望",持仓待涨
+- 第3天:流量950分,信号"卖出",立即减仓 ← **流量高潮!**
+- 第4天:流量600分,题材降温,股价回调
+
+**结论**:严格按流量信号操作,第3天卖出,规避后续回调。
+
+### 案例2:流量裂变 - 新能源题材(持续炒作)
+
+**背景**:
+- 政策支持,产业发展
+- K值>1.5,病毒式传播
+
+**流量特征**:
+- 增量流量型(裂变传播)
+- 流量逐步上升:300 → 500 → 700 → 850
+- 时间跨度:2-3周
+
+**交易信号**:
+- 第1周:流量300分,埋伏早期
+- 第2周:流量500分,加仓龙头
+- 第3周:流量700分,持仓待涨
+- 第4周:流量850分,逐步减仓
+
+**结论**:增量流量型时间窗口长,可分批进出,把握多波段行情。
+
+---
+
+## 📚 相关文档
+
+- **理论基础**: `新闻流量转化炒股法.md`
+- **API文档**: `新闻监测API调用说明.md`
+- **主程序**: `app.py`
+- **数据模块**: `news_flow_data.py`
+- **数据库**: `news_flow_db.py`
+- **分析引擎**: `news_flow_engine.py`
+- **UI界面**: `news_flow_ui.py`
+
+---
+
+## 🎯 后续优化方向
+
+1. **AI增强**
+ - 接入DeepSeek AI分析新闻内容
+ - 自动识别题材炒作阶段(萌芽/爆发/高潮/退潮)
+
+2. **流量追踪**
+ - 实时监控流量变化(每分钟)
+ - 流量拐点自动告警
+
+3. **个股关联**
+ - 从新闻中自动提取股票代码
+ - 与龙虎榜、主力资金数据关联
+
+4. **情绪量化**
+ - NLP情感分析(正面/负面)
+ - 社交媒体情绪指数
+
+5. **策略回测**
+ - 历史流量与股价涨跌相关性分析
+ - 优化流量阈值和交易信号
+
+---
+
+**祝你交易顺利!** 📈💰
+
+记住:"流量为王,见顶就跑!"
diff --git a/docs/新闻流量监测快速开始.md b/docs/新闻流量监测快速开始.md
new file mode 100644
index 0000000..934f0ec
--- /dev/null
+++ b/docs/新闻流量监测快速开始.md
@@ -0,0 +1,186 @@
+# 新闻流量监测功能 - 快速开始指南
+
+## 🚀 快速开始
+
+### 1. 安装依赖
+
+```bash
+# 激活虚拟环境(如果有)
+.\venv\Scripts\Activate.ps1
+
+# 安装新增依赖
+pip install jieba>=0.42.1
+```
+
+### 2. 启动系统
+
+```bash
+# 方式1:使用run.py
+python run.py
+
+# 方式2:直接启动streamlit
+streamlit run app.py
+```
+
+### 3. 访问功能
+
+1. 打开浏览器访问:http://localhost:8501
+2. 点击侧边栏"📊 策略分析" → "📰 新闻流量"
+3. 选择监测模式,点击"🚀 开始监测"
+
+---
+
+## 📊 功能概览
+
+### 核心功能
+- ✅ 实时监测20+主流平台新闻热点
+- ✅ 智能计算流量得分(0-1000分)
+- ✅ 自动生成交易信号(买入/观望/卖出)
+- ✅ 识别热门板块TOP3
+- ✅ 提取潜力个股TOP10
+- ✅ 流量趋势分析(7-30天)
+- ✅ 完整的历史记录管理
+
+### 支持平台
+**社交媒体**:微博、抖音、知乎、B站
+**财经平台**:新浪财经、东方财富、雪球、财联社
+**新闻媒体**:百度、今日头条、腾讯网
+
+---
+
+## 💡 使用示例
+
+### 场景1:盘前快速监测(推荐)
+
+**时间**:9:00-9:25
+**目的**:了解当日市场热点
+
+```
+1. 进入"📰 新闻流量"
+2. 选择"财经平台"模式
+3. 点击"开始监测"
+4. 查看:
+ - 流量得分和等级
+ - 交易信号
+ - 热门板块
+ - 潜力个股
+5. 制定当日交易计划
+```
+
+### 场景2:盘后复盘分析
+
+**时间**:15:30-16:00
+**目的**:复盘当日市场情绪
+
+```
+1. 进入"📰 新闻流量"
+2. 选择"全平台监测"
+3. 执行监测,查看结果
+4. 切换到"📈 流量趋势"标签
+5. 对比今日与历史流量
+6. 调整持仓和策略
+```
+
+### 场景3:深度研究热点题材
+
+**时间**:晚间20:00-21:00
+**目的**:准备次日策略
+
+```
+1. 全平台监测获取完整数据
+2. 重点关注:
+ - 股票相关新闻TOP10
+ - 热门话题词云
+ - 潜力个股列表
+3. 结合"🎯 智瞰龙虎"确认游资动向
+4. 结合"💰 主力选股"验证资金流向
+5. 精选次日目标股
+```
+
+---
+
+## 🎯 信号解读速查表
+
+| 流量得分 | 流量等级 | 交易信号 | 信心度 | 操作 |
+|---------|---------|---------|--------|------|
+| ≥900 | 极高 | 卖出 | 9/10 | 立即减仓 |
+| 800-900 | 极高 | 观望 | 7/10 | 逢高减仓 |
+| 500-800 | 高 | 买入 | 8/10 | 轻仓试探 |
+| 200-500 | 中 | 观望 | 5/10 | 保持观望 |
+| <200 | 低 | 观望 | 3/10 | 空仓观望 |
+
+---
+
+## ⚠️ 重要提示
+
+### 必读原则
+1. **流量高潮 = 价格高潮 = 逃命时刻**
+ 当流量得分≥900时,立即减仓,不要贪恋最后一跌
+
+2. **反向指标警惕**
+ 当你的反向指标(小区大妈、出租车司机)都在讨论某个股票时,往往是见顶信号
+
+3. **严格止损**
+ 题材炒作风险高,务必设置止损位(-5%)
+
+4. **仓位控制**
+ 单只股票不超过30%,避免重仓一把梭
+
+5. **配合使用**
+ 结合基本面、技术面、资金面综合判断,不要单纯依赖流量信号
+
+### 数据说明
+- 新闻API数据有10-30分钟延迟
+- 不适用于超高频交易
+- 仅供参考,不构成投资建议
+
+---
+
+## 🔧 故障排查
+
+### 问题1:监测失败
+**现象**:点击开始监测后报错
+
+**解决方案**:
+1. 检查网络连接是否正常
+2. 确认新闻API服务可用(访问:https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu)
+3. 查看终端错误日志
+
+### 问题2:jieba未安装
+**现象**:报错"ModuleNotFoundError: No module named 'jieba'"
+
+**解决方案**:
+```bash
+pip install jieba
+```
+
+### 问题3:分析时间过长
+**现象**:监测超过2分钟仍未完成
+
+**解决方案**:
+1. 减少监测平台数量(选择"财经平台"而非"全平台")
+2. 检查网络速度
+3. 等待完成(首次获取需要更长时间)
+
+---
+
+## 📚 深入学习
+
+### 相关文档
+- **功能详解**:`docs/新闻流量监测功能说明.md`
+- **理论基础**:`新闻流量转化炒股法.md`
+- **API文档**:`新闻监测API调用说明.md`
+
+### 配合使用
+- **智瞰龙虎**:验证游资动向
+- **智策板块**:分析板块轮动
+- **主力选股**:跟踪主力资金
+- **AI盯盘**:自动化交易执行
+
+---
+
+## 💬 反馈与支持
+
+如有问题或建议,请联系:ws3101001@126.com
+
+**祝你交易顺利!记住:流量为王,见顶就跑!** 📈💰
diff --git a/news_flow.db b/news_flow.db
new file mode 100644
index 0000000..5c23853
Binary files /dev/null and b/news_flow.db differ
diff --git a/news_flow_agents.py b/news_flow_agents.py
new file mode 100644
index 0000000..6ea47ba
--- /dev/null
+++ b/news_flow_agents.py
@@ -0,0 +1,996 @@
+"""
+新闻流量智能分析代理模块
+使用DeepSeek进行AI驱动的分析
+包含:板块影响分析、股票推荐、风险评估、投资建议
+"""
+import json
+import logging
+import time
+from datetime import datetime
+from typing import Dict, List, Optional
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowAgents:
+ """新闻流量智能分析代理"""
+
+ def __init__(self, model: str = "deepseek-chat"):
+ """
+ 初始化代理
+
+ Args:
+ model: 使用的模型,默认 deepseek-chat
+ """
+ self.model = model
+ self.deepseek_client = None
+ self._init_client()
+
+ def _init_client(self):
+ """初始化DeepSeek客户端"""
+ try:
+ from deepseek_client import DeepSeekClient
+ self.deepseek_client = DeepSeekClient(model=self.model)
+ logger.info(f"✅ DeepSeek客户端初始化成功,模型: {self.model}")
+ except Exception as e:
+ logger.error(f"❌ DeepSeek客户端初始化失败: {e}")
+ self.deepseek_client = None
+
+ def is_available(self) -> bool:
+ """检查AI是否可用"""
+ return self.deepseek_client is not None
+
+ def sector_impact_agent(self, hot_topics: List[Dict],
+ stock_news: List[Dict],
+ flow_data: Dict = None) -> Dict:
+ """
+ 板块影响分析代理
+
+ 分析热点可能影响的板块
+
+ Returns:
+ {
+ 'affected_sectors': List[Dict],
+ 'analysis': str,
+ 'success': bool,
+ }
+ """
+ if not self.is_available():
+ return self._fallback_sector_analysis(hot_topics, stock_news)
+
+ # 准备数据
+ topics_text = '\n'.join([
+ f"- {t['topic']} (热度:{t.get('heat', 0)}, 跨{t.get('cross_platform', 0)}平台)"
+ for t in hot_topics[:20]
+ ])
+
+ news_text = '\n'.join([
+ f"- [{n.get('platform_name', '')}] {n.get('title', '')}"
+ for n in stock_news[:15]
+ ])
+
+ flow_info = ""
+ if flow_data:
+ flow_info = f"""
+当前流量状态:
+- 流量得分: {flow_data.get('total_score', 'N/A')}/1000
+- 流量等级: {flow_data.get('level', 'N/A')}
+- 社交媒体热度: {flow_data.get('social_score', 'N/A')}
+- 财经平台热度: {flow_data.get('finance_score', 'N/A')}
+"""
+
+ prompt = f"""你是一名资深的A股短线投资分析师,专注于热点题材挖掘和板块轮动分析。
+
+【重要】请根据以下全网热点数据,进行深度的A股题材分析:
+
+=== 全网热门话题TOP20 ===
+{topics_text}
+
+=== 股票相关新闻TOP15 ===
+{news_text}
+{flow_info}
+
+请完成以下分析任务:
+
+1. **题材挖掘**:从以上热点中挖掘出可能引爆A股的核心题材概念
+2. **板块分析**:分析最可能受益的A股板块(要具体到申万行业或同花顺概念板块)
+3. **热度评估**:评估每个板块的潜在炒作热度和持续性
+4. **龙头预判**:推测可能的龙头股特征
+
+请以JSON格式输出:
+{{
+ "hot_themes": [
+ {{"theme": "题材名称", "source": "来源热点", "heat_level": "极高/高/中", "sustainability": "持续性评估"}}
+ ],
+ "benefited_sectors": [
+ {{
+ "name": "板块名称(要具体如:AI算力、低空经济、机器人等)",
+ "impact": "利好",
+ "confidence": 85,
+ "reason": "详细分析原因",
+ "related_concepts": ["相关概念1", "相关概念2"],
+ "leader_characteristics": "龙头股特征描述"
+ }}
+ ],
+ "damaged_sectors": [
+ {{"name": "板块名称", "impact": "利空", "confidence": 60, "reason": "原因"}}
+ ],
+ "opportunity_assessment": "今日A股投资机会综合评估(100字以内)",
+ "trading_suggestion": "短线操作建议",
+ "key_points": ["核心要点1", "核心要点2", "核心要点3"]
+}}
+
+只输出JSON,不要其他文字。"""
+
+ try:
+ messages = [
+ {"role": "system", "content": "你是专业的A股市场分析师,输出必须是纯JSON格式。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ response = self.deepseek_client.call_api(messages, temperature=0.5, max_tokens=2000)
+
+ # 解析JSON
+ result = self._parse_json_response(response)
+
+ if result:
+ return {
+ 'hot_themes': result.get('hot_themes', []),
+ 'affected_sectors': result.get('benefited_sectors', []) + result.get('damaged_sectors', []),
+ 'benefited_sectors': result.get('benefited_sectors', []),
+ 'damaged_sectors': result.get('damaged_sectors', []),
+ 'opportunity_assessment': result.get('opportunity_assessment', ''),
+ 'trading_suggestion': result.get('trading_suggestion', ''),
+ 'key_points': result.get('key_points', []),
+ 'success': True,
+ 'raw_response': response,
+ }
+ else:
+ return self._fallback_sector_analysis(hot_topics, stock_news)
+
+ except Exception as e:
+ logger.error(f"板块分析失败: {e}")
+ return self._fallback_sector_analysis(hot_topics, stock_news)
+
+ def stock_recommend_agent(self, hot_sectors: List[Dict],
+ flow_stage: str,
+ sentiment_class: str) -> Dict:
+ """
+ 股票推荐代理
+
+ 基于热门板块和市场状态推荐股票
+
+ Returns:
+ {
+ 'recommended_stocks': List[Dict],
+ 'strategy': str,
+ 'success': bool,
+ }
+ """
+ if not self.is_available():
+ return self._fallback_stock_recommend(hot_sectors)
+
+ sectors_text = '\n'.join([
+ f"- {s.get('name', '')}:{s.get('impact', '利好')},置信度{s.get('confidence', 50)}%\n 原因:{s.get('reason', '')}\n 龙头特征:{s.get('leader_characteristics', 'N/A')}"
+ for s in hot_sectors[:5]
+ ])
+
+ related_concepts = []
+ for s in hot_sectors[:5]:
+ related_concepts.extend(s.get('related_concepts', []))
+ concepts_text = ', '.join(list(set(related_concepts))[:10]) if related_concepts else '无'
+
+ prompt = f"""你是一名资深的A股短线游资操盘手,专注于热点题材龙头股挖掘。
+
+=== 当前市场状态 ===
+- 流量阶段: {flow_stage}
+- 情绪状态: {sentiment_class}
+- 相关概念: {concepts_text}
+
+=== 热门受益板块分析 ===
+{sectors_text}
+
+=== 选股要求 ===
+请根据"流量为王"理念,推荐5-8只A股短线标的:
+
+选股法则(必须遵循):
+1. **先涨为王**:优先选择已经启动、走势强势的股票
+2. **名字为王**:股票名称与热点高度相关(如AI概念选"智"字头)
+3. **龙头优先**:选择板块内最强势的龙头或人气股
+4. **题材纯正**:主业与热点题材高度相关
+5. **流通盘适中**:30-150亿市值为佳,便于资金操作
+
+请以JSON格式输出:
+{{
+ "recommended_stocks": [
+ {{
+ "code": "股票代码(6位数字,如000001或600001)",
+ "name": "股票名称",
+ "sector": "所属板块",
+ "market": "沪市/深市/创业板/科创板",
+ "market_cap": "市值(亿)",
+ "reason": "推荐理由(与热点的关联性)",
+ "catalyst": "催化剂/驱动因素",
+ "strategy": "操作策略(进场/加仓/止损建议)",
+ "target_space": "目标空间(如15-20%)",
+ "risk_level": "低/中/高",
+ "attention_points": ["注意事项1", "注意事项2"]
+ }}
+ ],
+ "overall_strategy": "整体操作策略和仓位建议",
+ "timing_advice": "最佳介入时机判断",
+ "risk_warning": "风险提示(必须包含投资风险提醒)"
+}}
+
+【重要】只推荐真实存在的A股股票,代码必须正确。只输出JSON。"""
+
+ try:
+ messages = [
+ {"role": "system", "content": "你是专业的A股投资顾问,只输出纯JSON格式。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ response = self.deepseek_client.call_api(messages, temperature=0.6, max_tokens=2000)
+ result = self._parse_json_response(response)
+
+ if result:
+ return {
+ 'recommended_stocks': result.get('recommended_stocks', []),
+ 'overall_strategy': result.get('overall_strategy', ''),
+ 'timing_advice': result.get('timing_advice', ''),
+ 'risk_warning': result.get('risk_warning', ''),
+ 'success': True,
+ 'raw_response': response,
+ }
+ else:
+ return self._fallback_stock_recommend(hot_sectors)
+
+ except Exception as e:
+ logger.error(f"股票推荐失败: {e}")
+ return self._fallback_stock_recommend(hot_sectors)
+
+ def risk_assess_agent(self, flow_stage: str,
+ sentiment_data: Dict,
+ viral_k: float,
+ flow_type: str) -> Dict:
+ """
+ 风险评估代理
+
+ 评估当前市场风险
+
+ Returns:
+ {
+ 'risk_level': str,
+ 'risk_factors': List[str],
+ 'risk_score': int,
+ 'analysis': str,
+ 'success': bool,
+ }
+ """
+ if not self.is_available():
+ return self._fallback_risk_assess(flow_stage, sentiment_data, viral_k)
+
+ prompt = f"""你是一名专业的风险管理分析师。
+
+请根据以下市场数据评估当前投资风险:
+
+市场状态:
+- 流量阶段: {flow_stage}
+- 情绪指数: {sentiment_data.get('sentiment_index', 50)}
+- 情绪分类: {sentiment_data.get('sentiment_class', '中性')}
+- K值(病毒系数): {viral_k}
+- 流量类型: {flow_type}
+
+核心理念:
+- 流量高潮 = 价格高潮 = 逃命时刻
+- K值>1.5表示指数型爆发,风险上升
+- 情绪极端(>85或<20)都意味着风险
+
+请分析:
+1. 当前风险等级(极低/低/中等/高/极高)
+2. 主要风险因素
+3. 风险分数(0-100)
+4. 详细分析
+
+以JSON格式输出:
+{{
+ "risk_level": "高",
+ "risk_score": 75,
+ "risk_factors": ["风险因素1", "风险因素2", ...],
+ "opportunities": ["机会1", "机会2", ...],
+ "analysis": "详细分析文字",
+ "key_warning": "最重要的警告"
+}}
+
+只输出JSON。"""
+
+ try:
+ messages = [
+ {"role": "system", "content": "你是专业的风险管理分析师,只输出纯JSON格式。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ response = self.deepseek_client.call_api(messages, temperature=0.4, max_tokens=1500)
+ result = self._parse_json_response(response)
+
+ if result:
+ return {
+ 'risk_level': result.get('risk_level', '中等'),
+ 'risk_score': result.get('risk_score', 50),
+ 'risk_factors': result.get('risk_factors', []),
+ 'opportunities': result.get('opportunities', []),
+ 'analysis': result.get('analysis', ''),
+ 'key_warning': result.get('key_warning', ''),
+ 'success': True,
+ 'raw_response': response,
+ }
+ else:
+ return self._fallback_risk_assess(flow_stage, sentiment_data, viral_k)
+
+ except Exception as e:
+ logger.error(f"风险评估失败: {e}")
+ return self._fallback_risk_assess(flow_stage, sentiment_data, viral_k)
+
+ def investment_advisor_agent(self, sector_analysis: Dict,
+ stock_recommend: Dict,
+ risk_assess: Dict,
+ flow_data: Dict,
+ sentiment_data: Dict) -> Dict:
+ """
+ 投资建议代理(综合)
+
+ 综合所有分析给出最终投资建议
+
+ Returns:
+ {
+ 'advice': str, # 买入/持有/观望/回避
+ 'confidence': int,
+ 'summary': str,
+ 'action_plan': List[str],
+ 'success': bool,
+ }
+ """
+ if not self.is_available():
+ return self._fallback_investment_advice(risk_assess, flow_data)
+
+ # 构建综合信息
+ sectors_text = ', '.join([s.get('name', '') for s in sector_analysis.get('benefited_sectors', [])[:3]])
+ stocks_text = ', '.join([f"{s.get('name', '')}({s.get('code', '')})"
+ for s in stock_recommend.get('recommended_stocks', [])[:3]])
+
+ prompt = f"""你是一名首席投资策略师,需要给出最终的投资建议。
+
+综合分析数据:
+
+【流量分析】
+- 流量得分: {flow_data.get('total_score', 'N/A')}
+- 流量等级: {flow_data.get('level', 'N/A')}
+
+【情绪分析】
+- 情绪指数: {sentiment_data.get('sentiment_index', 50)}
+- 情绪分类: {sentiment_data.get('sentiment_class', '中性')}
+- 流量阶段: {sentiment_data.get('flow_stage', '未知')}
+
+【板块分析】
+- 受益板块: {sectors_text}
+- 机会评估: {sector_analysis.get('opportunity_assessment', 'N/A')}
+
+【股票推荐】
+- 推荐股票: {stocks_text}
+
+【风险评估】
+- 风险等级: {risk_assess.get('risk_level', '中等')}
+- 风险分数: {risk_assess.get('risk_score', 50)}
+- 主要风险: {', '.join(risk_assess.get('risk_factors', [])[:3])}
+
+核心原则(流量为王):
+- 流量高潮 = 价格高潮 = 逃命时刻
+- 当热搜、媒体报道、KOL转发同时达到高潮时,就是出货时机
+- 短线操作:快进快出,紧跟龙头
+
+请给出最终投资建议:
+1. 操作建议(买入/持有/观望/回避)
+2. 置信度(0-100)
+3. 综合总结
+4. 具体行动计划
+
+以JSON格式输出:
+{{
+ "advice": "观望",
+ "confidence": 75,
+ "summary": "综合总结文字",
+ "action_plan": [
+ "行动1",
+ "行动2",
+ ...
+ ],
+ "position_suggestion": "仓位建议",
+ "timing": "时机判断",
+ "key_message": "最重要的一句话"
+}}
+
+只输出JSON。"""
+
+ try:
+ start_time = time.time()
+
+ messages = [
+ {"role": "system", "content": "你是首席投资策略师,必须给出明确的投资建议,只输出纯JSON格式。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ response = self.deepseek_client.call_api(messages, temperature=0.5, max_tokens=2000)
+ result = self._parse_json_response(response)
+
+ analysis_time = time.time() - start_time
+
+ if result:
+ return {
+ 'advice': result.get('advice', '观望'),
+ 'confidence': result.get('confidence', 50),
+ 'summary': result.get('summary', ''),
+ 'action_plan': result.get('action_plan', []),
+ 'position_suggestion': result.get('position_suggestion', ''),
+ 'timing': result.get('timing', ''),
+ 'key_message': result.get('key_message', ''),
+ 'success': True,
+ 'analysis_time': round(analysis_time, 2),
+ 'raw_response': response,
+ }
+ else:
+ return self._fallback_investment_advice(risk_assess, flow_data)
+
+ except Exception as e:
+ logger.error(f"投资建议生成失败: {e}")
+ return self._fallback_investment_advice(risk_assess, flow_data)
+
+ def run_full_analysis(self, hot_topics: List[Dict],
+ stock_news: List[Dict],
+ flow_data: Dict,
+ sentiment_data: Dict,
+ viral_k: float = 1.0,
+ flow_type: str = "未知") -> Dict:
+ """
+ 运行完整的AI分析
+
+ Returns:
+ {
+ 'sector_analysis': Dict,
+ 'stock_recommend': Dict,
+ 'risk_assess': Dict,
+ 'investment_advice': Dict,
+ 'success': bool,
+ 'analysis_time': float,
+ }
+ """
+ start_time = time.time()
+
+ logger.info("🤖 开始AI分析...")
+
+ # 1. 板块影响分析
+ logger.info(" 📊 分析板块影响...")
+ sector_analysis = self.sector_impact_agent(hot_topics, stock_news, flow_data)
+
+ # 2. 股票推荐
+ logger.info(" 📈 生成股票推荐...")
+ flow_stage = sentiment_data.get('flow_stage', {}).get('stage_name', '未知')
+ sentiment_class = sentiment_data.get('sentiment', {}).get('sentiment_class', '中性')
+ stock_recommend = self.stock_recommend_agent(
+ sector_analysis.get('benefited_sectors', []),
+ flow_stage,
+ sentiment_class
+ )
+
+ # 3. 风险评估
+ logger.info(" ⚠️ 评估风险...")
+ risk_assess = self.risk_assess_agent(
+ flow_stage,
+ sentiment_data.get('sentiment', {}),
+ viral_k,
+ flow_type
+ )
+
+ # 4. 综合投资建议
+ logger.info(" 💡 生成投资建议...")
+ investment_advice = self.investment_advisor_agent(
+ sector_analysis,
+ stock_recommend,
+ risk_assess,
+ flow_data,
+ sentiment_data.get('sentiment', {})
+ )
+
+ total_time = time.time() - start_time
+ logger.info(f"✅ AI分析完成,耗时 {total_time:.2f} 秒")
+
+ # 汇总结果
+ return {
+ 'sector_analysis': sector_analysis,
+ 'stock_recommend': stock_recommend,
+ 'risk_assess': risk_assess,
+ 'investment_advice': investment_advice,
+ 'success': all([
+ sector_analysis.get('success', False),
+ stock_recommend.get('success', False),
+ risk_assess.get('success', False),
+ investment_advice.get('success', False),
+ ]),
+ 'analysis_time': round(total_time, 2),
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ }
+
+ def analyze_sector_deep(self, sector_name: str, related_news: List[Dict],
+ hot_topics: List[Dict]) -> Dict:
+ """
+ 深度分析单个板块
+
+ 为每个热门板块单独调用DeepSeek进行深度分析
+ """
+ if not self.is_available():
+ return {'success': False, 'error': 'AI不可用'}
+
+ news_text = '\n'.join([
+ f"- [{n.get('platform_name', '')}] {n.get('title', '')}"
+ for n in related_news[:20]
+ ])
+
+ topics_text = '\n'.join([
+ f"- {t['topic']} (热度:{t.get('heat', 0)})"
+ for t in hot_topics[:10]
+ ])
+
+ prompt = f"""你是{sector_name}板块的专业分析师。
+
+请对以下与{sector_name}相关的新闻进行深度分析:
+
+【相关新闻】
+{news_text}
+
+【相关热点话题】
+{topics_text}
+
+请分析:
+1. {sector_name}板块当前的市场热度和关注度
+2. 驱动因素分析(政策/技术/资金/事件)
+3. 短期(1-3天)走势预判
+4. 核心龙头股分析(至少3只)
+5. 投资建议和风险提示
+
+以JSON格式输出:
+{{
+ "sector_name": "{sector_name}",
+ "heat_level": "极高/高/中/低",
+ "heat_score": 85,
+ "drivers": [
+ {{"type": "政策", "content": "具体驱动因素", "impact": "正面/负面"}}
+ ],
+ "short_term_outlook": "看涨/震荡/看跌",
+ "outlook_reason": "预判理由",
+ "leader_stocks": [
+ {{
+ "code": "600000",
+ "name": "股票名称",
+ "reason": "龙头理由",
+ "strategy": "操作策略"
+ }}
+ ],
+ "investment_advice": "具体投资建议",
+ "risk_warning": "风险提示",
+ "key_indicators": {{
+ "关注度": "高",
+ "资金流向": "净流入",
+ "情绪指数": 75
+ }}
+}}
+
+只输出JSON。"""
+
+ try:
+ messages = [
+ {"role": "system", "content": f"你是{sector_name}板块专业分析师,只输出JSON格式。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ response = self.deepseek_client.call_api(messages, temperature=0.5, max_tokens=2000)
+ result = self._parse_json_response(response)
+
+ if result:
+ result['success'] = True
+ return result
+ else:
+ return {'success': False, 'sector_name': sector_name}
+
+ except Exception as e:
+ logger.error(f"{sector_name}板块分析失败: {e}")
+ return {'success': False, 'sector_name': sector_name, 'error': str(e)}
+
+ def run_multi_sector_analysis(self, hot_topics: List[Dict],
+ stock_news: List[Dict],
+ target_sectors: List[str] = None) -> Dict:
+ """
+ 多板块并行分析
+
+ 对多个热门板块分别调用DeepSeek进行深度分析
+
+ Args:
+ hot_topics: 热门话题列表
+ stock_news: 股票相关新闻
+ target_sectors: 指定分析的板块列表,为None则自动识别
+
+ Returns:
+ {
+ 'sector_analyses': List[Dict], # 各板块分析结果
+ 'summary': str, # 综合总结
+ 'top_sectors': List[str], # 最热门板块
+ 'success': bool
+ }
+ """
+ if not self.is_available():
+ return {'success': False, 'error': 'AI不可用', 'sector_analyses': []}
+
+ start_time = time.time()
+
+ # 如果没有指定板块,先识别热门板块
+ if not target_sectors:
+ target_sectors = self._identify_hot_sectors(hot_topics, stock_news)
+
+ logger.info(f"🔍 开始分析 {len(target_sectors)} 个热门板块: {target_sectors}")
+
+ # 对每个板块进行深度分析
+ sector_analyses = []
+ for sector in target_sectors[:5]: # 最多分析5个板块
+ logger.info(f" 📊 分析板块: {sector}")
+
+ # 筛选与该板块相关的新闻
+ related_news = self._filter_news_by_sector(stock_news, sector)
+ related_topics = self._filter_topics_by_sector(hot_topics, sector)
+
+ analysis = self.analyze_sector_deep(sector, related_news, related_topics)
+ if analysis.get('success'):
+ sector_analyses.append(analysis)
+
+ # 生成综合总结
+ summary = self._generate_multi_sector_summary(sector_analyses)
+
+ total_time = time.time() - start_time
+ logger.info(f"✅ 多板块分析完成,耗时 {total_time:.2f} 秒")
+
+ return {
+ 'sector_analyses': sector_analyses,
+ 'summary': summary,
+ 'top_sectors': target_sectors[:5],
+ 'analysis_count': len(sector_analyses),
+ 'analysis_time': round(total_time, 2),
+ 'success': len(sector_analyses) > 0
+ }
+
+ def _identify_hot_sectors(self, hot_topics: List[Dict], stock_news: List[Dict]) -> List[str]:
+ """识别热门板块"""
+ # 板块关键词映射
+ sector_keywords = {
+ 'AI人工智能': ['AI', '人工智能', '大模型', 'ChatGPT', '算力', '智能', 'DeepSeek', '机器人'],
+ '新能源': ['新能源', '光伏', '锂电', '储能', '电池', '充电桩', '风电'],
+ '半导体芯片': ['芯片', '半导体', '光刻', '封装', '晶圆', '国产替代'],
+ '医药生物': ['医药', '生物', '疫苗', '创新药', '医疗', 'CXO'],
+ '消费': ['消费', '白酒', '食品', '零售', '餐饮', '旅游'],
+ '金融': ['银行', '保险', '券商', '证券', '金融'],
+ '房地产': ['房地产', '地产', '楼市', '房价'],
+ '军工': ['军工', '国防', '航空', '航天', '武器'],
+ '汽车': ['汽车', '新能源车', '智能驾驶', '无人驾驶'],
+ '低空经济': ['低空', '无人机', '飞行汽车', 'eVTOL'],
+ '机器人': ['机器人', '人形机器人', '工业机器人', '减速器'],
+ '数据要素': ['数据', '数据要素', '数据交易', '数字经济'],
+ }
+
+ # 统计各板块的热度
+ sector_scores = {}
+
+ # 从话题中统计
+ for topic in hot_topics:
+ topic_text = topic.get('topic', '')
+ for sector, keywords in sector_keywords.items():
+ for kw in keywords:
+ if kw in topic_text:
+ sector_scores[sector] = sector_scores.get(sector, 0) + topic.get('heat', 1)
+ break
+
+ # 从新闻中统计
+ for news in stock_news:
+ news_text = (news.get('title') or '') + (news.get('content') or '')
+ for sector, keywords in sector_keywords.items():
+ for kw in keywords:
+ if kw in news_text:
+ sector_scores[sector] = sector_scores.get(sector, 0) + news.get('weight', 1)
+ break
+
+ # 按热度排序
+ sorted_sectors = sorted(sector_scores.items(), key=lambda x: x[1], reverse=True)
+ return [s[0] for s in sorted_sectors[:5]]
+
+ def _filter_news_by_sector(self, news_list: List[Dict], sector: str) -> List[Dict]:
+ """筛选与板块相关的新闻"""
+ sector_keywords = {
+ 'AI人工智能': ['AI', '人工智能', '大模型', 'ChatGPT', '算力', '智能', 'DeepSeek', '机器人'],
+ '新能源': ['新能源', '光伏', '锂电', '储能', '电池', '充电桩', '风电'],
+ '半导体芯片': ['芯片', '半导体', '光刻', '封装', '晶圆'],
+ '医药生物': ['医药', '生物', '疫苗', '创新药', '医疗'],
+ '消费': ['消费', '白酒', '食品', '零售', '餐饮'],
+ '金融': ['银行', '保险', '券商', '证券', '金融'],
+ '房地产': ['房地产', '地产', '楼市'],
+ '军工': ['军工', '国防', '航空', '航天'],
+ '汽车': ['汽车', '新能源车', '智能驾驶'],
+ '低空经济': ['低空', '无人机', '飞行汽车'],
+ '机器人': ['机器人', '人形机器人', '减速器'],
+ '数据要素': ['数据', '数据要素', '数字经济'],
+ }
+
+ keywords = sector_keywords.get(sector, [sector])
+ related = []
+
+ for news in news_list:
+ text = (news.get('title') or '') + (news.get('content') or '')
+ for kw in keywords:
+ if kw in text:
+ related.append(news)
+ break
+
+ return related[:20]
+
+ def _filter_topics_by_sector(self, topics: List[Dict], sector: str) -> List[Dict]:
+ """筛选与板块相关的话题"""
+ sector_keywords = {
+ 'AI人工智能': ['AI', '人工智能', '大模型', 'ChatGPT', '算力', '智能'],
+ '新能源': ['新能源', '光伏', '锂电', '储能', '电池'],
+ '半导体芯片': ['芯片', '半导体', '光刻'],
+ '医药生物': ['医药', '生物', '疫苗', '医疗'],
+ '消费': ['消费', '白酒', '食品', '餐饮'],
+ }
+
+ keywords = sector_keywords.get(sector, [sector])
+ related = []
+
+ for topic in topics:
+ text = topic.get('topic', '')
+ for kw in keywords:
+ if kw in text:
+ related.append(topic)
+ break
+
+ return related[:10]
+
+ def _generate_multi_sector_summary(self, sector_analyses: List[Dict]) -> str:
+ """生成多板块分析总结"""
+ if not sector_analyses:
+ return "暂无板块分析数据"
+
+ # 按热度排序
+ sorted_analyses = sorted(
+ sector_analyses,
+ key=lambda x: x.get('heat_score', 0),
+ reverse=True
+ )
+
+ summary_parts = []
+ summary_parts.append(f"共分析{len(sector_analyses)}个热门板块:")
+
+ for i, analysis in enumerate(sorted_analyses[:3], 1):
+ sector = analysis.get('sector_name', '未知')
+ heat = analysis.get('heat_level', '中')
+ outlook = analysis.get('short_term_outlook', '震荡')
+ summary_parts.append(f"{i}. {sector}(热度{heat},{outlook})")
+
+ return ' '.join(summary_parts)
+
+ def _parse_json_response(self, response: str) -> Optional[Dict]:
+ """解析JSON响应"""
+ try:
+ # 清理响应文本
+ text = response.strip()
+
+ # 处理markdown代码块
+ if '```json' in text:
+ text = text.split('```json')[1].split('```')[0]
+ elif '```' in text:
+ text = text.split('```')[1].split('```')[0]
+
+ # 移除可能的推理过程
+ if '【推理过程】' in text:
+ parts = text.split('【推理过程】')
+ text = parts[-1] if len(parts) > 1 else parts[0]
+
+ # 查找JSON部分
+ start = text.find('{')
+ end = text.rfind('}') + 1
+
+ if start >= 0 and end > start:
+ json_text = text[start:end]
+ return json.loads(json_text)
+
+ return None
+
+ except json.JSONDecodeError as e:
+ logger.error(f"JSON解析失败: {e}")
+ return None
+
+ # ==================== 降级方法 ====================
+
+ def _fallback_sector_analysis(self, hot_topics: List[Dict],
+ stock_news: List[Dict]) -> Dict:
+ """板块分析降级方法"""
+ # 基于关键词的简单分析
+ sector_keywords = {
+ 'AI人工智能': ['AI', '人工智能', 'ChatGPT', '大模型', '算力', 'GPT'],
+ '新能源': ['新能源', '锂电', '光伏', '风电', '储能', '充电桩'],
+ '半导体': ['芯片', '半导体', '光刻机', '集成电路', '封测'],
+ '医药生物': ['医药', '疫苗', '创新药', '医疗', '生物'],
+ '消费': ['消费', '白酒', '食品', '零售', '餐饮'],
+ '金融': ['银行', '保险', '券商', '金融', '信托'],
+ }
+
+ sector_hits = {}
+ for topic in hot_topics:
+ topic_text = topic.get('topic', '')
+ heat = topic.get('heat', 0)
+ for sector, keywords in sector_keywords.items():
+ if any(kw in topic_text for kw in keywords):
+ if sector not in sector_hits:
+ sector_hits[sector] = 0
+ sector_hits[sector] += heat
+
+ # 排序获取TOP板块
+ sorted_sectors = sorted(sector_hits.items(), key=lambda x: x[1], reverse=True)
+
+ benefited_sectors = [
+ {
+ 'name': sector,
+ 'impact': '利好',
+ 'confidence': min(60, score // 2),
+ 'reason': f'热点话题关联度较高,热度得分{score}'
+ }
+ for sector, score in sorted_sectors[:5]
+ ]
+
+ return {
+ 'affected_sectors': benefited_sectors,
+ 'benefited_sectors': benefited_sectors,
+ 'damaged_sectors': [],
+ 'opportunity_assessment': '基于关键词匹配的简单分析,建议参考AI深度分析结果。',
+ 'key_points': ['AI分析不可用,使用降级方法'],
+ 'success': True,
+ 'fallback': True,
+ }
+
+ def _fallback_stock_recommend(self, hot_sectors: List[Dict]) -> Dict:
+ """股票推荐降级方法"""
+ return {
+ 'recommended_stocks': [],
+ 'overall_strategy': 'AI分析不可用,建议自行研究热门板块龙头股。',
+ 'risk_warning': '此为降级结果,请谨慎参考。',
+ 'success': True,
+ 'fallback': True,
+ }
+
+ def _fallback_risk_assess(self, flow_stage: str,
+ sentiment_data: Dict,
+ viral_k: float) -> Dict:
+ """风险评估降级方法"""
+ risk_score = 50
+ risk_factors = []
+
+ # 基于规则的简单风险评估
+ if flow_stage in ['一致', 'consensus']:
+ risk_score += 30
+ risk_factors.append('流量处于一致阶段,可能是顶部')
+ elif flow_stage in ['退潮', 'decline']:
+ risk_score += 20
+ risk_factors.append('流量正在退潮')
+
+ sentiment_index = sentiment_data.get('sentiment_index', 50)
+ if sentiment_index > 85:
+ risk_score += 15
+ risk_factors.append('情绪过度乐观')
+ elif sentiment_index < 20:
+ risk_score += 10
+ risk_factors.append('情绪过度悲观')
+
+ if viral_k > 1.5:
+ risk_score += 15
+ risk_factors.append(f'K值={viral_k},流量指数型增长')
+
+ risk_score = min(100, risk_score)
+
+ if risk_score >= 80:
+ risk_level = '极高'
+ elif risk_score >= 60:
+ risk_level = '高'
+ elif risk_score >= 40:
+ risk_level = '中等'
+ elif risk_score >= 20:
+ risk_level = '低'
+ else:
+ risk_level = '极低'
+
+ return {
+ 'risk_level': risk_level,
+ 'risk_score': risk_score,
+ 'risk_factors': risk_factors,
+ 'opportunities': [],
+ 'analysis': '基于规则的简单风险评估,AI分析不可用。',
+ 'key_warning': '请谨慎参考,建议开启AI分析获取更准确的评估。',
+ 'success': True,
+ 'fallback': True,
+ }
+
+ def _fallback_investment_advice(self, risk_assess: Dict,
+ flow_data: Dict) -> Dict:
+ """投资建议降级方法"""
+ risk_level = risk_assess.get('risk_level', '中等')
+
+ if risk_level in ['极高', '高']:
+ advice = '回避'
+ confidence = 70
+ summary = '当前风险较高,建议保持观望或减仓。'
+ elif risk_level == '中等':
+ advice = '观望'
+ confidence = 60
+ summary = '市场状态中性,建议观望等待更明确的信号。'
+ else:
+ advice = '关注'
+ confidence = 55
+ summary = '风险较低,可关注热点板块机会。'
+
+ return {
+ 'advice': advice,
+ 'confidence': confidence,
+ 'summary': summary,
+ 'action_plan': ['AI分析不可用,请自行判断'],
+ 'position_suggestion': '建议仓位不超过30%',
+ 'timing': '等待确认信号',
+ 'key_message': '此为降级结果,请谨慎参考。',
+ 'success': True,
+ 'fallback': True,
+ }
+
+
+# 全局实例
+news_flow_agents = NewsFlowAgents()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试新闻流量智能分析代理 ===")
+
+ # 检查AI是否可用
+ if news_flow_agents.is_available():
+ print("✅ AI客户端可用")
+ else:
+ print("⚠️ AI客户端不可用,将使用降级方法")
+
+ # 模拟数据
+ hot_topics = [
+ {'topic': 'AI芯片', 'heat': 95, 'cross_platform': 5},
+ {'topic': '新能源汽车', 'heat': 80, 'cross_platform': 4},
+ {'topic': '涨停板', 'heat': 75, 'cross_platform': 3},
+ ]
+
+ stock_news = [
+ {'platform_name': '东方财富', 'title': 'AI概念股集体大涨,龙头股涨停'},
+ {'platform_name': '雪球', 'title': '新能源板块反弹,锂电池领涨'},
+ ]
+
+ flow_data = {
+ 'total_score': 650,
+ 'level': '高',
+ }
+
+ sentiment_data = {
+ 'sentiment': {'sentiment_index': 72, 'sentiment_class': '乐观'},
+ 'flow_stage': {'stage_name': '加速'},
+ }
+
+ # 运行板块分析
+ print("\n--- 板块影响分析 ---")
+ sector_result = news_flow_agents.sector_impact_agent(hot_topics, stock_news, flow_data)
+ print(f"受益板块: {[s.get('name', '') for s in sector_result.get('benefited_sectors', [])]}")
+ print(f"是否降级: {sector_result.get('fallback', False)}")
diff --git a/news_flow_alert.py b/news_flow_alert.py
new file mode 100644
index 0000000..0c76dc4
--- /dev/null
+++ b/news_flow_alert.py
@@ -0,0 +1,514 @@
+"""
+新闻流量预警系统模块
+实现6种预警类型和通知推送
+"""
+import logging
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional, Tuple
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowAlertSystem:
+ """新闻流量预警系统"""
+
+ # 预警类型定义
+ ALERT_TYPES = {
+ 'heat_surge': {
+ 'name': '热度飙升',
+ 'level': 'warning',
+ 'description': '流量得分超过阈值,市场热度异常升高',
+ },
+ 'rank_change': {
+ 'name': '排名变化',
+ 'level': 'info',
+ 'description': '热点排名快速变化',
+ },
+ 'sentiment_extreme': {
+ 'name': '情绪极值',
+ 'level': 'warning',
+ 'description': '情绪指数处于极端状态(过高或过低)',
+ },
+ 'flow_peak': {
+ 'name': '流量高潮',
+ 'level': 'danger',
+ 'description': '进入"一致"阶段,可能是逃命时刻',
+ },
+ 'flow_decline': {
+ 'name': '流量退潮',
+ 'level': 'warning',
+ 'description': '进入"退潮"阶段,注意止盈止损',
+ },
+ 'viral_spread': {
+ 'name': '病毒传播',
+ 'level': 'warning',
+ 'description': 'K值超过阈值,流量呈指数型增长',
+ },
+ }
+
+ # 预警级别定义
+ ALERT_LEVELS = {
+ 'info': {'name': '提示', 'color': 'blue', 'priority': 1},
+ 'warning': {'name': '警告', 'color': 'orange', 'priority': 2},
+ 'danger': {'name': '危险', 'color': 'red', 'priority': 3},
+ }
+
+ def __init__(self):
+ """初始化预警系统"""
+ self.db = None
+ self.notification_service = None
+ self._init_dependencies()
+
+ # 默认阈值配置
+ self.default_thresholds = {
+ 'heat_threshold': 800,
+ 'rank_change_threshold': 10,
+ 'sentiment_high_threshold': 90,
+ 'sentiment_low_threshold': 20,
+ 'viral_k_threshold': 1.5,
+ }
+
+ def _init_dependencies(self):
+ """初始化依赖"""
+ try:
+ from news_flow_db import news_flow_db
+ self.db = news_flow_db
+ except Exception as e:
+ logger.warning(f"数据库初始化失败: {e}")
+
+ try:
+ from notification_service import notification_service
+ self.notification_service = notification_service
+ except Exception as e:
+ logger.warning(f"通知服务初始化失败: {e}")
+
+ def get_threshold(self, key: str) -> float:
+ """获取阈值配置"""
+ if self.db:
+ value = self.db.get_alert_config(key)
+ if value:
+ try:
+ return float(value)
+ except ValueError:
+ pass
+ return self.default_thresholds.get(key, 0)
+
+ def set_threshold(self, key: str, value: float):
+ """设置阈值配置"""
+ if self.db:
+ self.db.set_alert_config(key, str(value))
+
+ def check_alerts(self, current_data: Dict,
+ history_data: Dict = None,
+ sentiment_data: Dict = None,
+ snapshot_id: int = None) -> List[Dict]:
+ """
+ 检查所有预警条件
+
+ Args:
+ current_data: 当前数据,包含flow_data, hot_topics等
+ history_data: 历史数据,用于比较
+ sentiment_data: 情绪数据
+ snapshot_id: 快照ID
+
+ Returns:
+ List[Dict]: 触发的预警列表
+ """
+ alerts = []
+
+ flow_data = current_data.get('flow_data', {})
+ hot_topics = current_data.get('hot_topics', [])
+ viral_k = current_data.get('viral_k', {})
+ flow_stage = current_data.get('flow_stage', {})
+
+ # 1. 检查热度飙升
+ heat_alert = self._check_heat_surge(flow_data)
+ if heat_alert:
+ heat_alert['snapshot_id'] = snapshot_id
+ alerts.append(heat_alert)
+
+ # 2. 检查排名变化
+ if history_data:
+ rank_alert = self._check_rank_change(hot_topics,
+ history_data.get('hot_topics', []))
+ if rank_alert:
+ rank_alert['snapshot_id'] = snapshot_id
+ alerts.append(rank_alert)
+
+ # 3. 检查情绪极值
+ if sentiment_data:
+ sentiment_alert = self._check_sentiment_extreme(sentiment_data)
+ if sentiment_alert:
+ sentiment_alert['snapshot_id'] = snapshot_id
+ alerts.append(sentiment_alert)
+
+ # 4. 检查流量高潮(一致阶段)
+ peak_alert = self._check_flow_peak(flow_stage, sentiment_data)
+ if peak_alert:
+ peak_alert['snapshot_id'] = snapshot_id
+ alerts.append(peak_alert)
+
+ # 5. 检查流量退潮
+ decline_alert = self._check_flow_decline(flow_stage)
+ if decline_alert:
+ decline_alert['snapshot_id'] = snapshot_id
+ alerts.append(decline_alert)
+
+ # 6. 检查病毒传播
+ viral_alert = self._check_viral_spread(viral_k)
+ if viral_alert:
+ viral_alert['snapshot_id'] = snapshot_id
+ alerts.append(viral_alert)
+
+ # 按优先级排序
+ alerts.sort(key=lambda x: self.ALERT_LEVELS.get(
+ x.get('alert_level', 'info'), {}
+ ).get('priority', 0), reverse=True)
+
+ # 保存预警到数据库
+ if self.db and alerts:
+ for alert in alerts:
+ self.db.save_alert(alert)
+
+ return alerts
+
+ def _check_heat_surge(self, flow_data: Dict) -> Optional[Dict]:
+ """检查热度飙升"""
+ threshold = self.get_threshold('heat_threshold')
+ current_score = flow_data.get('total_score', 0)
+
+ if current_score >= threshold:
+ return {
+ 'alert_type': 'heat_surge',
+ 'alert_level': 'warning',
+ 'title': f'热度飙升预警:流量得分{current_score}',
+ 'content': f"当前流量得分{current_score},超过阈值{threshold}。"
+ f"市场热度异常升高,可能存在短期机会,但也要注意追高风险。",
+ 'related_topics': [],
+ 'trigger_value': current_score,
+ 'threshold_value': threshold,
+ }
+ return None
+
+ def _check_rank_change(self, current_topics: List[Dict],
+ previous_topics: List[Dict]) -> Optional[Dict]:
+ """检查排名变化"""
+ threshold = int(self.get_threshold('rank_change_threshold'))
+
+ if not previous_topics:
+ return None
+
+ # 建立之前的排名映射
+ prev_ranks = {t.get('topic', ''): i for i, t in enumerate(previous_topics)}
+
+ # 检查快速上升的话题
+ rapid_rise_topics = []
+ for i, topic in enumerate(current_topics[:20]):
+ topic_name = topic.get('topic', '')
+ if topic_name in prev_ranks:
+ rank_change = prev_ranks[topic_name] - i
+ if rank_change >= threshold:
+ rapid_rise_topics.append({
+ 'topic': topic_name,
+ 'current_rank': i + 1,
+ 'previous_rank': prev_ranks[topic_name] + 1,
+ 'change': rank_change,
+ })
+
+ if rapid_rise_topics:
+ topics_text = ', '.join([t['topic'] for t in rapid_rise_topics[:3]])
+ return {
+ 'alert_type': 'rank_change',
+ 'alert_level': 'info',
+ 'title': f'排名变化提示:{topics_text}',
+ 'content': f"{len(rapid_rise_topics)}个话题排名快速上升(上升{threshold}名以上),"
+ f"可能是新热点正在发酵。",
+ 'related_topics': [t['topic'] for t in rapid_rise_topics],
+ 'trigger_value': len(rapid_rise_topics),
+ 'threshold_value': threshold,
+ }
+ return None
+
+ def _check_sentiment_extreme(self, sentiment_data: Dict) -> Optional[Dict]:
+ """检查情绪极值"""
+ high_threshold = self.get_threshold('sentiment_high_threshold')
+ low_threshold = self.get_threshold('sentiment_low_threshold')
+
+ sentiment = sentiment_data.get('sentiment', {})
+ sentiment_index = sentiment.get('sentiment_index', 50)
+ sentiment_class = sentiment.get('sentiment_class', '中性')
+
+ if sentiment_index >= high_threshold:
+ return {
+ 'alert_type': 'sentiment_extreme',
+ 'alert_level': 'warning',
+ 'title': f'情绪极值警告:{sentiment_class}({sentiment_index}分)',
+ 'content': f"情绪指数{sentiment_index}分,处于极度乐观状态!"
+ f"根据'流量高潮=价格高潮'理论,市场可能接近顶部,注意及时止盈。",
+ 'related_topics': [],
+ 'trigger_value': sentiment_index,
+ 'threshold_value': high_threshold,
+ }
+ elif sentiment_index <= low_threshold:
+ return {
+ 'alert_type': 'sentiment_extreme',
+ 'alert_level': 'warning',
+ 'title': f'情绪极值警告:{sentiment_class}({sentiment_index}分)',
+ 'content': f"情绪指数{sentiment_index}分,处于极度悲观状态!"
+ f"市场恐慌情绪蔓延,可能存在超跌反弹机会,但需谨慎左侧布局。",
+ 'related_topics': [],
+ 'trigger_value': sentiment_index,
+ 'threshold_value': low_threshold,
+ }
+ return None
+
+ def _check_flow_peak(self, flow_stage: Dict,
+ sentiment_data: Dict = None) -> Optional[Dict]:
+ """
+ 检查流量高潮(逃命预警)
+
+ 当以下条件同时满足时触发:
+ 1. 流量阶段 = "一致"
+ 2. 情绪指数 > 85
+ 3. K值 > 1.5(可选)
+ """
+ stage = flow_stage.get('stage', '')
+ stage_name = flow_stage.get('stage_name', '')
+
+ # 主要触发条件:一致阶段
+ if stage not in ['consensus', '一致']:
+ return None
+
+ # 增强条件检查
+ sentiment_index = 50
+ if sentiment_data:
+ sentiment = sentiment_data.get('sentiment', {})
+ sentiment_index = sentiment.get('sentiment_index', 50)
+
+ # 一致阶段就触发危险预警
+ return {
+ 'alert_type': 'flow_peak',
+ 'alert_level': 'danger',
+ 'title': '⚠️ 流量高潮预警:准备跑路!',
+ 'content': f"流量阶段进入【{stage_name}】!这是最危险的信号!\n\n"
+ f"根据'流量为王'理论:流量高潮 = 价格高潮 = 逃命时刻\n\n"
+ f"当热搜、媒体报道、KOL转发同时达到高潮时,就是出货时机。\n\n"
+ f"建议:立即减仓或清仓,锁定利润!",
+ 'related_topics': [],
+ 'trigger_value': stage_name,
+ 'threshold_value': '一致阶段',
+ }
+
+ def _check_flow_decline(self, flow_stage: Dict) -> Optional[Dict]:
+ """检查流量退潮"""
+ stage = flow_stage.get('stage', '')
+ stage_name = flow_stage.get('stage_name', '')
+ avg_growth = flow_stage.get('avg_growth', 0)
+
+ if stage not in ['decline', '退潮']:
+ return None
+
+ return {
+ 'alert_type': 'flow_decline',
+ 'alert_level': 'warning',
+ 'title': f'流量退潮警告:及时止盈止损',
+ 'content': f"流量阶段进入【{stage_name}】,增速{avg_growth}%。\n\n"
+ f"题材热度正在消退,资金开始撤离。\n\n"
+ f"建议:持仓者及时止盈止损,不要恋战。空仓者不要抄底接飞刀。",
+ 'related_topics': [],
+ 'trigger_value': avg_growth,
+ 'threshold_value': '退潮阶段',
+ }
+
+ def _check_viral_spread(self, viral_k: Dict) -> Optional[Dict]:
+ """检查病毒传播"""
+ threshold = self.get_threshold('viral_k_threshold')
+ k_value = viral_k.get('k_value', 1.0)
+ trend = viral_k.get('trend', '')
+
+ if k_value >= threshold:
+ return {
+ 'alert_type': 'viral_spread',
+ 'alert_level': 'warning',
+ 'title': f'病毒传播预警:K值={k_value}',
+ 'content': f"K值={k_value},趋势:{trend}\n\n"
+ f"流量正在指数型增长,这是病毒式传播的特征。\n\n"
+ f"题材可能进入加速期,但也要注意:\n"
+ f"- K值过高意味着接近顶部的风险增加\n"
+ f"- 指数型增长往往伴随着指数型下跌\n"
+ f"- 密切关注后续K值变化,一旦开始下降就是离场信号",
+ 'related_topics': [],
+ 'trigger_value': k_value,
+ 'threshold_value': threshold,
+ }
+ return None
+
+ def send_notification(self, alerts: List[Dict]) -> bool:
+ """
+ 发送通知
+
+ Args:
+ alerts: 预警列表
+
+ Returns:
+ bool: 是否发送成功
+ """
+ if not alerts:
+ return True
+
+ if not self.notification_service:
+ logger.warning("通知服务不可用")
+ return False
+
+ try:
+ # 按级别分组
+ danger_alerts = [a for a in alerts if a.get('alert_level') == 'danger']
+ warning_alerts = [a for a in alerts if a.get('alert_level') == 'warning']
+ info_alerts = [a for a in alerts if a.get('alert_level') == 'info']
+
+ # 构建通知内容
+ lines = []
+ lines.append("📊 新闻流量预警通知")
+ lines.append(f"时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+ lines.append("")
+
+ if danger_alerts:
+ lines.append("🔴 【危险预警】")
+ for alert in danger_alerts:
+ lines.append(f" • {alert['title']}")
+ lines.append("")
+
+ if warning_alerts:
+ lines.append("🟠 【警告】")
+ for alert in warning_alerts:
+ lines.append(f" • {alert['title']}")
+ lines.append("")
+
+ if info_alerts:
+ lines.append("🔵 【提示】")
+ for alert in info_alerts:
+ lines.append(f" • {alert['title']}")
+
+ message = '\n'.join(lines)
+
+ # 发送通知
+ # 使用危险级别发送最高优先级预警
+ if danger_alerts:
+ subject = "⚠️ 新闻流量危险预警"
+ else:
+ subject = "📊 新闻流量预警通知"
+
+ # 调用通知服务
+ success = self.notification_service.send_analysis_result(
+ subject=subject,
+ content=message
+ )
+
+ # 标记为已通知
+ if success and self.db:
+ for alert in alerts:
+ if 'id' in alert:
+ self.db.mark_alert_notified(alert['id'])
+
+ return success
+
+ except Exception as e:
+ logger.error(f"发送通知失败: {e}")
+ return False
+
+ def get_alert_history(self, days: int = 7,
+ alert_type: str = None) -> List[Dict]:
+ """获取预警历史"""
+ if self.db:
+ return self.db.get_alerts(days, alert_type)
+ return []
+
+ def get_unnotified_alerts(self) -> List[Dict]:
+ """获取未通知的预警"""
+ if self.db:
+ return self.db.get_unnotified_alerts()
+ return []
+
+ def get_alert_summary(self, days: int = 7) -> Dict:
+ """获取预警统计摘要"""
+ alerts = self.get_alert_history(days)
+
+ # 按类型统计
+ type_counts = {}
+ for alert in alerts:
+ alert_type = alert.get('alert_type', 'unknown')
+ type_counts[alert_type] = type_counts.get(alert_type, 0) + 1
+
+ # 按级别统计
+ level_counts = {}
+ for alert in alerts:
+ level = alert.get('alert_level', 'info')
+ level_counts[level] = level_counts.get(level, 0) + 1
+
+ return {
+ 'total_count': len(alerts),
+ 'type_counts': type_counts,
+ 'level_counts': level_counts,
+ 'danger_count': level_counts.get('danger', 0),
+ 'warning_count': level_counts.get('warning', 0),
+ 'info_count': level_counts.get('info', 0),
+ }
+
+ def get_threshold_config(self) -> Dict:
+ """获取所有阈值配置"""
+ return {
+ 'heat_threshold': self.get_threshold('heat_threshold'),
+ 'rank_change_threshold': self.get_threshold('rank_change_threshold'),
+ 'sentiment_high_threshold': self.get_threshold('sentiment_high_threshold'),
+ 'sentiment_low_threshold': self.get_threshold('sentiment_low_threshold'),
+ 'viral_k_threshold': self.get_threshold('viral_k_threshold'),
+ }
+
+
+# 全局实例
+news_flow_alert_system = NewsFlowAlertSystem()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试预警系统 ===")
+
+ # 模拟数据
+ current_data = {
+ 'flow_data': {'total_score': 850, 'level': '极高'},
+ 'hot_topics': [
+ {'topic': 'AI芯片', 'heat': 95},
+ {'topic': '新能源', 'heat': 80},
+ ],
+ 'viral_k': {'k_value': 1.8, 'trend': '指数型爆发'},
+ 'flow_stage': {'stage': 'consensus', 'stage_name': '一致', 'avg_growth': 35},
+ }
+
+ sentiment_data = {
+ 'sentiment': {'sentiment_index': 92, 'sentiment_class': '极度乐观'},
+ }
+
+ history_data = {
+ 'hot_topics': [
+ {'topic': '新能源', 'heat': 70},
+ {'topic': 'AI芯片', 'heat': 60},
+ ],
+ }
+
+ # 检查预警
+ alerts = news_flow_alert_system.check_alerts(
+ current_data, history_data, sentiment_data
+ )
+
+ print(f"\n触发 {len(alerts)} 个预警:")
+ for alert in alerts:
+ level_info = NewsFlowAlertSystem.ALERT_LEVELS.get(alert['alert_level'], {})
+ print(f"\n[{level_info.get('name', alert['alert_level'])}] {alert['title']}")
+ print(f" {alert['content'][:100]}...")
+
+ # 获取阈值配置
+ print("\n当前阈值配置:")
+ config = news_flow_alert_system.get_threshold_config()
+ for key, value in config.items():
+ print(f" {key}: {value}")
diff --git a/news_flow_data.py b/news_flow_data.py
new file mode 100644
index 0000000..0bfe83a
--- /dev/null
+++ b/news_flow_data.py
@@ -0,0 +1,702 @@
+"""
+新闻流量数据获取模块
+用于获取各大平台的热点新闻和流量数据
+支持22个平台,包含排名、K值计算等功能
+"""
+import requests
+import logging
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional, Tuple
+import time
+from collections import Counter
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowDataFetcher:
+ """新闻流量数据获取器"""
+
+ def __init__(self):
+ self.base_url = "https://newsapi.ws4.cn/api/v1/dailynews/"
+ self.timeout = 10
+
+ # 支持的平台配置 - 扩展到22个平台
+ self.platforms = {
+ # 社交媒体平台(核心流量指标)- 8个
+ 'weibo': {'name': '微博热搜', 'category': 'social', 'weight': 10, 'influence': 'high'},
+ 'douyin': {'name': '抖音热点', 'category': 'social', 'weight': 9, 'influence': 'high'},
+ 'zhihu': {'name': '知乎热榜', 'category': 'social', 'weight': 7, 'influence': 'medium'},
+ 'bilibili': {'name': '哔哩哔哩', 'category': 'social', 'weight': 6, 'influence': 'medium'},
+ 'xiaohongshu': {'name': '小红书', 'category': 'social', 'weight': 7, 'influence': 'medium'},
+ 'kuaishou': {'name': '快手', 'category': 'social', 'weight': 6, 'influence': 'medium'},
+ 'tieba': {'name': '百度贴吧', 'category': 'social', 'weight': 5, 'influence': 'low'},
+ 'weixin': {'name': '微信热点', 'category': 'social', 'weight': 8, 'influence': 'high'},
+
+ # 新闻媒体平台 - 6个
+ 'baidu': {'name': '百度热搜', 'category': 'news', 'weight': 8, 'influence': 'high'},
+ 'jinritoutiao': {'name': '今日头条', 'category': 'news', 'weight': 7, 'influence': 'high'},
+ 'tenxunwang': {'name': '腾讯网', 'category': 'news', 'weight': 6, 'influence': 'medium'},
+ 'netease': {'name': '网易新闻', 'category': 'news', 'weight': 6, 'influence': 'medium'},
+ 'ifeng': {'name': '凤凰网', 'category': 'news', 'weight': 5, 'influence': 'medium'},
+ 'sina': {'name': '新浪新闻', 'category': 'news', 'weight': 6, 'influence': 'medium'},
+
+ # 财经平台(股市相关)- 5个
+ 'sina_finance': {'name': '新浪财经', 'category': 'finance', 'weight': 9, 'influence': 'high'},
+ 'eastmoney': {'name': '东方财富', 'category': 'finance', 'weight': 9, 'influence': 'high'},
+ 'xueqiu': {'name': '雪球', 'category': 'finance', 'weight': 8, 'influence': 'high'},
+ 'cls': {'name': '财联社', 'category': 'finance', 'weight': 8, 'influence': 'high'},
+ 'wallstreetcn': {'name': '华尔街见闻', 'category': 'finance', 'weight': 7, 'influence': 'medium'},
+
+ # 科技平台 - 3个
+ 'tskr': {'name': '36氪', 'category': 'tech', 'weight': 6, 'influence': 'medium'},
+ 'sspai': {'name': '少数派', 'category': 'tech', 'weight': 5, 'influence': 'low'},
+ 'juejin': {'name': '掘金', 'category': 'tech', 'weight': 5, 'influence': 'low'},
+ }
+
+ # 平台类别权重(用于转化率计算)
+ self.category_weights = {
+ 'finance': 1.5, # 财经平台转化率高
+ 'social': 1.2, # 社交媒体传播快
+ 'news': 1.0, # 新闻媒体正常
+ 'tech': 0.8, # 科技平台相关性低
+ }
+
+ # 停用词(过滤无意义的词)
+ self.stop_words = {
+ '的', '是', '在', '了', '和', '与', '等', '为', '将', '被',
+ '有', '一', '个', '上', '下', '中', '大', '新', '年', '月', '日',
+ '这', '那', '其', '之', '也', '要', '就', '不', '我', '你', '他',
+ '来', '去', '到', '说', '会', '能', '都', '对', '着', '让',
+ '从', '以', '及', '或', '如', '还', '没', '很', '更', '最',
+ }
+
+ def get_platform_news(self, platform: str) -> Dict:
+ """
+ 获取单个平台的新闻数据
+
+ Args:
+ platform: 平台代码(如 'weibo', 'baidu'等)
+
+ Returns:
+ {
+ 'success': bool,
+ 'platform': str,
+ 'platform_name': str,
+ 'category': str,
+ 'weight': int,
+ 'influence': str,
+ 'data': List[Dict],
+ 'count': int,
+ 'fetch_time': str,
+ 'error': str (如果失败)
+ }
+ """
+ try:
+ url = f"{self.base_url}?platform={platform}"
+
+ logger.info(f"正在获取 {platform} 平台数据...")
+ response = requests.get(url, timeout=self.timeout)
+ response.raise_for_status()
+
+ data = response.json()
+
+ if data.get('status') == '200':
+ news_list = data.get('data', [])
+ platform_info = self.platforms.get(platform, {})
+
+ # 为每条新闻添加排名信息
+ for i, news in enumerate(news_list):
+ news['rank'] = i + 1
+ news['platform'] = platform
+
+ return {
+ 'success': True,
+ 'platform': platform,
+ 'platform_name': platform_info.get('name', platform),
+ 'category': platform_info.get('category', 'other'),
+ 'weight': platform_info.get('weight', 5),
+ 'influence': platform_info.get('influence', 'medium'),
+ 'data': news_list,
+ 'count': len(news_list),
+ 'fetch_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ }
+ else:
+ return {
+ 'success': False,
+ 'platform': platform,
+ 'error': f"API返回错误: {data.get('msg', '未知错误')}"
+ }
+
+ except requests.exceptions.Timeout:
+ return {
+ 'success': False,
+ 'platform': platform,
+ 'error': f"请求超时({self.timeout}秒)"
+ }
+ except requests.exceptions.ConnectionError:
+ return {
+ 'success': False,
+ 'platform': platform,
+ 'error': "网络连接失败"
+ }
+ except Exception as e:
+ return {
+ 'success': False,
+ 'platform': platform,
+ 'error': f"获取数据失败: {str(e)}"
+ }
+
+ def get_multi_platform_news(self, platforms: List[str] = None,
+ category: str = None) -> Dict:
+ """
+ 获取多个平台的新闻数据
+
+ Args:
+ platforms: 平台列表,None表示获取所有
+ category: 按类别筛选('social', 'news', 'finance', 'tech')
+
+ Returns:
+ {
+ 'success': bool,
+ 'total_platforms': int,
+ 'success_count': int,
+ 'failed_count': int,
+ 'platforms_data': List[Dict],
+ 'fetch_time': str
+ }
+ """
+ # 确定要获取的平台列表
+ if platforms is None:
+ if category:
+ target_platforms = [
+ p for p, info in self.platforms.items()
+ if info.get('category') == category
+ ]
+ else:
+ target_platforms = list(self.platforms.keys())
+ else:
+ target_platforms = platforms
+
+ results = []
+ success_count = 0
+ failed_count = 0
+
+ for platform in target_platforms:
+ result = self.get_platform_news(platform)
+ results.append(result)
+
+ if result['success']:
+ success_count += 1
+ else:
+ failed_count += 1
+
+ # 避免请求过快,休息0.3秒
+ time.sleep(0.3)
+
+ return {
+ 'success': success_count > 0,
+ 'total_platforms': len(target_platforms),
+ 'success_count': success_count,
+ 'failed_count': failed_count,
+ 'platforms_data': results,
+ 'fetch_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ }
+
+ def extract_stock_related_news(self, platforms_data: List[Dict],
+ keywords: List[str] = None) -> List[Dict]:
+ """
+ 从新闻数据中提取股票相关的新闻
+
+ Args:
+ platforms_data: 平台数据列表
+ keywords: 股票相关关键词列表
+
+ Returns:
+ List[Dict]: 股票相关新闻列表
+ """
+ if keywords is None:
+ keywords = [
+ # 股市基础词汇
+ '股', '股市', '股票', 'A股', '港股', '美股', '创业板', '科创板', '北交所',
+ '涨停', '跌停', '大涨', '暴涨', '飙升', '暴跌', '涨幅', '跌幅', '翻倍',
+ '概念股', '龙头股', '妖股', '题材股', '白马股', '蓝筹股', '成长股',
+ '上市', 'IPO', '重组', '并购', '收购', '增发', '回购', '减持', '增持',
+ '业绩', '财报', '利好', '利空', '预增', '预减', '盈利', '亏损',
+ '牛市', '熊市', '反弹', '回调', '震荡', '突破', '新高',
+ '主力', '游资', '北向资金', '外资', '机构', '资金流入', '资金流出',
+ '板块', '行业', '赛道', '题材', '轮动', '热点',
+
+ # 热门板块关键词
+ '芯片', '半导体', '光刻机', '封装', '存储',
+ '新能源', '锂电', '光伏', '储能', '风电', '氢能',
+ 'AI', '人工智能', '大模型', 'ChatGPT', 'DeepSeek', '算力', 'GPU',
+ '机器人', '人形机器人', '工业机器人', '减速器', '伺服电机',
+ '低空经济', '无人机', 'eVTOL', '飞行汽车',
+ '数据要素', '数字经济', '信创', '国产替代',
+ '医药', '创新药', 'CXO', '医疗器械', '中药',
+ '消费', '白酒', '食品', '旅游', '免税',
+ '军工', '国防', '航空', '航天', '船舶',
+ '汽车', '新能源车', '智能驾驶', '无人驾驶', '充电桩',
+ '地产', '房地产', '楼市', '房价',
+ '金融', '银行', '保险', '券商', '证券',
+
+ # 政策相关
+ '政策', '利率', '降息', '降准', '货币政策', '财政政策',
+ '国常会', '证监会', '央行', '发改委', '工信部',
+ ]
+
+ stock_related = []
+
+ for platform_data in platforms_data:
+ if not platform_data.get('success'):
+ continue
+
+ platform = platform_data['platform']
+ platform_name = platform_data['platform_name']
+ category = platform_data['category']
+ weight = platform_data['weight']
+ influence = platform_data.get('influence', 'medium')
+
+ for news in platform_data.get('data', []):
+ title = news.get('title') or ''
+ content = news.get('content') or ''
+ rank = news.get('rank', 99)
+
+ # 检查是否包含股票关键词
+ text = f"{title} {content}"
+ matched_keywords = [kw for kw in keywords if kw in text]
+
+ if matched_keywords:
+ # 计算综合得分(排名越靠前、权重越高得分越高)
+ rank_score = max(0, 100 - rank * 2) # 排名1得98分,排名50得0分
+ weight_score = weight * 10
+ keyword_score = len(matched_keywords) * 5
+ total_score = rank_score + weight_score + keyword_score
+
+ stock_related.append({
+ 'platform': platform,
+ 'platform_name': platform_name,
+ 'category': category,
+ 'weight': weight,
+ 'influence': influence,
+ 'rank': rank,
+ 'title': title,
+ 'content': content,
+ 'url': news.get('url') or '',
+ 'source': news.get('source') or platform_name,
+ 'publish_time': news.get('publish_time') or '',
+ 'matched_keywords': matched_keywords,
+ 'keyword_count': len(matched_keywords),
+ 'score': total_score,
+ })
+
+ # 按综合得分排序
+ stock_related.sort(key=lambda x: x['score'], reverse=True)
+
+ return stock_related
+
+ def calculate_flow_score(self, platforms_data: List[Dict]) -> Dict:
+ """
+ 计算流量得分
+
+ 根据"流量为王"理念,评估当前市场热度
+
+ Returns:
+ {
+ 'total_score': int, # 总流量得分(0-1000)
+ 'social_score': int, # 社交媒体流量得分
+ 'news_score': int, # 新闻媒体流量得分
+ 'finance_score': int, # 财经平台流量得分
+ 'tech_score': int, # 科技平台流量得分
+ 'level': str, # 流量等级(低/中/高/极高)
+ 'analysis': str, # 流量分析
+ 'platform_details': List[Dict], # 各平台详情
+ }
+ """
+ scores = {
+ 'social': 0,
+ 'news': 0,
+ 'finance': 0,
+ 'tech': 0,
+ }
+
+ platform_details = []
+
+ for platform_data in platforms_data:
+ if not platform_data.get('success'):
+ continue
+
+ category = platform_data['category']
+ weight = platform_data['weight']
+ count = platform_data['count']
+ platform_name = platform_data['platform_name']
+
+ # 计算得分:权重 × 新闻数量
+ score = weight * count
+ scores[category] = scores.get(category, 0) + score
+
+ platform_details.append({
+ 'platform': platform_data['platform'],
+ 'platform_name': platform_name,
+ 'category': category,
+ 'count': count,
+ 'score': score,
+ })
+
+ # 计算总分
+ total_score = sum(scores.values())
+
+ # 归一化到0-1000
+ if total_score > 0:
+ normalized_score = min(int(total_score / 50), 1000)
+ else:
+ normalized_score = 0
+
+ # 确定流量等级
+ if normalized_score >= 800:
+ level = "极高"
+ analysis = "流量爆发!市场情绪极度活跃,大量新闻热点,存在热点题材炒作机会。建议:密切关注龙头股,注意追高风险。"
+ elif normalized_score >= 500:
+ level = "高"
+ analysis = "流量较高。市场有明确热点,资金活跃度较好。建议:关注热点板块,注意节奏把握。"
+ elif normalized_score >= 200:
+ level = "中"
+ analysis = "流量正常。市场处于常态,有一定热点但不突出。建议:观望为主,等待明确信号。"
+ else:
+ level = "低"
+ analysis = "流量较低。市场情绪低迷,缺乏热点。建议:控制仓位,等待市场转暖。"
+
+ return {
+ 'total_score': normalized_score,
+ 'social_score': scores.get('social', 0),
+ 'news_score': scores.get('news', 0),
+ 'finance_score': scores.get('finance', 0),
+ 'tech_score': scores.get('tech', 0),
+ 'level': level,
+ 'analysis': analysis,
+ 'platform_details': platform_details,
+ }
+
+ def get_hot_topics(self, platforms_data: List[Dict], top_n: int = 20) -> List[Dict]:
+ """
+ 获取热门话题(基于标题词频分析)
+
+ Returns:
+ List[Dict]: 热门话题列表
+ """
+ import jieba
+
+ # 收集所有标题及其来源信息
+ all_titles = []
+ title_sources = {} # 记录每个标题来自哪些平台
+
+ for platform_data in platforms_data:
+ if platform_data.get('success'):
+ platform_name = platform_data['platform_name']
+ for news in platform_data.get('data', []):
+ title = news.get('title') or ''
+ if title:
+ all_titles.append(title)
+ if title not in title_sources:
+ title_sources[title] = []
+ title_sources[title].append(platform_name)
+
+ # 分词并统计
+ word_counter = Counter()
+ word_sources = {} # 记录每个词出现在哪些平台
+
+ for title in all_titles:
+ if title:
+ words = jieba.cut(title)
+ for word in words:
+ if len(word) >= 2 and word not in self.stop_words:
+ word_counter[word] += 1
+ if word not in word_sources:
+ word_sources[word] = set()
+ for source in title_sources.get(title, []):
+ word_sources[word].add(source)
+
+ # 获取TOP N
+ hot_topics = []
+ total_titles = len(all_titles) if all_titles else 1
+
+ for word, count in word_counter.most_common(top_n):
+ sources = list(word_sources.get(word, []))
+ cross_platform = len(sources) # 跨平台数
+ heat = min(int(count / total_titles * 1000), 100)
+
+ # 跨平台加成
+ if cross_platform >= 5:
+ heat = min(heat + 20, 100)
+ elif cross_platform >= 3:
+ heat = min(heat + 10, 100)
+
+ hot_topics.append({
+ 'topic': word,
+ 'count': count,
+ 'heat': heat,
+ 'cross_platform': cross_platform,
+ 'sources': sources[:5], # 最多显示5个来源
+ })
+
+ return hot_topics
+
+ def get_platform_ranking(self, platforms_data: List[Dict]) -> List[Dict]:
+ """
+ 获取跨平台热度排名
+
+ 合并所有平台的新闻,按热度排序
+
+ Returns:
+ List[Dict]: 排名列表
+ """
+ all_news = []
+
+ for platform_data in platforms_data:
+ if not platform_data.get('success'):
+ continue
+
+ platform = platform_data['platform']
+ platform_name = platform_data['platform_name']
+ weight = platform_data['weight']
+
+ for news in platform_data.get('data', []):
+ title = news.get('title') or ''
+ rank = news.get('rank', 99)
+
+ if not title:
+ continue
+
+ # 计算综合热度分数
+ # 排名越靠前分数越高,平台权重越高分数越高
+ heat_score = (100 - rank) * weight
+
+ all_news.append({
+ 'title': title,
+ 'platform': platform,
+ 'platform_name': platform_name,
+ 'original_rank': rank,
+ 'heat_score': heat_score,
+ 'url': news.get('url') or '',
+ 'content': news.get('content') or '',
+ })
+
+ # 按热度分数排序
+ all_news.sort(key=lambda x: x['heat_score'], reverse=True)
+
+ # 添加全局排名
+ for i, news in enumerate(all_news):
+ news['global_rank'] = i + 1
+
+ return all_news
+
+ def calculate_viral_coefficient(self, current_data: Dict,
+ previous_data: Dict) -> Dict:
+ """
+ 计算病毒系数K值
+
+ K值 = 当前传播量 / 上期传播量
+
+ Args:
+ current_data: 当前数据
+ previous_data: 上期数据
+
+ Returns:
+ {
+ 'k_value': float, # K值
+ 'trend': str, # 趋势(指数型/线性/衰减)
+ 'analysis': str, # 分析
+ }
+ """
+ current_score = current_data.get('total_score', 0)
+ previous_score = previous_data.get('total_score', 0)
+
+ if previous_score == 0:
+ k_value = 1.0
+ trend = "无历史数据"
+ analysis = "首次采集,无法计算K值"
+ else:
+ k_value = round(current_score / previous_score, 2)
+
+ if k_value > 1.5:
+ trend = "指数型爆发"
+ analysis = f"K值={k_value},流量正在指数型增长!这是病毒式传播的特征,题材可能进入加速期。"
+ elif k_value > 1.0:
+ trend = "线性增长"
+ analysis = f"K值={k_value},流量稳步增长,题材正在发酵中。"
+ elif k_value == 1.0:
+ trend = "平稳"
+ analysis = f"K值={k_value},流量保持稳定,市场处于平衡状态。"
+ else:
+ trend = "衰减"
+ analysis = f"K值={k_value},流量正在衰减,题材热度下降,注意风险。"
+
+ return {
+ 'k_value': k_value,
+ 'current_score': current_score,
+ 'previous_score': previous_score,
+ 'trend': trend,
+ 'analysis': analysis,
+ }
+
+ def detect_flow_type(self, history_scores: List[int],
+ current_score: int) -> Dict:
+ """
+ 识别流量类型(存量流量型 vs 增量流量型)
+
+ 存量流量型:出生自带顶流,流量快速到位(政策/大事件)
+ 增量流量型:初始流量小,具备病毒传播能力(话题发酵)
+
+ Args:
+ history_scores: 历史得分列表(从旧到新)
+ current_score: 当前得分
+
+ Returns:
+ {
+ 'flow_type': str, # 存量流量型/增量流量型/未知
+ 'characteristics': List[str], # 特征
+ 'time_window': str, # 时间窗口建议
+ 'operation': str, # 操作建议
+ }
+ """
+ if len(history_scores) < 2:
+ return {
+ 'flow_type': '未知',
+ 'characteristics': ['历史数据不足'],
+ 'time_window': '无法判断',
+ 'operation': '继续观察',
+ }
+
+ # 计算初始得分和增长率
+ initial_score = history_scores[0] if history_scores else 0
+ avg_score = sum(history_scores) / len(history_scores)
+
+ # 计算增长趋势
+ growth_rates = []
+ for i in range(1, len(history_scores)):
+ if history_scores[i-1] > 0:
+ rate = (history_scores[i] - history_scores[i-1]) / history_scores[i-1]
+ growth_rates.append(rate)
+
+ avg_growth = sum(growth_rates) / len(growth_rates) if growth_rates else 0
+
+ # 判断流量类型
+ if initial_score >= 500:
+ # 初始热度高 -> 存量流量型
+ flow_type = "存量流量型"
+ characteristics = [
+ "初始热度高",
+ "流量快速到位",
+ "可能与政策/大事件相关",
+ ]
+ time_window = "时间窗口短(2-3天)"
+ operation = "快进快出,密切关注热度变化"
+ elif avg_growth > 0.2 and len([r for r in growth_rates if r > 0]) >= 2:
+ # 持续增长 -> 增量流量型
+ flow_type = "增量流量型"
+ characteristics = [
+ "初始热度低",
+ "逐步攀升",
+ "具备病毒传播特征",
+ ]
+ time_window = "时间窗口长(5-10天)"
+ operation = "可以埋伏,等待加速"
+ else:
+ flow_type = "常规流量"
+ characteristics = [
+ "热度波动正常",
+ "无明显趋势",
+ ]
+ time_window = "无特定窗口"
+ operation = "保持观望"
+
+ return {
+ 'flow_type': flow_type,
+ 'characteristics': characteristics,
+ 'time_window': time_window,
+ 'operation': operation,
+ 'initial_score': initial_score,
+ 'current_score': current_score,
+ 'avg_growth': round(avg_growth * 100, 1), # 百分比
+ }
+
+ def get_platform_list(self) -> List[Dict]:
+ """
+ 获取所有支持的平台列表
+
+ Returns:
+ List[Dict]: 平台列表
+ """
+ result = []
+ for code, info in self.platforms.items():
+ result.append({
+ 'code': code,
+ 'name': info['name'],
+ 'category': info['category'],
+ 'weight': info['weight'],
+ 'influence': info.get('influence', 'medium'),
+ })
+
+ # 按权重排序
+ result.sort(key=lambda x: x['weight'], reverse=True)
+ return result
+
+ def get_platforms_by_category(self) -> Dict[str, List[str]]:
+ """
+ 按类别获取平台列表
+
+ Returns:
+ Dict[str, List[str]]: 类别 -> 平台代码列表
+ """
+ categories = {}
+ for code, info in self.platforms.items():
+ category = info['category']
+ if category not in categories:
+ categories[category] = []
+ categories[category].append(code)
+ return categories
+
+
+# 测试代码
+if __name__ == "__main__":
+ fetcher = NewsFlowDataFetcher()
+
+ # 测试获取平台列表
+ print("=== 支持的平台列表 ===")
+ platforms = fetcher.get_platform_list()
+ print(f"共支持 {len(platforms)} 个平台:")
+ for p in platforms[:5]:
+ print(f" - {p['name']} ({p['code']}) - 权重:{p['weight']}")
+
+ # 测试获取单个平台
+ print("\n=== 测试获取微博热搜 ===")
+ result = fetcher.get_platform_news('weibo')
+ if result['success']:
+ print(f"✅ 成功获取 {result['count']} 条新闻")
+ print(f"前3条标题:")
+ for news in result['data'][:3]:
+ print(f" {news['rank']}. {news['title']}")
+ else:
+ print(f"❌ 失败: {result['error']}")
+
+ print("\n=== 测试获取财经平台 ===")
+ result = fetcher.get_multi_platform_news(category='finance')
+ print(f"总共 {result['total_platforms']} 个平台")
+ print(f"成功 {result['success_count']} 个")
+ print(f"失败 {result['failed_count']} 个")
+
+ # 提取股票相关新闻
+ stock_news = fetcher.extract_stock_related_news(result['platforms_data'])
+ print(f"\n✅ 提取到 {len(stock_news)} 条股票相关新闻")
+
+ # 计算流量得分
+ flow_score = fetcher.calculate_flow_score(result['platforms_data'])
+ print(f"\n流量得分: {flow_score['total_score']}")
+ print(f"流量等级: {flow_score['level']}")
+
+ # 获取热门话题
+ hot_topics = fetcher.get_hot_topics(result['platforms_data'])
+ print(f"\n热门话题TOP5:")
+ for i, topic in enumerate(hot_topics[:5], 1):
+ print(f" {i}. {topic['topic']} (热度:{topic['heat']}, 跨{topic['cross_platform']}平台)")
diff --git a/news_flow_db.py b/news_flow_db.py
new file mode 100644
index 0000000..03ea779
--- /dev/null
+++ b/news_flow_db.py
@@ -0,0 +1,1021 @@
+"""
+新闻流量数据库模块
+用于存储和管理新闻流量监测数据
+包含:快照、新闻、情绪、预警、AI分析、定时任务日志
+"""
+import sqlite3
+import json
+import logging
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional
+from collections import Counter
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowDatabase:
+ """新闻流量数据库管理类"""
+
+ def __init__(self, db_path: str = "news_flow.db"):
+ self.db_path = db_path
+ self.init_database()
+
+ def get_connection(self):
+ """获取数据库连接"""
+ conn = sqlite3.connect(self.db_path)
+ conn.row_factory = sqlite3.Row
+ return conn
+
+ def init_database(self):
+ """初始化数据库表"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ # 1. 新闻流量快照表(记录每次监测的整体情况)
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS flow_snapshots (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ fetch_time TEXT NOT NULL,
+ total_platforms INTEGER NOT NULL,
+ success_count INTEGER NOT NULL,
+ total_score INTEGER NOT NULL,
+ flow_level TEXT NOT NULL,
+ social_score INTEGER,
+ news_score INTEGER,
+ finance_score INTEGER,
+ tech_score INTEGER,
+ analysis TEXT,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # 2. 平台新闻表(存储各平台的新闻数据)
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS platform_news (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ snapshot_id INTEGER NOT NULL,
+ platform TEXT NOT NULL,
+ platform_name TEXT NOT NULL,
+ category TEXT NOT NULL,
+ weight INTEGER NOT NULL,
+ title TEXT NOT NULL,
+ content TEXT,
+ url TEXT,
+ source TEXT,
+ publish_time TEXT,
+ rank INTEGER,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id)
+ )
+ ''')
+
+ # 3. 股票相关新闻表
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS stock_related_news (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ snapshot_id INTEGER NOT NULL,
+ platform TEXT NOT NULL,
+ platform_name TEXT NOT NULL,
+ category TEXT NOT NULL,
+ weight INTEGER NOT NULL,
+ title TEXT NOT NULL,
+ content TEXT,
+ url TEXT,
+ source TEXT,
+ publish_time TEXT,
+ matched_keywords TEXT,
+ keyword_count INTEGER,
+ score INTEGER,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id)
+ )
+ ''')
+
+ # 4. 热门话题表
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS hot_topics (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ snapshot_id INTEGER NOT NULL,
+ topic TEXT NOT NULL,
+ count INTEGER NOT NULL,
+ heat INTEGER NOT NULL,
+ cross_platform INTEGER,
+ sources TEXT,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id)
+ )
+ ''')
+
+ # 5. 监测历史统计表(按天汇总)
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS flow_statistics (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ date TEXT NOT NULL UNIQUE,
+ avg_score INTEGER,
+ max_score INTEGER,
+ min_score INTEGER,
+ snapshot_count INTEGER,
+ top_topics TEXT,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # 6. 情绪指标记录表【新增】
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS sentiment_records (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ snapshot_id INTEGER,
+ sentiment_index INTEGER NOT NULL,
+ sentiment_class TEXT NOT NULL,
+ flow_stage TEXT NOT NULL,
+ momentum REAL,
+ viral_k REAL,
+ flow_type TEXT,
+ stage_analysis TEXT,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id)
+ )
+ ''')
+
+ # 7. 预警记录表【新增】
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS flow_alerts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ alert_type TEXT NOT NULL,
+ alert_level TEXT NOT NULL,
+ title TEXT NOT NULL,
+ content TEXT,
+ related_topics TEXT,
+ trigger_value TEXT,
+ threshold_value TEXT,
+ is_notified INTEGER DEFAULT 0,
+ snapshot_id INTEGER,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id)
+ )
+ ''')
+
+ # 8. AI分析记录表【新增】
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS ai_analysis (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ snapshot_id INTEGER,
+ affected_sectors TEXT,
+ recommended_stocks TEXT,
+ risk_level TEXT,
+ risk_factors TEXT,
+ advice TEXT,
+ confidence INTEGER,
+ summary TEXT,
+ raw_response TEXT,
+ model_used TEXT,
+ analysis_time REAL,
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id)
+ )
+ ''')
+
+ # 9. 定时任务日志表【新增】
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS scheduler_logs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ task_name TEXT NOT NULL,
+ task_type TEXT,
+ status TEXT NOT NULL,
+ message TEXT,
+ duration REAL,
+ snapshot_id INTEGER,
+ executed_at TEXT DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # 10. 预警配置表【新增】
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS alert_config (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ config_key TEXT NOT NULL UNIQUE,
+ config_value TEXT NOT NULL,
+ description TEXT,
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # 初始化预警配置默认值
+ default_configs = [
+ ('heat_threshold', '800', '热度飙升阈值'),
+ ('rank_change_threshold', '10', '排名变化阈值'),
+ ('sentiment_high_threshold', '90', '情绪高位阈值'),
+ ('sentiment_low_threshold', '20', '情绪低位阈值'),
+ ('viral_k_threshold', '1.5', 'K值阈值'),
+ ('alert_enabled', 'true', '预警开关'),
+ ('notification_enabled', 'true', '通知开关'),
+ ]
+
+ for key, value, desc in default_configs:
+ cursor.execute('''
+ INSERT OR IGNORE INTO alert_config (config_key, config_value, description)
+ VALUES (?, ?, ?)
+ ''', (key, value, desc))
+
+ # 数据库迁移:添加缺失的列
+ self._migrate_database(cursor)
+
+ conn.commit()
+ conn.close()
+ logger.info("✅ 新闻流量数据库初始化完成")
+
+ def _migrate_database(self, cursor):
+ """数据库迁移:添加缺失的列"""
+ # 定义需要迁移的列
+ migrations = [
+ # (表名, 列名, 列定义)
+ ('stock_related_news', 'score', 'INTEGER DEFAULT 0'),
+ ('stock_related_news', 'rank', 'INTEGER'),
+ ('platform_news', 'rank', 'INTEGER'),
+ ('hot_topics', 'cross_platform', 'INTEGER'),
+ ('hot_topics', 'sources', 'TEXT'),
+ ]
+
+ for table, column, column_def in migrations:
+ try:
+ # 检查列是否存在
+ cursor.execute(f"PRAGMA table_info({table})")
+ columns = [row[1] for row in cursor.fetchall()]
+
+ if column not in columns:
+ cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {column_def}")
+ logger.info(f"✅ 迁移: 向 {table} 添加列 {column}")
+ except Exception as e:
+ logger.warning(f"迁移列 {table}.{column} 时出错: {e}")
+
+ # ==================== 快照相关方法 ====================
+
+ def save_flow_snapshot(self, flow_data: Dict, platforms_data: List[Dict],
+ stock_news: List[Dict], hot_topics: List[Dict]) -> int:
+ """
+ 保存完整的流量快照
+
+ Args:
+ flow_data: 流量得分数据
+ platforms_data: 平台新闻数据
+ stock_news: 股票相关新闻
+ hot_topics: 热门话题
+
+ Returns:
+ snapshot_id: 快照ID
+ """
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ try:
+ # 1. 保存快照主表
+ cursor.execute('''
+ INSERT INTO flow_snapshots
+ (fetch_time, total_platforms, success_count, total_score, flow_level,
+ social_score, news_score, finance_score, tech_score, analysis)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (
+ datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ len(platforms_data),
+ sum(1 for p in platforms_data if p.get('success')),
+ flow_data['total_score'],
+ flow_data['level'],
+ flow_data.get('social_score', 0),
+ flow_data.get('news_score', 0),
+ flow_data.get('finance_score', 0),
+ flow_data.get('tech_score', 0),
+ flow_data.get('analysis', '')
+ ))
+
+ snapshot_id = cursor.lastrowid
+
+ # 2. 保存平台新闻
+ for platform_data in platforms_data:
+ if not platform_data.get('success'):
+ continue
+
+ for news in platform_data.get('data', []):
+ cursor.execute('''
+ INSERT INTO platform_news
+ (snapshot_id, platform, platform_name, category, weight,
+ title, content, url, source, publish_time, rank)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (
+ snapshot_id,
+ platform_data['platform'],
+ platform_data['platform_name'],
+ platform_data['category'],
+ platform_data['weight'],
+ news.get('title') or '',
+ news.get('content') or '',
+ news.get('url') or '',
+ news.get('source') or '',
+ news.get('publish_time') or '',
+ news.get('rank', 0)
+ ))
+
+ # 3. 保存股票相关新闻
+ for news in stock_news:
+ cursor.execute('''
+ INSERT INTO stock_related_news
+ (snapshot_id, platform, platform_name, category, weight,
+ title, content, url, source, publish_time, matched_keywords, keyword_count, score)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (
+ snapshot_id,
+ news['platform'],
+ news['platform_name'],
+ news['category'],
+ news['weight'],
+ news['title'],
+ news.get('content') or '',
+ news.get('url') or '',
+ news.get('source') or '',
+ news.get('publish_time') or '',
+ json.dumps(news.get('matched_keywords', []), ensure_ascii=False),
+ news.get('keyword_count', 0),
+ news.get('score', 0)
+ ))
+
+ # 4. 保存热门话题
+ for topic in hot_topics:
+ cursor.execute('''
+ INSERT INTO hot_topics
+ (snapshot_id, topic, count, heat, cross_platform, sources)
+ VALUES (?, ?, ?, ?, ?, ?)
+ ''', (
+ snapshot_id,
+ topic['topic'],
+ topic['count'],
+ topic['heat'],
+ topic.get('cross_platform', 0),
+ json.dumps(topic.get('sources', []), ensure_ascii=False)
+ ))
+
+ # 5. 更新每日统计
+ self._update_daily_statistics(cursor, flow_data['total_score'], hot_topics)
+
+ conn.commit()
+ logger.info(f"✅ 保存流量快照成功,ID: {snapshot_id}")
+ return snapshot_id
+
+ except Exception as e:
+ conn.rollback()
+ logger.error(f"❌ 保存流量快照失败: {e}")
+ raise
+ finally:
+ conn.close()
+
+ def _update_daily_statistics(self, cursor, score: int, hot_topics: List[Dict]):
+ """更新每日统计"""
+ today = datetime.now().strftime('%Y-%m-%d')
+
+ cursor.execute('''
+ SELECT avg_score, max_score, min_score, snapshot_count, top_topics
+ FROM flow_statistics WHERE date = ?
+ ''', (today,))
+
+ row = cursor.fetchone()
+
+ if row:
+ old_avg = row['avg_score'] or 0
+ old_count = row['snapshot_count'] or 0
+ new_avg = int((old_avg * old_count + score) / (old_count + 1))
+ new_max = max(row['max_score'] or 0, score)
+ new_min = min(row['min_score'] or 999999, score)
+
+ old_topics = json.loads(row['top_topics']) if row['top_topics'] else []
+ new_topics = old_topics + [t['topic'] for t in hot_topics[:10]]
+ topic_counter = Counter(new_topics)
+ top_topics = [topic for topic, _ in topic_counter.most_common(20)]
+
+ cursor.execute('''
+ UPDATE flow_statistics
+ SET avg_score = ?, max_score = ?, min_score = ?,
+ snapshot_count = ?, top_topics = ?
+ WHERE date = ?
+ ''', (new_avg, new_max, new_min, old_count + 1,
+ json.dumps(top_topics, ensure_ascii=False), today))
+ else:
+ top_topics = [t['topic'] for t in hot_topics[:20]]
+ cursor.execute('''
+ INSERT INTO flow_statistics
+ (date, avg_score, max_score, min_score, snapshot_count, top_topics)
+ VALUES (?, ?, ?, ?, ?, ?)
+ ''', (today, score, score, score, 1,
+ json.dumps(top_topics, ensure_ascii=False)))
+
+ def get_latest_snapshot(self) -> Optional[Dict]:
+ """获取最新的流量快照"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT * FROM flow_snapshots
+ ORDER BY created_at DESC LIMIT 1
+ ''')
+
+ row = cursor.fetchone()
+ conn.close()
+
+ if row:
+ return dict(row)
+ return None
+
+ def get_recent_snapshots(self, limit: int = 10) -> List[Dict]:
+ """获取最近的流量快照列表"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT * FROM flow_snapshots
+ ORDER BY created_at DESC LIMIT ?
+ ''', (limit,))
+
+ snapshots = []
+ for row in cursor.fetchall():
+ snapshots.append(dict(row))
+
+ conn.close()
+ return snapshots
+
+ def get_snapshot_detail(self, snapshot_id: int) -> Dict:
+ """获取快照详细信息"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('SELECT * FROM flow_snapshots WHERE id = ?', (snapshot_id,))
+ row = cursor.fetchone()
+ if not row:
+ conn.close()
+ return {}
+
+ snapshot = dict(row)
+
+ cursor.execute('''
+ SELECT * FROM stock_related_news
+ WHERE snapshot_id = ?
+ ORDER BY COALESCE(score, 0) DESC, weight DESC
+ ''', (snapshot_id,))
+
+ stock_news = []
+ for row in cursor.fetchall():
+ news = dict(row)
+ news['matched_keywords'] = json.loads(news['matched_keywords']) if news['matched_keywords'] else []
+ stock_news.append(news)
+
+ cursor.execute('''
+ SELECT * FROM hot_topics
+ WHERE snapshot_id = ?
+ ORDER BY heat DESC
+ ''', (snapshot_id,))
+
+ hot_topics = []
+ for row in cursor.fetchall():
+ topic = dict(row)
+ topic['sources'] = json.loads(topic['sources']) if topic['sources'] else []
+ hot_topics.append(topic)
+
+ # 获取情绪记录
+ cursor.execute('''
+ SELECT * FROM sentiment_records
+ WHERE snapshot_id = ?
+ ORDER BY created_at DESC LIMIT 1
+ ''', (snapshot_id,))
+ sentiment_row = cursor.fetchone()
+ sentiment = dict(sentiment_row) if sentiment_row else None
+
+ # 获取AI分析
+ cursor.execute('''
+ SELECT * FROM ai_analysis
+ WHERE snapshot_id = ?
+ ORDER BY created_at DESC LIMIT 1
+ ''', (snapshot_id,))
+ ai_row = cursor.fetchone()
+ ai_analysis = None
+ if ai_row:
+ ai_analysis = dict(ai_row)
+ ai_analysis['affected_sectors'] = json.loads(ai_analysis['affected_sectors']) if ai_analysis['affected_sectors'] else []
+ ai_analysis['recommended_stocks'] = json.loads(ai_analysis['recommended_stocks']) if ai_analysis['recommended_stocks'] else []
+ ai_analysis['risk_factors'] = json.loads(ai_analysis['risk_factors']) if ai_analysis['risk_factors'] else []
+
+ conn.close()
+
+ return {
+ 'snapshot': snapshot,
+ 'stock_news': stock_news,
+ 'hot_topics': hot_topics,
+ 'sentiment': sentiment,
+ 'ai_analysis': ai_analysis,
+ }
+
+ def get_history_snapshots(self, limit: int = 50) -> List[Dict]:
+ """获取历史快照列表"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT id, fetch_time, total_score, flow_level,
+ success_count, total_platforms, analysis
+ FROM flow_snapshots
+ ORDER BY created_at DESC
+ LIMIT ?
+ ''', (limit,))
+
+ snapshots = [dict(row) for row in cursor.fetchall()]
+ conn.close()
+
+ return snapshots
+
+ def get_daily_statistics(self, days: int = 7) -> List[Dict]:
+ """获取每日统计数据"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT * FROM flow_statistics
+ ORDER BY date DESC
+ LIMIT ?
+ ''', (days,))
+
+ stats = []
+ for row in cursor.fetchall():
+ stat = dict(row)
+ stat['top_topics'] = json.loads(stat['top_topics']) if stat['top_topics'] else []
+ stats.append(stat)
+
+ conn.close()
+ return stats
+
+ def get_recent_scores(self, hours: int = 24) -> List[Dict]:
+ """获取最近N小时的得分记录"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ since = (datetime.now() - timedelta(hours=hours)).strftime('%Y-%m-%d %H:%M:%S')
+
+ cursor.execute('''
+ SELECT id, fetch_time, total_score, flow_level
+ FROM flow_snapshots
+ WHERE fetch_time >= ?
+ ORDER BY fetch_time ASC
+ ''', (since,))
+
+ scores = [dict(row) for row in cursor.fetchall()]
+ conn.close()
+ return scores
+
+ def search_stock_news(self, keyword: str, limit: int = 50) -> List[Dict]:
+ """搜索股票相关新闻"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT srn.*, fs.fetch_time, fs.flow_level
+ FROM stock_related_news srn
+ JOIN flow_snapshots fs ON srn.snapshot_id = fs.id
+ WHERE srn.title LIKE ? OR srn.content LIKE ?
+ ORDER BY srn.created_at DESC
+ LIMIT ?
+ ''', (f'%{keyword}%', f'%{keyword}%', limit))
+
+ results = []
+ for row in cursor.fetchall():
+ news = dict(row)
+ news['matched_keywords'] = json.loads(news['matched_keywords']) if news['matched_keywords'] else []
+ results.append(news)
+
+ conn.close()
+ return results
+
+ # ==================== 情绪记录相关方法 ====================
+
+ def save_sentiment_record(self, snapshot_id: int, sentiment_data: Dict) -> int:
+ """保存情绪记录"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ INSERT INTO sentiment_records
+ (snapshot_id, sentiment_index, sentiment_class, flow_stage,
+ momentum, viral_k, flow_type, stage_analysis)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (
+ snapshot_id,
+ sentiment_data.get('sentiment_index', 50),
+ sentiment_data.get('sentiment_class', '中性'),
+ sentiment_data.get('flow_stage', '未知'),
+ sentiment_data.get('momentum', 0),
+ sentiment_data.get('viral_k', 1.0),
+ sentiment_data.get('flow_type', '未知'),
+ sentiment_data.get('stage_analysis', '')
+ ))
+
+ record_id = cursor.lastrowid
+ conn.commit()
+ conn.close()
+
+ return record_id
+
+ def get_sentiment_history(self, limit: int = 50) -> List[Dict]:
+ """获取情绪历史记录"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT sr.*, fs.fetch_time, fs.total_score
+ FROM sentiment_records sr
+ LEFT JOIN flow_snapshots fs ON sr.snapshot_id = fs.id
+ ORDER BY sr.created_at DESC
+ LIMIT ?
+ ''', (limit,))
+
+ records = [dict(row) for row in cursor.fetchall()]
+ conn.close()
+ return records
+
+ def get_latest_sentiment(self) -> Optional[Dict]:
+ """获取最新情绪记录"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT sr.*, fs.fetch_time, fs.total_score, fs.flow_level
+ FROM sentiment_records sr
+ LEFT JOIN flow_snapshots fs ON sr.snapshot_id = fs.id
+ ORDER BY sr.created_at DESC
+ LIMIT 1
+ ''')
+
+ row = cursor.fetchone()
+ conn.close()
+
+ return dict(row) if row else None
+
+ # ==================== 预警相关方法 ====================
+
+ def save_alert(self, alert_data: Dict) -> int:
+ """保存预警记录"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ INSERT INTO flow_alerts
+ (alert_type, alert_level, title, content, related_topics,
+ trigger_value, threshold_value, is_notified, snapshot_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (
+ alert_data['alert_type'],
+ alert_data.get('alert_level', 'info'),
+ alert_data['title'],
+ alert_data.get('content', ''),
+ json.dumps(alert_data.get('related_topics', []), ensure_ascii=False),
+ str(alert_data.get('trigger_value', '')),
+ str(alert_data.get('threshold_value', '')),
+ 1 if alert_data.get('is_notified') else 0,
+ alert_data.get('snapshot_id')
+ ))
+
+ alert_id = cursor.lastrowid
+ conn.commit()
+ conn.close()
+
+ return alert_id
+
+ def get_alerts(self, days: int = 7, alert_type: str = None) -> List[Dict]:
+ """获取预警记录"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
+
+ if alert_type:
+ cursor.execute('''
+ SELECT * FROM flow_alerts
+ WHERE created_at >= ? AND alert_type = ?
+ ORDER BY created_at DESC
+ ''', (since, alert_type))
+ else:
+ cursor.execute('''
+ SELECT * FROM flow_alerts
+ WHERE created_at >= ?
+ ORDER BY created_at DESC
+ ''', (since,))
+
+ alerts = []
+ for row in cursor.fetchall():
+ alert = dict(row)
+ alert['related_topics'] = json.loads(alert['related_topics']) if alert['related_topics'] else []
+ alerts.append(alert)
+
+ conn.close()
+ return alerts
+
+ def get_unnotified_alerts(self) -> List[Dict]:
+ """获取未通知的预警"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT * FROM flow_alerts
+ WHERE is_notified = 0
+ ORDER BY created_at DESC
+ ''')
+
+ alerts = []
+ for row in cursor.fetchall():
+ alert = dict(row)
+ alert['related_topics'] = json.loads(alert['related_topics']) if alert['related_topics'] else []
+ alerts.append(alert)
+
+ conn.close()
+ return alerts
+
+ def mark_alert_notified(self, alert_id: int):
+ """标记预警为已通知"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ UPDATE flow_alerts SET is_notified = 1 WHERE id = ?
+ ''', (alert_id,))
+
+ conn.commit()
+ conn.close()
+
+ # ==================== AI分析相关方法 ====================
+
+ def save_ai_analysis(self, snapshot_id: int, analysis_data: Dict) -> int:
+ """保存AI分析结果"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ INSERT INTO ai_analysis
+ (snapshot_id, affected_sectors, recommended_stocks, risk_level,
+ risk_factors, advice, confidence, summary, raw_response, model_used, analysis_time)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ''', (
+ snapshot_id,
+ json.dumps(analysis_data.get('affected_sectors', []), ensure_ascii=False),
+ json.dumps(analysis_data.get('recommended_stocks', []), ensure_ascii=False),
+ analysis_data.get('risk_level', '未知'),
+ json.dumps(analysis_data.get('risk_factors', []), ensure_ascii=False),
+ analysis_data.get('advice', '观望'),
+ analysis_data.get('confidence', 50),
+ analysis_data.get('summary', ''),
+ analysis_data.get('raw_response', ''),
+ analysis_data.get('model_used', 'deepseek-chat'),
+ analysis_data.get('analysis_time', 0)
+ ))
+
+ analysis_id = cursor.lastrowid
+ conn.commit()
+ conn.close()
+
+ return analysis_id
+
+ def get_latest_ai_analysis(self) -> Optional[Dict]:
+ """获取最新AI分析结果"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT aa.*, fs.fetch_time, fs.total_score, fs.flow_level
+ FROM ai_analysis aa
+ LEFT JOIN flow_snapshots fs ON aa.snapshot_id = fs.id
+ ORDER BY aa.created_at DESC
+ LIMIT 1
+ ''')
+
+ row = cursor.fetchone()
+ conn.close()
+
+ if row:
+ analysis = dict(row)
+ analysis['affected_sectors'] = json.loads(analysis['affected_sectors']) if analysis['affected_sectors'] else []
+ analysis['recommended_stocks'] = json.loads(analysis['recommended_stocks']) if analysis['recommended_stocks'] else []
+ analysis['risk_factors'] = json.loads(analysis['risk_factors']) if analysis['risk_factors'] else []
+ return analysis
+ return None
+
+ def get_ai_analysis_history(self, limit: int = 20) -> List[Dict]:
+ """获取AI分析历史"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT aa.*, fs.fetch_time, fs.total_score, fs.flow_level
+ FROM ai_analysis aa
+ LEFT JOIN flow_snapshots fs ON aa.snapshot_id = fs.id
+ ORDER BY aa.created_at DESC
+ LIMIT ?
+ ''', (limit,))
+
+ results = []
+ for row in cursor.fetchall():
+ analysis = dict(row)
+ analysis['affected_sectors'] = json.loads(analysis['affected_sectors']) if analysis['affected_sectors'] else []
+ analysis['recommended_stocks'] = json.loads(analysis['recommended_stocks']) if analysis['recommended_stocks'] else []
+ analysis['risk_factors'] = json.loads(analysis['risk_factors']) if analysis['risk_factors'] else []
+ results.append(analysis)
+
+ conn.close()
+ return results
+
+ # ==================== 定时任务日志相关方法 ====================
+
+ def save_scheduler_log(self, task_name: str, task_type: str,
+ status: str, message: str = '',
+ duration: float = 0, snapshot_id: int = None) -> int:
+ """保存定时任务日志"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ INSERT INTO scheduler_logs
+ (task_name, task_type, status, message, duration, snapshot_id)
+ VALUES (?, ?, ?, ?, ?, ?)
+ ''', (task_name, task_type, status, message, duration, snapshot_id))
+
+ log_id = cursor.lastrowid
+ conn.commit()
+ conn.close()
+
+ return log_id
+
+ def get_scheduler_logs(self, days: int = 7, task_type: str = None) -> List[Dict]:
+ """获取定时任务日志"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
+
+ if task_type:
+ cursor.execute('''
+ SELECT * FROM scheduler_logs
+ WHERE executed_at >= ? AND task_type = ?
+ ORDER BY executed_at DESC
+ ''', (since, task_type))
+ else:
+ cursor.execute('''
+ SELECT * FROM scheduler_logs
+ WHERE executed_at >= ?
+ ORDER BY executed_at DESC
+ ''', (since,))
+
+ logs = [dict(row) for row in cursor.fetchall()]
+ conn.close()
+ return logs
+
+ # ==================== 预警配置相关方法 ====================
+
+ def get_alert_config(self, key: str) -> Optional[str]:
+ """获取预警配置"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT config_value FROM alert_config WHERE config_key = ?
+ ''', (key,))
+
+ row = cursor.fetchone()
+ conn.close()
+
+ return row['config_value'] if row else None
+
+ def set_alert_config(self, key: str, value: str, description: str = None):
+ """设置预警配置"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ INSERT OR REPLACE INTO alert_config (config_key, config_value, description, updated_at)
+ VALUES (?, ?, ?, ?)
+ ''', (key, value, description, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
+
+ conn.commit()
+ conn.close()
+
+ def get_all_alert_configs(self) -> Dict[str, str]:
+ """获取所有预警配置"""
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute('SELECT config_key, config_value FROM alert_config')
+
+ configs = {row['config_key']: row['config_value'] for row in cursor.fetchall()}
+ conn.close()
+ return configs
+
+
+# 全局数据库实例
+news_flow_db = NewsFlowDatabase()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试新闻流量数据库 ===")
+
+ # 测试保存快照
+ flow_data = {
+ 'total_score': 650,
+ 'social_score': 200,
+ 'news_score': 180,
+ 'finance_score': 220,
+ 'tech_score': 50,
+ 'level': '高',
+ 'analysis': '流量较高,市场活跃'
+ }
+
+ platforms_data = [{
+ 'success': True,
+ 'platform': 'weibo',
+ 'platform_name': '微博热搜',
+ 'category': 'social',
+ 'weight': 10,
+ 'data': [
+ {'title': '某某股票大涨', 'content': '今日涨停', 'url': 'http://example.com',
+ 'source': '微博', 'publish_time': '2026-01-25 10:00:00', 'rank': 1}
+ ]
+ }]
+
+ stock_news = [{
+ 'platform': 'weibo',
+ 'platform_name': '微博热搜',
+ 'category': 'social',
+ 'weight': 10,
+ 'title': '某某股票大涨',
+ 'content': '今日涨停',
+ 'url': 'http://example.com',
+ 'source': '微博',
+ 'publish_time': '2026-01-25 10:00:00',
+ 'matched_keywords': ['股票', '涨停'],
+ 'keyword_count': 2,
+ 'score': 100
+ }]
+
+ hot_topics = [
+ {'topic': 'AI', 'count': 50, 'heat': 95, 'cross_platform': 5, 'sources': ['微博', '抖音']},
+ {'topic': '新能源', 'count': 30, 'heat': 80, 'cross_platform': 3, 'sources': ['微博']}
+ ]
+
+ snapshot_id = news_flow_db.save_flow_snapshot(flow_data, platforms_data, stock_news, hot_topics)
+ print(f"✅ 保存快照成功,ID: {snapshot_id}")
+
+ # 测试保存情绪记录
+ sentiment_data = {
+ 'sentiment_index': 75,
+ 'sentiment_class': '乐观',
+ 'flow_stage': '加速',
+ 'momentum': 1.5,
+ 'viral_k': 1.2,
+ 'flow_type': '增量流量型',
+ 'stage_analysis': '流量正在快速上升'
+ }
+ sentiment_id = news_flow_db.save_sentiment_record(snapshot_id, sentiment_data)
+ print(f"✅ 保存情绪记录成功,ID: {sentiment_id}")
+
+ # 测试保存预警
+ alert_data = {
+ 'alert_type': 'heat_surge',
+ 'alert_level': 'warning',
+ 'title': '热度飙升预警',
+ 'content': '当前流量得分650,超过阈值500',
+ 'related_topics': ['AI', '新能源'],
+ 'trigger_value': 650,
+ 'threshold_value': 500,
+ 'snapshot_id': snapshot_id
+ }
+ alert_id = news_flow_db.save_alert(alert_data)
+ print(f"✅ 保存预警成功,ID: {alert_id}")
+
+ # 测试保存AI分析
+ ai_data = {
+ 'affected_sectors': [{'name': 'AI', 'impact': '利好', 'reason': '政策支持'}],
+ 'recommended_stocks': [{'code': '000001', 'name': '平安银行', 'reason': '龙头'}],
+ 'risk_level': '中等',
+ 'risk_factors': ['追高风险', '流动性风险'],
+ 'advice': '观望',
+ 'confidence': 75,
+ 'summary': '当前市场热度较高,建议观望',
+ 'model_used': 'deepseek-chat',
+ 'analysis_time': 2.5
+ }
+ ai_id = news_flow_db.save_ai_analysis(snapshot_id, ai_data)
+ print(f"✅ 保存AI分析成功,ID: {ai_id}")
+
+ # 测试保存任务日志
+ log_id = news_flow_db.save_scheduler_log(
+ '热点同步', 'sync_hotspots', 'success',
+ '成功同步22个平台', 5.2, snapshot_id
+ )
+ print(f"✅ 保存任务日志成功,ID: {log_id}")
+
+ # 测试获取详情
+ detail = news_flow_db.get_snapshot_detail(snapshot_id)
+ print(f"\n快照详情:")
+ print(f" 流量得分: {detail['snapshot']['total_score']}")
+ print(f" 情绪指数: {detail['sentiment']['sentiment_index'] if detail['sentiment'] else 'N/A'}")
+ print(f" AI建议: {detail['ai_analysis']['advice'] if detail['ai_analysis'] else 'N/A'}")
diff --git a/news_flow_engine.py b/news_flow_engine.py
new file mode 100644
index 0000000..e575331
--- /dev/null
+++ b/news_flow_engine.py
@@ -0,0 +1,620 @@
+"""
+新闻流量分析引擎
+基于"流量为王"理念的短线炒股指导系统
+整合数据获取、流量模型、情绪分析、AI分析、预警系统
+"""
+import logging
+import time
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowEngine:
+ """新闻流量分析引擎"""
+
+ def __init__(self):
+ """初始化引擎"""
+ # 核心模块
+ self.fetcher = None
+ self.model = None
+ self.sentiment = None
+ self.agents = None
+ self.alerts = None
+ self.db = None
+
+ self._init_modules()
+ logger.info("✅ 新闻流量引擎初始化完成")
+
+ def _init_modules(self):
+ """初始化所有模块"""
+ try:
+ from news_flow_data import NewsFlowDataFetcher
+ self.fetcher = NewsFlowDataFetcher()
+ except Exception as e:
+ logger.error(f"数据获取模块初始化失败: {e}")
+
+ try:
+ from news_flow_model import NewsFlowModel
+ self.model = NewsFlowModel()
+ except Exception as e:
+ logger.error(f"流量模型模块初始化失败: {e}")
+
+ try:
+ from news_flow_sentiment import SentimentAnalyzer
+ self.sentiment = SentimentAnalyzer()
+ except Exception as e:
+ logger.error(f"情绪分析模块初始化失败: {e}")
+
+ try:
+ from news_flow_agents import NewsFlowAgents
+ self.agents = NewsFlowAgents()
+ except Exception as e:
+ logger.error(f"AI分析模块初始化失败: {e}")
+
+ try:
+ from news_flow_alert import NewsFlowAlertSystem
+ self.alerts = NewsFlowAlertSystem()
+ except Exception as e:
+ logger.error(f"预警系统模块初始化失败: {e}")
+
+ try:
+ from news_flow_db import news_flow_db
+ self.db = news_flow_db
+ except Exception as e:
+ logger.error(f"数据库模块初始化失败: {e}")
+
+ def run_quick_analysis(self, platforms: List[str] = None,
+ category: str = None) -> Dict:
+ """
+ 运行快速分析(不含AI)
+
+ 用于定时同步和快速查看
+
+ Returns:
+ {
+ 'success': bool,
+ 'snapshot_id': int,
+ 'flow_data': Dict,
+ 'model_data': Dict,
+ 'sentiment_data': Dict,
+ 'stock_news': List,
+ 'hot_topics': List,
+ 'fetch_time': str,
+ }
+ """
+ try:
+ logger.info("🚀 开始快速分析...")
+ start_time = time.time()
+
+ # 1. 获取多平台新闻数据
+ logger.info("📊 获取新闻数据...")
+ if not self.fetcher:
+ return {'success': False, 'error': '数据获取模块不可用'}
+
+ multi_result = self.fetcher.get_multi_platform_news(
+ platforms=platforms, category=category
+ )
+
+ if not multi_result['success']:
+ return {'success': False, 'error': '获取新闻数据失败'}
+
+ platforms_data = multi_result['platforms_data']
+ success_count = multi_result['success_count']
+
+ # 2. 提取股票相关新闻
+ logger.info("🔍 提取股票相关新闻...")
+ stock_news = self.fetcher.extract_stock_related_news(platforms_data)
+
+ # 3. 获取热门话题
+ logger.info("🔥 分析热门话题...")
+ hot_topics = self.fetcher.get_hot_topics(platforms_data, top_n=20)
+
+ # 4. 计算流量得分(基础)
+ logger.info("📈 计算流量得分...")
+ flow_data = self.fetcher.calculate_flow_score(platforms_data)
+
+ # 5. 运行流量模型
+ logger.info("🔬 运行流量模型...")
+ history_scores = self._get_history_scores(hours=24)
+ model_data = None
+ if self.model:
+ model_data = self.model.run_full_model(
+ platforms_data, hot_topics, history_scores
+ )
+
+ # 6. 情绪分析
+ logger.info("💭 分析市场情绪...")
+ sentiment_data = None
+ if self.sentiment:
+ history_sentiments = self._get_history_sentiments(limit=10)
+ sentiment_data = self.sentiment.run_full_sentiment_analysis(
+ platforms_data, stock_news, history_scores,
+ flow_data['total_score'], history_sentiments
+ )
+
+ # 7. 保存到数据库
+ logger.info("💾 保存分析结果...")
+ snapshot_id = None
+ if self.db:
+ snapshot_id = self.db.save_flow_snapshot(
+ flow_data, platforms_data, stock_news, hot_topics
+ )
+
+ # 保存情绪记录
+ if sentiment_data and snapshot_id:
+ sentiment_record = {
+ 'sentiment_index': sentiment_data.get('sentiment', {}).get('sentiment_index', 50),
+ 'sentiment_class': sentiment_data.get('sentiment', {}).get('sentiment_class', '中性'),
+ 'flow_stage': sentiment_data.get('flow_stage', {}).get('stage_name', '未知'),
+ 'momentum': sentiment_data.get('momentum', {}).get('momentum', 1.0),
+ 'viral_k': model_data.get('viral_k', {}).get('k_value', 1.0) if model_data else 1.0,
+ 'flow_type': model_data.get('flow_type', {}).get('flow_type', '未知') if model_data else '未知',
+ 'stage_analysis': sentiment_data.get('flow_stage', {}).get('analysis', ''),
+ }
+ self.db.save_sentiment_record(snapshot_id, sentiment_record)
+
+ duration = time.time() - start_time
+ logger.info(f"✅ 快速分析完成,耗时 {duration:.2f} 秒")
+
+ return {
+ 'success': True,
+ 'snapshot_id': snapshot_id,
+ 'success_count': success_count,
+ 'flow_data': flow_data,
+ 'model_data': model_data,
+ 'sentiment_data': sentiment_data,
+ 'stock_news': stock_news,
+ 'hot_topics': hot_topics,
+ 'platforms_data': platforms_data,
+ 'fetch_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ 'duration': round(duration, 2),
+ }
+
+ except Exception as e:
+ logger.error(f"❌ 快速分析失败: {e}")
+ return {'success': False, 'error': str(e)}
+
+ def run_full_analysis(self, platforms: List[str] = None,
+ category: str = None,
+ include_ai: bool = True) -> Dict:
+ """
+ 运行完整分析(含AI)
+
+ Returns:
+ {
+ 'success': bool,
+ 'snapshot_id': int,
+ 'flow_data': Dict,
+ 'model_data': Dict,
+ 'sentiment_data': Dict,
+ 'ai_analysis': Dict,
+ 'trading_signals': Dict,
+ 'stock_news': List,
+ 'hot_topics': List,
+ }
+ """
+ try:
+ logger.info("🚀 开始完整分析...")
+ start_time = time.time()
+
+ # 1. 先运行快速分析
+ quick_result = self.run_quick_analysis(platforms, category)
+
+ if not quick_result['success']:
+ return quick_result
+
+ # 2. AI智能分析
+ ai_analysis = None
+ if include_ai:
+ if not self.agents:
+ logger.warning("⚠️ AI代理模块未初始化")
+ elif not self.agents.is_available():
+ logger.warning("⚠️ DeepSeek API不可用,请检查API密钥配置")
+ else:
+ logger.info("🤖 运行AI分析...")
+
+ model_data = quick_result.get('model_data', {})
+ sentiment_data = quick_result.get('sentiment_data', {})
+
+ # 基础AI分析
+ ai_analysis = self.agents.run_full_analysis(
+ quick_result['hot_topics'],
+ quick_result['stock_news'],
+ quick_result['flow_data'],
+ sentiment_data,
+ viral_k=model_data.get('viral_k', {}).get('k_value', 1.0) if model_data else 1.0,
+ flow_type=model_data.get('flow_type', {}).get('flow_type', '未知') if model_data else '未知',
+ )
+
+ # 多板块深度分析(多次调用DeepSeek)
+ logger.info("🔍 开始多板块深度分析...")
+ multi_sector_analysis = self.agents.run_multi_sector_analysis(
+ quick_result['hot_topics'],
+ quick_result['stock_news']
+ )
+
+ # 合并多板块分析结果
+ if ai_analysis and multi_sector_analysis.get('success'):
+ ai_analysis['multi_sector'] = multi_sector_analysis
+
+ # 保存AI分析结果
+ if ai_analysis and self.db and quick_result.get('snapshot_id'):
+ ai_record = {
+ 'affected_sectors': ai_analysis.get('sector_analysis', {}).get('benefited_sectors', []),
+ 'recommended_stocks': ai_analysis.get('stock_recommend', {}).get('recommended_stocks', []),
+ 'risk_level': ai_analysis.get('risk_assess', {}).get('risk_level', '未知'),
+ 'risk_factors': ai_analysis.get('risk_assess', {}).get('risk_factors', []),
+ 'advice': ai_analysis.get('investment_advice', {}).get('advice', '观望'),
+ 'confidence': ai_analysis.get('investment_advice', {}).get('confidence', 50),
+ 'summary': ai_analysis.get('investment_advice', {}).get('summary', ''),
+ 'model_used': 'deepseek-chat',
+ 'analysis_time': ai_analysis.get('analysis_time', 0),
+ }
+ self.db.save_ai_analysis(quick_result['snapshot_id'], ai_record)
+
+ # 3. 生成交易信号
+ trading_signals = self._generate_trading_signals(
+ quick_result.get('flow_data', {}),
+ quick_result.get('model_data', {}),
+ quick_result.get('sentiment_data', {}),
+ ai_analysis
+ )
+
+ duration = time.time() - start_time
+ logger.info(f"✅ 完整分析完成,耗时 {duration:.2f} 秒")
+
+ return {
+ 'success': True,
+ 'snapshot_id': quick_result.get('snapshot_id'),
+ 'flow_data': quick_result.get('flow_data'),
+ 'model_data': quick_result.get('model_data'),
+ 'sentiment_data': quick_result.get('sentiment_data'),
+ 'ai_analysis': ai_analysis,
+ 'trading_signals': trading_signals,
+ 'stock_news': quick_result.get('stock_news'),
+ 'hot_topics': quick_result.get('hot_topics'),
+ 'platforms_data': quick_result.get('platforms_data'),
+ 'fetch_time': quick_result.get('fetch_time'),
+ 'duration': round(duration, 2),
+ }
+
+ except Exception as e:
+ logger.error(f"❌ 完整分析失败: {e}")
+ return {'success': False, 'error': str(e)}
+
+ def run_alert_check(self) -> Dict:
+ """
+ 运行预警检查
+
+ Returns:
+ {
+ 'success': bool,
+ 'alerts': List[Dict],
+ }
+ """
+ try:
+ logger.info("⚠️ 开始预警检查...")
+
+ if not self.alerts:
+ return {'success': False, 'error': '预警系统不可用'}
+
+ # 获取当前数据
+ quick_result = self.run_quick_analysis()
+
+ if not quick_result['success']:
+ return {'success': False, 'error': quick_result.get('error')}
+
+ # 获取历史数据
+ history_data = self._get_previous_snapshot()
+
+ # 构建检查数据
+ current_data = {
+ 'flow_data': quick_result.get('flow_data', {}),
+ 'hot_topics': quick_result.get('hot_topics', []),
+ 'viral_k': quick_result.get('model_data', {}).get('viral_k', {}),
+ 'flow_stage': quick_result.get('sentiment_data', {}).get('flow_stage', {}),
+ }
+
+ # 检查预警
+ alerts = self.alerts.check_alerts(
+ current_data,
+ history_data,
+ quick_result.get('sentiment_data'),
+ quick_result.get('snapshot_id')
+ )
+
+ logger.info(f"✅ 预警检查完成,触发 {len(alerts)} 个预警")
+
+ return {
+ 'success': True,
+ 'alerts': alerts,
+ 'snapshot_id': quick_result.get('snapshot_id'),
+ }
+
+ except Exception as e:
+ logger.error(f"❌ 预警检查失败: {e}")
+ return {'success': False, 'error': str(e)}
+
+ def get_dashboard_data(self) -> Dict:
+ """
+ 获取仪表盘数据
+
+ Returns:
+ {
+ 'latest_snapshot': Dict,
+ 'latest_sentiment': Dict,
+ 'latest_ai_analysis': Dict,
+ 'recent_alerts': List,
+ 'flow_trend': Dict,
+ 'scheduler_status': Dict,
+ }
+ """
+ try:
+ data = {}
+
+ if self.db:
+ # 最新快照
+ data['latest_snapshot'] = self.db.get_latest_snapshot()
+
+ # 最新情绪
+ data['latest_sentiment'] = self.db.get_latest_sentiment()
+
+ # 最新AI分析
+ data['latest_ai_analysis'] = self.db.get_latest_ai_analysis()
+
+ # 最近预警
+ data['recent_alerts'] = self.db.get_alerts(days=1)
+
+ # 流量趋势(7天)
+ data['flow_trend'] = self.get_flow_trend(days=7)
+
+ # 调度器状态
+ try:
+ from news_flow_scheduler import news_flow_scheduler
+ data['scheduler_status'] = news_flow_scheduler.get_status()
+ except:
+ data['scheduler_status'] = None
+
+ return data
+
+ except Exception as e:
+ logger.error(f"获取仪表盘数据失败: {e}")
+ return {}
+
+ def get_flow_trend(self, days: int = 7) -> Dict:
+ """获取流量趋势"""
+ if not self.db:
+ return {'dates': [], 'scores': [], 'trend': '无数据'}
+
+ stats = self.db.get_daily_statistics(days)
+
+ if not stats:
+ return {'dates': [], 'scores': [], 'trend': '无数据', 'analysis': '暂无历史数据'}
+
+ # 反转(从旧到新)
+ stats.reverse()
+
+ dates = [s['date'] for s in stats]
+ avg_scores = [s['avg_score'] for s in stats]
+ max_scores = [s['max_score'] for s in stats]
+ min_scores = [s['min_score'] for s in stats]
+
+ # 判断趋势
+ if len(avg_scores) >= 3:
+ recent_avg = sum(avg_scores[-3:]) / 3
+ earlier_avg = sum(avg_scores[:3]) / 3
+
+ if recent_avg > earlier_avg * 1.2:
+ trend = '上升'
+ analysis = f"近期流量持续上升(近3日均值{recent_avg:.0f} > 前3日均值{earlier_avg:.0f}),市场热度升温。"
+ elif recent_avg < earlier_avg * 0.8:
+ trend = '下降'
+ analysis = f"近期流量持续下降(近3日均值{recent_avg:.0f} < 前3日均值{earlier_avg:.0f}),市场热度降温。"
+ else:
+ trend = '平稳'
+ analysis = f"近期流量波动不大(近3日均值{recent_avg:.0f} ≈ 前3日均值{earlier_avg:.0f}),市场处于平衡状态。"
+ else:
+ trend = '数据不足'
+ analysis = '历史数据不足,无法判断趋势'
+
+ return {
+ 'dates': dates,
+ 'avg_scores': avg_scores,
+ 'max_scores': max_scores,
+ 'min_scores': min_scores,
+ 'trend': trend,
+ 'analysis': analysis,
+ }
+
+ def _generate_trading_signals(self, flow_data: Dict,
+ model_data: Dict,
+ sentiment_data: Dict,
+ ai_analysis: Dict = None) -> Dict:
+ """生成交易信号"""
+ signals = {
+ 'overall_signal': '观望',
+ 'confidence': 50,
+ 'risk_level': '中等',
+ 'hot_sectors': [],
+ 'operation_advice': '',
+ 'key_message': '',
+ }
+
+ # 获取各项指标
+ total_score = flow_data.get('total_score', 0)
+ flow_level = flow_data.get('level', '中')
+
+ sentiment_index = 50
+ flow_stage = '未知'
+ if sentiment_data:
+ sentiment_index = sentiment_data.get('sentiment', {}).get('sentiment_index', 50)
+ flow_stage = sentiment_data.get('flow_stage', {}).get('stage_name', '未知')
+
+ viral_k = 1.0
+ if model_data:
+ viral_k = model_data.get('viral_k', {}).get('k_value', 1.0)
+
+ # 核心判断逻辑
+ if flow_stage in ['一致', 'consensus']:
+ # 流量高潮 = 逃命时刻
+ signals['overall_signal'] = '卖出'
+ signals['confidence'] = 90
+ signals['risk_level'] = '极高'
+ signals['key_message'] = '⚠️ 流量高潮 = 价格高潮 = 逃命时刻!立即减仓或清仓!'
+ signals['operation_advice'] = '立即减仓或清仓,锁定利润。不要贪婪,不要犹豫。'
+
+ elif flow_stage in ['退潮', 'decline']:
+ signals['overall_signal'] = '观望'
+ signals['confidence'] = 80
+ signals['risk_level'] = '高'
+ signals['key_message'] = '流量退潮,及时止盈止损'
+ signals['operation_advice'] = '持仓者及时止盈止损,空仓者继续观望。'
+
+ elif flow_stage in ['加速', 'acceleration'] and viral_k > 1.2:
+ signals['overall_signal'] = '买入'
+ signals['confidence'] = 75
+ signals['risk_level'] = '中等'
+ signals['key_message'] = '流量加速期,可参与龙头'
+ signals['operation_advice'] = '关注龙头股,轻仓试探。设置止损位(-5%),止盈位(+15%)。'
+
+ elif flow_stage in ['启动', 'startup']:
+ signals['overall_signal'] = '关注'
+ signals['confidence'] = 65
+ signals['risk_level'] = '低'
+ signals['key_message'] = '流量启动期,可以关注'
+ signals['operation_advice'] = '密切关注,等待确认后介入。'
+
+ elif flow_level == "极高" and sentiment_index > 85:
+ signals['overall_signal'] = '观望'
+ signals['confidence'] = 70
+ signals['risk_level'] = '高'
+ signals['key_message'] = '流量极高+情绪过热,追高风险大'
+ signals['operation_advice'] = '不建议追高,等待回调机会。'
+
+ else:
+ signals['overall_signal'] = '观望'
+ signals['confidence'] = 50
+ signals['risk_level'] = '中等'
+ signals['key_message'] = '市场无明确方向,保持观望'
+ signals['operation_advice'] = '保持观望,等待流量信号明确。'
+
+ # 整合AI分析结果
+ if ai_analysis:
+ advice = ai_analysis.get('investment_advice', {})
+ if advice.get('advice'):
+ signals['ai_advice'] = advice.get('advice')
+ signals['ai_confidence'] = advice.get('confidence', 50)
+ signals['ai_summary'] = advice.get('summary', '')
+
+ sectors = ai_analysis.get('sector_analysis', {}).get('benefited_sectors', [])
+ signals['hot_sectors'] = sectors[:3]
+
+ return signals
+
+ def _get_history_scores(self, hours: int = 24) -> List[int]:
+ """获取历史流量得分"""
+ if not self.db:
+ return []
+
+ scores = self.db.get_recent_scores(hours)
+ return [s['total_score'] for s in scores]
+
+ def _get_history_sentiments(self, limit: int = 10) -> List[Dict]:
+ """获取历史情绪记录"""
+ if not self.db:
+ return []
+
+ return self.db.get_sentiment_history(limit)
+
+ def _get_previous_snapshot(self) -> Optional[Dict]:
+ """获取上一次快照"""
+ if not self.db:
+ return None
+
+ snapshots = self.db.get_history_snapshots(limit=2)
+ if len(snapshots) >= 2:
+ detail = self.db.get_snapshot_detail(snapshots[1]['id'])
+ return {
+ 'hot_topics': detail.get('hot_topics', []),
+ 'snapshot': detail.get('snapshot', {}),
+ }
+ return None
+
+ def compare_with_history(self, current_score: int) -> Dict:
+ """与历史数据对比"""
+ if not self.db:
+ return {
+ 'percentile': 50,
+ 'level_description': '无历史对比',
+ 'comparison': '暂无足够的历史数据进行对比'
+ }
+
+ stats = self.db.get_daily_statistics(30)
+
+ if not stats:
+ return {
+ 'percentile': 50,
+ 'level_description': '无历史对比',
+ 'comparison': '暂无足够的历史数据进行对比'
+ }
+
+ all_scores = []
+ for stat in stats:
+ all_scores.extend([stat['avg_score'], stat['max_score'], stat['min_score']])
+
+ all_scores.sort()
+
+ lower_count = sum(1 for s in all_scores if s < current_score)
+ percentile = int(lower_count / len(all_scores) * 100) if all_scores else 50
+
+ if percentile >= 90:
+ level_description = "极高水平"
+ comparison = f"当前流量得分{current_score}处于历史极高水平(超过{percentile}%的历史记录),流量极度爆发!"
+ elif percentile >= 70:
+ level_description = "较高水平"
+ comparison = f"当前流量得分{current_score}处于历史较高水平(超过{percentile}%的历史记录),流量活跃。"
+ elif percentile >= 30:
+ level_description = "正常水平"
+ comparison = f"当前流量得分{current_score}处于历史正常水平(超过{percentile}%的历史记录)。"
+ else:
+ level_description = "较低水平"
+ comparison = f"当前流量得分{current_score}处于历史较低水平(仅超过{percentile}%的历史记录),流量低迷。"
+
+ return {
+ 'percentile': percentile,
+ 'level_description': level_description,
+ 'comparison': comparison
+ }
+
+
+# 全局引擎实例
+news_flow_engine = NewsFlowEngine()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试新闻流量分析引擎 ===")
+
+ # 运行快速分析
+ print("\n--- 快速分析 ---")
+ result = news_flow_engine.run_quick_analysis(category='finance')
+
+ if result['success']:
+ print(f"✅ 分析成功!快照ID: {result.get('snapshot_id')}")
+ print(f"\n流量得分: {result['flow_data']['total_score']}")
+ print(f"流量等级: {result['flow_data']['level']}")
+ print(f"股票相关新闻: {len(result['stock_news'])} 条")
+ print(f"热门话题: {len(result['hot_topics'])} 个")
+
+ if result.get('sentiment_data'):
+ sentiment = result['sentiment_data'].get('sentiment', {})
+ print(f"\n情绪指数: {sentiment.get('sentiment_index', 'N/A')}")
+ print(f"情绪分类: {sentiment.get('sentiment_class', 'N/A')}")
+
+ flow_stage = result['sentiment_data'].get('flow_stage', {})
+ print(f"流量阶段: {flow_stage.get('stage_name', 'N/A')}")
+ else:
+ print(f"❌ 分析失败: {result.get('error')}")
diff --git a/news_flow_model.py b/news_flow_model.py
new file mode 100644
index 0000000..2274a78
--- /dev/null
+++ b/news_flow_model.py
@@ -0,0 +1,557 @@
+"""
+新闻流量模型计算模块
+核心公式:接盘总量 = 流量 × 转化率 × 客单价
+实现流量为王理念的量化分析
+"""
+import logging
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional, Tuple
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowModel:
+ """新闻流量模型计算器"""
+
+ def __init__(self):
+ # 平台类别权重(用于转化率计算)
+ self.category_weights = {
+ 'finance': 1.5, # 财经平台转化率高
+ 'social': 1.2, # 社交媒体传播快
+ 'news': 1.0, # 新闻媒体正常
+ 'tech': 0.8, # 科技平台相关性低
+ }
+
+ # 话题类型转化率系数
+ self.topic_weights = {
+ # 高转化话题
+ '政策': 2.0,
+ '利好': 1.8,
+ '涨停': 1.8,
+ '龙头': 1.7,
+ '机构': 1.6,
+ '外资': 1.6,
+ '北向': 1.6,
+ '重组': 1.5,
+ '并购': 1.5,
+ 'IPO': 1.5,
+
+ # 中转化话题
+ '业绩': 1.3,
+ '财报': 1.3,
+ '板块': 1.2,
+ '概念': 1.2,
+ '题材': 1.2,
+
+ # 一般话题
+ '股票': 1.0,
+ '股市': 1.0,
+ 'A股': 1.0,
+ }
+
+ # 基础转化率(万分之一)
+ self.base_conversion_rate = 0.0001
+
+ # 平均客单价(元)- 散户平均投资金额
+ self.avg_investment = 50000
+
+ # 时效因子衰减系数(每小时衰减)
+ self.time_decay_rate = 0.95
+
+ def calculate_traffic_score(self, platforms_data: List[Dict]) -> Dict:
+ """
+ 计算流量分数
+
+ 流量分数 = Σ(平台权重 × 热度 × 时效因子)
+
+ Args:
+ platforms_data: 平台数据列表
+
+ Returns:
+ {
+ 'total_score': int,
+ 'category_scores': Dict[str, int],
+ 'platform_details': List[Dict],
+ 'normalized_score': int, # 归一化到0-1000
+ }
+ """
+ category_scores = {
+ 'social': 0,
+ 'news': 0,
+ 'finance': 0,
+ 'tech': 0,
+ }
+
+ platform_details = []
+ total_raw_score = 0
+
+ for platform_data in platforms_data:
+ if not platform_data.get('success'):
+ continue
+
+ category = platform_data.get('category', 'other')
+ weight = platform_data.get('weight', 5)
+ count = platform_data.get('count', 0)
+ platform_name = platform_data.get('platform_name', '')
+
+ # 计算平台得分:权重 × 新闻数量 × 类别权重
+ category_weight = self.category_weights.get(category, 1.0)
+ platform_score = weight * count * category_weight
+
+ category_scores[category] = category_scores.get(category, 0) + platform_score
+ total_raw_score += platform_score
+
+ platform_details.append({
+ 'platform': platform_data.get('platform', ''),
+ 'platform_name': platform_name,
+ 'category': category,
+ 'count': count,
+ 'weight': weight,
+ 'score': int(platform_score),
+ })
+
+ # 归一化到0-1000
+ normalized_score = min(int(total_raw_score / 50), 1000)
+
+ return {
+ 'total_score': int(total_raw_score),
+ 'normalized_score': normalized_score,
+ 'category_scores': {k: int(v) for k, v in category_scores.items()},
+ 'platform_details': platform_details,
+ }
+
+ def estimate_conversion_rate(self, hot_topics: List[Dict],
+ category_distribution: Dict[str, int]) -> Dict:
+ """
+ 估算转化率
+
+ 转化率 = 基础转化率 × 话题系数 × 平台系数
+
+ Args:
+ hot_topics: 热门话题列表
+ category_distribution: 类别得分分布
+
+ Returns:
+ {
+ 'conversion_rate': float,
+ 'topic_factor': float,
+ 'platform_factor': float,
+ 'analysis': str,
+ }
+ """
+ # 1. 计算话题系数(基于热门话题的类型)
+ topic_factor = 1.0
+ matched_topics = []
+
+ for topic in hot_topics[:10]: # 只看TOP10热门话题
+ topic_text = topic.get('topic', '')
+ topic_heat = topic.get('heat', 0)
+
+ for keyword, weight in self.topic_weights.items():
+ if keyword in topic_text:
+ # 热度加权
+ heat_bonus = 1 + (topic_heat / 100) * 0.5
+ topic_factor = max(topic_factor, weight * heat_bonus)
+ matched_topics.append({
+ 'topic': topic_text,
+ 'keyword': keyword,
+ 'factor': weight,
+ })
+ break
+
+ # 2. 计算平台系数(基于类别分布)
+ total_score = sum(category_distribution.values())
+ if total_score > 0:
+ platform_factor = sum(
+ (score / total_score) * self.category_weights.get(cat, 1.0)
+ for cat, score in category_distribution.items()
+ )
+ else:
+ platform_factor = 1.0
+
+ # 3. 计算最终转化率
+ conversion_rate = self.base_conversion_rate * topic_factor * platform_factor
+
+ # 4. 生成分析
+ if conversion_rate >= 0.0003:
+ analysis = f"转化率极高({conversion_rate:.4%})!话题与股市高度相关,投资者关注度极高。"
+ elif conversion_rate >= 0.0002:
+ analysis = f"转化率较高({conversion_rate:.4%})。话题具有较强的股市联动性。"
+ elif conversion_rate >= 0.0001:
+ analysis = f"转化率正常({conversion_rate:.4%})。话题与股市有一定关联。"
+ else:
+ analysis = f"转化率较低({conversion_rate:.4%})。话题与股市关联度不高。"
+
+ return {
+ 'conversion_rate': conversion_rate,
+ 'topic_factor': round(topic_factor, 2),
+ 'platform_factor': round(platform_factor, 2),
+ 'matched_topics': matched_topics,
+ 'analysis': analysis,
+ }
+
+ def calculate_potential(self, flow_score: int, conversion_rate: float,
+ avg_investment: float = None) -> Dict:
+ """
+ 计算接盘潜力
+
+ 核心公式:接盘总量 = 流量 × 转化率 × 客单价
+
+ Args:
+ flow_score: 流量分数(代表潜在触达人数,按比例换算)
+ conversion_rate: 转化率
+ avg_investment: 平均客单价(元)
+
+ Returns:
+ {
+ 'potential_volume': float, # 接盘总量(亿元)
+ 'potential_level': str, # 潜力等级
+ 'estimated_participants': int, # 预估参与人数
+ 'analysis': str,
+ }
+ """
+ if avg_investment is None:
+ avg_investment = self.avg_investment
+
+ # 流量分数换算为潜在触达人数(假设满分1000对应1000万人)
+ potential_reach = flow_score * 10000 # 分数 × 10000 = 潜在触达人数
+
+ # 预估参与人数
+ estimated_participants = int(potential_reach * conversion_rate)
+
+ # 接盘总量 = 参与人数 × 平均投资额(转换为亿元)
+ potential_volume = (estimated_participants * avg_investment) / 100000000
+
+ # 确定潜力等级
+ if potential_volume >= 100:
+ potential_level = "超大"
+ analysis = f"预估接盘资金{potential_volume:.1f}亿元,市场资金充裕,热点题材可能持续发酵。"
+ elif potential_volume >= 50:
+ potential_level = "大"
+ analysis = f"预估接盘资金{potential_volume:.1f}亿元,资金量较大,可支撑短期行情。"
+ elif potential_volume >= 20:
+ potential_level = "中"
+ analysis = f"预估接盘资金{potential_volume:.1f}亿元,资金量适中,行情可能分化。"
+ elif potential_volume >= 5:
+ potential_level = "小"
+ analysis = f"预估接盘资金{potential_volume:.1f}亿元,资金量较小,注意风险。"
+ else:
+ potential_level = "极小"
+ analysis = f"预估接盘资金{potential_volume:.1f}亿元,资金量不足,不建议追高。"
+
+ return {
+ 'potential_volume': round(potential_volume, 2),
+ 'potential_level': potential_level,
+ 'potential_reach': potential_reach,
+ 'estimated_participants': estimated_participants,
+ 'avg_investment': avg_investment,
+ 'analysis': analysis,
+ }
+
+ def classify_flow_type(self, history_scores: List[int],
+ current_score: int) -> Dict:
+ """
+ 判断流量类型
+
+ 存量流量型:出生自带顶流,流量快速到位(政策/大事件)
+ 增量流量型:初始流量小,具备病毒传播能力(话题发酵)
+
+ Args:
+ history_scores: 历史得分列表(从旧到新)
+ current_score: 当前得分
+
+ Returns:
+ {
+ 'flow_type': str,
+ 'characteristics': List[str],
+ 'time_window': str,
+ 'operation': str,
+ 'confidence': int,
+ }
+ """
+ if len(history_scores) < 2:
+ return {
+ 'flow_type': '未知',
+ 'characteristics': ['历史数据不足,无法判断'],
+ 'time_window': '无法判断',
+ 'operation': '继续观察,积累数据',
+ 'confidence': 0,
+ }
+
+ # 计算关键指标
+ initial_score = history_scores[0]
+ avg_score = sum(history_scores) / len(history_scores)
+ max_score = max(history_scores)
+
+ # 计算增长率序列
+ growth_rates = []
+ for i in range(1, len(history_scores)):
+ if history_scores[i-1] > 0:
+ rate = (history_scores[i] - history_scores[i-1]) / history_scores[i-1]
+ growth_rates.append(rate)
+
+ avg_growth = sum(growth_rates) / len(growth_rates) if growth_rates else 0
+ positive_growth_count = len([r for r in growth_rates if r > 0])
+
+ # 判断流量类型
+ if initial_score >= 500 and (current_score - initial_score) / (initial_score + 1) < 0.3:
+ # 初始热度高,增长有限 -> 存量流量型
+ flow_type = "存量流量型"
+ characteristics = [
+ f"初始热度高({initial_score}分)",
+ "流量快速到位",
+ "可能与政策/大事件相关",
+ "来得快去得也快",
+ ]
+ time_window = "时间窗口短(2-3天)"
+ operation = "快进快出,首日参与最佳,及时止盈"
+ confidence = 80
+
+ elif avg_growth > 0.15 and positive_growth_count >= len(growth_rates) * 0.6:
+ # 持续增长 -> 增量流量型
+ flow_type = "增量流量型"
+ characteristics = [
+ f"初始热度较低({initial_score}分)",
+ f"增长率{avg_growth*100:.1f}%",
+ "逐步攀升,病毒式传播",
+ "有埋伏机会",
+ ]
+ time_window = "时间窗口长(5-10天)"
+ operation = "可以埋伏,等待加速,分批建仓"
+ confidence = 75
+
+ elif avg_growth < -0.1:
+ # 持续下降 -> 衰退期
+ flow_type = "流量衰退"
+ characteristics = [
+ "热度持续下降",
+ f"平均跌幅{abs(avg_growth)*100:.1f}%",
+ "题材热度消退",
+ ]
+ time_window = "窗口已关闭"
+ operation = "及时止盈止损,不宜追入"
+ confidence = 70
+
+ else:
+ # 波动 -> 常规流量
+ flow_type = "常规流量"
+ characteristics = [
+ "热度波动正常",
+ "无明显趋势",
+ ]
+ time_window = "无特定窗口"
+ operation = "保持观望,等待方向明确"
+ confidence = 50
+
+ return {
+ 'flow_type': flow_type,
+ 'characteristics': characteristics,
+ 'time_window': time_window,
+ 'operation': operation,
+ 'confidence': confidence,
+ 'initial_score': initial_score,
+ 'current_score': current_score,
+ 'avg_growth': round(avg_growth * 100, 1),
+ 'history_length': len(history_scores),
+ }
+
+ def calculate_viral_k(self, current_score: int, previous_score: int) -> Dict:
+ """
+ 计算病毒系数K值
+
+ K值 = 当前传播量 / 上期传播量
+
+ K > 1.5: 指数型爆发
+ K ≈ 1: 线性增长
+ K < 1: 自然死亡
+
+ Returns:
+ {
+ 'k_value': float,
+ 'trend': str,
+ 'risk_level': str,
+ 'analysis': str,
+ }
+ """
+ if previous_score == 0:
+ return {
+ 'k_value': 1.0,
+ 'trend': '无历史数据',
+ 'risk_level': '未知',
+ 'analysis': '首次采集,无法计算K值',
+ }
+
+ k_value = round(current_score / previous_score, 2)
+
+ if k_value > 2.0:
+ trend = "爆发式增长"
+ risk_level = "高风险"
+ analysis = f"K值={k_value},流量正在爆发式增长!这是极端情况,可能接近顶部,注意风险。"
+ elif k_value > 1.5:
+ trend = "指数型爆发"
+ risk_level = "中高风险"
+ analysis = f"K值={k_value},流量指数型增长!题材正在加速发酵,关注龙头但注意追高风险。"
+ elif k_value > 1.2:
+ trend = "快速增长"
+ risk_level = "中风险"
+ analysis = f"K值={k_value},流量快速增长,题材正在升温,可适度参与。"
+ elif k_value > 1.0:
+ trend = "稳步增长"
+ risk_level = "低风险"
+ analysis = f"K值={k_value},流量稳步增长,题材处于发酵期。"
+ elif k_value == 1.0:
+ trend = "平稳"
+ risk_level = "低风险"
+ analysis = f"K值={k_value},流量保持稳定,市场平衡状态。"
+ elif k_value > 0.8:
+ trend = "轻微下降"
+ risk_level = "中风险"
+ analysis = f"K值={k_value},流量轻微下降,题材热度开始消退。"
+ else:
+ trend = "快速衰减"
+ risk_level = "高风险"
+ analysis = f"K值={k_value},流量快速衰减!题材进入退潮期,注意及时止盈止损。"
+
+ return {
+ 'k_value': k_value,
+ 'current_score': current_score,
+ 'previous_score': previous_score,
+ 'trend': trend,
+ 'risk_level': risk_level,
+ 'analysis': analysis,
+ }
+
+ def run_full_model(self, platforms_data: List[Dict],
+ hot_topics: List[Dict],
+ history_scores: List[int] = None) -> Dict:
+ """
+ 运行完整的流量模型分析
+
+ Returns:
+ {
+ 'traffic': Dict, # 流量分析
+ 'conversion': Dict, # 转化率分析
+ 'potential': Dict, # 接盘潜力
+ 'flow_type': Dict, # 流量类型
+ 'viral_k': Dict, # K值分析
+ 'summary': str, # 总结
+ }
+ """
+ # 1. 计算流量分数
+ traffic = self.calculate_traffic_score(platforms_data)
+ current_score = traffic['normalized_score']
+
+ # 2. 估算转化率
+ conversion = self.estimate_conversion_rate(
+ hot_topics,
+ traffic['category_scores']
+ )
+
+ # 3. 计算接盘潜力
+ potential = self.calculate_potential(
+ current_score,
+ conversion['conversion_rate']
+ )
+
+ # 4. 判断流量类型
+ if history_scores and len(history_scores) >= 2:
+ flow_type = self.classify_flow_type(history_scores, current_score)
+ # 计算K值(与上一次对比)
+ viral_k = self.calculate_viral_k(current_score, history_scores[-1])
+ else:
+ flow_type = {
+ 'flow_type': '未知',
+ 'characteristics': ['历史数据不足'],
+ 'time_window': '无法判断',
+ 'operation': '继续观察',
+ 'confidence': 0,
+ }
+ viral_k = {
+ 'k_value': 1.0,
+ 'trend': '无历史数据',
+ 'risk_level': '未知',
+ 'analysis': '首次分析,无法计算K值',
+ }
+
+ # 5. 生成总结
+ summary = self._generate_summary(traffic, conversion, potential, flow_type, viral_k)
+
+ return {
+ 'traffic': traffic,
+ 'conversion': conversion,
+ 'potential': potential,
+ 'flow_type': flow_type,
+ 'viral_k': viral_k,
+ 'summary': summary,
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ }
+
+ def _generate_summary(self, traffic: Dict, conversion: Dict,
+ potential: Dict, flow_type: Dict, viral_k: Dict) -> str:
+ """生成分析总结"""
+ lines = []
+
+ # 流量概况
+ score = traffic['normalized_score']
+ if score >= 800:
+ lines.append(f"【流量】当前流量分数{score},处于极高水平")
+ elif score >= 500:
+ lines.append(f"【流量】当前流量分数{score},处于较高水平")
+ elif score >= 200:
+ lines.append(f"【流量】当前流量分数{score},处于正常水平")
+ else:
+ lines.append(f"【流量】当前流量分数{score},处于低位")
+
+ # 转化率
+ lines.append(f"【转化】{conversion['analysis']}")
+
+ # 接盘潜力
+ lines.append(f"【潜力】{potential['analysis']}")
+
+ # 流量类型
+ if flow_type['flow_type'] != '未知':
+ lines.append(f"【类型】{flow_type['flow_type']},{flow_type['operation']}")
+
+ # K值
+ if viral_k['k_value'] != 1.0 or viral_k['trend'] != '无历史数据':
+ lines.append(f"【趋势】K值={viral_k['k_value']},{viral_k['trend']}")
+
+ return '\n'.join(lines)
+
+
+# 全局实例
+news_flow_model = NewsFlowModel()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试新闻流量模型 ===")
+
+ # 模拟数据
+ platforms_data = [
+ {'success': True, 'platform': 'weibo', 'platform_name': '微博',
+ 'category': 'social', 'weight': 10, 'count': 50},
+ {'success': True, 'platform': 'eastmoney', 'platform_name': '东方财富',
+ 'category': 'finance', 'weight': 9, 'count': 30},
+ {'success': True, 'platform': 'baidu', 'platform_name': '百度',
+ 'category': 'news', 'weight': 8, 'count': 40},
+ ]
+
+ hot_topics = [
+ {'topic': 'AI芯片', 'heat': 95, 'count': 50},
+ {'topic': '新能源政策', 'heat': 80, 'count': 30},
+ {'topic': '涨停板', 'heat': 75, 'count': 25},
+ ]
+
+ history_scores = [300, 350, 420, 500]
+
+ # 运行完整分析
+ result = news_flow_model.run_full_model(platforms_data, hot_topics, history_scores)
+
+ print(f"\n流量分数: {result['traffic']['normalized_score']}")
+ print(f"转化率: {result['conversion']['conversion_rate']:.4%}")
+ print(f"接盘潜力: {result['potential']['potential_volume']:.1f}亿元 ({result['potential']['potential_level']})")
+ print(f"流量类型: {result['flow_type']['flow_type']}")
+ print(f"K值: {result['viral_k']['k_value']} ({result['viral_k']['trend']})")
+ print(f"\n===总结===\n{result['summary']}")
diff --git a/news_flow_pdf.py b/news_flow_pdf.py
new file mode 100644
index 0000000..be58f77
--- /dev/null
+++ b/news_flow_pdf.py
@@ -0,0 +1,491 @@
+"""
+新闻流量分析PDF报告生成器
+生成包含AI分析结果的PDF格式报告
+"""
+
+import io
+import os
+import tempfile
+from datetime import datetime
+from typing import Dict, List, Optional
+
+from reportlab.lib.pagesizes import A4
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.units import inch, cm
+from reportlab.lib import colors
+from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowPDFGenerator:
+ """新闻流量分析PDF报告生成器"""
+
+ def __init__(self):
+ self.chinese_font = self._register_chinese_fonts()
+ self.styles = self._create_styles()
+
+ def _register_chinese_fonts(self) -> str:
+ """注册中文字体"""
+ try:
+ if 'ChineseFont' in pdfmetrics.getRegisteredFontNames():
+ return 'ChineseFont'
+
+ # 字体路径列表
+ font_paths = [
+ 'C:/Windows/Fonts/simsun.ttc',
+ 'C:/Windows/Fonts/simhei.ttf',
+ 'C:/Windows/Fonts/msyh.ttc',
+ '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
+ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
+ ]
+
+ for font_path in font_paths:
+ if os.path.exists(font_path):
+ try:
+ pdfmetrics.registerFont(TTFont('ChineseFont', font_path))
+ return 'ChineseFont'
+ except:
+ continue
+
+ return 'Helvetica'
+ except:
+ return 'Helvetica'
+
+ def _create_styles(self) -> Dict:
+ """创建PDF样式"""
+ styles = getSampleStyleSheet()
+
+ # 标题样式
+ styles.add(ParagraphStyle(
+ name='ChineseTitle',
+ fontName=self.chinese_font,
+ fontSize=24,
+ alignment=TA_CENTER,
+ spaceAfter=30,
+ textColor=colors.HexColor('#1a1a2e')
+ ))
+
+ # 副标题样式
+ styles.add(ParagraphStyle(
+ name='ChineseSubtitle',
+ fontName=self.chinese_font,
+ fontSize=14,
+ alignment=TA_CENTER,
+ spaceAfter=20,
+ textColor=colors.HexColor('#666666')
+ ))
+
+ # 章节标题
+ styles.add(ParagraphStyle(
+ name='ChineseHeading',
+ fontName=self.chinese_font,
+ fontSize=16,
+ spaceBefore=20,
+ spaceAfter=10,
+ textColor=colors.HexColor('#2d3436'),
+ borderPadding=5,
+ ))
+
+ # 正文样式
+ styles.add(ParagraphStyle(
+ name='ChineseBody',
+ fontName=self.chinese_font,
+ fontSize=11,
+ leading=18,
+ alignment=TA_JUSTIFY,
+ spaceBefore=6,
+ spaceAfter=6,
+ ))
+
+ # 小标题
+ styles.add(ParagraphStyle(
+ name='ChineseSmallHeading',
+ fontName=self.chinese_font,
+ fontSize=12,
+ spaceBefore=10,
+ spaceAfter=5,
+ textColor=colors.HexColor('#0984e3'),
+ ))
+
+ # 重点提示
+ styles.add(ParagraphStyle(
+ name='ChineseHighlight',
+ fontName=self.chinese_font,
+ fontSize=11,
+ leading=16,
+ backColor=colors.HexColor('#fff3cd'),
+ borderPadding=8,
+ spaceBefore=10,
+ spaceAfter=10,
+ ))
+
+ # 风险警告
+ styles.add(ParagraphStyle(
+ name='ChineseWarning',
+ fontName=self.chinese_font,
+ fontSize=10,
+ leading=14,
+ backColor=colors.HexColor('#f8d7da'),
+ borderPadding=8,
+ spaceBefore=10,
+ spaceAfter=10,
+ textColor=colors.HexColor('#721c24'),
+ ))
+
+ return styles
+
+ def generate_report(self, analysis_result: Dict) -> Optional[str]:
+ """
+ 生成PDF分析报告
+
+ Args:
+ analysis_result: 完整分析结果
+
+ Returns:
+ PDF文件路径
+ """
+ try:
+ # 创建临时文件
+ temp_dir = tempfile.gettempdir()
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+ pdf_path = os.path.join(temp_dir, f'news_flow_report_{timestamp}.pdf')
+
+ # 创建PDF文档
+ doc = SimpleDocTemplate(
+ pdf_path,
+ pagesize=A4,
+ rightMargin=50,
+ leftMargin=50,
+ topMargin=50,
+ bottomMargin=50
+ )
+
+ # 构建内容
+ content = []
+
+ # 封面
+ content.extend(self._build_cover(analysis_result))
+ content.append(PageBreak())
+
+ # 概要
+ content.extend(self._build_summary(analysis_result))
+ content.append(Spacer(1, 20))
+
+ # 流量分析
+ content.extend(self._build_flow_analysis(analysis_result))
+ content.append(Spacer(1, 20))
+
+ # AI分析结果
+ content.extend(self._build_ai_analysis(analysis_result))
+ content.append(PageBreak())
+
+ # 板块深度分析
+ content.extend(self._build_sector_analysis(analysis_result))
+ content.append(Spacer(1, 20))
+
+ # 股票推荐
+ content.extend(self._build_stock_recommendations(analysis_result))
+ content.append(Spacer(1, 20))
+
+ # 风险提示
+ content.extend(self._build_risk_warning(analysis_result))
+
+ # 生成PDF
+ doc.build(content)
+
+ logger.info(f"✅ PDF报告生成成功: {pdf_path}")
+ return pdf_path
+
+ except Exception as e:
+ logger.error(f"❌ PDF生成失败: {e}")
+ return None
+
+ def _build_cover(self, result: Dict) -> List:
+ """构建封面"""
+ content = []
+
+ content.append(Spacer(1, 100))
+ content.append(Paragraph("新闻流量分析报告", self.styles['ChineseTitle']))
+ content.append(Spacer(1, 30))
+
+ # 生成时间
+ fetch_time = result.get('fetch_time', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
+ content.append(Paragraph(f"生成时间:{fetch_time}", self.styles['ChineseSubtitle']))
+
+ # 分析耗时
+ duration = result.get('duration', 0)
+ content.append(Paragraph(f"分析耗时:{duration} 秒", self.styles['ChineseSubtitle']))
+
+ content.append(Spacer(1, 50))
+
+ # 核心指标概览
+ flow_data = result.get('flow_data', {})
+ sentiment_data = result.get('sentiment_data', {})
+
+ overview_text = f"""
+ 流量得分:{flow_data.get('total_score', 0)}/1000 ({flow_data.get('level', '中')})
+ 情绪指数:{sentiment_data.get('sentiment', {}).get('sentiment_index', 50)}/100
+ 流量阶段:{sentiment_data.get('flow_stage', {}).get('stage_name', '未知')}
+ """
+ content.append(Paragraph(overview_text, self.styles['ChineseBody']))
+
+ return content
+
+ def _build_summary(self, result: Dict) -> List:
+ """构建分析概要"""
+ content = []
+
+ content.append(Paragraph("📊 分析概要", self.styles['ChineseHeading']))
+
+ trading_signals = result.get('trading_signals', {})
+ ai_analysis = result.get('ai_analysis', {})
+
+ # 交易信号
+ signal = trading_signals.get('overall_signal', '观望')
+ confidence = trading_signals.get('confidence', 50)
+
+ content.append(Paragraph(
+ f"【AI建议】{signal}(置信度:{confidence}%)",
+ self.styles['ChineseHighlight']
+ ))
+
+ # 核心提示
+ key_message = trading_signals.get('key_message', '')
+ if key_message:
+ content.append(Paragraph(f"核心提示:{key_message}", self.styles['ChineseBody']))
+
+ # AI总结
+ advice = ai_analysis.get('investment_advice', {})
+ summary = advice.get('summary', '')
+ if summary:
+ content.append(Paragraph(f"AI总结:{summary}", self.styles['ChineseBody']))
+
+ return content
+
+ def _build_flow_analysis(self, result: Dict) -> List:
+ """构建流量分析"""
+ content = []
+
+ content.append(Paragraph("📈 流量分析", self.styles['ChineseHeading']))
+
+ flow_data = result.get('flow_data', {})
+ model_data = result.get('model_data', {})
+
+ # 流量得分表格
+ flow_table_data = [
+ ['指标', '数值', '等级'],
+ ['总流量得分', str(flow_data.get('total_score', 0)), flow_data.get('level', '中')],
+ ['社交媒体', str(flow_data.get('social_score', 0)), '-'],
+ ['财经平台', str(flow_data.get('finance_score', 0)), '-'],
+ ['新闻媒体', str(flow_data.get('news_score', 0)), '-'],
+ ]
+
+ table = Table(flow_table_data, colWidths=[150, 100, 100])
+ table.setStyle(TableStyle([
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4a69bd')),
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
+ ('FONTNAME', (0, 0), (-1, -1), self.chinese_font),
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+ ('GRID', (0, 0), (-1, -1), 1, colors.grey),
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.whitesmoke, colors.white]),
+ ]))
+ content.append(table)
+
+ # K值分析
+ viral_k = model_data.get('viral_k', {})
+ if viral_k:
+ k_value = viral_k.get('k_value', 1.0)
+ trend = viral_k.get('trend', '稳定')
+ content.append(Spacer(1, 10))
+ content.append(Paragraph(
+ f"K值(病毒系数):{k_value:.2f} - {trend}",
+ self.styles['ChineseBody']
+ ))
+
+ return content
+
+ def _build_ai_analysis(self, result: Dict) -> List:
+ """构建AI分析结果"""
+ content = []
+
+ content.append(Paragraph("🤖 AI智能分析", self.styles['ChineseHeading']))
+
+ ai_analysis = result.get('ai_analysis', {})
+ if not ai_analysis:
+ content.append(Paragraph("暂无AI分析数据", self.styles['ChineseBody']))
+ return content
+
+ # 受益板块
+ sector_analysis = ai_analysis.get('sector_analysis', {})
+ benefited_sectors = sector_analysis.get('benefited_sectors', [])
+
+ if benefited_sectors:
+ content.append(Paragraph("受益板块分析", self.styles['ChineseSmallHeading']))
+
+ sector_table_data = [['板块', '置信度', '分析理由']]
+ for sector in benefited_sectors[:5]:
+ sector_table_data.append([
+ sector.get('name', ''),
+ f"{sector.get('confidence', 0)}%",
+ sector.get('reason', '')[:40] + '...' if len(sector.get('reason', '')) > 40 else sector.get('reason', '')
+ ])
+
+ table = Table(sector_table_data, colWidths=[100, 60, 280])
+ table.setStyle(TableStyle([
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#00b894')),
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
+ ('FONTNAME', (0, 0), (-1, -1), self.chinese_font),
+ ('FONTSIZE', (0, 0), (-1, -1), 9),
+ ('ALIGN', (0, 0), (1, -1), 'CENTER'),
+ ('ALIGN', (2, 0), (2, -1), 'LEFT'),
+ ('GRID', (0, 0), (-1, -1), 1, colors.grey),
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.whitesmoke, colors.white]),
+ ]))
+ content.append(table)
+
+ # 热门题材
+ hot_themes = sector_analysis.get('hot_themes', [])
+ if hot_themes:
+ content.append(Spacer(1, 15))
+ content.append(Paragraph("今日热门题材", self.styles['ChineseSmallHeading']))
+
+ themes_text = []
+ for theme in hot_themes[:5]:
+ themes_text.append(f"• {theme.get('theme', '')} ({theme.get('heat_level', '中')})")
+
+ content.append(Paragraph('
'.join(themes_text), self.styles['ChineseBody']))
+
+ return content
+
+ def _build_sector_analysis(self, result: Dict) -> List:
+ """构建板块深度分析"""
+ content = []
+
+ ai_analysis = result.get('ai_analysis', {})
+ multi_sector = ai_analysis.get('multi_sector', {})
+ sector_analyses = multi_sector.get('sector_analyses', [])
+
+ if not sector_analyses:
+ return content
+
+ content.append(Paragraph("🎯 板块深度分析", self.styles['ChineseHeading']))
+ content.append(Paragraph(
+ f"共分析 {len(sector_analyses)} 个热门板块",
+ self.styles['ChineseBody']
+ ))
+
+ for sector in sector_analyses[:5]:
+ sector_name = sector.get('sector_name', '未知')
+ heat_level = sector.get('heat_level', '中')
+ outlook = sector.get('short_term_outlook', '震荡')
+
+ content.append(Spacer(1, 10))
+ content.append(Paragraph(
+ f"【{sector_name}】热度:{heat_level} | 短期预判:{outlook}",
+ self.styles['ChineseSmallHeading']
+ ))
+
+ # 预判理由
+ outlook_reason = sector.get('outlook_reason', '')
+ if outlook_reason:
+ content.append(Paragraph(f"预判理由:{outlook_reason}", self.styles['ChineseBody']))
+
+ # 龙头股
+ leaders = sector.get('leader_stocks', [])
+ if leaders:
+ leader_names = [f"{s.get('code', '')}{s.get('name', '')}" for s in leaders[:3]]
+ content.append(Paragraph(f"龙头股:{', '.join(leader_names)}", self.styles['ChineseBody']))
+
+ # 投资建议
+ advice = sector.get('investment_advice', '')
+ if advice:
+ content.append(Paragraph(f"建议:{advice}", self.styles['ChineseBody']))
+
+ return content
+
+ def _build_stock_recommendations(self, result: Dict) -> List:
+ """构建股票推荐"""
+ content = []
+
+ ai_analysis = result.get('ai_analysis', {})
+ stock_recommend = ai_analysis.get('stock_recommend', {})
+ recommended_stocks = stock_recommend.get('recommended_stocks', [])
+
+ if not recommended_stocks:
+ return content
+
+ content.append(Paragraph("💰 AI选股推荐", self.styles['ChineseHeading']))
+
+ # 股票推荐表格
+ stock_table_data = [['代码', '名称', '板块', '风险', '推荐理由']]
+ for stock in recommended_stocks[:8]:
+ stock_table_data.append([
+ stock.get('code', ''),
+ stock.get('name', ''),
+ stock.get('sector', ''),
+ stock.get('risk_level', '中'),
+ stock.get('reason', '')[:30] + '...' if len(stock.get('reason', '')) > 30 else stock.get('reason', '')
+ ])
+
+ table = Table(stock_table_data, colWidths=[60, 70, 70, 40, 200])
+ table.setStyle(TableStyle([
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#e17055')),
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
+ ('FONTNAME', (0, 0), (-1, -1), self.chinese_font),
+ ('FONTSIZE', (0, 0), (-1, -1), 9),
+ ('ALIGN', (0, 0), (3, -1), 'CENTER'),
+ ('ALIGN', (4, 0), (4, -1), 'LEFT'),
+ ('GRID', (0, 0), (-1, -1), 1, colors.grey),
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.whitesmoke, colors.white]),
+ ]))
+ content.append(table)
+
+ # 整体策略
+ overall_strategy = stock_recommend.get('overall_strategy', '')
+ if overall_strategy:
+ content.append(Spacer(1, 10))
+ content.append(Paragraph(f"整体策略:{overall_strategy}", self.styles['ChineseBody']))
+
+ return content
+
+ def _build_risk_warning(self, result: Dict) -> List:
+ """构建风险提示"""
+ content = []
+
+ content.append(Paragraph("⚠️ 风险提示", self.styles['ChineseHeading']))
+
+ ai_analysis = result.get('ai_analysis', {})
+ risk_assess = ai_analysis.get('risk_assess', {})
+
+ risk_level = risk_assess.get('risk_level', '中等')
+ risk_score = risk_assess.get('risk_score', 50)
+ risk_factors = risk_assess.get('risk_factors', [])
+
+ content.append(Paragraph(
+ f"风险等级:{risk_level}(分数:{risk_score}/100)",
+ self.styles['ChineseBody']
+ ))
+
+ if risk_factors:
+ factors_text = '
'.join([f"• {f}" for f in risk_factors[:5]])
+ content.append(Paragraph(f"风险因素:
{factors_text}", self.styles['ChineseBody']))
+
+ # 免责声明
+ disclaimer = """
+ 【免责声明】
+ 本报告由AI自动生成,仅供参考,不构成任何投资建议。
+ 股市有风险,投资需谨慎。请投资者根据自身情况独立判断,
+ 理性投资,自负盈亏。本报告作者及生成系统不对投资决策
+ 产生的任何损失承担责任。
+ """
+ content.append(Spacer(1, 20))
+ content.append(Paragraph(disclaimer, self.styles['ChineseWarning']))
+
+ return content
diff --git a/news_flow_scheduler.py b/news_flow_scheduler.py
new file mode 100644
index 0000000..61753e9
--- /dev/null
+++ b/news_flow_scheduler.py
@@ -0,0 +1,383 @@
+"""
+新闻流量定时任务调度模块
+实现三种定时任务:热点同步(30分钟)、预警生成(1小时)、深度分析(2小时)
+"""
+import schedule
+import threading
+import time
+import logging
+from datetime import datetime
+from typing import Dict, List, Optional, Callable
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class NewsFlowScheduler:
+ """新闻流量定时任务调度器"""
+
+ # 任务类型定义
+ TASK_TYPES = {
+ 'sync_hotspots': {
+ 'name': '热点同步',
+ 'interval': 30, # 分钟
+ 'description': '同步22个平台的热点数据',
+ },
+ 'generate_alerts': {
+ 'name': '预警生成',
+ 'interval': 60, # 分钟
+ 'description': '检查预警条件并生成预警',
+ },
+ 'deep_analysis': {
+ 'name': '深度分析',
+ 'interval': 120, # 分钟
+ 'description': '运行完整的AI分析',
+ },
+ }
+
+ def __init__(self):
+ """初始化调度器"""
+ self.running = False
+ self.thread = None
+ self.lock = threading.Lock()
+
+ # 任务配置
+ self.task_enabled = {
+ 'sync_hotspots': True,
+ 'generate_alerts': True,
+ 'deep_analysis': True,
+ }
+
+ # 任务间隔(分钟)
+ self.task_intervals = {
+ 'sync_hotspots': 30,
+ 'generate_alerts': 60,
+ 'deep_analysis': 120,
+ }
+
+ # 上次运行时间
+ self.last_run_times = {}
+
+ # 依赖模块
+ self.engine = None
+ self.db = None
+ self.alert_system = None
+
+ self._init_dependencies()
+ logger.info("[新闻流量] 调度器初始化完成")
+
+ def _init_dependencies(self):
+ """初始化依赖模块"""
+ try:
+ from news_flow_db import news_flow_db
+ self.db = news_flow_db
+ except Exception as e:
+ logger.warning(f"数据库模块初始化失败: {e}")
+
+ try:
+ from news_flow_alert import news_flow_alert_system
+ self.alert_system = news_flow_alert_system
+ except Exception as e:
+ logger.warning(f"预警系统初始化失败: {e}")
+
+ def _get_engine(self):
+ """延迟加载引擎(避免循环导入)"""
+ if self.engine is None:
+ try:
+ from news_flow_engine import news_flow_engine
+ self.engine = news_flow_engine
+ except Exception as e:
+ logger.error(f"引擎模块初始化失败: {e}")
+ return self.engine
+
+ def start(self):
+ """启动调度器"""
+ if self.running:
+ logger.warning("[新闻流量] 调度器已在运行")
+ return
+
+ with self.lock:
+ # 清除旧任务
+ self._clear_jobs()
+
+ # 注册任务
+ if self.task_enabled.get('sync_hotspots'):
+ interval = self.task_intervals.get('sync_hotspots', 30)
+ job = schedule.every(interval).minutes.do(self._run_sync_hotspots)
+ job.tag('news_flow', 'sync_hotspots')
+ logger.info(f"[新闻流量] 注册热点同步任务,间隔{interval}分钟")
+
+ if self.task_enabled.get('generate_alerts'):
+ interval = self.task_intervals.get('generate_alerts', 60)
+ job = schedule.every(interval).minutes.do(self._run_generate_alerts)
+ job.tag('news_flow', 'generate_alerts')
+ logger.info(f"[新闻流量] 注册预警生成任务,间隔{interval}分钟")
+
+ if self.task_enabled.get('deep_analysis'):
+ interval = self.task_intervals.get('deep_analysis', 120)
+ job = schedule.every(interval).minutes.do(self._run_deep_analysis)
+ job.tag('news_flow', 'deep_analysis')
+ logger.info(f"[新闻流量] 注册深度分析任务,间隔{interval}分钟")
+
+ # 启动调度线程
+ self.running = True
+ self.thread = threading.Thread(target=self._schedule_loop, daemon=True)
+ self.thread.start()
+
+ logger.info("[新闻流量] 调度器已启动")
+
+ def stop(self):
+ """停止调度器"""
+ with self.lock:
+ self.running = False
+ self._clear_jobs()
+ logger.info("[新闻流量] 调度器已停止")
+
+ def _clear_jobs(self):
+ """清除本模块的任务"""
+ jobs_to_remove = [job for job in schedule.jobs if 'news_flow' in job.tags]
+ for job in jobs_to_remove:
+ schedule.cancel_job(job)
+
+ def _schedule_loop(self):
+ """调度循环"""
+ while self.running:
+ try:
+ schedule.run_pending()
+ except Exception as e:
+ logger.error(f"[新闻流量] 调度循环异常: {e}")
+ time.sleep(30) # 每30秒检查一次
+
+ def _log_task(self, task_name: str, task_type: str,
+ status: str, message: str = '',
+ duration: float = 0, snapshot_id: int = None):
+ """记录任务日志"""
+ try:
+ if self.db:
+ self.db.save_scheduler_log(
+ task_name, task_type, status, message, duration, snapshot_id
+ )
+ except Exception as e:
+ logger.error(f"记录任务日志失败: {e}")
+
+ def _run_sync_hotspots(self):
+ """运行热点同步任务"""
+ task_type = 'sync_hotspots'
+ task_name = self.TASK_TYPES[task_type]['name']
+
+ logger.info(f"[新闻流量] 开始执行: {task_name}")
+ start_time = time.time()
+
+ try:
+ engine = self._get_engine()
+ if not engine:
+ raise Exception("引擎模块不可用")
+
+ # 运行快速分析(仅数据同步和基础计算)
+ result = engine.run_quick_analysis()
+
+ duration = time.time() - start_time
+
+ if result.get('success'):
+ snapshot_id = result.get('snapshot_id')
+ message = f"成功同步{result.get('success_count', 0)}个平台数据"
+ self._log_task(task_name, task_type, 'success', message, duration, snapshot_id)
+ logger.info(f"[新闻流量] {task_name}完成: {message}")
+ else:
+ message = result.get('error', '未知错误')
+ self._log_task(task_name, task_type, 'failed', message, duration)
+ logger.error(f"[新闻流量] {task_name}失败: {message}")
+
+ self.last_run_times[task_type] = datetime.now()
+
+ except Exception as e:
+ duration = time.time() - start_time
+ message = str(e)
+ self._log_task(task_name, task_type, 'error', message, duration)
+ logger.error(f"[新闻流量] {task_name}异常: {e}")
+
+ def _run_generate_alerts(self):
+ """运行预警生成任务"""
+ task_type = 'generate_alerts'
+ task_name = self.TASK_TYPES[task_type]['name']
+
+ logger.info(f"[新闻流量] 开始执行: {task_name}")
+ start_time = time.time()
+
+ try:
+ engine = self._get_engine()
+ if not engine:
+ raise Exception("引擎模块不可用")
+
+ # 运行预警检查
+ result = engine.run_alert_check()
+
+ duration = time.time() - start_time
+
+ if result.get('success'):
+ alert_count = len(result.get('alerts', []))
+ message = f"生成{alert_count}个预警"
+ self._log_task(task_name, task_type, 'success', message, duration)
+ logger.info(f"[新闻流量] {task_name}完成: {message}")
+
+ # 发送通知
+ if alert_count > 0 and self.alert_system:
+ self.alert_system.send_notification(result['alerts'])
+ else:
+ message = result.get('error', '未知错误')
+ self._log_task(task_name, task_type, 'failed', message, duration)
+ logger.error(f"[新闻流量] {task_name}失败: {message}")
+
+ self.last_run_times[task_type] = datetime.now()
+
+ except Exception as e:
+ duration = time.time() - start_time
+ message = str(e)
+ self._log_task(task_name, task_type, 'error', message, duration)
+ logger.error(f"[新闻流量] {task_name}异常: {e}")
+
+ def _run_deep_analysis(self):
+ """运行深度分析任务"""
+ task_type = 'deep_analysis'
+ task_name = self.TASK_TYPES[task_type]['name']
+
+ logger.info(f"[新闻流量] 开始执行: {task_name}")
+ start_time = time.time()
+
+ try:
+ engine = self._get_engine()
+ if not engine:
+ raise Exception("引擎模块不可用")
+
+ # 运行完整分析(包含AI)
+ result = engine.run_full_analysis(include_ai=True)
+
+ duration = time.time() - start_time
+
+ if result.get('success'):
+ snapshot_id = result.get('snapshot_id')
+ advice = result.get('ai_analysis', {}).get('investment_advice', {}).get('advice', 'N/A')
+ message = f"深度分析完成,建议:{advice}"
+ self._log_task(task_name, task_type, 'success', message, duration, snapshot_id)
+ logger.info(f"[新闻流量] {task_name}完成: {message}")
+ else:
+ message = result.get('error', '未知错误')
+ self._log_task(task_name, task_type, 'failed', message, duration)
+ logger.error(f"[新闻流量] {task_name}失败: {message}")
+
+ self.last_run_times[task_type] = datetime.now()
+
+ except Exception as e:
+ duration = time.time() - start_time
+ message = str(e)
+ self._log_task(task_name, task_type, 'error', message, duration)
+ logger.error(f"[新闻流量] {task_name}异常: {e}")
+
+ # ==================== 手动触发方法 ====================
+
+ def run_sync_now(self) -> Dict:
+ """立即执行热点同步"""
+ logger.info("[新闻流量] 手动触发热点同步")
+ self._run_sync_hotspots()
+ return {'success': True, 'message': '热点同步已执行'}
+
+ def run_alerts_now(self) -> Dict:
+ """立即执行预警生成"""
+ logger.info("[新闻流量] 手动触发预警生成")
+ self._run_generate_alerts()
+ return {'success': True, 'message': '预警生成已执行'}
+
+ def run_analysis_now(self) -> Dict:
+ """立即执行深度分析"""
+ logger.info("[新闻流量] 手动触发深度分析")
+ self._run_deep_analysis()
+ return {'success': True, 'message': '深度分析已执行'}
+
+ # ==================== 配置方法 ====================
+
+ def set_task_enabled(self, task_type: str, enabled: bool):
+ """设置任务开关"""
+ if task_type in self.task_enabled:
+ self.task_enabled[task_type] = enabled
+ logger.info(f"[新闻流量] 任务 {task_type} {'启用' if enabled else '禁用'}")
+
+ # 如果调度器正在运行,重新注册任务
+ if self.running:
+ self.stop()
+ self.start()
+
+ def set_task_interval(self, task_type: str, interval: int):
+ """设置任务间隔(分钟)"""
+ if task_type in self.task_intervals:
+ self.task_intervals[task_type] = interval
+ logger.info(f"[新闻流量] 任务 {task_type} 间隔设置为 {interval} 分钟")
+
+ # 如果调度器正在运行,重新注册任务
+ if self.running:
+ self.stop()
+ self.start()
+
+ def get_status(self) -> Dict:
+ """获取调度器状态"""
+ return {
+ 'running': self.running,
+ 'task_enabled': self.task_enabled.copy(),
+ 'task_intervals': self.task_intervals.copy(),
+ 'last_run_times': {
+ k: v.strftime('%Y-%m-%d %H:%M:%S') if v else None
+ for k, v in self.last_run_times.items()
+ },
+ 'next_run_times': self._get_next_run_times(),
+ }
+
+ def _get_next_run_times(self) -> Dict:
+ """获取下次运行时间"""
+ next_times = {}
+ for job in schedule.jobs:
+ if 'news_flow' in job.tags:
+ for tag in job.tags:
+ if tag in self.TASK_TYPES:
+ next_times[tag] = str(job.next_run)
+ return next_times
+
+ def get_task_logs(self, days: int = 7, task_type: str = None) -> List[Dict]:
+ """获取任务日志"""
+ if self.db:
+ return self.db.get_scheduler_logs(days, task_type)
+ return []
+
+
+# 全局实例
+news_flow_scheduler = NewsFlowScheduler()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试新闻流量调度器 ===")
+
+ # 获取状态
+ status = news_flow_scheduler.get_status()
+ print(f"\n调度器状态:")
+ print(f" 运行中: {status['running']}")
+ print(f" 任务配置: {status['task_enabled']}")
+ print(f" 任务间隔: {status['task_intervals']}")
+
+ # 启动调度器
+ print("\n启动调度器...")
+ news_flow_scheduler.start()
+
+ # 再次获取状态
+ status = news_flow_scheduler.get_status()
+ print(f"\n调度器状态:")
+ print(f" 运行中: {status['running']}")
+ print(f" 下次运行: {status['next_run_times']}")
+
+ # 等待一会儿
+ print("\n等待5秒...")
+ time.sleep(5)
+
+ # 停止调度器
+ print("\n停止调度器...")
+ news_flow_scheduler.stop()
+
+ print("\n测试完成")
diff --git a/news_flow_sentiment.py b/news_flow_sentiment.py
new file mode 100644
index 0000000..dc25adf
--- /dev/null
+++ b/news_flow_sentiment.py
@@ -0,0 +1,562 @@
+"""
+新闻流量情绪分析模块
+实现情绪指数、情绪分类、流量阶段判断、情绪动量计算
+"""
+import logging
+from datetime import datetime
+from typing import Dict, List, Optional, Tuple
+import statistics
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class SentimentAnalyzer:
+ """情绪分析器"""
+
+ def __init__(self):
+ # 情绪分类阈值
+ self.sentiment_thresholds = {
+ 'extremely_pessimistic': 20, # 极度悲观
+ 'pessimistic': 40, # 悲观
+ 'neutral': 60, # 中性
+ 'optimistic': 80, # 乐观
+ 'extremely_optimistic': 100, # 极度乐观
+ }
+
+ # 流量阶段定义
+ self.flow_stages = {
+ 'startup': '启动', # 刚开始发酵
+ 'acceleration': '加速', # 增速加快
+ 'divergence': '分歧', # 多空分歧
+ 'consensus': '一致', # 流量高潮(危险!)
+ 'decline': '退潮', # 热度下降
+ }
+
+ # 阶段判断阈值
+ self.stage_thresholds = {
+ 'startup_growth': 0.20, # 启动期增速阈值
+ 'acceleration_growth': 0.20, # 加速期增速阈值
+ 'divergence_volatility': 0.30, # 分歧期波动率阈值
+ 'consensus_k': 1.5, # 一致期K值阈值
+ 'decline_growth': -0.20, # 退潮期增速阈值
+ }
+
+ # 正面/负面关键词(用于情绪分析)
+ self.positive_keywords = [
+ '利好', '大涨', '暴涨', '涨停', '新高', '突破', '牛市',
+ '反弹', '加仓', '买入', '增持', '推荐', '看好', '机遇',
+ '政策支持', '业绩预增', '超预期', '景气度', '高增长',
+ ]
+
+ self.negative_keywords = [
+ '利空', '大跌', '暴跌', '跌停', '新低', '破位', '熊市',
+ '回调', '减仓', '卖出', '减持', '风险', '看空', '危机',
+ '政策收紧', '业绩下滑', '不及预期', '亏损', '退市',
+ ]
+
+ def calculate_sentiment_index(self, platforms_data: List[Dict],
+ stock_news: List[Dict] = None) -> Dict:
+ """
+ 计算情绪指数(0-100)
+
+ 基于以下因素:
+ 1. 流量规模(占40%)
+ 2. 财经平台活跃度(占30%)
+ 3. 正负面关键词比例(占30%)
+
+ Returns:
+ {
+ 'sentiment_index': int,
+ 'flow_factor': int,
+ 'finance_factor': int,
+ 'keyword_factor': int,
+ 'sentiment_class': str,
+ 'analysis': str,
+ }
+ """
+ # 1. 流量规模因子(40%)
+ total_news = 0
+ finance_news = 0
+
+ for platform_data in platforms_data:
+ if not platform_data.get('success'):
+ continue
+ count = platform_data.get('count', 0)
+ total_news += count
+ if platform_data.get('category') == 'finance':
+ finance_news += count
+
+ # 流量分数:基于新闻数量
+ if total_news >= 500:
+ flow_factor = 90
+ elif total_news >= 300:
+ flow_factor = 70
+ elif total_news >= 150:
+ flow_factor = 50
+ elif total_news >= 50:
+ flow_factor = 30
+ else:
+ flow_factor = 10
+
+ # 2. 财经平台活跃度因子(30%)
+ if total_news > 0:
+ finance_ratio = finance_news / total_news
+ finance_factor = min(int(finance_ratio * 200), 100)
+ else:
+ finance_factor = 50
+
+ # 3. 关键词情绪因子(30%)
+ positive_count = 0
+ negative_count = 0
+
+ if stock_news:
+ for news in stock_news:
+ title = news.get('title', '')
+ content = news.get('content', '')
+ text = f"{title} {content}"
+
+ for kw in self.positive_keywords:
+ if kw in text:
+ positive_count += 1
+ break
+
+ for kw in self.negative_keywords:
+ if kw in text:
+ negative_count += 1
+ break
+
+ total_sentiment_news = positive_count + negative_count
+ if total_sentiment_news > 0:
+ positive_ratio = positive_count / total_sentiment_news
+ keyword_factor = int(positive_ratio * 100)
+ else:
+ keyword_factor = 50 # 中性
+
+ # 4. 综合计算情绪指数
+ sentiment_index = int(
+ flow_factor * 0.4 +
+ finance_factor * 0.3 +
+ keyword_factor * 0.3
+ )
+
+ # 限制范围
+ sentiment_index = max(0, min(100, sentiment_index))
+
+ # 5. 情绪分类
+ sentiment_class = self.classify_sentiment(sentiment_index)
+
+ # 6. 生成分析
+ analysis = self._generate_sentiment_analysis(
+ sentiment_index, sentiment_class,
+ flow_factor, finance_factor, keyword_factor
+ )
+
+ return {
+ 'sentiment_index': sentiment_index,
+ 'flow_factor': flow_factor,
+ 'finance_factor': finance_factor,
+ 'keyword_factor': keyword_factor,
+ 'positive_count': positive_count,
+ 'negative_count': negative_count,
+ 'sentiment_class': sentiment_class,
+ 'analysis': analysis,
+ }
+
+ def classify_sentiment(self, index: int) -> str:
+ """
+ 情绪分类
+
+ 极度悲观(<20) / 悲观(20-40) / 中性(40-60) / 乐观(60-80) / 极度乐观(>80)
+ """
+ if index < 20:
+ return "极度悲观"
+ elif index < 40:
+ return "悲观"
+ elif index < 60:
+ return "中性"
+ elif index < 80:
+ return "乐观"
+ else:
+ return "极度乐观"
+
+ def _generate_sentiment_analysis(self, index: int, sentiment_class: str,
+ flow: int, finance: int, keyword: int) -> str:
+ """生成情绪分析文本"""
+ if sentiment_class == "极度乐观":
+ return f"情绪指数{index},市场极度乐观!流量爆发({flow}),财经活跃({finance})。警告:可能是情绪顶部,注意获利了结。"
+ elif sentiment_class == "乐观":
+ return f"情绪指数{index},市场情绪乐观。题材正在发酵,可关注龙头机会,但需注意节奏。"
+ elif sentiment_class == "中性":
+ return f"情绪指数{index},市场情绪中性。缺乏明确方向,建议观望为主。"
+ elif sentiment_class == "悲观":
+ return f"情绪指数{index},市场情绪偏悲观。负面因素较多,控制仓位,等待转机。"
+ else: # 极度悲观
+ return f"情绪指数{index},市场极度悲观!恐慌情绪蔓延。可能是超跌机会,但需谨慎左侧布局。"
+
+ def determine_flow_stage(self, history_scores: List[int],
+ current_score: int,
+ current_k: float = None) -> Dict:
+ """
+ 判断流量阶段
+
+ 流量阶段:
+ 1. 启动 - 刚开始发酵,关注
+ 2. 加速 - 增速加快,参与
+ 3. 分歧 - 多空分歧,谨慎
+ 4. 一致 - 流量高潮,危险!准备跑路
+ 5. 退潮 - 热度下降,及时止盈止损
+
+ Returns:
+ {
+ 'stage': str,
+ 'stage_name': str,
+ 'confidence': int,
+ 'signal': str, # 关注/参与/谨慎/危险/离场
+ 'analysis': str,
+ }
+ """
+ if len(history_scores) < 3:
+ return {
+ 'stage': 'unknown',
+ 'stage_name': '未知',
+ 'confidence': 0,
+ 'signal': '观察',
+ 'analysis': '历史数据不足,继续观察积累数据',
+ }
+
+ # 计算增长率序列
+ all_scores = history_scores + [current_score]
+ growth_rates = []
+ for i in range(1, len(all_scores)):
+ if all_scores[i-1] > 0:
+ rate = (all_scores[i] - all_scores[i-1]) / all_scores[i-1]
+ growth_rates.append(rate)
+
+ # 计算关键指标
+ recent_growth_rates = growth_rates[-3:] if len(growth_rates) >= 3 else growth_rates
+ avg_growth = sum(recent_growth_rates) / len(recent_growth_rates) if recent_growth_rates else 0
+
+ # 波动率(标准差)
+ if len(recent_growth_rates) >= 2:
+ volatility = statistics.stdev(recent_growth_rates)
+ else:
+ volatility = 0
+
+ # 计算K值(如果没有提供)
+ if current_k is None and len(all_scores) >= 2:
+ previous_score = all_scores[-2]
+ current_k = current_score / previous_score if previous_score > 0 else 1.0
+ elif current_k is None:
+ current_k = 1.0
+
+ # 判断上升/下降趋势
+ positive_count = sum(1 for r in recent_growth_rates if r > 0)
+ negative_count = sum(1 for r in recent_growth_rates if r < 0)
+
+ # 阶段判断逻辑
+ stage = 'unknown'
+ stage_name = '未知'
+ confidence = 50
+ signal = '观察'
+ analysis = ''
+
+ # 1. 一致阶段(最危险)- K值>1.5 且 高增速
+ if current_k >= 1.5 and avg_growth > 0.3:
+ stage = 'consensus'
+ stage_name = '一致'
+ confidence = 90
+ signal = '危险'
+ analysis = f"流量高潮!K值={current_k:.2f},增速{avg_growth*100:.1f}%。市场一致看多,这往往是顶部信号。立即减仓或清仓!"
+
+ # 2. 退潮阶段 - 连续下降
+ elif negative_count >= 2 and avg_growth < -0.15:
+ stage = 'decline'
+ stage_name = '退潮'
+ confidence = 85
+ signal = '离场'
+ analysis = f"流量退潮!连续下降,增速{avg_growth*100:.1f}%。题材热度消退,及时止盈止损,不要恋战。"
+
+ # 3. 分歧阶段 - 高波动,涨跌交替
+ elif volatility > 0.25 and positive_count > 0 and negative_count > 0:
+ stage = 'divergence'
+ stage_name = '分歧'
+ confidence = 75
+ signal = '谨慎'
+ analysis = f"多空分歧!波动率{volatility*100:.1f}%,市场观点不一。高抛低吸,控制仓位,设好止损。"
+
+ # 4. 加速阶段 - 持续上涨,增速加快
+ elif avg_growth > 0.2 and current_k > 1.1 and positive_count >= 2:
+ stage = 'acceleration'
+ stage_name = '加速'
+ confidence = 80
+ signal = '参与'
+ analysis = f"流量加速!增速{avg_growth*100:.1f}%,K值{current_k:.2f}。题材正在快速发酵,可参与龙头,但注意仓位。"
+
+ # 5. 启动阶段 - 刚开始上涨
+ elif avg_growth > 0.05 and positive_count >= 2:
+ stage = 'startup'
+ stage_name = '启动'
+ confidence = 70
+ signal = '关注'
+ analysis = f"流量启动!增速{avg_growth*100:.1f}%,题材刚开始发酵。可以关注,等待确认后介入。"
+
+ # 6. 其他情况
+ else:
+ stage = 'stable'
+ stage_name = '平稳'
+ confidence = 60
+ signal = '观察'
+ analysis = f"流量平稳。增速{avg_growth*100:.1f}%,无明显趋势。保持观望,等待方向明确。"
+
+ return {
+ 'stage': stage,
+ 'stage_name': stage_name,
+ 'confidence': confidence,
+ 'signal': signal,
+ 'analysis': analysis,
+ 'avg_growth': round(avg_growth * 100, 1),
+ 'volatility': round(volatility * 100, 1),
+ 'current_k': round(current_k, 2),
+ }
+
+ def calculate_momentum(self, history_data: List[Dict]) -> Dict:
+ """
+ 计算情绪动量
+
+ 情绪动量 = 当前变化速率 / 平均变化速率
+
+ 动量 > 1.5: 情绪加速
+ 动量 ≈ 1: 情绪稳定
+ 动量 < 0.5: 情绪减速
+
+ Args:
+ history_data: 历史数据列表,每项包含 'sentiment_index' 和 'timestamp'
+
+ Returns:
+ {
+ 'momentum': float,
+ 'momentum_level': str,
+ 'trend': str,
+ 'analysis': str,
+ }
+ """
+ if len(history_data) < 3:
+ return {
+ 'momentum': 1.0,
+ 'momentum_level': '正常',
+ 'trend': '数据不足',
+ 'analysis': '历史数据不足,无法计算动量',
+ }
+
+ # 提取情绪指数序列
+ sentiment_values = [d.get('sentiment_index', 50) for d in history_data]
+
+ # 计算变化率序列
+ changes = []
+ for i in range(1, len(sentiment_values)):
+ change = sentiment_values[i] - sentiment_values[i-1]
+ changes.append(abs(change))
+
+ # 计算平均变化率
+ avg_change = sum(changes) / len(changes) if changes else 1
+
+ # 计算当前变化率(最近的变化)
+ current_change = abs(changes[-1]) if changes else 0
+
+ # 计算动量
+ if avg_change > 0:
+ momentum = round(current_change / avg_change, 2)
+ else:
+ momentum = 1.0
+
+ # 确定动量级别和趋势
+ if momentum >= 2.0:
+ momentum_level = '极高'
+ trend = '急剧变化'
+ analysis = f"情绪动量{momentum},情绪正在急剧变化!市场可能出现转折点,密切关注。"
+ elif momentum >= 1.5:
+ momentum_level = '高'
+ trend = '加速变化'
+ analysis = f"情绪动量{momentum},情绪变化正在加速。趋势可能强化或反转。"
+ elif momentum >= 0.8:
+ momentum_level = '正常'
+ trend = '稳定'
+ analysis = f"情绪动量{momentum},情绪变化平稳,市场处于正常状态。"
+ elif momentum >= 0.3:
+ momentum_level = '低'
+ trend = '减速'
+ analysis = f"情绪动量{momentum},情绪变化正在减速,可能进入盘整期。"
+ else:
+ momentum_level = '极低'
+ trend = '停滞'
+ analysis = f"情绪动量{momentum},情绪几乎没有变化,市场陷入僵局。"
+
+ # 判断方向
+ if len(sentiment_values) >= 2:
+ direction = sentiment_values[-1] - sentiment_values[-2]
+ if direction > 0:
+ trend += "(向上)"
+ elif direction < 0:
+ trend += "(向下)"
+
+ return {
+ 'momentum': momentum,
+ 'momentum_level': momentum_level,
+ 'trend': trend,
+ 'current_change': current_change,
+ 'avg_change': round(avg_change, 1),
+ 'analysis': analysis,
+ }
+
+ def run_full_sentiment_analysis(self, platforms_data: List[Dict],
+ stock_news: List[Dict],
+ history_scores: List[int],
+ current_score: int,
+ history_sentiments: List[Dict] = None) -> Dict:
+ """
+ 运行完整的情绪分析
+
+ Returns:
+ {
+ 'sentiment': Dict, # 情绪指数
+ 'flow_stage': Dict, # 流量阶段
+ 'momentum': Dict, # 情绪动量
+ 'summary': str, # 总结
+ 'risk_level': str, # 风险等级
+ 'advice': str, # 操作建议
+ }
+ """
+ # 1. 计算情绪指数
+ sentiment = self.calculate_sentiment_index(platforms_data, stock_news)
+
+ # 2. 判断流量阶段
+ flow_stage = self.determine_flow_stage(history_scores, current_score)
+
+ # 3. 计算情绪动量
+ if history_sentiments and len(history_sentiments) >= 3:
+ momentum = self.calculate_momentum(history_sentiments)
+ else:
+ momentum = {
+ 'momentum': 1.0,
+ 'momentum_level': '正常',
+ 'trend': '数据不足',
+ 'analysis': '历史情绪数据不足',
+ }
+
+ # 4. 综合风险评估
+ risk_level, advice = self._assess_risk(sentiment, flow_stage, momentum)
+
+ # 5. 生成总结
+ summary = self._generate_summary(sentiment, flow_stage, momentum, risk_level)
+
+ return {
+ 'sentiment': sentiment,
+ 'flow_stage': flow_stage,
+ 'momentum': momentum,
+ 'summary': summary,
+ 'risk_level': risk_level,
+ 'advice': advice,
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ }
+
+ def _assess_risk(self, sentiment: Dict, flow_stage: Dict,
+ momentum: Dict) -> Tuple[str, str]:
+ """综合风险评估"""
+ risk_score = 0
+
+ # 情绪因素
+ sentiment_index = sentiment['sentiment_index']
+ if sentiment_index > 85:
+ risk_score += 3 # 过度乐观
+ elif sentiment_index < 25:
+ risk_score += 2 # 过度悲观
+
+ # 流量阶段因素
+ stage = flow_stage['stage']
+ if stage == 'consensus':
+ risk_score += 4 # 一致阶段最危险
+ elif stage == 'decline':
+ risk_score += 3
+ elif stage == 'divergence':
+ risk_score += 2
+
+ # 动量因素
+ momentum_value = momentum['momentum']
+ if momentum_value > 2.0:
+ risk_score += 2 # 变化过快
+
+ # 确定风险等级和建议
+ if risk_score >= 7:
+ risk_level = "极高"
+ advice = "立即减仓或清仓!市场处于极端状态,控制风险为第一要务。"
+ elif risk_score >= 5:
+ risk_level = "高"
+ advice = "谨慎操作,建议减仓。不追高,设置严格止损。"
+ elif risk_score >= 3:
+ risk_level = "中等"
+ advice = "正常操作,注意仓位控制。逢高减仓,逢低观察。"
+ elif risk_score >= 1:
+ risk_level = "低"
+ advice = "可适度参与,关注龙头机会。"
+ else:
+ risk_level = "极低"
+ advice = "风险较低,可积极参与,但仍需设置止损。"
+
+ return risk_level, advice
+
+ def _generate_summary(self, sentiment: Dict, flow_stage: Dict,
+ momentum: Dict, risk_level: str) -> str:
+ """生成综合总结"""
+ lines = [
+ f"【情绪】{sentiment['sentiment_class']}({sentiment['sentiment_index']}分)",
+ f"【阶段】{flow_stage['stage_name']}期,信号:{flow_stage['signal']}",
+ f"【动量】{momentum['momentum_level']},趋势{momentum['trend']}",
+ f"【风险】{risk_level}",
+ ]
+
+ return '\n'.join(lines)
+
+
+# 全局实例
+sentiment_analyzer = SentimentAnalyzer()
+
+
+# 测试代码
+if __name__ == "__main__":
+ print("=== 测试情绪分析模块 ===")
+
+ # 模拟数据
+ platforms_data = [
+ {'success': True, 'category': 'finance', 'count': 100},
+ {'success': True, 'category': 'social', 'count': 200},
+ {'success': True, 'category': 'news', 'count': 150},
+ ]
+
+ stock_news = [
+ {'title': 'AI板块大涨,龙头股涨停', 'content': '利好政策推动'},
+ {'title': '新能源概念股反弹', 'content': '业绩预增超预期'},
+ {'title': '市场观望情绪浓厚', 'content': '回调风险加大'},
+ ]
+
+ history_scores = [300, 350, 420, 500, 580]
+ current_score = 650
+
+ history_sentiments = [
+ {'sentiment_index': 55},
+ {'sentiment_index': 60},
+ {'sentiment_index': 68},
+ {'sentiment_index': 72},
+ ]
+
+ # 运行完整分析
+ result = sentiment_analyzer.run_full_sentiment_analysis(
+ platforms_data, stock_news, history_scores, current_score, history_sentiments
+ )
+
+ print(f"\n情绪指数: {result['sentiment']['sentiment_index']} ({result['sentiment']['sentiment_class']})")
+ print(f"流量阶段: {result['flow_stage']['stage_name']} - {result['flow_stage']['signal']}")
+ print(f"情绪动量: {result['momentum']['momentum']} ({result['momentum']['momentum_level']})")
+ print(f"风险等级: {result['risk_level']}")
+ print(f"\n===总结===\n{result['summary']}")
+ print(f"\n操作建议: {result['advice']}")
diff --git a/news_flow_ui.py b/news_flow_ui.py
new file mode 100644
index 0000000..0a9760e
--- /dev/null
+++ b/news_flow_ui.py
@@ -0,0 +1,1229 @@
+"""
+新闻流量监测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()
diff --git a/requirements.txt b/requirements.txt
index 5c7930e..c1f5dc0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,4 +13,5 @@ ta>=0.10.2
reportlab>=4.0.0
peewee>=3.17.0
schedule>=1.2.0
-pywencai>=0.7.0
\ No newline at end of file
+pywencai>=0.7.0
+jieba>=0.42.1
\ No newline at end of file
diff --git a/新闻流量转化炒股法.md b/新闻流量转化炒股法.md
new file mode 100644
index 0000000..e88d632
--- /dev/null
+++ b/新闻流量转化炒股法.md
@@ -0,0 +1,7 @@
+标题:流量为王:揭秘投机市场的炒作密码
+
+简介:从互联网思维拆解投机炒作,揭秘流量如何驱动市场。无论你是币圈玩家、A股散户还是价值投资者,这套框架都能帮你看清游戏规则,少走弯路。
+
+============================================================
+
+大家好,我是脑总。今天我们聊聊Meme币和A股的热点炒作。我自己虽然不炒这些东西,但因为曾经做过金融科技和数字资产创业,就需要去理解市场、发现需求。对于市场上由来已久、用户量非常大的这类投机行为,我也有过大量思考。而且,我自己作为价值投资者和金融市场从业者,又能以一个或许更宏大的全新视角看待这个问题。我相信今天讲的内容,如果你是玩这个领域的投机者,应该也会有所收获。虽然我不建议你参与,但如果你实在要入局,今天这套框架应该能让你少走很多弯路、少交很多学费。即使你是价值投资者,这套框架也会对你理解投资标的估值变化有极大提升。今天我的理论基础有两点:第一点是所谓的互联网思维,从流量和转化率看待这些毫无价值基础的炒作;第二点是李笑来当年的一句名言:傻子的共识也是共识。只要有足够多的傻子愿意接盘,这个东西就能炒起来。有傻子接盘,聪明人自然会来做流动性套利。互联网金融时代,流量就是流动性。做过互联网的人都知道,电商有一个最基本的公式:GMV等于流量乘以转化率乘以客单价。GMV就是成交总额。这个公式拆开看很简单:你的网站来了多少人就是流量,这些人里面有多少买了东西就是转化率,平均每个人花了多少钱就是客单价。把这三个数乘起来就是你能赚多少钱。现在我告诉你一个降维打击的认知:投机市场完全可以套用这个公式。一个题材、一个Meme能炒多高,取决于接盘流动性总量,而这个总量等于流量乘以转化率,再乘以客单价。流量就是曝光量,有多少人看到了这个消息;转化率就是看到的人里面,有多少傻子会冲进去买相关标的;客单价就是散户平均一个账户能投多少钱。这三个变量决定了最终会有多少资金进场接盘。接盘资金的规模,搭配相关标的的盘子大小,基本上就能算出这个东西的价格天花板大概在哪里。听起来很玄学对不对?别急,我一个一个给你拆解。在拆解之前,你得先搞清楚一件事情:流量有两种类型。第一种叫做存量流量型,出生自带顶流,流量快速到位,不需要发酵。比如某位前国际政要发币就是这种。他本人是全球顶级流量,有几千万社交媒体粉丝,全世界的媒体都盯着他。他一发推,立刻就是十亿级别的曝光量,不需要铺垫、不需要等待,直接到底。不过这个事情在投机上也还是有时间差的:从他发布这个消息,到他的粉丝都看到,再到社交媒体用户都看到,再到各种媒体报道让全世界人民都看到,这中间有时间差,可以用来获利。深度求索概念也是这样。东大自研AI大模型技术突破,这是什么级别的新闻?主流媒体报道,所有媒体跟进,短视频平台热搜霸榜,连不炒股的人都在转发。而且这件事情还带有民族情绪的加成。在部分投资者眼里,咱们东大人搞出来的AI能够取得突破,那么这个叙事实在是太有吸引力了,即使亏钱也仿佛带有某种情怀。流量瞬间爆炸:从海外社交媒体上英语世界AI圈开始发酵,然后国内媒体跟进,再到构造一个“美股调整是被深度求索干下去的”叙事,最后全民讨论进入高潮、出货结束。存量流量型有一个特点:来得快去得也快,时间窗口比较短,你手快吃肉、手慢接盘。第二种叫做增量流量型,初始流量很小,但是有病毒传播的能力,会指数增长。柴犬币早期就是这样:一开始只是一个币圈玩家搞的“狗狗币杀手”,没有什么人知道。但是柴犬这个形象全世界都认识,而且还能蹭到狗狗币的热度,传播门槛几乎为零。买了的人会发推炫耀,没买的人看到了FOMO(害怕错过)也会冲进去,然后这些人再发推、再带来新人,一轮一轮裂变下去,从小圈子传到整个币圈,再传到圈外。几个月时间,市值从几百万美元涨到最高三百多亿美元。A股的热点概念炒作有类似的现象,但是不明显。原因很简单:市值起点太高了。市场上小市值标的,最少也是二三十亿了。对比币圈从零起步的Meme币,这最肥的一段就没有了。所以我虽然不建议你参与这个零和博弈游戏,但是如果你实在要参与,觉得自己能成为镰刀而不是韭菜,那么币圈显然是更好的地方。如果一波流量浪潮能够支撑某个标的涨到几亿美元,对币圈来说就是一个巨大的造富机会,而对于A股就基本上没有机会。增量流量型的特点是:时间窗口长,有埋伏的机会。你可以在早期流量刚爆发的时候进场,等着它反复裂变,往往有多波段,可以反复进出。你看,同样是炒作,节奏完全不一样。你得先判断你面对的是哪一种,才知道该怎么玩。讲到这里你可能会问:这套流量模型是不是只适用于币圈和A股?美股为什么不这么玩?很好的问题。美股确实很少出现币圈、A股那种纯筹码博弈的炒作。即使有题材炒作,也基本上遵循产业逻辑。原因很简单:市场结构完全不一样。首先是市场参与者的结构天壤之别。币圈、A股什么情况?散户主导。币圈玩Meme币的绝大部分是散户。A股虽然散户持股市值占比只有30%-40%,但是交易量占比高达70%以上,市场定价权在散户手里。而且这些散户里面大部分是听消息炒股、追涨杀跌的普通投资者。美股机构主导,机构持股占比70%以上,散户只有30%。这意味着什么?币圈、A股的主要玩家是散户,他们的决策依据是:社交媒体新闻、知名人物发推、交易平台广场、短视频热搜、投资社区讨论、视频号推荐……流量在哪里,资金就在哪里。美股的主要玩家是机构,他们的决策依据是:专业数据终端、公司财报、产业调研。他们不看热搜,看基本面。同样的流量事件,散户和机构的反应完全不同。因为筹码已经被机构拿在手里了,散户更加不敢去炒作那种标的,一拉起来就被机构砸盘。如果你玩过港股就知道,流动性比空气还珍贵。很多股票一天成交量才几万块钱,你一个大户或者机构想止损都止损不了,几百万都得卖大半年才能卖出来,几千万你想都不要想。这个时候要是有“车头”或者说游资,带着散户来给你提供退出的流动性,你肯定是来多少买单全砸给他们了。其次是信息流的分散性。币圈、A股的信息流是高度集中的。什么算顶级流量入口?社交媒体上面几位加密领域的顶流人物;东大的主流新闻节目、短视频热搜、热点榜单……整个市场的散户都在看这些地方。一旦某个题材上了这些热点,立刻就是亿人次的曝光,流量瞬间爆炸。美股的信息流是分散的:彭博、路透、CNBC、华尔街日报、社交媒体、Reddit……信息源太多了,不同圈层看到的信息源完全不一样。机构投资者看彭博终端,散户看CNBC,Reddit用户看华尔街赌场版块。没有一个统一的流量入口能覆盖所有人,很难形成A股那种全民性的题材炒作。即使知名人物发推提到某个公司,影响力也主要在社交媒体圈层,传不到机构投资者那里。机构该看基本面还是看基本面。第三个更致命的是做空机制的制约。币圈的Meme币在链上阶段是完全没有做空机制的。A股做空极其困难:融券标的少,融券费率高。纯粹的题材炒作很难被阻击。所以当年有“奥巴马当选,澳柯玛涨停”、“某位国际政要当选,相关股票涨停”甚至把跨境ETF当成概念股炒出50%以上溢价率的现象,就是因为缺乏做空机制。大家都靠做多获利,所以这样的炒作长期盛行。美股做空机制极其发达:任何股票都可以卖空,借券成本低,而且没有涨跌停限制。一旦出现脱离基本面的炒作,立刻就有做空资金进场套利。最典型的就是做空机构的做空报告。如果一支股票纯粹靠流量炒作,基本面跟不上,都不用做空机构出报告,各种量化套利马上就把炒作资金给收割了。所以即使美股有题材炒作,也必须有产业逻辑支撑,否则撑不住。你可能会说:美股不是也有游戏驿站、AMC这种Meme股吗?没错,这就是美股版的题材炒作。它们的底层逻辑跟A股的题材炒作是一样的,都是流量驱动。游戏驿站为什么能从几美元涨到几百美元?因为Reddit的华尔街赌场版块制造了流量。两百万散户在论坛里面互相鼓励:“我们要逼空华尔街”。这个叙事有情绪共鸣:反精英、反建制,病毒式裂变,一个人能带来好几个新人,最终形成了一波散户对基金的“圣战”。这完全符合我们前面讲的流量模型:流量乘以转化率乘以客单价。但是这种情况发生的机会窗口极其短暂,可以说需要天时地利人和。美股的表现,其实更证明了这个模型的准确性。为什么?因为流量模型的核心是流量乘以转化率乘以客单价。在不同的市场结构下,这三个变量的表现形式不同,但底层逻辑不变。A股是流量集中式爆发:主流媒体加短视频平台,散户主导,高转化率1%-2%,客单价3-5万人民币,结果就是系统性题材炒作、涨停板潮。美股是流量分散式传播:机构主导,低转化率0.1%-0.5%,机构单笔金额更大但是决策更慢,结果就是即使有流量事件,也很难形成系统性炒作。币圈是流量全球性传播:社交媒体加通讯软件,赌徒心态,超高的转化率2%-5%,客单价高度两极分化,结果就是Meme币满天飞,动辄百倍千倍。你看,同样的流量模型,在不同市场结构下,结果完全不同,但是核心逻辑不变:流量在哪里,资金就在哪里。只是流量的形式、转化率、客单价不一样。理解了这个差异之后,你就知道:在A股和币圈,流量为王。主流媒体报道、短视频热搜、知名人物推特,就是你的交易信号。在美股,主要靠产业逻辑,光有流量不够,还得有基本面支撑。游戏驿站、AMC这种纯Meme股,参与要谨慎,因为机构随时会做空阻击。如果你想用流量模型炒股,A股和币圈就是最好的战场。美股更适合做产业研究、基本面分析,流量只是锦上添花。增量流量型能不能炒起来,关键看一个指标:K值。这是互联网增长黑客里面的概念,叫做病毒系数,意思是平均每个用户能够带来几个新用户。K值大于1.5,是指数型爆发,停不下来;K值等于1左右,是线性增长,温吞水;K值小于1,是自然死亡,没救了。套到炒作市场,就是一个参与者平均能带来几个新“韭菜”。比特币的成功,老币圈在传播上对它的叙事改造、降低理解门槛,其实就占了很大一部分。什么数字黄金、长期持有、通往财务自由之路……如果没有这些东西,你去给人家讲什么UTXO、哈希、私钥、公钥,可能它依然是极客圈子里的小众玩具。买了比特币的人,会主动跟所有人讲比特币:朋友聚会讲,家庭聚餐讲,甚至跟出租车司机都要“传教”一番。这就是免费广告。那些区块链技术项目呢?你好意思跟朋友说吗?说了朋友也不感冒,传播不出去。而且比特币戳中了什么情绪?反法币通胀、反政府滥发货币、反华尔街金融霸权——“我们要用去中心化货币对抗美联储印钞机”。这个叙事有感染力,能让人产生共鸣,尤其是08年金融危机之后。你看,这三个条件一满足,K值自然就高。那怎么快速判断一个题材或者Meme的K值呢?问你自己两个问题:我会不会把这个事情转发到群里?我朋友看了会不会也想买?如果两个答案是“会”,那K值非常高,可以等着裂变。如果有一个答案“不是”,K值不够,别碰。但是K值只能判断增量流量型。存量流量型怎么办?靠“眼缘”判断。什么叫“眼缘”判断?就是你一眼就能看出来这个东西的流量天花板有多高。我给你列几个维度:首先看政策事件的级别。东大的政策是分层级的,流量天花板完全不一样。中央级事件,像雄安新区这种,主流媒体报道,所有媒体跟进,社交网络刷屏,流量天花板是全国级,十亿人次曝光。这种有机会成为一段时间的炒作主线。部委级政策,像之前海关总署限定稀土出口这种,流量天花板就是行业级,专业媒体加财经圈,大概几千万人次。这种基本上快速炒几个涨停就拉倒。地方性政策,就是某个省或者市的产业规划,流量天花板是地方级,基本上出不了圈,几百万人次。这种别碰,炒不起来。其次,看热点是全球性热点还是区域性热点。ChatGPT这种就是全球的,所有人都在讨论,而且作为一个技术起点,后续必然还会有源源不断的新技术引爆流量。所以那个时候,任何概念股的炒作都有确定性,可以等待后续一轮接一轮的流量热点。而东大即使是中央层面的特定政策,出不了国门,流量就小一个数量级。再看KOL的流量级别。币圈看KOL更明显:马斯克、某前国际政要,这种是十亿级别的全球媒体关注流量,他们发一条推,可能某个币立刻起飞。币安创始人、以太坊创始人,是覆盖千万级别的币圈用户,有影响力,但是覆盖面比马斯克小一个数量级。中腰部KOL几十万粉丝,只能做局部行情,撑不起大盘。你看,流量是分级的。顶级流量和二级流量,接盘资金可能差十倍、一百倍。我教你一个一眼判断法:看到一个题材或者币,立刻问自己:这件事情能够上主流新闻吗?能上就是全国性流量,深度求索就上了。这个事情马斯克会提吗?会提就是全球性流量,狗狗币就是。这件事情最终会传到街头大妈那里去吗?会的话就是顶流。这个事情只有圈内人知道吗?是的话就出不了圈,不要碰。好,流量说完了,咱们再说转化率。转化率就是“傻子浓度”:看到这个消息的人里面,有多少真的会冲进去买。不同市场、不同题材的“傻子浓度”是不一样的。币圈和A股哪个市场浓度更高?这个我说不准,但是题材之间还是很明显的。比如这些年A股就特别愿意为民族主义叙事买单,所以深度求索、光刻机概念就很容易炒起来。那么你就应该记住:民族主义叙事的“傻子浓度”特别高,相关题材更值得参与博弈。客单价,就是散户平均一个账户能投多少钱。A股散户平均账户大概3到5万人民币。币圈分化严重,平均下来可能也有500到1000美元。这两个数乘起来,就是单个用户的接盘金额。但是光有这三个变量还不够,你还得加两个修正系数。首先是板块的总盘子。你算出来会有10亿资金进场,最终能打到什么高度,需要看板块总盘子有多大。而且最好是盘子适中:盘子太小意味着大资金、大游资没办法深度参与;盘子太大拉不起来。盘子适中才有机会出大行情。再就是竞品分流效应。如果市场同时有几个热点在炒呢?“傻子”的注意力是稀缺资源。如果A股同时有5个题材在抢流量,每个平均分到20%。如果你的故事最性感,可能拿到30%;如果只是跟风,可能只有10%。好,现在我把完整的公式写出来:题材炒作高度等于流量规模乘以转化率乘以客单价,除以板块总盘子,乘以竞品分流折扣。听起来复杂,但实战中你不需要精确计算,只要粗略估算就行了。差一两倍,完全不影响投机。比如某前国际政要发币,最终流通市值150亿美元,你即使估错了,只估到50亿美元,能够在几亿阶段上车,也有十倍空间。这一部分的关键,是判断题材值不值得参与,过滤一切一眼看天花板就很低的噪音题材,只专注于天花板足够高的大题材。这就是第一部分:流量公式算天花板。记住一句话:投机市场就是流量生意。通过估算流量乘以转化率乘以客单价,你就知道天花板大致有多高。根据天花板判断值不值得参与只是第一步。更关键的是,你得知道什么时候跑。我告诉你一个铁律:价格高潮跟流量高潮基本上是同步发生的。为什么?因为这种炒作本质上就是庞氏、击鼓传花,后面的人给前面的人抬轿。一旦没有足够的新增流量,游戏就结束了。这里面有一个正反馈循环:价格拉升吸引眼球,制造流量。一个股票开始涨,涨30%,小圈子知道;涨一倍,行业媒体报道;涨三五倍,短视频热搜、微博热点;涨十倍,主流媒体都来关注了。流量爆发,“傻子”蜂拥,推高价格。热搜一上,散户FOMO情绪爆棚。这波人的资金体量最大,因为基数大,但是也是最后一波,进来就是山顶。流量见顶,资金断流,亏钱效应,死亡螺旋。热搜掉下来,就没有新“韭菜”了。早进来的人开始止盈,晚进来的人套在山顶,割肉出逃。价格跌破重要支撑,技术派也开始跑。最后就是负反馈循环,绵绵阴跌。流量高潮等于价格高潮,等于逃命时刻。绝大多数散户恰恰是在这个时候冲进去的,接最后一棒。关键问题来了:你又不是职业机构,没有全网流量监测系统,你怎么去判断流量是不是见顶了?专业炒家就是掏空自己去感知市场情绪的变化。我这里也可以教你一些简单粗暴的指标。A股指标:第一,看短视频热搜排名和评论增速。不光看排名,更要看评论数的环比增速。环比快速增长,意味着正在快速爆发;环比增长速度放缓,就要小心,随时准备跑路了。一旦增速下降,就要无脑出货。第二,看投资社区股吧的发帖频率。去龙头标的股吧,看每分钟新帖的数量。还有一个更直接的,看新用户占比。高潮期新“韭菜”在涌入,见顶期都是老“韭菜”互相壮胆,没有新增流量了。第三,看主流媒体这种媒体的报道。第一天报道是利好,持续深入报道是高潮,不再报道、凉了,准备跑。媒体的注意力也是有限的,他们不报道说明热度过了。第四,看龙虎榜的变化。早期著名游资席位在买,聪明钱在建仓;中期有进有出混战;晚期游资全在卖,接盘的都是散户营业部。聪明钱跑了你还不跑?币圈指标:第一,看社交媒体趋势持续时间和顶级KOL的转发时间轴。币安创始人、以太坊创始人这种顶级KOL转发就还早;中腰部KOL疯狂转发是中段;你各种群里面的群友都在问,那就该跑了。第二,看链上数据的增速拐点。看新增持币地址数的日环比增速:增速加快是好事,增速放缓危险。还有看大额转账的方向:大量从交易所提币到钱包,是囤货看涨;大量从钱包转回交易所,是准备砸盘,危险。第三,看电报、Discord群的活跃度,消息发送频率是不是在放缓。第四,看中心化交易所的挂单深度。如果买盘深度越来越浅,卖盘越来越厚,没人接盘了,那么就该跑。当然,以上这些流量监测手段,纯属我根据经验想出来的,因为我自己不炒这个。但是根据一些我自己的关键词热度数据复盘过一些炒作,发现一致性非常高,甚至比特币当年也符合这一点。所以我想这就是投机市场的共同特征。你如果想在这种九死一生的市场里面有所成就,那么该花钱买数据就去买数据,该自己下苦功夫建立流量监测模型就要下这个苦功夫。否则就不要去投机。钱哪有那么好赚?任何一门金融功夫,无论是正派还是“邪修”,你不吃一番苦是不可能有所成的。最后讲一条简单粗暴、准确率极高的经验:你要有自己的反向指标。什么叫反向指标?就是那些标志着“傻子浓度”最高的圈层已经进场的信号。一旦这个圈层关注或者进场,肯定完蛋。为什么?因为流量已经扩散到“傻子浓度”最高的圈层了,后面没有更傻的“傻子”了。这个是有历史依据的:1929年大崩盘之前,华尔街大亨约瑟夫·肯尼迪去擦鞋,擦鞋童一边擦一边跟他讲股票,给他推荐买什么。肯尼迪当场就意识到:完了,连擦鞋童都在炒股了。立刻清仓所有股票。几个月后大崩盘,无数人跳楼,肯尼迪全身而退。你要比别人更早意识到这个信号。当全世界都知道这个故事的时候,就是没有退路的时候。这就是第二部分:流量高潮看时机。记住一句话:在投机市场,流量就是你的退路。前面两部分解决了“能炒多高”和“什么时候跑”的问题。现在还剩最后一个问题:该买哪个标的?我先说一个内行看来是常识、但是外行可能会觉得颠覆性的认知:龙头不一定是因果关系最纯正的,而是辨识度最高的,或者说最容易被想起、被发现的。逻辑最硬、基本面最好的不一定是龙头,抢到眼球的才是龙头。因为这种炒作本质上就是流量生意。散户不看基本面,只看涨幅板和热搜。标的选择上面有四大选择法则。法则一:先涨为王。先涨的标的占据心智资源,后来者再正宗也没用。为什么?因为散户每天打开软件,第一件事情就是看涨幅板。谁涨得最猛,散户就点进去看。涨停板列表就是流量入口。散户只有这么多注意力,谁先抓住谁就赢了。法则二:名字最重要。互联网思维就是:每多一步操作,流失50%用户。投机市场也是一样。散户看到热搜,搜索标的,找到代码,下单买入。名字直接相关的,中间少一个环节,转化率就高一倍。而且名字有流量垄断效应。你的名字如果天然就是搜索关键词,你就赢在了起跑线上面了。币圈更夸张,Meme币的名字几乎就是一切。法则三:龙头的分化规律。龙头吃肉,龙二喝汤,龙三舔碗,后面的饿死。但不同市场的分化程度不一样。A股有一个特殊的机制:涨停板。一个题材炒起来,涨停板列表上面可能同时出现10只股票。散户一看:哇这么多涨停的,随便买一个吧。A股的龙二、龙三跟龙一的差距没有那么夸张。比如深度求索概念,可能同时有10只AI股涨停。虽然龙一涨得最多,但是龙二、龙三也能吃到肉,因为涨停板列表给了它们曝光的机会。但有个关键点:龙二、龙三能吃多少,取决于它们在涨停板列表上面能够停留多久。如果第二天就炸板了,流量立刻归零。币圈没有涨停板,链上DEX涨幅板能显示一大堆,而且是实时变动的。龙一吃99%,龙二只能捡点残渣。为什么差距这么大?因为币圈的流量高度集中,散户只盯着涨幅板第一名,第二名根本没人看。而且币圈还有一个恶性循环:龙一涨得猛,吸引更多眼球,更多人买,涨得更猛;龙二涨得慢,没人关注,没人买,彻底凉凉。马太效应在币圈被放大到极致。但是有一个例外:如果“傻子”真的太多了。如果一个题材足够火,火到“傻子”溢出来了,龙二、龙三就有机会。判断标准是:当龙一的流动性已经撑不住新增资金的时候,“傻子”就会分流到龙二。这就是溢出效应。龙一的盘子太小,或者涨得太快,“傻子”的钱没地方去,就只能往龙二、龙三里面塞。币圈也一样。如果行情足够大,不同的头部交易所也会考虑刻意去造不同的龙头,因为交易所他们自己也有流量。比如币安上了龙一,把龙一的用户流量拿到了。这个时候OKX再上龙一,他就拿不到多少用户流量了。那么他可能宁愿上龙二。这种情况下,龙一和龙二的涨幅差距就不会太大。法则四:KOL的造龙权。谁掌握流量入口,谁就能够定义什么是龙头。经典案例就是徐翔时代的宁波涨停板敢死队。他们不是等市场选出龙头来,而是自己做一个龙头出来:提前建仓,拉涨停,吸引眼球,找水军造势,把这支票包装成龙头,散户跟风抬轿子,高位出货。现在的KOL也是一样,只是换了个平台:从股吧、QQ群换成了短视频、视频号、小红书。币圈这边,马斯克发一条推,狗狗币能够涨50%,这就是流量的权力。但是更狠的是那些二线KOL。他们没有马斯克那么大的影响力,但是可以定点爆破。套路是:提前买入一些小市值的Meme币,发推说这个Meme有潜力,粉丝冲进去,币价翻倍,KOL出货。而且这些KOL也可能是互相配合的。现在短视频直播时代,这一点更加明显了。行情火热的时候,那些“头股老师”、“大V老师”直播间里面万人在线,只要荐股,可能几千人同时去下单这支股票,立刻涨停。这已经不是分析了,直接就是操纵。而且这里面可能还很难被监管。比如一个财经博主的MCN公司,能够直接影响到上万人决策,那么能够影响的资金可能是几亿甚至几十亿。它完全可以让全公司一起推某个小盘股,一推一个涨停。你如果是个小白,看到这个情况,是不是当场就激动了,然后就交了几万块钱,进了收费荐股群?人家也没有做老鼠仓,可能还有投顾牌照,你最后亏死也拿它没有办法。所以说,流量话语权就是硬通货,这就是赤裸裸的真相。所以这年头,如果你是一个一无所有的年轻人,想要赚大钱、赚快钱,知道应该去哪些产业了吧?我不是在教你学坏,但是“韭菜”是可怜之人必有可恨之处,谁割不是割,对吧?我不割是没有必要冒任何法律风险。但是如果你想刀口舔血,除了拿自己的钱去这条投机之路上修炼,不要忘了还可以用杠铃策略:做一门副业,赚流量、做KOL。这就是第三部分:标的选择就是眼球竞争。记住一句话:先涨为王,名字为王,谁抢到眼球,谁就是龙头。好,咱们把整个逻辑串一遍。第一层是流量公式算天花板:接盘总量等于流量乘以转化率乘以客单价。先判断是存量流量还是增量流量。存量型看KOL级别、政策级别,一眼判断流量天花板;增量型看K值能不能病毒传播。算出接盘资金总量,匹配盘子大小,基本上就知道大致能够炒多高。第二层是流量高潮看时机:价格高潮等于流量高潮等于逃命时刻。存量型几天决战,看热搜排名、主流媒体报道、龙虎榜,手快有手慢无;增量型等裂变,看增速拐点,第一波小高潮继续拿,第二波大高潮准备跑,第三波出圈高潮立刻跑。第三层是眼球竞争选标的:先涨为王,名字为王,KOL造龙。不要迷信逻辑最硬的,要买最容易被发现的。涨停板列表、涨幅板就是流量入口,谁先上板谁就是龙头。最后执行上面,你应该勤复盘,去找出过去几年所有炒作的历史数据,对着时间节点,按照我前面说的逻辑挨个复盘,看看持续性、看看市值空间、看看当时市场的情绪,然后把这一套内化成肌肉记忆,才有可能有所成就。我之前做过一条视频讲技术分析的,评论区很多抬杠的。最经典的一类抬杠就是说:游资难道不看技术?那些人都赚了这么多钱了,你怎么还敢说技术分析是巫术?我恰恰碰巧真认识几个游资,很幸运在很早的时候就知道了这套游资短线炒作的逻辑,也很早意识到自己并不适合这条道路,没有在这上面浪费青春。很多散户以为,游资炒短线靠的是技术分析、预测短期波动算命,那纯粹就是“以为皇上种地用金锄头,东宫娘娘烙大饼,西宫娘娘卷大葱”。无论是币圈的“喊单”,还是A股的游资,核心做的事情都是在用互联网思维对热点定价。这个东西玩多了就是个经验。很多时候消息一出来,你就知道龙头大概能干到多少市值,成交量最大能干到多大。剩下的无非就是抛弃杂毛,死都要死在龙头上,以及克服恐高心理、参与博弈的胆量。听起来很简单对不对?我今天讲的这一套:识别什么级别的消息能够触发什么级别的资金,不同市场环境下面龙头的合理市值区间,情绪周期的阶段划分——从启动到加速,到分歧到一致,最后退潮……这些东西都好学。但问题是,这些东西只占三成。学会怎么看,不等于你能够干。真正决定上限的是三种天生的能力。第一是对极端不确定性的耐受度。龙头连续涨停,账面翻倍,你敢不敢继续拿?很多人理论上懂龙头,但是仓位一上去,身体就先投降了。我怀疑这都没办法通过认知解决。你的肾上腺素、你的皮质醇分泌,可能就让你不适合做这个。第二是对错过的免疫力。高手对错过机会基本上没有情绪波动。而大部分人会在错过之后急于扳回,然后在下一个不确定性的标的上面重仓爆亏。第三是把博弈当成职业,而不是自我证明。游资是在执行概率,而不是证明自己聪明。但是很多聪明人没有办法接受“我做得对,但是亏了”,需要从交易中获得“我很牛逼”的情感回馈。一旦有了这个需求,交易就变形了:你会为了证明自己要死扛,为了挽回面子而加仓,这些都是死因。所以你看,今天讲的这套流量定价的框架,只是入门票。它能让你看懂游戏规则,但不能让你成为赢家。真正决定你能走多远的,是你的神经系统、你的情绪结构、你的自我认知模式。这些东西可能在你青少年时代就基本上定型了,后天很难改。我的建议很简单:如果你发现自己没有这三种天赋,那么投机市场迟早亏钱,我建议你还是踏踏实实投资。如果你认为自己有这三种天赋,那么我这条视频应该能让你少走好多年弯路。但是你要知道,盈亏同源。这条道路想成功,依然需要运气。如果你没有做好面对运气不好的思想准备,这条路依然不适合你。如果你毅然决然踏上这条道路,那么我祝你好运。以上就是本期视频的全部内容,我们下期再见。
\ No newline at end of file
diff --git a/新闻监测API调用说明.md b/新闻监测API调用说明.md
new file mode 100644
index 0000000..d95b514
--- /dev/null
+++ b/新闻监测API调用说明.md
@@ -0,0 +1,666 @@
+# 每日热点新闻 API - 调用说明文档
+
+## 📋 目录
+- [基础信息](#基础信息)
+- [快速开始](#快速开始)
+- [接口说明](#接口说明)
+- [支持的平台](#支持的平台)
+- [调用示例](#调用示例)
+- [错误处理](#错误处理)
+- [注意事项](#注意事项)
+- [常见问题](#常见问题)
+
+---
+
+## 基础信息
+
+### API 地址
+```
+生产环境:https://newsapi.ws4.cn/api/v1/dailynews/
+```
+
+### 接口说明
+获取各大平台的热点新闻数据,包括微博热搜、百度热搜、知乎热榜等20+个平台。
+
+### 特性
+- ✅ 支持多种主流平台
+- ✅ 实时更新数据
+- ✅ 无需认证即可使用
+- ✅ JSON 格式返回
+- ✅ 支持跨平台聚合查询
+
+---
+
+## 快速开始
+
+### 最简单的调用
+```bash
+curl "https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu"
+```
+
+### Python 快速示例
+```python
+import requests
+
+response = requests.get("https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu")
+data = response.json()
+
+print(f"获取到 {len(data['data'])} 条新闻")
+```
+
+---
+
+## 接口说明
+
+### 请求信息
+- **方法**: GET
+- **Content-Type**: application/json
+- **编码**: UTF-8
+
+### 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 | 示例 |
+|--------|------|------|------|------|
+| platform | String | 是 | 平台代码,支持多个平台用逗号分隔 | `baidu` 或 `baidu,weibo,zhihu` |
+
+### 返回格式
+
+#### 成功响应
+```json
+{
+ "status": "200",
+ "data": [
+ {
+ "title": "新闻标题",
+ "url": "新闻链接",
+ "content": "新闻内容或描述",
+ "source": "平台名称",
+ "publish_time": "2026-01-24 11:52:52"
+ }
+ ],
+ "msg": "success"
+}
+```
+
+#### 字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| status | String | 状态码,200 表示成功 |
+| data | Array | 新闻数据列表 |
+| data[].title | String | 新闻标题 |
+| data[].url | String | 新闻链接 |
+| data[].content | String | 新闻内容或描述 |
+| data[].source | String | 平台来源 |
+| data[].publish_time | String | 发布时间 |
+| msg | String | 响应消息 |
+
+---
+
+## 支持的平台
+
+| 序号 | 平台名称 | 平台代码 | 说明 |
+|------|----------|----------|------|
+| 1 | 百度热搜 | `baidu` | 社会热点、娱乐、事件 |
+| 2 | 少数派 | `sspai` | 科技、数码、生活方式 |
+| 3 | 微博热搜 | `weibo` | 社交媒体热点 |
+| 4 | 知乎热榜 | `zhihu` | 问答、深度内容 |
+| 5 | 36氪 | `tskr` | 科技创业、商业资讯 |
+| 6 | 吾爱破解 | `ftpojie` | 技术、软件、安全 |
+| 7 | 哔哩哔哩 | `bilibili` | 视频、动漫、游戏 |
+| 8 | 豆瓣 | `douban` | 书影音、文化 |
+| 9 | 虎扑 | `hupu` | 体育、游戏 |
+| 10 | 百度贴吧 | `tieba` | 兴趣社区 |
+| 11 | 掘金 | `juejin` | 编程、技术 |
+| 12 | 抖音 | `douyin` | 短视频热点 |
+| 13 | V2EX | `v2ex` | 技术、编程 |
+| 14 | 今日头条 | `jinritoutiao` | 新闻热点 |
+| 15 | Stack Overflow | `stackoverflow` | 编程问答 |
+| 16 | GitHub Trending | `github` | 开源项目 |
+| 17 | Hacker News | `hackernews` | 科技新闻 |
+| 18 | 新浪财经 | `sina_finance` | 财经新闻 |
+| 19 | 东方财富 | `eastmoney` | 财经资讯 |
+| 20 | 雪球 | `xueqiu` | 股票投资 |
+| 21 | 财联社 | `cls` | 财经快讯 |
+| 22 | 腾讯网 | `tenxunwang` | 综合新闻 |
+
+---
+
+## 调用示例
+
+### 1. cURL
+
+#### 获取单个平台
+```bash
+curl "https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu"
+```
+
+#### 获取多个平台
+```bash
+curl "https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu,weibo,zhihu"
+```
+
+#### 完整示例
+```bash
+curl -X GET "https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu" \
+ -H "Content-Type: application/json"
+```
+
+---
+
+### 2. Python
+
+#### 使用 requests 库
+```python
+import requests
+
+def get_news(platform="baidu"):
+ url = "https://newsapi.ws4.cn/api/v1/dailynews/"
+ params = {"platform": platform}
+
+ try:
+ response = requests.get(url, params=params)
+ data = response.json()
+
+ if data['status'] == '200':
+ print(f"获取到 {len(data['data'])} 条新闻")
+ return data['data']
+ else:
+ print(f"请求失败: {data['msg']}")
+ return []
+ except Exception as e:
+ print(f"发生错误: {e}")
+ return []
+
+# 使用示例
+news_list = get_news("baidu")
+for news in news_list[:5]:
+ print(f"标题: {news['title']}")
+ print(f"链接: {news['url']}")
+ print(f"时间: {news['publish_time']}\n")
+```
+
+#### 获取多个平台
+```python
+def get_multiple_news(platforms):
+ platforms_str = ",".join(platforms)
+ url = f"https://newsapi.ws4.cn/api/v1/dailynews/?platform={platforms_str}"
+
+ response = requests.get(url)
+ return response.json()
+
+# 使用示例
+data = get_multiple_news(["baidu", "weibo", "zhihu"])
+print(f"总共获取到 {len(data['data'])} 条新闻")
+```
+
+#### 异步请求(多个平台)
+```python
+import asyncio
+import aiohttp
+
+async def fetch_news(session, platform):
+ url = "https://newsapi.ws4.cn/api/v1/dailynews/"
+ async with session.get(url, params={"platform": platform}) as response:
+ return await response.json()
+
+async def get_news_async(platforms):
+ async with aiohttp.ClientSession() as session:
+ tasks = [fetch_news(session, p) for p in platforms]
+ results = await asyncio.gather(*tasks)
+ return results
+
+# 使用示例
+import asyncio
+platforms = ["baidu", "weibo", "zhihu"]
+results = asyncio.run(get_news_async(platforms))
+
+for i, result in enumerate(results):
+ print(f"{platforms[i]}: {len(result['data'])} 条新闻")
+```
+
+---
+
+### 3. JavaScript / Node.js
+
+#### 使用 fetch(浏览器或现代 Node.js)
+```javascript
+async function getNews(platform = 'baidu') {
+ const url = `https://newsapi.ws4.cn/api/v1/dailynews/?platform=${platform}`;
+
+ try {
+ const response = await fetch(url);
+ const data = await response.json();
+
+ if (data.status === '200') {
+ console.log(`获取到 ${data.data.length} 条新闻`);
+ return data.data;
+ } else {
+ console.log('请求失败:', data.msg);
+ return [];
+ }
+ } catch (error) {
+ console.log('发生错误:', error);
+ return [];
+ }
+}
+
+// 使用示例
+getNews('baidu').then(newsList => {
+ newsList.slice(0, 5).forEach(news => {
+ console.log(`标题: ${news.title}`);
+ console.log(`链接: ${news.url}\n`);
+ });
+});
+```
+
+#### 使用 axios
+```javascript
+const axios = require('axios');
+
+async function getNews(platform) {
+ try {
+ const response = await axios.get('https://newsapi.ws4.cn/api/v1/dailynews/', {
+ params: { platform }
+ });
+
+ console.log(`获取到 ${response.data.data.length} 条新闻`);
+ return response.data.data;
+ } catch (error) {
+ console.error('发生错误:', error);
+ return [];
+ }
+}
+
+// 使用示例
+getNews('baidu').then(newsList => {
+ newsList.forEach((news, index) => {
+ console.log(`${index + 1}. ${news.title}`);
+ });
+});
+```
+
+#### Node.js 使用 https 模块
+```javascript
+const https = require('https');
+
+function getNews(platform, callback) {
+ const url = `https://newsapi.ws4.cn/api/v1/dailynews/?platform=${platform}`;
+
+ https.get(url, (res) => {
+ let data = '';
+
+ res.on('data', (chunk) => {
+ data += chunk;
+ });
+
+ res.on('end', () => {
+ try {
+ const result = JSON.parse(data);
+ callback(null, result);
+ } catch (error) {
+ callback(error, null);
+ }
+ });
+ }).on('error', (error) => {
+ callback(error, null);
+ });
+}
+
+// 使用示例
+getNews('baidu', (error, result) => {
+ if (error) {
+ console.error('请求失败:', error);
+ return;
+ }
+
+ console.log(`获取到 ${result.data.length} 条新闻`);
+});
+```
+
+---
+
+### 4. Java
+
+#### 使用 HttpURLConnection
+```java
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+public class NewsApiClient {
+
+ public static String getNews(String platform) {
+ try {
+ String url = "https://newsapi.ws4.cn/api/v1/dailynews/?platform=" + platform;
+ URL urlObj = new URL(url);
+ HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();
+
+ conn.setRequestMethod("GET");
+ conn.setRequestProperty("Content-Type", "application/json");
+
+ int responseCode = conn.getResponseCode();
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(conn.getInputStream())
+ );
+
+ String inputLine;
+ StringBuilder response = new StringBuilder();
+
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ in.close();
+
+ return response.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static void main(String[] args) {
+ String jsonResponse = getNews("baidu");
+ System.out.println(jsonResponse);
+ }
+}
+```
+
+#### 使用 OkHttp
+```java
+import okhttp3.*;
+import java.io.IOException;
+
+public class NewsApi {
+
+ private static final OkHttpClient client = new OkHttpClient();
+
+ public static void getNews(String platform) throws IOException {
+ HttpUrl url = HttpUrl.parse("https://newsapi.ws4.cn/api/v1/dailynews/")
+ .newBuilder()
+ .addQueryParameter("platform", platform)
+ .build();
+
+ Request request = new Request.Builder()
+ .url(url)
+ .get()
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ String jsonData = response.body().string();
+ System.out.println(jsonData);
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ getNews("baidu");
+ }
+}
+```
+
+---
+
+### 5. Go
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+type News struct {
+ Title string `json:"title"`
+ URL string `json:"url"`
+ Content string `json:"content"`
+ Source string `json:"source"`
+ PublishTime string `json:"publish_time"`
+}
+
+type Response struct {
+ Status string `json:"status"`
+ Data []News `json:"data"`
+ Msg string `json:"msg"`
+}
+
+func getNews(platform string) (*Response, error) {
+ url := fmt.Sprintf("https://newsapi.ws4.cn/api/v1/dailynews/?platform=%s", platform)
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ var result Response
+ if err := json.Unmarshal(body, &result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func main() {
+ data, err := getNews("baidu")
+ if err != nil {
+ fmt.Println("Error:", err)
+ return
+ }
+
+ fmt.Printf("获取到 %d 条新闻\n", len(data.Data))
+ for i, news := range data.Data {
+ if i >= 5 {
+ break
+ }
+ fmt.Printf("%d. %s\n", i+1, news.Title)
+ }
+}
+```
+
+---
+
+### 6. PHP
+
+```php
+
+```
+
+---
+
+### 7. Ruby
+
+```ruby
+require 'net/http'
+require 'json'
+require 'uri'
+
+def get_news(platform = 'baidu')
+ uri = URI.parse("https://newsapi.ws4.cn/api/v1/dailynews/?platform=#{platform}")
+ response = Net::HTTP.get_response(uri)
+
+ if response.is_a?(Net::HTTPSuccess)
+ data = JSON.parse(response.body)
+ return data
+ else
+ puts "请求失败: #{response.code}"
+ return nil
+ end
+end
+
+# 使用示例
+result = get_news('baidu')
+
+if result && result['status'] == '200'
+ puts "获取到 #{result['data'].size} 条新闻"
+
+ result['data'].first(5).each_with_index do |news, index|
+ puts "#{index + 1}. #{news['title']}"
+ end
+end
+```
+
+---
+
+## 错误处理
+
+### 常见错误码
+
+| 状态码 | 说明 | 解决方案 |
+|--------|------|----------|
+| 200 | 成功 | 正常处理数据 |
+| 404 | 平台代码错误 | 检查平台代码是否正确 |
+| 500 | 服务器内部错误 | 稍后重试 |
+
+### 错误处理示例(Python)
+
+```python
+import requests
+import time
+
+def get_news_with_retry(platform, max_retries=3):
+ url = "https://newsapi.ws4.cn/api/v1/dailynews/"
+
+ for attempt in range(max_retries):
+ try:
+ response = requests.get(url, params={"platform": platform}, timeout=10)
+
+ if response.status_code == 200:
+ data = response.json()
+ if data.get('status') == '200':
+ return data.get('data', [])
+ else:
+ print(f"API返回错误: {data.get('msg')}")
+ return []
+ elif response.status_code == 404:
+ print(f"平台代码错误: {platform}")
+ return []
+ elif response.status_code == 500:
+ print(f"服务器错误,第 {attempt + 1} 次重试...")
+ time.sleep(2)
+ else:
+ print(f"未知错误: {response.status_code}")
+ return []
+
+ except requests.exceptions.Timeout:
+ print(f"请求超时,第 {attempt + 1} 次重试...")
+ time.sleep(2)
+ except requests.exceptions.ConnectionError:
+ print(f"连接错误,第 {attempt + 1} 次重试...")
+ time.sleep(2)
+ except Exception as e:
+ print(f"发生未知错误: {e}")
+ return []
+
+ print("达到最大重试次数,请求失败")
+ return []
+```
+
+---
+
+## 注意事项
+
+### 1. 数据时效性
+- 数据实时更新,建议不要频繁请求
+- 推荐缓存时间:5-10 分钟
+- 不同平台更新频率不同
+
+### 2. 请求频率限制
+- 目前没有明确的速率限制
+- 建议合理使用,避免过度请求
+- 生产环境建议加入请求队列
+
+### 3. 数据准确性
+- 数据仅供参考,不应作为新闻的主要来源
+- 建议与官方平台数据交叉验证
+
+### 4. 合法使用
+- API 仅供合法使用
+- 任何非法使用均不受支持
+- 用户需自行承担使用责任
+
+### 5. HTTPS 要求
+- 必须使用 HTTPS 协议
+- HTTP 请求可能会被拒绝
+
+---
+
+## 常见问题
+
+### Q1: 如何获取多个平台的数据?
+**A**: 使用逗号分隔多个平台代码
+```bash
+curl "https://newsapi.ws4.cn/api/v1/dailynews/?platform=baidu,weibo,zhihu"
+```
+
+### Q2: 数据多久更新一次?
+**A**: 不同平台更新频率不同,一般为 30 分钟到 2 小时不等。建议缓存数据,避免频繁请求。
+
+### Q3: 是否需要 API 密钥?
+**A**: 目前不需要,接口可以直接调用。
+
+### Q4: 支持哪些编程语言?
+**A**: 支持所有支持 HTTP 请求的编程语言,本文档提供了 Python、JavaScript、Java、Go、PHP、Ruby 等示例。
+
+### Q5: 接口限流吗?
+**A**: 目前没有明确的限流策略,但建议合理使用。
+
+### Q6: 如何处理中文编码?
+**A**: 接口返回的是 UTF-8 编码的 JSON,大多数现代语言都能自动处理。如果遇到乱码,请确保使用 UTF-8 解码。
+
+### Q7: 如何获取新闻的详细内容?
+**A**: API 只返回标题和链接,详细内容需要访问链接页面获取。
+
+### Q8: 数据可以商用吗?
+**A**: 数据来源于各公开平台,使用时请遵守各平台的使用协议和相关法律法规。
+
+---
+
+## 技术支持
+
+- **API 文档**: https://newsapi.ws4.cn/docs
+- **健康检查**: https://newsapi.ws4.cn/health
+
+---
+
+## 更新日志
+
+| 日期 | 版本 | 说明 |
+|------|------|------|
+| 2026-01-24 | 1.0.0 | 初始版本,支持 20+ 个平台 |
+
+---
+
+**免责声明**: 本 API 提供的信息仅供参考,使用者应从其他平台验证信息的准确性和时效性。