feat(trades): 优化从币安同步历史订单的逻辑

更新 `sync_trades_from_binance` 接口,新增 `account_id` 参数以支持多账户同步。改进了订单同步逻辑,仅对数据库中有记录的交易对进行拉取,避免全市场请求,提升效率。同时,增强了异常处理和日志记录,确保同步过程的稳定性和可追溯性。
This commit is contained in:
薇薇安 2026-02-16 15:51:51 +08:00
parent c6126a42c9
commit ec5c76c546

View File

@ -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] # 取第一个