diff --git a/backend/database/models.py b/backend/database/models.py index 575a2c8..76e1855 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -573,10 +573,12 @@ class Trade: account_id: int = None, entry_context=None, status: str = "open", + entry_time=None, ): """创建交易记录(使用北京时间) Args: + entry_time: 开仓时间(Unix 时间戳秒,或 None 表示当前北京时间)。补建/同步时建议从币安订单或成交取真实时间。 symbol: 交易对 status: 状态,默认 "open";先落库等 WS 成交时可传 "pending" side: 方向 @@ -595,7 +597,16 @@ class Trade: margin_usdt: 保证金(USDT,可选) entry_context: 入场思路/过程(dict,将存为 JSON):信号强度、市场状态、趋势、过滤通过情况等,便于事后分析策略执行效果 """ - entry_time = get_beijing_time() + if entry_time is not None: + try: + if hasattr(entry_time, "timestamp"): + entry_time = int(entry_time.timestamp()) + else: + entry_time = int(float(entry_time)) + except (TypeError, ValueError): + entry_time = get_beijing_time() + else: + entry_time = get_beijing_time() # 自动计算 notional/margin(若调用方没传) try: diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 0cbfcd2..b390318 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2626,6 +2626,7 @@ class PositionManager: 'initialStopLoss': stop_loss_price, 'leverage': leverage, 'entryReason': trade.get('entry_reason') or 'db_sync', + 'entryTime': trade.get('entry_time'), 'atr': trade.get('atr'), 'maxProfit': 0.0, 'trailingStopActivated': False, @@ -3374,6 +3375,24 @@ class PositionManager: continue except Exception: pass + # 从币安取真实开仓时间,避免补建后开仓时间全是“当前时间” + entry_time_ts = None + if entry_order_id: + try: + oi = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000) + if oi and oi.get("time"): + entry_time_ts = int(oi["time"]) // 1000 + except Exception: + pass + if entry_time_ts is None: + try: + recent = await self.client.get_recent_trades(symbol, limit=100) + same_side = [t for t in (recent or []) if str(t.get("side", "")).upper() == side] + if same_side: + same_side.sort(key=lambda x: int(x.get("time", 0))) + entry_time_ts = int(same_side[0].get("time", 0)) // 1000 + except Exception: + pass trade_id = Trade.create( symbol=symbol, side=side, @@ -3386,6 +3405,7 @@ class PositionManager: 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, + entry_time=entry_time_ts, ) logger.info(f" ✓ {symbol} [状态同步] 已补建交易记录 (ID: {trade_id}, orderId: {entry_order_id or '-'}, entry_reason={entry_reason_sync})") ticker = await self.client.get_ticker_24h(symbol) @@ -3406,6 +3426,7 @@ class PositionManager: "changePercent": 0, "orderId": entry_order_id, "tradeId": trade_id, "stopLoss": stop_loss_price, "takeProfit": take_profit_price, "initialStopLoss": stop_loss_price, "leverage": lev, "entryReason": entry_reason_sync, "atr": None, "maxProfit": 0.0, "trailingStopActivated": False, + "entryTime": entry_time_ts if entry_time_ts is not None else get_beijing_time(), } self.active_positions[symbol] = position_info # 补建后立即在交易所挂/修正止损止盈(替换可能存在的异常远止损、缺止盈等) @@ -3447,8 +3468,9 @@ class PositionManager: f"{symbol} [状态同步] 检测到手动开仓,创建数据库记录... " f"({side} {quantity:.4f} @ {entry_price:.4f})" ) - # 尽量从币安成交取 entry_order_id(limit=100、空时重试一次),便于订单记录/统计对账 + # 尽量从币安成交取 entry_order_id 与真实开仓时间(limit=100、空时重试一次) entry_order_id = None + entry_time_ts = None try: recent = await self.client.get_recent_trades(symbol, limit=100) if not recent: @@ -3459,9 +3481,19 @@ class PositionManager: if same_side: same_side.sort(key=lambda x: int(x.get('time', 0)), reverse=True) entry_order_id = same_side[0].get('orderId') + # 开仓时间取同方向最早一笔成交(oldest),避免显示为“当前时间” + same_side_asc = sorted(same_side, key=lambda x: int(x.get('time', 0))) + entry_time_ts = int(same_side_asc[0].get('time', 0)) // 1000 except Exception: pass - # 创建数据库记录(显式传入 account_id,避免多账号混用) + if entry_time_ts is None and entry_order_id: + try: + oi = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000) + if oi and oi.get('time'): + entry_time_ts = int(oi['time']) // 1000 + except Exception: + pass + # 创建数据库记录(显式传入 account_id、真实开仓时间) trade_id = Trade.create( symbol=symbol, side=side, @@ -3473,6 +3505,7 @@ class PositionManager: 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, + entry_time=entry_time_ts, ) logger.info(f"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})") @@ -3510,14 +3543,14 @@ class PositionManager: 'quantity': quantity, 'entryPrice': entry_price, 'changePercent': 0, # 手动开仓,无法计算涨跌幅 - 'orderId': None, + 'orderId': entry_order_id, 'tradeId': trade_id, 'stopLoss': stop_loss_price, 'takeProfit': take_profit_price, 'initialStopLoss': stop_loss_price, 'leverage': leverage, 'entryReason': 'manual_entry', - 'entryTime': get_beijing_time(), # 补全入场时间 + 'entryTime': entry_time_ts if entry_time_ts is not None else get_beijing_time(), # 真实开仓时间(来自币安成交/订单) 'atr': None, 'maxProfit': 0.0, 'trailingStopActivated': False