a
This commit is contained in:
parent
0c489bfdee
commit
f1bc8413df
|
|
@ -1895,9 +1895,11 @@ class PositionManager:
|
|||
# 使用 try-except 包裹,确保异常被正确处理
|
||||
try:
|
||||
# 计算持仓持续时间和策略类型
|
||||
# exit_reason 细分:优先看币安平仓订单类型,其次用价格接近止损/止盈价做兜底
|
||||
# exit_reason 细分:优先用价格匹配和特征判断,其次看币安订单类型
|
||||
exit_reason = "sync"
|
||||
exit_time_ts = None
|
||||
is_reduce_only = False
|
||||
|
||||
try:
|
||||
if latest_close_order and isinstance(latest_close_order, dict):
|
||||
otype = str(
|
||||
|
|
@ -1905,65 +1907,107 @@ class PositionManager:
|
|||
or latest_close_order.get("origType")
|
||||
or ""
|
||||
).upper()
|
||||
# 检查订单的 reduceOnly 字段:如果是 true,说明是自动平仓,不应该标记为 manual
|
||||
is_reduce_only = latest_close_order.get("reduceOnly", False)
|
||||
|
||||
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"):
|
||||
# 如果是 reduceOnly 订单,说明是自动平仓(可能是保护单触发的),先标记为 sync,后续用价格判断
|
||||
if is_reduce_only:
|
||||
exit_reason = "sync" # 临时标记,后续用价格判断
|
||||
else:
|
||||
exit_reason = "manual" # 非 reduceOnly 的 MARKET/LIMIT 订单才是真正的手动平仓
|
||||
|
||||
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
|
||||
# 这对于保护单触发的 MARKET 订单特别重要(币安的保护单触发后会生成 MARKET 订单)
|
||||
# ⚠️ 关键改进:优先使用价格匹配和特征判断(提高准确性)
|
||||
# 这对于保护单触发的 MARKET 订单特别重要(币安的保护单触发后会生成 MARKET 订单,但 reduceOnly 可能不准确)
|
||||
try:
|
||||
def _close_to(a: float, b: float, max_pct: float = 0.05) -> bool: # 从2%放宽到5%,以应对极端滑点下的同步识别
|
||||
def _close_to(a: float, b: float, max_pct: float = 0.10) -> bool: # 从5%放宽到10%,以应对极端滑点
|
||||
if a <= 0 or b <= 0:
|
||||
return False
|
||||
return abs((a - b) / b) <= max_pct
|
||||
|
||||
ep = float(exit_price or 0)
|
||||
if ep > 0:
|
||||
entry_price_val = float(trade.get("entry_price", 0) or 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.05):
|
||||
|
||||
# 计算持仓时间和亏损比例(用于特征判断)
|
||||
entry_time = trade.get("entry_time")
|
||||
duration_minutes = None
|
||||
if entry_time and exit_time_ts:
|
||||
try:
|
||||
duration_minutes = (exit_time_ts - int(entry_time)) / 60.0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pnl_percent = float(trade.get("pnl_percent", 0) or 0)
|
||||
|
||||
# 1. 优先检查止损价格匹配(提高容忍度到10%)
|
||||
if sl is not None and entry_price_val > 0 and ep > 0:
|
||||
sl_val = float(sl)
|
||||
# 价格匹配:平仓价接近止损价
|
||||
if _close_to(ep, sl_val, max_pct=0.10):
|
||||
exit_reason = "stop_loss"
|
||||
# 然后检查止盈
|
||||
elif tp is not None and _close_to(ep, float(tp), max_pct=0.05):
|
||||
# 方向匹配:BUY时平仓价 < 止损价,SELL时平仓价 > 止损价
|
||||
elif (trade.get("side") == "BUY" and ep < sl_val) or (trade.get("side") == "SELL" and ep > sl_val):
|
||||
# 如果价格在止损方向,且亏损比例较大,更可能是止损触发
|
||||
if pnl_percent < -5.0: # 亏损超过5%
|
||||
exit_reason = "stop_loss"
|
||||
logger.info(f"{trade.get('symbol')} [同步] 价格方向匹配止损,且亏损{pnl_percent:.2f}%,标记为止损")
|
||||
|
||||
# 2. 检查止盈价格匹配
|
||||
if exit_reason == "sync" and ep > 0:
|
||||
if tp is not None and _close_to(ep, float(tp), max_pct=0.10):
|
||||
exit_reason = "take_profit"
|
||||
elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.05):
|
||||
elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.10):
|
||||
exit_reason = "take_profit"
|
||||
elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.05):
|
||||
elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.10):
|
||||
exit_reason = "take_profit"
|
||||
# 如果之前标记为 sync 且是 reduceOnly 订单,但价格不匹配止损/止盈,可能是其他自动平仓(如移动止损)
|
||||
elif exit_reason == "sync" and is_reduce_only:
|
||||
|
||||
# 3. 特征判断:如果价格不匹配,但满足止损特征,也标记为止损
|
||||
if exit_reason == "sync" and entry_price_val > 0 and ep > 0:
|
||||
# 特征1:持仓时间短(< 30分钟)且亏损
|
||||
if duration_minutes and duration_minutes < 30 and pnl_percent < -5.0:
|
||||
# 特征2:价格在止损方向
|
||||
if sl is not None:
|
||||
sl_val = float(sl)
|
||||
if (trade.get("side") == "BUY" and ep < sl_val) or (trade.get("side") == "SELL" and ep > sl_val):
|
||||
exit_reason = "stop_loss"
|
||||
logger.info(f"{trade.get('symbol')} [同步] 特征判断:持仓{duration_minutes:.1f}分钟,亏损{pnl_percent:.2f}%,价格在止损方向,标记为止损")
|
||||
|
||||
# 4. 如果之前标记为 sync 且是 reduceOnly 订单,但价格不匹配止损/止盈,可能是其他自动平仓(如移动止损)
|
||||
if exit_reason == "sync" and is_reduce_only:
|
||||
# 检查是否是移动止损:如果价格接近入场价,可能是移动止损触发的
|
||||
entry_price_val = float(trade.get("entry_price", 0) or 0)
|
||||
if entry_price_val > 0 and _close_to(ep, entry_price_val, max_pct=0.01):
|
||||
exit_reason = "trailing_stop"
|
||||
except Exception:
|
||||
|
||||
# 5. 最后才看币安订单类型(作为兜底)
|
||||
if exit_reason == "sync" and latest_close_order and isinstance(latest_close_order, dict):
|
||||
otype = str(
|
||||
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:
|
||||
exit_reason = "stop_loss"
|
||||
elif otype in ("MARKET", "LIMIT"):
|
||||
# 只有在价格和特征都不匹配,且不是 reduceOnly 时,才标记为手动平仓
|
||||
if not is_reduce_only:
|
||||
# 再次检查:如果亏损很大,更可能是止损触发(币安API可能不准确)
|
||||
if pnl_percent < -10.0:
|
||||
exit_reason = "stop_loss" # 大额亏损,更可能是止损
|
||||
logger.warning(f"{trade.get('symbol')} [同步] 大额亏损{pnl_percent:.2f}%,即使reduceOnly=false也标记为止损")
|
||||
else:
|
||||
exit_reason = "manual"
|
||||
except Exception as e:
|
||||
logger.warning(f"判断平仓原因失败: {e}")
|
||||
pass
|
||||
|
||||
# 持仓持续时间(分钟):优先用币安订单时间,否则用当前时间
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user