update pay ui
This commit is contained in:
273
app.py
273
app.py
@@ -12458,6 +12458,279 @@ def get_daily_top_concepts():
|
||||
}), 500
|
||||
|
||||
|
||||
# ==================== 热点概览 API ====================
|
||||
|
||||
@app.route('/api/market/hotspot-overview', methods=['GET'])
|
||||
def get_hotspot_overview():
|
||||
"""
|
||||
获取热点概览数据(用于个股中心的热点概览图表)
|
||||
返回:指数分时数据 + 概念异动标注
|
||||
"""
|
||||
try:
|
||||
trade_date = request.args.get('date')
|
||||
index_code = request.args.get('index', '000001.SH')
|
||||
|
||||
# 如果没有指定日期,使用最新交易日
|
||||
if not trade_date:
|
||||
today = date.today()
|
||||
if today in trading_days_set:
|
||||
trade_date = today.strftime('%Y-%m-%d')
|
||||
else:
|
||||
target_date = get_trading_day_near_date(today)
|
||||
trade_date = target_date.strftime('%Y-%m-%d') if target_date else today.strftime('%Y-%m-%d')
|
||||
|
||||
# 1. 获取指数分时数据
|
||||
client = get_clickhouse_client()
|
||||
target_date_obj = datetime.strptime(trade_date, '%Y-%m-%d').date()
|
||||
|
||||
index_data = client.execute(
|
||||
"""
|
||||
SELECT timestamp, open, high, low, close, volume
|
||||
FROM index_minute
|
||||
WHERE code = %(code)s
|
||||
AND toDate(timestamp) = %(date)s
|
||||
ORDER BY timestamp
|
||||
""",
|
||||
{
|
||||
'code': index_code,
|
||||
'date': target_date_obj
|
||||
}
|
||||
)
|
||||
|
||||
# 获取昨收价
|
||||
code_no_suffix = index_code.split('.')[0]
|
||||
prev_close = None
|
||||
with engine.connect() as conn:
|
||||
prev_result = conn.execute(text("""
|
||||
SELECT F006N FROM ea_exchangetrade
|
||||
WHERE INDEXCODE = :code
|
||||
AND TRADEDATE < :today
|
||||
ORDER BY TRADEDATE DESC LIMIT 1
|
||||
"""), {
|
||||
'code': code_no_suffix,
|
||||
'today': target_date_obj
|
||||
}).fetchone()
|
||||
if prev_result and prev_result[0]:
|
||||
prev_close = float(prev_result[0])
|
||||
|
||||
# 格式化指数数据
|
||||
index_timeline = []
|
||||
for row in index_data:
|
||||
ts, open_p, high_p, low_p, close_p, vol = row
|
||||
change_pct = None
|
||||
if prev_close and close_p:
|
||||
change_pct = round((float(close_p) - prev_close) / prev_close * 100, 4)
|
||||
|
||||
index_timeline.append({
|
||||
'time': ts.strftime('%H:%M'),
|
||||
'timestamp': ts.isoformat(),
|
||||
'price': float(close_p) if close_p else None,
|
||||
'open': float(open_p) if open_p else None,
|
||||
'high': float(high_p) if high_p else None,
|
||||
'low': float(low_p) if low_p else None,
|
||||
'volume': int(vol) if vol else 0,
|
||||
'change_pct': change_pct
|
||||
})
|
||||
|
||||
# 2. 获取概念异动数据
|
||||
alerts = []
|
||||
with engine.connect() as conn:
|
||||
alert_result = conn.execute(text("""
|
||||
SELECT
|
||||
concept_id, concept_name, alert_time, alert_type,
|
||||
change_pct, change_delta, limit_up_count, limit_up_delta,
|
||||
rank_position, index_price, index_change_pct,
|
||||
stock_count, concept_type, extra_info,
|
||||
prev_change_pct, zscore, importance_score
|
||||
FROM concept_minute_alert
|
||||
WHERE trade_date = :trade_date
|
||||
ORDER BY alert_time
|
||||
"""), {'trade_date': trade_date})
|
||||
|
||||
for row in alert_result:
|
||||
alert_time = row[2]
|
||||
extra_info = None
|
||||
if row[13]:
|
||||
try:
|
||||
extra_info = json.loads(row[13]) if isinstance(row[13], str) else row[13]
|
||||
except:
|
||||
pass
|
||||
|
||||
# 从 extra_info 提取 zscore 和 importance_score(兼容旧数据)
|
||||
zscore = None
|
||||
importance_score = None
|
||||
if len(row) > 15:
|
||||
zscore = float(row[15]) if row[15] else None
|
||||
importance_score = float(row[16]) if row[16] else None
|
||||
if extra_info:
|
||||
zscore = zscore or extra_info.get('zscore')
|
||||
importance_score = importance_score or extra_info.get('importance_score')
|
||||
|
||||
alerts.append({
|
||||
'concept_id': row[0],
|
||||
'concept_name': row[1],
|
||||
'time': alert_time.strftime('%H:%M') if alert_time else None,
|
||||
'timestamp': alert_time.isoformat() if alert_time else None,
|
||||
'alert_type': row[3],
|
||||
'change_pct': float(row[4]) if row[4] else None,
|
||||
'change_delta': float(row[5]) if row[5] else None,
|
||||
'limit_up_count': row[6],
|
||||
'limit_up_delta': row[7],
|
||||
'rank_position': row[8],
|
||||
'index_price': float(row[9]) if row[9] else None,
|
||||
'index_change_pct': float(row[10]) if row[10] else None,
|
||||
'stock_count': row[11],
|
||||
'concept_type': row[12],
|
||||
'extra_info': extra_info,
|
||||
'prev_change_pct': float(row[14]) if len(row) > 14 and row[14] else None,
|
||||
'zscore': zscore,
|
||||
'importance_score': importance_score
|
||||
})
|
||||
|
||||
# 计算统计信息
|
||||
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)
|
||||
latest_price = index_timeline[-1]['price'] if index_timeline else None
|
||||
latest_change_pct = index_timeline[-1]['change_pct'] if index_timeline else None
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'trade_date': trade_date,
|
||||
'index': {
|
||||
'code': index_code,
|
||||
'name': '上证指数' if index_code == '000001.SH' else index_code,
|
||||
'prev_close': prev_close,
|
||||
'latest_price': latest_price,
|
||||
'change_pct': latest_change_pct,
|
||||
'high': day_high,
|
||||
'low': day_low,
|
||||
'timeline': index_timeline
|
||||
},
|
||||
'alerts': alerts,
|
||||
'alert_count': len(alerts),
|
||||
'alert_summary': {
|
||||
'surge': len([a for a in alerts if a['alert_type'] == 'surge']),
|
||||
'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']),
|
||||
'limit_up': len([a for a in alerts if a['alert_type'] == 'limit_up']),
|
||||
'rank_jump': len([a for a in alerts if a['alert_type'] == 'rank_jump'])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.error(f"获取热点概览数据失败: {traceback.format_exc()}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/market/concept-alerts', methods=['GET'])
|
||||
def get_concept_alerts():
|
||||
"""
|
||||
获取概念异动列表(支持分页和筛选)
|
||||
"""
|
||||
try:
|
||||
trade_date = request.args.get('date')
|
||||
alert_type = request.args.get('type') # surge/limit_up/rank_jump
|
||||
concept_type = request.args.get('concept_type') # leaf/lv1/lv2/lv3
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
offset = request.args.get('offset', 0, type=int)
|
||||
|
||||
# 构建查询条件
|
||||
conditions = []
|
||||
params = {'limit': limit, 'offset': offset}
|
||||
|
||||
if trade_date:
|
||||
conditions.append("trade_date = :trade_date")
|
||||
params['trade_date'] = trade_date
|
||||
else:
|
||||
conditions.append("trade_date = CURDATE()")
|
||||
|
||||
if alert_type:
|
||||
conditions.append("alert_type = :alert_type")
|
||||
params['alert_type'] = alert_type
|
||||
|
||||
if concept_type:
|
||||
conditions.append("concept_type = :concept_type")
|
||||
params['concept_type'] = concept_type
|
||||
|
||||
where_clause = " AND ".join(conditions) if conditions else "1=1"
|
||||
|
||||
with engine.connect() as conn:
|
||||
# 获取总数
|
||||
count_sql = text(f"SELECT COUNT(*) FROM concept_minute_alert WHERE {where_clause}")
|
||||
total = conn.execute(count_sql, params).scalar()
|
||||
|
||||
# 获取数据
|
||||
query_sql = text(f"""
|
||||
SELECT
|
||||
id, concept_id, concept_name, alert_time, alert_type, trade_date,
|
||||
change_pct, prev_change_pct, change_delta,
|
||||
limit_up_count, prev_limit_up_count, limit_up_delta,
|
||||
rank_position, prev_rank_position, rank_delta,
|
||||
index_price, index_change_pct,
|
||||
stock_count, concept_type, extra_info
|
||||
FROM concept_minute_alert
|
||||
WHERE {where_clause}
|
||||
ORDER BY alert_time DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
""")
|
||||
|
||||
result = conn.execute(query_sql, params)
|
||||
|
||||
alerts = []
|
||||
for row in result:
|
||||
extra_info = None
|
||||
if row[19]:
|
||||
try:
|
||||
extra_info = json.loads(row[19]) if isinstance(row[19], str) else row[19]
|
||||
except:
|
||||
pass
|
||||
|
||||
alerts.append({
|
||||
'id': row[0],
|
||||
'concept_id': row[1],
|
||||
'concept_name': row[2],
|
||||
'alert_time': row[3].isoformat() if row[3] else None,
|
||||
'alert_type': row[4],
|
||||
'trade_date': row[5].isoformat() if row[5] else None,
|
||||
'change_pct': float(row[6]) if row[6] else None,
|
||||
'prev_change_pct': float(row[7]) if row[7] else None,
|
||||
'change_delta': float(row[8]) if row[8] else None,
|
||||
'limit_up_count': row[9],
|
||||
'prev_limit_up_count': row[10],
|
||||
'limit_up_delta': row[11],
|
||||
'rank_position': row[12],
|
||||
'prev_rank_position': row[13],
|
||||
'rank_delta': row[14],
|
||||
'index_price': float(row[15]) if row[15] else None,
|
||||
'index_change_pct': float(row[16]) if row[16] else None,
|
||||
'stock_count': row[17],
|
||||
'concept_type': row[18],
|
||||
'extra_info': extra_info
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': alerts,
|
||||
'total': total,
|
||||
'limit': limit,
|
||||
'offset': offset
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.error(f"获取概念异动列表失败: {traceback.format_exc()}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/market/rise-analysis/<seccode>', methods=['GET'])
|
||||
def get_rise_analysis(seccode):
|
||||
"""获取股票涨幅分析数据(从 Elasticsearch 获取)"""
|
||||
|
||||
Reference in New Issue
Block a user