From 11cd55ff7b6555fcd7a9ce372b7636cc806e76de 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 19:24:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20client=5Forder=5Fid=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E8=AE=B0=E5=BD=95=E4=B8=AD=E4=B8=8E=E5=B8=81=E5=AE=89?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E8=AE=A2=E5=8D=95=E5=8F=B7=E4=B8=80?= =?UTF-8?q?=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/routes/account.py | 3 +++ backend/database/add_client_order_id.sql | 5 +++++ backend/database/models.py | 6 ++++++ frontend/src/components/GlobalConfig.jsx | 4 ++++ frontend/src/components/TradeList.jsx | 9 +++++++++ trading_system/position_manager.py | 5 +++++ 6 files changed, 32 insertions(+) create mode 100644 backend/database/add_client_order_id.sql diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index a9bdc87..9fe3228 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -1821,6 +1821,7 @@ async def sync_positions( entry_order_id = same_side[0].get('orderId') except Exception as e: logger.debug(f"获取 {symbol} 成交记录失败: {e}") + client_order_id = None if system_order_prefix: if not entry_order_id: logger.debug(f" {symbol} 无法获取开仓订单号,跳过补建") @@ -1828,6 +1829,7 @@ async def sync_positions( 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 not cid.startswith(system_order_prefix): logger.debug(f" {symbol} 开仓订单 clientOrderId={cid!r} 非系统前缀,跳过补建") continue @@ -1868,6 +1870,7 @@ async def sync_positions( leverage=leverage, entry_reason='sync_recovered', entry_order_id=entry_order_id, + client_order_id=client_order_id, notional_usdt=notional, margin_usdt=(notional / leverage) if leverage > 0 else None, account_id=account_id, diff --git a/backend/database/add_client_order_id.sql b/backend/database/add_client_order_id.sql new file mode 100644 index 0000000..72e9e06 --- /dev/null +++ b/backend/database/add_client_order_id.sql @@ -0,0 +1,5 @@ +-- 为 trades 表增加「自定义订单号」字段,用于存储币安 clientOrderId,便于在订单记录中核对系统单 +-- 若已存在该列可跳过本句 +ALTER TABLE trades ADD COLUMN client_order_id VARCHAR(64) NULL COMMENT '币安自定义订单号 clientOrderId(系统单格式: 前缀_时间戳_随机)' AFTER entry_order_id; +-- 可选:为按自定义订单号查询建索引(若已存在可跳过) +-- CREATE INDEX idx_client_order_id ON trades (client_order_id); diff --git a/backend/database/models.py b/backend/database/models.py index 05d9a8a..d54b050 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -440,6 +440,7 @@ class Trade: leverage=10, entry_reason=None, entry_order_id=None, + client_order_id=None, stop_loss_price=None, take_profit_price=None, take_profit_1=None, @@ -460,6 +461,7 @@ class Trade: leverage: 杠杆 entry_reason: 入场原因(简短文本) entry_order_id: 币安开仓订单号(可选,用于对账) + client_order_id: 币安自定义订单号 clientOrderId(可选,用于在订单记录中核对系统单) stop_loss_price: 实际使用的止损价格(考虑了ATR等动态计算) take_profit_price: 实际使用的止盈价格(考虑了ATR等动态计算) take_profit_1: 第一目标止盈价(可选) @@ -503,6 +505,10 @@ class Trade: columns.append("entry_order_id") values.append(entry_order_id) + if _has_column("client_order_id"): + columns.append("client_order_id") + values.append(client_order_id) + if _has_column("notional_usdt"): columns.append("notional_usdt") values.append(notional_usdt) diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index cf511bd..d7593ba 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -433,6 +433,10 @@ const GlobalConfig = () => { ATR_STOP_LOSS_MULTIPLIER: { value: 3.0, type: 'number', category: 'risk', description: 'ATR 止损倍数(2026-02-12:3 减少噪音止损)。' }, USE_FIXED_RISK_SIZING: { value: true, type: 'boolean', category: 'risk', description: '是否使用固定风险仓位计算(基于止损距离和账户余额)。' }, FIXED_RISK_PERCENT: { value: 0.03, type: 'number', category: 'risk', description: '每笔交易固定风险百分比(如 0.01=1%)。' }, + // 仓位/同步相关(保证全局配置页能看到) + SYNC_RECOVER_MISSING_POSITIONS: { value: true, type: 'boolean', category: 'position', description: '同步时补建「币安有仓、DB 无记录」的交易记录。' }, + SYNC_RECOVER_ONLY_WHEN_HAS_SLTP: { value: true, type: 'boolean', category: 'position', description: '仅当该持仓存在止损/止盈单时才补建(未配置 SYSTEM_ORDER_ID_PREFIX 时生效)。' }, + SYSTEM_ORDER_ID_PREFIX: { value: 'SYS', type: 'string', category: 'position', description: '系统单标识:下单时写入 newClientOrderId 前缀,同步时仅对「开仓订单 clientOrderId 以此前缀开头」的持仓补建;设空则用「是否有止损止盈单」判断。' }, } const loadConfigs = async () => { diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index 576cd6f..2c49c44 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -118,6 +118,7 @@ const TradeList = () => { 状态: trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消', 平仓类型: trade.exit_reason_display || '-', 开仓订单号: trade.entry_order_id || '-', + 自定义订单号: trade.client_order_id || '-', 平仓订单号: trade.exit_order_id || '-', 入场时间: trade.entry_time, 平仓时间: trade.exit_time || null, @@ -634,6 +635,7 @@ const TradeList = () => { 状态 平仓类型 币安订单号 + 自定义订单号 入场时间 平仓时间 @@ -731,6 +733,7 @@ const TradeList = () => { {trade.exit_reason_display || '-'} {formatOrderIds()} + {trade.client_order_id || '-'} {formatTime(trade.entry_time)} {trade.exit_time ? formatTime(trade.exit_time) : '-'} @@ -861,6 +864,12 @@ const TradeList = () => { )} + {trade.client_order_id && ( +
+ 自定义订单号 + {trade.client_order_id} +
+ )}
diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 806b65a..1331593 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -678,6 +678,7 @@ class PositionManager: if DB_AVAILABLE and Trade: try: logger.info(f"正在保存 {symbol} 交易记录到数据库...") + client_order_id = (order.get("clientOrderId") if order else None) or None trade_id = Trade.create( symbol=symbol, side=side, @@ -686,6 +687,7 @@ class PositionManager: leverage=leverage, entry_reason=entry_reason, entry_order_id=entry_order_id, # 保存币安订单号 + client_order_id=client_order_id, # 自定义订单号,便于在订单记录中核对系统单 stop_loss_price=stop_loss_price, take_profit_price=take_profit_price, take_profit_1=take_profit_1, @@ -2834,6 +2836,7 @@ class PositionManager: entry_order_id = same_side[0].get("orderId") except Exception: pass + client_order_id_sync = None if system_order_prefix: if not entry_order_id: logger.debug(f" {symbol} 无法获取开仓订单号,跳过补建") @@ -2841,6 +2844,7 @@ class PositionManager: 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 not cid.startswith(system_order_prefix): logger.debug(f" {symbol} 开仓订单 clientOrderId={cid!r} 非系统前缀,跳过补建") continue @@ -2864,6 +2868,7 @@ class PositionManager: leverage=binance_position.get("leverage", 10), entry_reason="sync_recovered", entry_order_id=entry_order_id, + client_order_id=client_order_id_sync, notional_usdt=notional, margin_usdt=(notional / float(binance_position.get("leverage", 10) or 10)) if float(binance_position.get("leverage", 10) or 0) > 0 else None, account_id=self.account_id,