feat(position_manager, database): 添加开仓时间记录功能以优化交易记录
在 `position_manager.py` 中引入了真实开仓时间的获取逻辑,确保在补建交易记录时使用币安的实际开仓时间,避免时间显示为“当前时间”。同时,在 `models.py` 中更新了 `Trade` 类,新增 `entry_time` 参数以存储开仓时间,提升了交易记录的准确性与分析能力。
This commit is contained in:
parent
2c5524bdcf
commit
9299d70a31
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user