update pay ui
This commit is contained in:
118
app.py
118
app.py
@@ -12465,6 +12465,10 @@ def get_hotspot_overview():
|
|||||||
"""
|
"""
|
||||||
获取热点概览数据(用于个股中心的热点概览图表)
|
获取热点概览数据(用于个股中心的热点概览图表)
|
||||||
返回:指数分时数据 + 概念异动标注
|
返回:指数分时数据 + 概念异动标注
|
||||||
|
|
||||||
|
数据来源:
|
||||||
|
- 指数分时:ClickHouse index_minute 表
|
||||||
|
- 概念异动:MySQL concept_anomaly_hybrid 表(来自 realtime_detector.py)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
trade_date = request.args.get('date')
|
trade_date = request.args.get('date')
|
||||||
@@ -12532,61 +12536,100 @@ def get_hotspot_overview():
|
|||||||
'change_pct': change_pct
|
'change_pct': change_pct
|
||||||
})
|
})
|
||||||
|
|
||||||
# 2. 获取概念异动数据
|
# 2. 获取概念异动数据(从 concept_anomaly_hybrid 表)
|
||||||
alerts = []
|
alerts = []
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
|
# 查询 concept_anomaly_hybrid 表
|
||||||
alert_result = conn.execute(text("""
|
alert_result = conn.execute(text("""
|
||||||
SELECT
|
SELECT
|
||||||
concept_id, concept_name, alert_time, alert_type,
|
a.concept_id,
|
||||||
change_pct, change_delta, limit_up_count, limit_up_delta,
|
a.alert_time,
|
||||||
rank_position, index_price, index_change_pct,
|
a.trade_date,
|
||||||
stock_count, concept_type, extra_info,
|
a.alert_type,
|
||||||
prev_change_pct, zscore, importance_score
|
a.final_score,
|
||||||
FROM concept_minute_alert
|
a.rule_score,
|
||||||
WHERE trade_date = :trade_date
|
a.ml_score,
|
||||||
ORDER BY alert_time
|
a.trigger_reason,
|
||||||
|
a.alpha,
|
||||||
|
a.alpha_delta,
|
||||||
|
a.amt_ratio,
|
||||||
|
a.amt_delta,
|
||||||
|
a.rank_pct,
|
||||||
|
a.limit_up_ratio,
|
||||||
|
a.stock_count,
|
||||||
|
a.total_amt,
|
||||||
|
a.triggered_rules
|
||||||
|
FROM concept_anomaly_hybrid a
|
||||||
|
WHERE a.trade_date = :trade_date
|
||||||
|
ORDER BY a.alert_time
|
||||||
"""), {'trade_date': trade_date})
|
"""), {'trade_date': trade_date})
|
||||||
|
|
||||||
|
# 获取概念名称映射(从 ES 或缓存)
|
||||||
|
concept_names = {}
|
||||||
|
|
||||||
for row in alert_result:
|
for row in alert_result:
|
||||||
alert_time = row[2]
|
concept_id = row[0]
|
||||||
extra_info = None
|
alert_time = row[1]
|
||||||
if row[13]:
|
triggered_rules = None
|
||||||
|
if row[16]:
|
||||||
try:
|
try:
|
||||||
extra_info = json.loads(row[13]) if isinstance(row[13], str) else row[13]
|
triggered_rules = json.loads(row[16]) if isinstance(row[16], str) else row[16]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 从 extra_info 提取 zscore 和 importance_score(兼容旧数据)
|
# 获取概念名称(优先从缓存,否则使用 concept_id)
|
||||||
zscore = None
|
concept_name = concept_names.get(concept_id) or concept_id
|
||||||
importance_score = None
|
|
||||||
if len(row) > 15:
|
# 计算涨停数量(从 limit_up_ratio 和 stock_count 估算)
|
||||||
zscore = float(row[15]) if row[15] else None
|
limit_up_ratio = float(row[13]) if row[13] else 0
|
||||||
importance_score = float(row[16]) if row[16] else None
|
stock_count = int(row[14]) if row[14] else 0
|
||||||
if extra_info:
|
limit_up_count = int(limit_up_ratio * stock_count) if stock_count > 0 else 0
|
||||||
zscore = zscore or extra_info.get('zscore')
|
|
||||||
importance_score = importance_score or extra_info.get('importance_score')
|
|
||||||
|
|
||||||
alerts.append({
|
alerts.append({
|
||||||
'concept_id': row[0],
|
'concept_id': concept_id,
|
||||||
'concept_name': row[1],
|
'concept_name': concept_name,
|
||||||
'time': alert_time.strftime('%H:%M') if alert_time else None,
|
'time': alert_time.strftime('%H:%M') if alert_time else None,
|
||||||
'timestamp': alert_time.isoformat() if alert_time else None,
|
'timestamp': alert_time.isoformat() if alert_time else None,
|
||||||
'alert_type': row[3],
|
'alert_type': row[3],
|
||||||
'change_pct': float(row[4]) if row[4] else None,
|
'final_score': float(row[4]) if row[4] else None,
|
||||||
'change_delta': float(row[5]) if row[5] else None,
|
'rule_score': float(row[5]) if row[5] else None,
|
||||||
'limit_up_count': row[6],
|
'ml_score': float(row[6]) if row[6] else None,
|
||||||
'limit_up_delta': row[7],
|
'trigger_reason': row[7],
|
||||||
'rank_position': row[8],
|
'alpha': float(row[8]) if row[8] else None,
|
||||||
'index_price': float(row[9]) if row[9] else None,
|
'alpha_delta': float(row[9]) if row[9] else None,
|
||||||
'index_change_pct': float(row[10]) if row[10] else None,
|
'amt_ratio': float(row[10]) if row[10] else None,
|
||||||
'stock_count': row[11],
|
'amt_delta': float(row[11]) if row[11] else None,
|
||||||
'concept_type': row[12],
|
'rank_pct': float(row[12]) if row[12] else None,
|
||||||
'extra_info': extra_info,
|
'limit_up_ratio': limit_up_ratio,
|
||||||
'prev_change_pct': float(row[14]) if len(row) > 14 and row[14] else None,
|
'limit_up_count': limit_up_count,
|
||||||
'zscore': zscore,
|
'stock_count': stock_count,
|
||||||
'importance_score': importance_score
|
'total_amt': float(row[15]) if row[15] else None,
|
||||||
|
'triggered_rules': triggered_rules,
|
||||||
|
# 兼容旧字段
|
||||||
|
'importance_score': float(row[4]) / 100 if row[4] else None,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 尝试批量获取概念名称
|
||||||
|
if alerts:
|
||||||
|
concept_ids = list(set(a['concept_id'] for a in alerts))
|
||||||
|
try:
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
es_client = Elasticsearch(['http://127.0.0.1:9200'])
|
||||||
|
es_result = es_client.mget(
|
||||||
|
index='concept_library_v3',
|
||||||
|
body={'ids': concept_ids},
|
||||||
|
_source=['concept']
|
||||||
|
)
|
||||||
|
for doc in es_result.get('docs', []):
|
||||||
|
if doc.get('found') and doc.get('_source'):
|
||||||
|
concept_names[doc['_id']] = doc['_source'].get('concept', doc['_id'])
|
||||||
|
# 更新 alerts 中的概念名称
|
||||||
|
for alert in alerts:
|
||||||
|
if alert['concept_id'] in concept_names:
|
||||||
|
alert['concept_name'] = concept_names[alert['concept_id']]
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取概念名称失败: {e}")
|
||||||
|
|
||||||
# 计算统计信息
|
# 计算统计信息
|
||||||
day_high = max([d['price'] for d in index_timeline if d['price']], default=None)
|
day_high = max([d['price'] for d in index_timeline if d['price']], default=None)
|
||||||
day_low = min([d['price'] for d in index_timeline if d['price']], default=None)
|
day_low = min([d['price'] for d in index_timeline if d['price']], default=None)
|
||||||
@@ -12614,6 +12657,7 @@ def get_hotspot_overview():
|
|||||||
'surge_up': len([a for a in alerts if a['alert_type'] == 'surge_up']),
|
'surge_up': len([a for a in alerts if a['alert_type'] == 'surge_up']),
|
||||||
'surge_down': len([a for a in alerts if a['alert_type'] == 'surge_down']),
|
'surge_down': len([a for a in alerts if a['alert_type'] == 'surge_down']),
|
||||||
'limit_up': len([a for a in alerts if a['alert_type'] == 'limit_up']),
|
'limit_up': len([a for a in alerts if a['alert_type'] == 'limit_up']),
|
||||||
|
'volume_spike': len([a for a in alerts if a['alert_type'] == 'volume_spike']),
|
||||||
'rank_jump': len([a for a in alerts if a['alert_type'] == 'rank_jump'])
|
'rank_jump': len([a for a in alerts if a['alert_type'] == 'rank_jump'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user