diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index ac460bf..777dafc 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -391,13 +391,13 @@ async def get_trade_stats( @router.post("/sync-binance") async def sync_trades_from_binance( - days: int = Query(7, ge=1, le=30, description="同步最近N天的订单") + account_id: int = Depends(get_account_id), + days: int = Query(7, ge=1, le=30, description="同步最近N天的订单"), ): """ - 从币安同步历史订单,确保数据库与币安一致 - - Args: - days: 同步最近N天的订单(默认7天) + 从币安同步历史订单,补全 DB 中缺失的 exit_order_id / 平仓信息。 + **WS 已接入后**:开仓/平仓订单号主要由 User Data Stream 回写,此接口仅作**冷启动或补漏**,建议降低调用频率。 + 仅对「DB 中近期有记录的 symbol」拉取订单,避免全市场逐 symbol 请求。 """ try: logger.info(f"开始从币安同步历史订单(最近{days}天)...") @@ -416,7 +416,45 @@ async def sync_trades_from_binance( from binance_client import BinanceClient import config - + from database.models import DEFAULT_ACCOUNT_ID + + # 计算时间范围(秒,供 DB 查询) + end_time_ms = int(time.time() * 1000) + start_time_ms = int((datetime.now() - timedelta(days=days)).timestamp() * 1000) + start_ts_sec = start_time_ms // 1000 + end_ts_sec = end_time_ms // 1000 + + # 仅对 DB 中在时间范围内有 open/closed 记录的 symbol 拉取订单(WS 已回写订单号,大幅减少请求) + symbol_list = [] + try: + trades_in_range = Trade.get_all( + start_timestamp=start_ts_sec, + end_timestamp=end_ts_sec, + account_id=account_id or DEFAULT_ACCOUNT_ID, + ) + symbol_list = list({t.get("symbol") for t in (trades_in_range or []) if t.get("symbol")}) + except Exception as e: + logger.warning(f"从 DB 获取 symbol 列表失败,将跳过同步: {e}") + return { + "success": True, + "message": "未获取到需同步的交易对,跳过(WS 正常时订单号已由推送回写)", + "total_orders": 0, + "updated_trades": 0, + "close_orders": 0, + "open_orders": 0, + } + + if not symbol_list: + logger.info("DB 内在时间范围内无交易记录,跳过全量拉取订单(WS 为主时无需频繁同步)") + return { + "success": True, + "message": "近期无交易记录,未请求币安订单;WS 正常时订单号已由推送回写", + "total_orders": 0, + "updated_trades": 0, + "close_orders": 0, + "open_orders": 0, + } + # 初始化客户端 client = BinanceClient( api_key=config.BINANCE_API_KEY, @@ -427,42 +465,23 @@ async def sync_trades_from_binance( await client.connect() try: - import time - from datetime import datetime, timedelta - - # 计算时间范围 - end_time = int(time.time() * 1000) # 当前时间(毫秒) - start_time = int((datetime.now() - timedelta(days=days)).timestamp() * 1000) - - # 获取所有已成交的订单(包括开仓和平仓) + # 仅对上述 symbol 拉取订单 all_orders = [] try: - # 获取所有交易对的订单 - # 注意:币安API可能需要分交易对查询,这里先获取所有交易对 - symbols = await client.client.futures_exchange_info() - symbol_list = [s['symbol'] for s in symbols.get('symbols', []) if s.get('contractType') == 'PERPETUAL'] - - logger.info(f"开始同步 {len(symbol_list)} 个交易对的订单...") - + logger.info(f"仅对 {len(symbol_list)} 个有 DB 记录的 symbol 拉取订单(已跳过全市场请求)") for symbol in symbol_list: try: - # 获取该交易对的历史订单 orders = await client.client.futures_get_all_orders( symbol=symbol, - startTime=start_time, - endTime=end_time + startTime=start_time_ms, + endTime=end_time_ms ) - - # 只保留已成交的订单 filled_orders = [o for o in orders if o.get('status') == 'FILLED'] all_orders.extend(filled_orders) - - # 避免请求过快 await asyncio.sleep(0.1) except Exception as e: logger.debug(f"获取 {symbol} 订单失败: {e}") continue - logger.info(f"从币安获取到 {len(all_orders)} 个已成交订单") except Exception as e: logger.error(f"获取币安订单失败: {e}") @@ -504,8 +523,8 @@ async def sync_trades_from_binance( logger.debug(f"订单 {order_id} 已同步过且 exit_reason={existing_trade.get('exit_reason')},跳过") continue - # 查找数据库中该交易对的open状态记录(仅当前账号) - open_trades = Trade.get_by_symbol(symbol, status='open', account_id=None) + # 查找数据库中该交易对的 open 状态记录(仅当前账号) + open_trades = Trade.get_by_symbol(symbol, status='open', account_id=account_id or DEFAULT_ACCOUNT_ID) if existing_trade or open_trades: # 找到匹配的交易记录(通过symbol匹配,如果有多个则取最近的) trade = existing_trade or open_trades[0] # 取第一个