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