增加新闻流量监测板块
This commit is contained in:
@@ -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}")
|
||||
Reference in New Issue
Block a user