增加新闻流量监测板块
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
- 初心:在股市摸爬滚打多年,自学自编各种指标,花冤柉钱学习了各种战法各种策略,也曾入各种小班,总是赚少赔多,逐渐失去在股市玩的信心。自从去年deepseek上市,一直探索用ai辅助分析,且近日受tradingagents项目启发(感谢原作),多agent结合跟踪主力资金战法(某指每年收费6000rmb),用各种ai辅助编程,拼凑了这么个小程序,根据软件提供的辅助信息,实盘测试盈率还是挺高的,并且逐步形成了自己的交易系统,近一个月来,账户也慢慢在扰亏为盈。开源此软件的目的,就是为了使像我一样的小散,不再迷范。也许这个软件不能让你发大财,但是他能给你足够的信心。最后提醒:股市有风险,入市需谨慎!
|
- 初心:在股市摸爬滚打多年,自学自编各种指标,花冤柉钱学习了各种战法各种策略,也曾入各种小班,总是赚少赔多,逐渐失去在股市玩的信心。自从去年deepseek上市,一直探索用ai辅助分析,且近日受tradingagents项目启发(感谢原作),多agent结合跟踪主力资金战法(某指每年收费6000rmb),用各种ai辅助编程,拼凑了这么个小程序,根据软件提供的辅助信息,实盘测试盈率还是挺高的,并且逐步形成了自己的交易系统,近一个月来,账户也慢慢在扰亏为盈。开源此软件的目的,就是为了使像我一样的小散,不再迷范。也许这个软件不能让你发大财,但是他能给你足够的信心。最后提醒:股市有风险,入市需谨慎!
|
||||||
|
|
||||||
|
## ⭐20261.25第一更 - 新闻流量监测 📈
|
||||||
|
|
||||||
|
实时监测百度、微博、东财、财联社、抖音、B站等20个平台热点新闻,调用ai分析对A股板块及股票的影响,生成分析报告
|
||||||
|
|
||||||
## ⭐ 1214更新 - 净利增长策略 📈
|
## ⭐ 1214更新 - 净利增长策略 📈
|
||||||
|
|
||||||
新增“净利增长策略”选股板块,专注稳健成长股票:
|
新增“净利增长策略”选股板块,专注稳健成长股票:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from main_force_ui import display_main_force_selector
|
|||||||
from sector_strategy_ui import display_sector_strategy
|
from sector_strategy_ui import display_sector_strategy
|
||||||
from longhubang_ui import display_longhubang
|
from longhubang_ui import display_longhubang
|
||||||
from smart_monitor_ui import smart_monitor_ui
|
from smart_monitor_ui import smart_monitor_ui
|
||||||
|
from news_flow_ui import display_news_flow_monitor
|
||||||
|
|
||||||
# 页面配置
|
# 页面配置
|
||||||
st.set_page_config(
|
st.set_page_config(
|
||||||
@@ -294,7 +295,7 @@ def main():
|
|||||||
if st.button("🏠 股票分析", width='stretch', key="nav_home", help="返回首页,进行单只股票的深度分析"):
|
if st.button("🏠 股票分析", width='stretch', key="nav_home", help="返回首页,进行单只股票的深度分析"):
|
||||||
# 清除所有功能页面标志
|
# 清除所有功能页面标志
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
@@ -307,28 +308,28 @@ def main():
|
|||||||
if st.button("💰 主力选股", width='stretch', key="nav_main_force", help="基于主力资金流向的选股策略"):
|
if st.button("💰 主力选股", width='stretch', key="nav_main_force", help="基于主力资金流向的选股策略"):
|
||||||
st.session_state.show_main_force = True
|
st.session_state.show_main_force = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
if st.button("🐂 低价擒牛", width='stretch', key="nav_low_price_bull", help="低价高成长股票筛选策略"):
|
if st.button("🐂 低价擒牛", width='stretch', key="nav_low_price_bull", help="低价高成长股票筛选策略"):
|
||||||
st.session_state.show_low_price_bull = True
|
st.session_state.show_low_price_bull = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
if st.button("📊 小市值策略", width='stretch', key="nav_small_cap", help="小盘高成长股票筛选策略"):
|
if st.button("📊 小市值策略", width='stretch', key="nav_small_cap", help="小盘高成长股票筛选策略"):
|
||||||
st.session_state.show_small_cap = True
|
st.session_state.show_small_cap = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
if st.button("📈 净利增长", width='stretch', key="nav_profit_growth", help="净利润增长稳健股票筛选策略"):
|
if st.button("📈 净利增长", width='stretch', key="nav_profit_growth", help="净利润增长稳健股票筛选策略"):
|
||||||
st.session_state.show_profit_growth = True
|
st.session_state.show_profit_growth = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
@@ -339,14 +340,21 @@ def main():
|
|||||||
if st.button("🎯 智策板块", width='stretch', key="nav_sector_strategy", help="AI板块策略分析"):
|
if st.button("🎯 智策板块", width='stretch', key="nav_sector_strategy", help="AI板块策略分析"):
|
||||||
st.session_state.show_sector_strategy = True
|
st.session_state.show_sector_strategy = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
if st.button("🐉 智瞰龙虎", width='stretch', key="nav_longhubang", help="龙虎榜深度分析"):
|
if st.button("🐉 智瞰龙虎", width='stretch', key="nav_longhubang", help="龙虎榜深度分析"):
|
||||||
st.session_state.show_longhubang = True
|
st.session_state.show_longhubang = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
@@ -357,21 +365,21 @@ def main():
|
|||||||
if st.button("📊 持仓分析", width='stretch', key="nav_portfolio", help="投资组合分析与定时跟踪"):
|
if st.button("📊 持仓分析", width='stretch', key="nav_portfolio", help="投资组合分析与定时跟踪"):
|
||||||
st.session_state.show_portfolio = True
|
st.session_state.show_portfolio = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
if st.button("🤖 AI盯盘", width='stretch', key="nav_smart_monitor", help="DeepSeek AI自动盯盘决策交易(支持A股T+1)"):
|
if st.button("🤖 AI盯盘", width='stretch', key="nav_smart_monitor", help="DeepSeek AI自动盯盘决策交易(支持A股T+1)"):
|
||||||
st.session_state.show_smart_monitor = True
|
st.session_state.show_smart_monitor = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
if st.button("📡 实时监测", width='stretch', key="nav_monitor", help="价格监控与预警提醒"):
|
if st.button("📡 实时监测", width='stretch', key="nav_monitor", help="价格监控与预警提醒"):
|
||||||
st.session_state.show_monitor = True
|
st.session_state.show_monitor = True
|
||||||
for key in ['show_history', 'show_main_force', 'show_longhubang', 'show_portfolio',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
@@ -381,7 +389,7 @@ def main():
|
|||||||
if st.button("📖 历史记录", width='stretch', key="nav_history", help="查看历史分析记录"):
|
if st.button("📖 历史记录", width='stretch', key="nav_history", help="查看历史分析记录"):
|
||||||
st.session_state.show_history = True
|
st.session_state.show_history = True
|
||||||
for key in ['show_monitor', 'show_longhubang', 'show_portfolio', 'show_config',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
@@ -389,7 +397,7 @@ def main():
|
|||||||
if st.button("⚙️ 环境配置", width='stretch', key="nav_config", help="系统设置与API配置"):
|
if st.button("⚙️ 环境配置", width='stretch', key="nav_config", help="系统设置与API配置"):
|
||||||
st.session_state.show_config = True
|
st.session_state.show_config = True
|
||||||
for key in ['show_history', 'show_monitor', 'show_main_force', 'show_sector_strategy',
|
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:
|
if key in st.session_state:
|
||||||
del st.session_state[key]
|
del st.session_state[key]
|
||||||
|
|
||||||
@@ -521,6 +529,11 @@ def main():
|
|||||||
display_portfolio_manager()
|
display_portfolio_manager()
|
||||||
return
|
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:
|
if 'show_config' in st.session_state and st.session_state.show_config:
|
||||||
display_config_manager()
|
display_config_manager()
|
||||||
|
|||||||
@@ -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. **策略回测**
|
||||||
|
- 历史流量与股价涨跌相关性分析
|
||||||
|
- 优化流量阈值和交易信号
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**祝你交易顺利!** 📈💰
|
||||||
|
|
||||||
|
记住:"流量为王,见顶就跑!"
|
||||||
@@ -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
|
||||||
|
|
||||||
|
**祝你交易顺利!记住:流量为王,见顶就跑!** 📈💰
|
||||||
Binary file not shown.
@@ -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)}")
|
||||||
@@ -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}")
|
||||||
@@ -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']}平台)")
|
||||||
+1021
File diff suppressed because it is too large
Load Diff
@@ -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')}")
|
||||||
@@ -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']}")
|
||||||
@@ -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', '中')})<br/>
|
||||||
|
情绪指数:{sentiment_data.get('sentiment', {}).get('sentiment_index', 50)}/100<br/>
|
||||||
|
流量阶段:{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('<br/>'.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 = '<br/>'.join([f"• {f}" for f in risk_factors[:5]])
|
||||||
|
content.append(Paragraph(f"风险因素:<br/>{factors_text}", self.styles['ChineseBody']))
|
||||||
|
|
||||||
|
# 免责声明
|
||||||
|
disclaimer = """
|
||||||
|
【免责声明】
|
||||||
|
本报告由AI自动生成,仅供参考,不构成任何投资建议。
|
||||||
|
股市有风险,投资需谨慎。请投资者根据自身情况独立判断,
|
||||||
|
理性投资,自负盈亏。本报告作者及生成系统不对投资决策
|
||||||
|
产生的任何损失承担责任。
|
||||||
|
"""
|
||||||
|
content.append(Spacer(1, 20))
|
||||||
|
content.append(Paragraph(disclaimer, self.styles['ChineseWarning']))
|
||||||
|
|
||||||
|
return content
|
||||||
@@ -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测试完成")
|
||||||
@@ -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']}")
|
||||||
+1229
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -13,4 +13,5 @@ ta>=0.10.2
|
|||||||
reportlab>=4.0.0
|
reportlab>=4.0.0
|
||||||
peewee>=3.17.0
|
peewee>=3.17.0
|
||||||
schedule>=1.2.0
|
schedule>=1.2.0
|
||||||
pywencai>=0.7.0
|
pywencai>=0.7.0
|
||||||
|
jieba>=0.42.1
|
||||||
File diff suppressed because one or more lines are too long
+666
@@ -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
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function getNews($platform = 'baidu') {
|
||||||
|
$url = 'https://newsapi.ws4.cn/api/v1/dailynews/?platform=' . $platform;
|
||||||
|
|
||||||
|
$response = file_get_contents($url);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
$newsData = getNews('baidu');
|
||||||
|
|
||||||
|
if ($newsData && $newsData['status'] === '200') {
|
||||||
|
echo "获取到 " . count($newsData['data']) . " 条新闻\n";
|
||||||
|
|
||||||
|
foreach (array_slice($newsData['data'], 0, 5) as $news) {
|
||||||
|
echo "标题: " . $news['title'] . "\n";
|
||||||
|
echo "链接: " . $news['url'] . "\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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 提供的信息仅供参考,使用者应从其他平台验证信息的准确性和时效性。
|
||||||
Reference in New Issue
Block a user