a
This commit is contained in:
parent
e3b6dfb65d
commit
4023f7807e
|
|
@ -788,6 +788,29 @@ async def sync_positions():
|
||||||
|
|
||||||
for symbol in missing_in_binance:
|
for symbol in missing_in_binance:
|
||||||
try:
|
try:
|
||||||
|
# 尝试从币安历史订单获取“真实平仓信息”(价格/时间/原因/订单号)
|
||||||
|
latest_close_order = None
|
||||||
|
try:
|
||||||
|
end_time_ms = int(time.time() * 1000)
|
||||||
|
start_time_ms = end_time_ms - (7 * 24 * 60 * 60 * 1000)
|
||||||
|
orders = await client.client.futures_get_all_orders(
|
||||||
|
symbol=symbol,
|
||||||
|
startTime=start_time_ms,
|
||||||
|
endTime=end_time_ms,
|
||||||
|
)
|
||||||
|
if isinstance(orders, list) and orders:
|
||||||
|
close_orders = [
|
||||||
|
o for o in orders
|
||||||
|
if isinstance(o, dict)
|
||||||
|
and o.get("reduceOnly") is True
|
||||||
|
and o.get("status") == "FILLED"
|
||||||
|
]
|
||||||
|
if close_orders:
|
||||||
|
close_orders.sort(key=lambda x: x.get("updateTime", 0), reverse=True)
|
||||||
|
latest_close_order = close_orders[0]
|
||||||
|
except Exception:
|
||||||
|
latest_close_order = None
|
||||||
|
|
||||||
# 获取该交易对的所有open记录
|
# 获取该交易对的所有open记录
|
||||||
open_trades = Trade.get_by_symbol(symbol, status='open')
|
open_trades = Trade.get_by_symbol(symbol, status='open')
|
||||||
|
|
||||||
|
|
@ -797,6 +820,40 @@ async def sync_positions():
|
||||||
quantity = float(trade['quantity'])
|
quantity = float(trade['quantity'])
|
||||||
|
|
||||||
# 获取当前价格作为平仓价格
|
# 获取当前价格作为平仓价格
|
||||||
|
exit_price = None
|
||||||
|
exit_order_id = None
|
||||||
|
exit_time_ts = None
|
||||||
|
exit_reason = "sync"
|
||||||
|
otype = ""
|
||||||
|
|
||||||
|
if latest_close_order and isinstance(latest_close_order, dict):
|
||||||
|
try:
|
||||||
|
exit_price = float(latest_close_order.get("avgPrice", 0) or 0) or None
|
||||||
|
except Exception:
|
||||||
|
exit_price = None
|
||||||
|
exit_order_id = latest_close_order.get("orderId") or None
|
||||||
|
otype = str(
|
||||||
|
latest_close_order.get("type")
|
||||||
|
or latest_close_order.get("origType")
|
||||||
|
or ""
|
||||||
|
).upper()
|
||||||
|
try:
|
||||||
|
ms = latest_close_order.get("updateTime") or latest_close_order.get("time")
|
||||||
|
if ms:
|
||||||
|
exit_time_ts = int(int(ms) / 1000)
|
||||||
|
except Exception:
|
||||||
|
exit_time_ts = None
|
||||||
|
|
||||||
|
if "TRAILING" in otype:
|
||||||
|
exit_reason = "trailing_stop"
|
||||||
|
elif "TAKE_PROFIT" in otype:
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif "STOP" in otype:
|
||||||
|
exit_reason = "stop_loss"
|
||||||
|
elif otype in ("MARKET", "LIMIT"):
|
||||||
|
exit_reason = "manual"
|
||||||
|
|
||||||
|
if not exit_price or exit_price <= 0:
|
||||||
ticker = await client.get_ticker_24h(symbol)
|
ticker = await client.get_ticker_24h(symbol)
|
||||||
exit_price = float(ticker['price']) if ticker else entry_price
|
exit_price = float(ticker['price']) if ticker else entry_price
|
||||||
|
|
||||||
|
|
@ -813,18 +870,32 @@ async def sync_positions():
|
||||||
pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0
|
pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0
|
||||||
|
|
||||||
# 更新数据库记录
|
# 更新数据库记录
|
||||||
|
duration_minutes = None
|
||||||
|
try:
|
||||||
|
et = trade.get("entry_time")
|
||||||
|
if et is not None and exit_time_ts is not None:
|
||||||
|
et_i = int(et)
|
||||||
|
if exit_time_ts >= et_i:
|
||||||
|
duration_minutes = int((exit_time_ts - et_i) / 60)
|
||||||
|
except Exception:
|
||||||
|
duration_minutes = None
|
||||||
|
|
||||||
Trade.update_exit(
|
Trade.update_exit(
|
||||||
trade_id=trade_id,
|
trade_id=trade_id,
|
||||||
exit_price=exit_price,
|
exit_price=exit_price,
|
||||||
exit_reason='sync', # 标记为同步平仓
|
exit_reason=exit_reason,
|
||||||
pnl=pnl,
|
pnl=pnl,
|
||||||
pnl_percent=pnl_percent_margin, # 使用基于保证金的盈亏百分比
|
pnl_percent=pnl_percent_margin, # 使用基于保证金的盈亏百分比
|
||||||
exit_order_id=None
|
exit_order_id=exit_order_id,
|
||||||
|
duration_minutes=duration_minutes,
|
||||||
|
exit_time_ts=exit_time_ts,
|
||||||
)
|
)
|
||||||
updated_count += 1
|
updated_count += 1
|
||||||
logger.info(
|
logger.info(
|
||||||
f"✓ {symbol} 已更新为closed (ID: {trade_id}, "
|
f"✓ {symbol} 已更新为closed (ID: {trade_id}, "
|
||||||
f"盈亏: {pnl:.2f} USDT, {pnl_percent_margin:.2f}% of margin)"
|
f"盈亏: {pnl:.2f} USDT, {pnl_percent_margin:.2f}% of margin, "
|
||||||
|
f"原因: {exit_reason}, 类型: {otype or '-'}"
|
||||||
|
f")"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ {symbol} 更新失败: {e}")
|
logger.error(f"❌ {symbol} 更新失败: {e}")
|
||||||
|
|
|
||||||
|
|
@ -231,16 +231,29 @@ async def get_trade_stats(
|
||||||
# 平仓原因分布(用来快速定位胜率低的主要来源:止损/止盈/同步等)
|
# 平仓原因分布(用来快速定位胜率低的主要来源:止损/止盈/同步等)
|
||||||
exit_reason_counts = Counter((t.get("exit_reason") or "unknown") for t in meaningful_trades)
|
exit_reason_counts = Counter((t.get("exit_reason") or "unknown") for t in meaningful_trades)
|
||||||
|
|
||||||
# 平均持仓时长(分钟):优先使用 duration_minutes 字段(若为空则跳过)
|
# 平均持仓时长(分钟):
|
||||||
|
# - 优先使用 duration_minutes(若历史没写入,则用 exit_time-entry_time 实时计算)
|
||||||
durations = []
|
durations = []
|
||||||
for t in meaningful_trades:
|
for t in meaningful_trades:
|
||||||
dm = t.get("duration_minutes")
|
dm = t.get("duration_minutes")
|
||||||
|
if dm is not None:
|
||||||
try:
|
try:
|
||||||
if dm is None:
|
|
||||||
continue
|
|
||||||
dm_f = float(dm)
|
dm_f = float(dm)
|
||||||
if dm_f >= 0:
|
if dm_f >= 0:
|
||||||
durations.append(dm_f)
|
durations.append(dm_f)
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
et = t.get("entry_time")
|
||||||
|
xt = t.get("exit_time")
|
||||||
|
try:
|
||||||
|
if et is None or xt is None:
|
||||||
|
continue
|
||||||
|
et_i = int(et)
|
||||||
|
xt_i = int(xt)
|
||||||
|
if xt_i >= et_i:
|
||||||
|
durations.append((xt_i - et_i) / 60.0)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
avg_duration_minutes = (sum(durations) / len(durations)) if durations else None
|
avg_duration_minutes = (sum(durations) / len(durations)) if durations else None
|
||||||
|
|
@ -384,6 +397,14 @@ async def sync_trades_from_binance(
|
||||||
avg_price = float(order.get('avgPrice', 0))
|
avg_price = float(order.get('avgPrice', 0))
|
||||||
order_time = datetime.fromtimestamp(order.get('time', 0) / 1000)
|
order_time = datetime.fromtimestamp(order.get('time', 0) / 1000)
|
||||||
reduce_only = order.get('reduceOnly', False)
|
reduce_only = order.get('reduceOnly', False)
|
||||||
|
otype = str(order.get('type') or order.get('origType') or '').upper()
|
||||||
|
exit_time_ts = None
|
||||||
|
try:
|
||||||
|
ms = order.get('updateTime') or order.get('time')
|
||||||
|
if ms:
|
||||||
|
exit_time_ts = int(int(ms) / 1000)
|
||||||
|
except Exception:
|
||||||
|
exit_time_ts = None
|
||||||
|
|
||||||
if quantity <= 0 or avg_price <= 0:
|
if quantity <= 0 or avg_price <= 0:
|
||||||
continue
|
continue
|
||||||
|
|
@ -393,15 +414,15 @@ async def sync_trades_from_binance(
|
||||||
# 这是平仓订单
|
# 这是平仓订单
|
||||||
# 首先检查是否已经通过订单号同步过(避免重复)
|
# 首先检查是否已经通过订单号同步过(避免重复)
|
||||||
existing_trade = Trade.get_by_exit_order_id(order_id)
|
existing_trade = Trade.get_by_exit_order_id(order_id)
|
||||||
if existing_trade:
|
if existing_trade and (existing_trade.get("exit_reason") not in (None, "", "sync")):
|
||||||
logger.debug(f"订单 {order_id} 已同步过,跳过")
|
logger.debug(f"订单 {order_id} 已同步过且 exit_reason={existing_trade.get('exit_reason')},跳过")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 查找数据库中该交易对的open状态记录
|
# 查找数据库中该交易对的open状态记录
|
||||||
open_trades = Trade.get_by_symbol(symbol, status='open')
|
open_trades = Trade.get_by_symbol(symbol, status='open')
|
||||||
if open_trades:
|
if existing_trade or open_trades:
|
||||||
# 找到匹配的交易记录(通过symbol匹配,如果有多个则取最近的)
|
# 找到匹配的交易记录(通过symbol匹配,如果有多个则取最近的)
|
||||||
trade = open_trades[0] # 取第一个
|
trade = existing_trade or open_trades[0] # 取第一个
|
||||||
trade_id = trade['id']
|
trade_id = trade['id']
|
||||||
|
|
||||||
# 计算盈亏
|
# 计算盈亏
|
||||||
|
|
@ -418,17 +439,67 @@ async def sync_trades_from_binance(
|
||||||
pnl = (entry_price - avg_price) * actual_quantity
|
pnl = (entry_price - avg_price) * actual_quantity
|
||||||
pnl_percent = ((entry_price - avg_price) / entry_price) * 100
|
pnl_percent = ((entry_price - avg_price) / entry_price) * 100
|
||||||
|
|
||||||
|
# 细分 exit_reason:优先使用币安订单类型,其次用价格接近止损/止盈做兜底
|
||||||
|
exit_reason = "sync"
|
||||||
|
if "TRAILING" in otype:
|
||||||
|
exit_reason = "trailing_stop"
|
||||||
|
elif "TAKE_PROFIT" in otype:
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif "STOP" in otype:
|
||||||
|
exit_reason = "stop_loss"
|
||||||
|
elif otype in ("MARKET", "LIMIT"):
|
||||||
|
exit_reason = "manual"
|
||||||
|
|
||||||
|
try:
|
||||||
|
def _close_to(a: float, b: float, max_pct: float = 0.01) -> bool:
|
||||||
|
if a <= 0 or b <= 0:
|
||||||
|
return False
|
||||||
|
return abs((a - b) / b) <= max_pct
|
||||||
|
|
||||||
|
ep = float(avg_price or 0)
|
||||||
|
if ep > 0:
|
||||||
|
sl = trade.get("stop_loss_price")
|
||||||
|
tp = trade.get("take_profit_price")
|
||||||
|
tp1 = trade.get("take_profit_1")
|
||||||
|
tp2 = trade.get("take_profit_2")
|
||||||
|
if sl is not None and _close_to(ep, float(sl), max_pct=0.01):
|
||||||
|
exit_reason = "stop_loss"
|
||||||
|
elif tp is not None and _close_to(ep, float(tp), max_pct=0.01):
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.01):
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.01):
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 持仓持续时间(分钟)
|
||||||
|
duration_minutes = None
|
||||||
|
try:
|
||||||
|
et = trade.get("entry_time")
|
||||||
|
if et is not None and exit_time_ts is not None:
|
||||||
|
et_i = int(et)
|
||||||
|
if exit_time_ts >= et_i:
|
||||||
|
duration_minutes = int((exit_time_ts - et_i) / 60)
|
||||||
|
except Exception:
|
||||||
|
duration_minutes = None
|
||||||
|
|
||||||
# 更新数据库(包含订单号)
|
# 更新数据库(包含订单号)
|
||||||
Trade.update_exit(
|
Trade.update_exit(
|
||||||
trade_id=trade_id,
|
trade_id=trade_id,
|
||||||
exit_price=avg_price,
|
exit_price=avg_price,
|
||||||
exit_reason='sync',
|
exit_reason=exit_reason,
|
||||||
pnl=pnl,
|
pnl=pnl,
|
||||||
pnl_percent=pnl_percent,
|
pnl_percent=pnl_percent,
|
||||||
exit_order_id=order_id # 保存订单号,确保唯一性
|
exit_order_id=order_id, # 保存订单号,确保唯一性
|
||||||
|
duration_minutes=duration_minutes,
|
||||||
|
exit_time_ts=exit_time_ts,
|
||||||
)
|
)
|
||||||
updated_count += 1
|
updated_count += 1
|
||||||
logger.debug(f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, 成交价: {avg_price:.4f})")
|
logger.debug(
|
||||||
|
f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, "
|
||||||
|
f"类型: {otype or '-'}, 原因: {exit_reason}, 成交价: {avg_price:.4f})"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# 这是开仓订单,检查数据库中是否已存在(通过订单号)
|
# 这是开仓订单,检查数据库中是否已存在(通过订单号)
|
||||||
existing_trade = Trade.get_by_entry_order_id(order_id)
|
existing_trade = Trade.get_by_entry_order_id(order_id)
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,17 @@ class Trade:
|
||||||
return db.execute_one("SELECT LAST_INSERT_ID() as id")['id']
|
return db.execute_one("SELECT LAST_INSERT_ID() as id")['id']
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent, exit_order_id=None, strategy_type=None, duration_minutes=None):
|
def update_exit(
|
||||||
|
trade_id,
|
||||||
|
exit_price,
|
||||||
|
exit_reason,
|
||||||
|
pnl,
|
||||||
|
pnl_percent,
|
||||||
|
exit_order_id=None,
|
||||||
|
strategy_type=None,
|
||||||
|
duration_minutes=None,
|
||||||
|
exit_time_ts=None,
|
||||||
|
):
|
||||||
"""更新平仓信息(使用北京时间)
|
"""更新平仓信息(使用北京时间)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -199,6 +209,10 @@ class Trade:
|
||||||
|
|
||||||
注意:如果 exit_order_id 已存在且属于其他交易记录,会跳过更新 exit_order_id 以避免唯一约束冲突
|
注意:如果 exit_order_id 已存在且属于其他交易记录,会跳过更新 exit_order_id 以避免唯一约束冲突
|
||||||
"""
|
"""
|
||||||
|
# exit_time_ts: 允许外部传入“真实成交时间”(Unix秒)以便统计持仓时长更准确
|
||||||
|
try:
|
||||||
|
exit_time = int(exit_time_ts) if exit_time_ts is not None else get_beijing_time()
|
||||||
|
except Exception:
|
||||||
exit_time = get_beijing_time()
|
exit_time = get_beijing_time()
|
||||||
|
|
||||||
# 如果提供了 exit_order_id,先检查是否已被其他交易记录使用
|
# 如果提供了 exit_order_id,先检查是否已被其他交易记录使用
|
||||||
|
|
@ -207,9 +221,11 @@ class Trade:
|
||||||
existing_trade = Trade.get_by_exit_order_id(exit_order_id)
|
existing_trade = Trade.get_by_exit_order_id(exit_order_id)
|
||||||
if existing_trade:
|
if existing_trade:
|
||||||
if existing_trade['id'] == trade_id:
|
if existing_trade['id'] == trade_id:
|
||||||
# 如果 exit_order_id 属于当前交易记录,说明已经更新过了,直接返回
|
# exit_order_id 属于当前交易记录:允许继续更新(比如补写 exit_reason / exit_time / duration)
|
||||||
logger.debug(f"交易记录 {trade_id} 的 exit_order_id {exit_order_id} 已存在,跳过更新")
|
# 不需要提前 return
|
||||||
return
|
logger.debug(
|
||||||
|
f"交易记录 {trade_id} 的 exit_order_id {exit_order_id} 已存在,将继续更新其他字段"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# 如果 exit_order_id 已被其他交易记录使用,记录警告但不更新 exit_order_id
|
# 如果 exit_order_id 已被其他交易记录使用,记录警告但不更新 exit_order_id
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|
|
||||||
|
|
@ -1672,26 +1672,90 @@ class PositionManager:
|
||||||
|
|
||||||
# 从历史订单中获取平仓订单号
|
# 从历史订单中获取平仓订单号
|
||||||
exit_order_id = None
|
exit_order_id = None
|
||||||
|
latest_close_order = None
|
||||||
if close_orders:
|
if close_orders:
|
||||||
exit_order_id = close_orders[0].get('orderId')
|
exit_order_id = close_orders[0].get('orderId')
|
||||||
|
latest_close_order = close_orders[0]
|
||||||
if exit_order_id:
|
if exit_order_id:
|
||||||
logger.info(f"{symbol} [状态同步] 找到平仓订单号: {exit_order_id}")
|
logger.info(f"{symbol} [状态同步] 找到平仓订单号: {exit_order_id}")
|
||||||
|
|
||||||
# 使用 try-except 包裹,确保异常被正确处理
|
# 使用 try-except 包裹,确保异常被正确处理
|
||||||
try:
|
try:
|
||||||
# 计算持仓持续时间和策略类型
|
# 计算持仓持续时间和策略类型
|
||||||
entry_time = trade.get('entry_time')
|
# exit_reason 细分:优先看币安平仓订单类型,其次用价格接近止损/止盈价做兜底
|
||||||
duration_minutes = None
|
exit_reason = "sync"
|
||||||
if entry_time:
|
exit_time_ts = None
|
||||||
try:
|
try:
|
||||||
from datetime import datetime
|
if latest_close_order and isinstance(latest_close_order, dict):
|
||||||
if isinstance(entry_time, str):
|
otype = str(
|
||||||
entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
|
latest_close_order.get("type")
|
||||||
|
or latest_close_order.get("origType")
|
||||||
|
or ""
|
||||||
|
).upper()
|
||||||
|
if "TRAILING" in otype:
|
||||||
|
exit_reason = "trailing_stop"
|
||||||
|
elif "TAKE_PROFIT" in otype:
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif "STOP" in otype:
|
||||||
|
# STOP / STOP_MARKET 通常对应止损触发
|
||||||
|
exit_reason = "stop_loss"
|
||||||
|
elif otype in ("MARKET", "LIMIT"):
|
||||||
|
exit_reason = "manual"
|
||||||
|
|
||||||
|
ms = latest_close_order.get("updateTime") or latest_close_order.get("time")
|
||||||
|
try:
|
||||||
|
if ms:
|
||||||
|
exit_time_ts = int(int(ms) / 1000)
|
||||||
|
except Exception:
|
||||||
|
exit_time_ts = None
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# 保持默认 sync
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 价格兜底:如果能明显命中止损/止盈价,则覆盖 exit_reason
|
||||||
|
try:
|
||||||
|
def _close_to(a: float, b: float, max_pct: float = 0.01) -> bool:
|
||||||
|
if a <= 0 or b <= 0:
|
||||||
|
return False
|
||||||
|
return abs((a - b) / b) <= max_pct
|
||||||
|
|
||||||
|
ep = float(exit_price or 0)
|
||||||
|
if ep > 0:
|
||||||
|
sl = trade.get("stop_loss_price")
|
||||||
|
tp = trade.get("take_profit_price")
|
||||||
|
tp1 = trade.get("take_profit_1")
|
||||||
|
tp2 = trade.get("take_profit_2")
|
||||||
|
if sl is not None and _close_to(ep, float(sl), max_pct=0.01):
|
||||||
|
exit_reason = "stop_loss"
|
||||||
|
elif tp is not None and _close_to(ep, float(tp), max_pct=0.01):
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.01):
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.01):
|
||||||
|
exit_reason = "take_profit"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 持仓持续时间(分钟):优先用币安订单时间,否则用当前时间
|
||||||
|
entry_time = trade.get("entry_time")
|
||||||
|
duration_minutes = None
|
||||||
|
try:
|
||||||
|
et = None
|
||||||
|
if isinstance(entry_time, (int, float)):
|
||||||
|
et = int(entry_time)
|
||||||
|
elif isinstance(entry_time, str) and entry_time.strip():
|
||||||
|
# 兼容旧格式:字符串时间戳/日期字符串
|
||||||
|
s = entry_time.strip()
|
||||||
|
if s.isdigit():
|
||||||
|
et = int(s)
|
||||||
else:
|
else:
|
||||||
entry_dt = entry_time
|
from datetime import datetime
|
||||||
exit_dt = get_beijing_time() # 使用北京时间计算持续时间
|
et = int(datetime.fromisoformat(s).timestamp())
|
||||||
duration = exit_dt - entry_dt
|
|
||||||
duration_minutes = int(duration.total_seconds() / 60)
|
xt = int(exit_time_ts) if exit_time_ts is not None else int(get_beijing_time())
|
||||||
|
if et is not None and xt >= et:
|
||||||
|
duration_minutes = int((xt - et) / 60)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"计算持仓持续时间失败: {e}")
|
logger.debug(f"计算持仓持续时间失败: {e}")
|
||||||
|
|
||||||
|
|
@ -1700,12 +1764,13 @@ class PositionManager:
|
||||||
Trade.update_exit(
|
Trade.update_exit(
|
||||||
trade_id=trade_id,
|
trade_id=trade_id,
|
||||||
exit_price=exit_price,
|
exit_price=exit_price,
|
||||||
exit_reason='sync', # 标记为同步更新
|
exit_reason=exit_reason,
|
||||||
pnl=pnl,
|
pnl=pnl,
|
||||||
pnl_percent=pnl_percent,
|
pnl_percent=pnl_percent,
|
||||||
exit_order_id=exit_order_id, # 保存币安平仓订单号
|
exit_order_id=exit_order_id, # 保存币安平仓订单号
|
||||||
strategy_type=strategy_type,
|
strategy_type=strategy_type,
|
||||||
duration_minutes=duration_minutes
|
duration_minutes=duration_minutes,
|
||||||
|
exit_time_ts=exit_time_ts,
|
||||||
)
|
)
|
||||||
except Exception as update_error:
|
except Exception as update_error:
|
||||||
# update_exit 内部已经有异常处理,但如果仍然失败,记录错误但不中断同步流程
|
# update_exit 内部已经有异常处理,但如果仍然失败,记录错误但不中断同步流程
|
||||||
|
|
@ -1719,13 +1784,13 @@ class PositionManager:
|
||||||
try:
|
try:
|
||||||
from database.connection import db
|
from database.connection import db
|
||||||
from database.models import get_beijing_time
|
from database.models import get_beijing_time
|
||||||
exit_time = get_beijing_time()
|
exit_time = int(exit_time_ts) if exit_time_ts is not None else get_beijing_time()
|
||||||
db.execute_update(
|
db.execute_update(
|
||||||
"""UPDATE trades
|
"""UPDATE trades
|
||||||
SET exit_price = %s, exit_time = %s,
|
SET exit_price = %s, exit_time = %s,
|
||||||
exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed'
|
exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed'
|
||||||
WHERE id = %s""",
|
WHERE id = %s""",
|
||||||
(exit_price, exit_time, 'sync', pnl, pnl_percent, trade_id)
|
(exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id)
|
||||||
)
|
)
|
||||||
logger.info(f"{symbol} [状态同步] ✓ 已更新(跳过 exit_order_id)")
|
logger.info(f"{symbol} [状态同步] ✓ 已更新(跳过 exit_order_id)")
|
||||||
except Exception as retry_error:
|
except Exception as retry_error:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user