diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index e813132..494ec80 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -1920,12 +1920,15 @@ async def sync_positions( or "" ) system_order_prefix = (system_order_prefix or "").strip() + # 无配置时用默认前缀 ats_,与 position_manager 一致,便于补建「系统限价/条件单事后成交」的记录 + if not system_order_prefix: + system_order_prefix = "ats_" except Exception: - pass + system_order_prefix = "ats_" if missing_in_db: logger.info(f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: {', '.join(missing_in_db)}") if system_order_prefix: - logger.info(f" → 仅对开仓订单 clientOrderId 前缀为「{system_order_prefix}」的持仓补建(系统单标识)") + logger.info(f" → 对开仓订单 clientOrderId 前缀为「{system_order_prefix}」的持仓补建(系统单);匹配到则不再要求有止损/止盈单") elif only_recover_when_has_sltp: logger.info(" → 仅对「存在止损/止盈单」的持仓补建记录(视为系统单),避免手动单误建") for symbol in missing_in_db: @@ -2022,7 +2025,12 @@ async def sync_positions( pass if is_clearly_manual: continue - elif only_recover_when_has_sltp: + # 已通过系统前缀匹配到开仓订单的,直接补建,不要求有止损/止盈单(系统单一定能对应 DB) + is_system_order_by_prefix = bool( + system_order_prefix and client_order_id + and str(client_order_id).strip().startswith(system_order_prefix) + ) + if only_recover_when_has_sltp and not is_system_order_by_prefix: has_sltp = False try: normal = await client.get_open_orders(symbol) @@ -2039,7 +2047,7 @@ async def sync_positions( except Exception as e: logger.debug(f"检查 {symbol} 止盈止损单失败: {e}") if not has_sltp: - logger.debug(f" {symbol} 无止损/止盈单,跳过补建(视为非系统单)") + logger.debug(f" {symbol} 无止损/止盈单且未匹配到系统前缀,跳过补建") continue if entry_order_id and hasattr(Trade, 'get_by_entry_order_id'): try: diff --git a/docs/common/订单记录与币安对账流程.md b/docs/common/订单记录与币安对账流程.md index f052201..4039844 100644 --- a/docs/common/订单记录与币安对账流程.md +++ b/docs/common/订单记录与币安对账流程.md @@ -4,6 +4,15 @@ --- +## 〇、常见误解:为什么「持仓 5 单、订单记录只有 1 单」? + +- **持仓列表**:数据来自**币安 API**(`get_open_positions()`),是交易所上真实存在的持仓,与 DB 无关。 +- **订单记录**:数据来自**本系统数据库 `trades` 表**,只有「本系统开仓并成功写库」或「同步/补建」时才会有一条记录。 + +因此**不是**「持仓存在就一定先在 DB 有了」——恰恰相反:币安上可以有 5 个持仓(本系统开的、手动在 App 开的、其它工具开的,或本系统开了但当时写库失败),而 DB 里只有我们成功写入或补建的那几条,所以订单记录会少于或等于持仓数。要对齐做法:使用**持仓同步**(见下文「补建」)或**同步币安订单**,把「币安有仓、DB 无记录」的持仓补建到 DB,订单记录就会和持仓一致。 + +--- + ## 一、数据模型(与币安对应的关键字段) | 字段 | 含义 | 与币安对应 | diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 225916c..19e667a 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -490,13 +490,12 @@ class PositionManager: filled_quantity = 0.0 entry_mode_used = "limit-only" if not smart_entry_enabled else ("limit+fallback" if allow_market_fallback else "limit-chase") - # 生成 client_order_id:先落库 pending,WS 按 o.c 匹配完善,减少对 REST 同步依赖 + # 生成 client_order_id:先落库 pending,WS/对账 按 client_order_id 匹配完善,确保限价/条件单事后成交也能对应 DB prefix = (config.TRADING_CONFIG.get("SYSTEM_ORDER_ID_PREFIX") or "").strip() - client_order_id = None - if prefix: - client_order_id = f"{prefix}_{int(time.time() * 1000)}_{random.randint(0, 0xFFFF):04x}"[:36] + prefix = prefix or "ats_" # 无配置时用默认前缀,保证系统单始终有 pending 可对账 + client_order_id = f"{prefix}_{int(time.time() * 1000)}_{random.randint(0, 0xFFFF):04x}"[:36] pending_trade_id = None - if DB_AVAILABLE and Trade and client_order_id: + if DB_AVAILABLE and Trade: try: pending_trade_id = Trade.create( symbol=symbol,