From 72836fa5d440433619f6a25430102042e1dc39ee Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Mon, 15 Dec 2025 18:08:06 +0800 Subject: [PATCH] update pay ui --- app.py | 189 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 107 insertions(+), 82 deletions(-) diff --git a/app.py b/app.py index 0bbd23f3..7b420935 100755 --- a/app.py +++ b/app.py @@ -5514,7 +5514,7 @@ def remove_from_watchlist(stock_code): @app.route('/api/account/watchlist/realtime', methods=['GET']) def get_watchlist_realtime(): - """获取自选股实时行情数据(基于分钟线)""" + """获取自选股实时行情数据(基于分钟线)- 优化为批量查询""" try: if 'user_id' not in session: return jsonify({'success': False, 'error': '未登录'}), 401 @@ -5524,103 +5524,127 @@ def get_watchlist_realtime(): if not watchlist: return jsonify({'success': True, 'data': []}) - # 获取股票代码列表 - stock_codes = [] + # 获取股票代码列表并标准化 + code_mapping = {} # code6 -> full_code 映射 + full_codes = [] for item in watchlist: code6, _ = _normalize_stock_input(item.stock_code) - # 统一内部查询代码 normalized = code6 or str(item.stock_code).strip().upper() - stock_codes.append(normalized) - # 使用现有的分钟线接口获取最新行情 - client = get_clickhouse_client() - quotes_data = {} - - # 获取最新交易日 - today = datetime.now().date() - - # 获取每只股票的最新价格 - for code in stock_codes: - raw_code = str(code).strip().upper() - if '.' in raw_code: - stock_code_full = raw_code - elif raw_code.startswith('6'): - stock_code_full = f"{raw_code}.SH" # 上海 - elif raw_code.startswith(('8', '9', '4')): - stock_code_full = f"{raw_code}.BJ" # 北交所 + # 转换为带后缀的完整代码 + if '.' in normalized: + full_code = normalized + elif normalized.startswith('6'): + full_code = f"{normalized}.SH" + elif normalized.startswith(('8', '9', '4')): + full_code = f"{normalized}.BJ" else: - stock_code_full = f"{raw_code}.SZ" # 深圳 + full_code = f"{normalized}.SZ" - # 获取最新分钟线数据(先查近7天,若无数据再兜底倒序取最近一条) - query = """ - SELECT - close, timestamp, high, low, volume, amt - FROM stock_minute - WHERE code = %(code)s - AND timestamp >= %(start)s - ORDER BY timestamp DESC - LIMIT 1 \ - """ + code_mapping[normalized] = full_code + full_codes.append(full_code) - # 获取最近7天的分钟数据 - start_date = today - timedelta(days=7) + if not full_codes: + return jsonify({'success': True, 'data': []}) - result = client.execute(query, { - 'code': stock_code_full, - 'start': datetime.combine(start_date, dt_time(9, 30)) - }) + # 使用批量查询获取最新行情(单次查询) + client = get_clickhouse_client() + today = datetime.now().date() + start_date = today - timedelta(days=7) - # 若近7天无数据,兜底直接取最近一条 - if not result: - fallback_query = """ - SELECT - close, timestamp, high, low, volume, amt - FROM stock_minute - WHERE code = %(code)s - ORDER BY timestamp DESC - LIMIT 1 \ - """ - result = client.execute(fallback_query, {'code': stock_code_full}) + # 批量查询:获取每只股票的最新一条分钟数据 + batch_query = """ + WITH latest AS ( + SELECT + code, + close, + timestamp, + high, + low, + volume, + amt, + ROW_NUMBER() OVER (PARTITION BY code ORDER BY timestamp DESC) as rn + FROM stock_minute + WHERE code IN %(codes)s + AND timestamp >= %(start)s + ) + SELECT code, close, timestamp, high, low, volume, amt + FROM latest + WHERE rn = 1 + """ - if result: - latest_data = result[0] - latest_ts = latest_data[1] + result = client.execute(batch_query, { + 'codes': full_codes, + 'start': datetime.combine(start_date, dt_time(9, 30)) + }) - # 获取该bar所属交易日前一个交易日的收盘价 - prev_close_query = """ - SELECT close - FROM stock_minute - WHERE code = %(code)s - AND timestamp \ - < %(start)s - ORDER BY timestamp DESC - LIMIT 1 \ - """ + # 构建最新价格映射 + latest_data_map = {} + for row in result: + code, close, ts, high, low, volume, amt = row + latest_data_map[code] = { + 'close': float(close), + 'timestamp': ts, + 'high': float(high), + 'low': float(low), + 'volume': int(volume), + 'amount': float(amt) + } - prev_result = client.execute(prev_close_query, { - 'code': stock_code_full, - 'start': datetime.combine(latest_ts.date(), dt_time(9, 30)) - }) + # 批量查询前收盘价(使用 ea_trade 表,更准确) + prev_close_map = {} + if latest_data_map: + # 获取前一交易日 + prev_trading_day = None + for td in reversed(trading_days): + if td < today: + prev_trading_day = td + break - prev_close = float(prev_result[0][0]) if prev_result else float(latest_data[0]) + if prev_trading_day: + base_codes = [code.split('.')[0] for code in full_codes] + prev_day_str = prev_trading_day.strftime('%Y%m%d') - # 计算涨跌幅 - change = float(latest_data[0]) - prev_close - change_percent = (change / prev_close * 100) if prev_close > 0 else 0.0 + with engine.connect() as conn: + placeholders = ','.join([f':code{i}' for i in range(len(base_codes))]) + params = {f'code{i}': code for i, code in enumerate(base_codes)} + params['trade_date'] = prev_day_str - quotes_data[code] = { - 'price': float(latest_data[0]), - 'prev_close': float(prev_close), - 'change': float(change), - 'change_percent': float(change_percent), - 'high': float(latest_data[2]), - 'low': float(latest_data[3]), - 'volume': int(latest_data[4]), - 'amount': float(latest_data[5]), - 'update_time': latest_ts.strftime('%H:%M:%S') - } + prev_result = conn.execute(text(f""" + SELECT SECCODE, F007N as close_price + FROM ea_trade + WHERE SECCODE IN ({placeholders}) + AND TRADEDATE = :trade_date + """), params).fetchall() + + for row in prev_result: + base_code, close_price = row[0], row[1] + if close_price: + prev_close_map[base_code] = float(close_price) # 构建响应数据 + quotes_data = {} + for code6, full_code in code_mapping.items(): + latest = latest_data_map.get(full_code) + if latest: + base_code = full_code.split('.')[0] + prev_close = prev_close_map.get(base_code, latest['close']) + + change = latest['close'] - prev_close + change_percent = (change / prev_close * 100) if prev_close > 0 else 0.0 + + quotes_data[code6] = { + 'price': latest['close'], + 'prev_close': prev_close, + 'change': change, + 'change_percent': change_percent, + 'high': latest['high'], + 'low': latest['low'], + 'volume': latest['volume'], + 'amount': latest['amount'], + 'update_time': latest['timestamp'].strftime('%H:%M:%S') + } + response_data = [] for item in watchlist: code6, _ = _normalize_stock_input(item.stock_code) @@ -5637,7 +5661,6 @@ def get_watchlist_realtime(): 'volume': quote.get('volume', 0), 'amount': quote.get('amount', 0), 'update_time': quote.get('update_time', ''), - # industry 字段在 Watchlist 模型中不存在,先不返回该字段 }) return jsonify({ @@ -5647,6 +5670,8 @@ def get_watchlist_realtime(): except Exception as e: print(f"获取实时行情失败: {str(e)}") + import traceback + traceback.print_exc() return jsonify({'success': False, 'error': '获取实时行情失败'}), 500