-
Notifications
You must be signed in to change notification settings - Fork 560
Expand file tree
/
Copy pathnews_flow_alert.py
More file actions
514 lines (438 loc) · 19.1 KB
/
Copy pathnews_flow_alert.py
File metadata and controls
514 lines (438 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
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}")