From d985b941611ef7a0e726cf7536af82d44f72bdb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 14 Feb 2026 23:52:22 +0800 Subject: [PATCH] 1 --- backend/api/routes/account.py | 36 ++++++-------------------- docs/订单与统计一致性说明.md | 7 ++--- trading_system/position_manager.py | 30 ++++++++++----------- 3 files changed, 26 insertions(+), 47 deletions(-) diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index 1f70bbd..c9aa8bf 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -1822,40 +1822,20 @@ async def sync_positions( except Exception as e: logger.debug(f"获取 {symbol} 成交记录失败: {e}") client_order_id = None - prefix_matched = False + is_clearly_manual = False if system_order_prefix: - if not entry_order_id: - logger.debug(f" {symbol} 无法获取开仓订单号,将用「是否有止损/止盈单」兜底判断") - else: + if entry_order_id: try: order_info = await client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000) cid = (order_info or {}).get("clientOrderId") or "" client_order_id = cid or None - if cid.startswith(system_order_prefix): - prefix_matched = True - else: - logger.debug(f" {symbol} 开仓订单 clientOrderId={cid!r} 非系统前缀,将用「是否有止损/止盈单」兜底判断") + if cid and not cid.startswith(system_order_prefix): + is_clearly_manual = True + logger.debug(f" {symbol} 开仓订单 clientOrderId={cid!r} 非系统前缀,视为手动单,跳过补建") except Exception as e: - logger.debug(f" {symbol} 查询开仓订单失败: {e},将用「是否有止损/止盈单」兜底判断") - if not prefix_matched: - has_sltp = False - try: - normal = await client.get_open_orders(symbol) - for o in (normal or []): - if _order_is_sltp(o, "type"): - has_sltp = True - break - if not has_sltp: - algo = await client.futures_get_open_algo_orders(symbol=symbol, algo_type="CONDITIONAL") - for o in (algo or []): - if _order_is_sltp(o, "orderType"): - has_sltp = True - break - except Exception as e: - logger.debug(f"检查 {symbol} 止盈止损单失败: {e}") - if only_recover_when_has_sltp and not has_sltp: - logger.debug(f" {symbol} 未匹配系统前缀且无止损/止盈单,跳过补建") - continue + logger.debug(f" {symbol} 查询开仓订单失败: {e},按系统单补建") + if is_clearly_manual: + continue elif only_recover_when_has_sltp: has_sltp = False try: diff --git a/docs/订单与统计一致性说明.md b/docs/订单与统计一致性说明.md index 1ffdac8..825e50d 100644 --- a/docs/订单与统计一致性说明.md +++ b/docs/订单与统计一致性说明.md @@ -9,14 +9,15 @@ ### 1. 持仓与币安一致 - **仪表板「当前持仓」**:数据来自 **币安实时持仓**(`get_open_positions()`),与币安页面一致(仅受 `POSITION_MIN_NOTIONAL_USDT` 过滤影响)。 -- **补建逻辑**:只有「开仓订单 `clientOrderId` 以配置前缀开头」的持仓会补建 DB 记录,避免把手动单算进系统。 +- **补建逻辑**:配置了 `SYSTEM_ORDER_ID_PREFIX` 时,**仅当**能明确查到开仓订单且 `clientOrderId` 非空且**不以**该前缀开头时视为手动单并跳过;其余(含历史单、查不到订单、cid 为空)一律补建并加入监控,避免漏掉系统单。 ### 2. 订单记录与币安可对账 -- **开仓**:系统下单时写入 `newClientOrderId = 前缀_时间戳_随机`,并保存 `entry_order_id` 到 DB。 +- **开仓**:交易系统**所有开仓单**(非 reduce_only)在配置了 `SYSTEM_ORDER_ID_PREFIX` 时都会带 `newClientOrderId = 前缀_时间戳_随机`(如 `SYS_xxx`),成交后写入 DB 的 `entry_order_id` 与 `client_order_id`,与币安一致。 +- **成交后落库**:开仓成交后 `position_manager` 会调用 `Trade.create(..., entry_order_id=..., client_order_id=...)`,订单记录与币安一一对应;若当时落库失败(超时/崩溃),后续同步会通过「补建」把缺失记录补上并纳入监控。 - **平仓**:平仓时保存 `exit_order_id`,`Trade.update_exit` 会做 `get_by_exit_order_id` 防重复。 - **同步**: - - `POST /api/account/positions/sync`:只对「开仓订单 clientOrderId 前缀匹配」的持仓补建,且从成交里取 `orderId` 作为 `entry_order_id`。 + - `POST /api/account/positions/sync` 与进程内定时同步:仅当开仓订单 `clientOrderId` **明确非**系统前缀时跳过补建,其余一律补建(含历史单、未带前缀的单),保证「币安有仓且为系统单」都会被监控。 - `POST /api/trades/sync-binance`:用 `get_by_exit_order_id(order_id)` 判断是否已同步,避免重复;开仓侧用 `get_by_entry_order_id(order_id)` 判断是否已存在。 - 因此:**每条 DB 记录都可与币安订单一一对应**(通过 `entry_order_id` / `exit_order_id`),订单记录与币安在「谁开的、谁平的」上一致。 diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index ac29f30..1675cfe 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2864,8 +2864,7 @@ class PositionManager: system_order_prefix = (config.TRADING_CONFIG.get("SYSTEM_ORDER_ID_PREFIX") or "").strip() if system_order_prefix: logger.info( - f" → 补建「系统单」记录:开仓订单 clientOrderId 前缀为 {system_order_prefix!r} 时创建;" - "未匹配前缀时若有止损/止盈单也补建(便于历史单/监控一致)" + f" → 补建「系统单」记录:仅当开仓订单 clientOrderId 明确非 {system_order_prefix!r} 前缀时跳过(视为手动单),其余一律补建" ) else: logger.info(" → 补建「系统单」记录(仅当存在止损/止盈单时创建,避免手动单误建)" if sync_recover_only_has_sltp else " → 补建缺失持仓记录") @@ -2892,27 +2891,26 @@ class PositionManager: except Exception: pass client_order_id_sync = None - prefix_matched = False # 是否通过 clientOrderId 前缀确认为系统单 + # 仅当「明确查到开仓订单且 clientOrderId 非空且不以系统前缀开头」时视为手动单并跳过;其余一律补建(避免漏掉系统单) + is_clearly_manual = False if system_order_prefix: - if not entry_order_id: - logger.debug(f" {symbol} 无法获取开仓订单号,将用「是否有止损/止盈单」兜底判断") - else: + if entry_order_id: try: order_info = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000) cid = (order_info or {}).get("clientOrderId") or "" client_order_id_sync = cid or None - if cid.startswith(system_order_prefix): - prefix_matched = True - else: - logger.debug(f" {symbol} 开仓订单 clientOrderId={cid!r} 非系统前缀,将用「是否有止损/止盈单」兜底判断") + if cid and not cid.startswith(system_order_prefix): + is_clearly_manual = True + logger.debug(f" {symbol} 开仓订单 clientOrderId={cid!r} 非系统前缀,视为手动单,跳过补建") except Exception as e: - logger.debug(f" {symbol} 查询开仓订单失败: {e},将用「是否有止损/止盈单」兜底判断") - # 未通过前缀确认时:若有止损/止盈单则仍补建(历史单或前缀未写入时也能被监控) - if not prefix_matched and sync_recover_only_has_sltp and not (await _symbol_has_sltp(symbol)): - logger.debug(f" {symbol} 未匹配系统前缀且无止损/止盈单,跳过补建") + logger.debug(f" {symbol} 查询开仓订单失败: {e},按系统单补建") + # 无法获取订单或 cid 为空(历史单/未带前缀)时不视为手动单,继续补建 + else: + # 未配置前缀时,用「是否有止损/止盈单」区分 + if sync_recover_only_has_sltp and not (await _symbol_has_sltp(symbol)): + logger.debug(f" {symbol} 无止损/止盈单,跳过补建") continue - elif sync_recover_only_has_sltp and not (await _symbol_has_sltp(symbol)): - logger.debug(f" {symbol} 无止损/止盈单,跳过补建") + if is_clearly_manual: continue if entry_order_id and hasattr(Trade, "get_by_entry_order_id"): try: