fix期权复盘与数据统计双口径盈亏,并修复卖回封顶导致盈利单冻成亏损。
平仓卖光可卖币后再按账户差分结算复盘;数据统计改为历史币盈亏×卖回价;复盘展示初始权利金与盈利率,并附历史修复脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -451,14 +451,14 @@ def sell_residual_after_option_flat(
|
||||
underlying: str,
|
||||
inst_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""期权已平:卖掉本桥残留标的币;优先关闭 matching holding/pending 桥.
|
||||
"""期权已平:卖光交易户该标的可卖币(一次一仓,含盈利币).
|
||||
|
||||
规格:平仓卖回禁止按开仓权利金 coin_bought 封顶;仍有可卖则不关桥、不结算.
|
||||
可传 get_db 可调用对象(推荐)或已打开的 Connection。
|
||||
卖币(尤其模拟盘写钱包)期间绝不握着业务连接,否则会 database is locked。
|
||||
"""
|
||||
get_db = conn_or_get_db if callable(conn_or_get_db) else None
|
||||
bid: int | None = None
|
||||
sell_cap: float | None = None
|
||||
want_inst = str(inst_id or "").strip()
|
||||
|
||||
def _pick_bridge(bridges: list[dict[str, Any]]) -> dict[str, Any] | None:
|
||||
@@ -485,7 +485,6 @@ def sell_residual_after_option_flat(
|
||||
target = _pick_bridge(list_open_bridges(conn))
|
||||
if target is not None:
|
||||
bid = int(target["id"])
|
||||
sell_cap = _safe_float(target.get("coin_bought"))
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
@@ -501,7 +500,6 @@ def sell_residual_after_option_flat(
|
||||
target = _pick_bridge(list_open_bridges(conn))
|
||||
if target is not None:
|
||||
bid = int(target["id"])
|
||||
sell_cap = _safe_float(target.get("coin_bought"))
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
@@ -512,13 +510,10 @@ def sell_residual_after_option_flat(
|
||||
avail_chk = fetch_trading_coin_available(ex, underlying)
|
||||
except Exception:
|
||||
avail_chk = None
|
||||
dust_amt = avail_chk
|
||||
if dust_amt is None and sell_cap is not None:
|
||||
dust_amt = sell_cap
|
||||
if dust_amt is not None and not is_sellable_spot_coin(ex, underlying, dust_amt):
|
||||
if avail_chk is not None and not is_sellable_spot_coin(ex, underlying, avail_chk):
|
||||
dust_msg = "粉尘低于现货最小可卖,已忽略"
|
||||
if bid is None:
|
||||
return {"ok": True, "msg": dust_msg, "skipped": True, "dust": True}
|
||||
return {"ok": True, "msg": dust_msg, "skipped": True, "dust": True, "sell_complete": True}
|
||||
def _close_dust(c: sqlite3.Connection) -> dict[str, Any]:
|
||||
update_bridge(
|
||||
c,
|
||||
@@ -533,6 +528,7 @@ def sell_residual_after_option_flat(
|
||||
"msg": dust_msg,
|
||||
"skipped": True,
|
||||
"dust": True,
|
||||
"sell_complete": True,
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_CLOSED,
|
||||
}
|
||||
@@ -547,20 +543,33 @@ def sell_residual_after_option_flat(
|
||||
pass
|
||||
return _close_dust(conn_or_get_db)
|
||||
|
||||
# 有桥时按 coin_bought 封顶,避免误卖无关余额;无桥空仓扫尾仍卖光可用
|
||||
# 一次一仓空仓后卖光可用(含盈利币);禁止用 coin_bought/权利金封顶
|
||||
# 此处无业务连接:模拟盘 convert 可安全另开写钱包
|
||||
sell = spot_market_sell_coin_to_usdt(
|
||||
ex,
|
||||
underlying=underlying,
|
||||
coin_amount=sell_cap if (bid is not None and sell_cap is not None and sell_cap > 0) else None,
|
||||
coin_amount=None,
|
||||
)
|
||||
if bid is None:
|
||||
if not sell.get("ok"):
|
||||
msg = str(sell.get("msg") or "")
|
||||
if "无可用" in msg or "过小" in msg:
|
||||
return {"ok": True, "msg": "无残留币需卖回", "skipped": True}
|
||||
return {"ok": False, "msg": msg, "bridge_status": BRIDGE_PENDING_SELL}
|
||||
return {"ok": True, "sell": sell, "bridge_status": None}
|
||||
return {"ok": True, "msg": "无残留币需卖回", "skipped": True, "sell_complete": True}
|
||||
return {"ok": False, "msg": msg, "bridge_status": BRIDGE_PENDING_SELL, "sell_complete": False}
|
||||
# 再确认无可卖残留
|
||||
try:
|
||||
left = fetch_trading_coin_available(ex, underlying)
|
||||
except Exception:
|
||||
left = None
|
||||
if left is not None and is_sellable_spot_coin(ex, underlying, left):
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": f"卖回未完成,仍有可卖 {str(underlying or '').upper()}",
|
||||
"incomplete": True,
|
||||
"sell_complete": False,
|
||||
"sell": sell,
|
||||
}
|
||||
return {"ok": True, "sell": sell, "bridge_status": None, "sell_complete": True}
|
||||
|
||||
def _apply_bridge(c: sqlite3.Connection) -> dict[str, Any]:
|
||||
if not sell.get("ok"):
|
||||
@@ -580,6 +589,7 @@ def sell_residual_after_option_flat(
|
||||
"msg": "粉尘低于现货最小可卖,已忽略",
|
||||
"skipped": True,
|
||||
"dust": True,
|
||||
"sell_complete": True,
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_CLOSED,
|
||||
}
|
||||
@@ -595,10 +605,36 @@ def sell_residual_after_option_flat(
|
||||
"msg": msg,
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_PENDING_SELL,
|
||||
"sell_complete": False,
|
||||
}
|
||||
recovered = _safe_float(sell.get("usdt_recovered"))
|
||||
if recovered is not None and recovered > 0:
|
||||
add_bridge_usdt_recovered(c, bid, recovered)
|
||||
# 卖后仍有可卖 → 不关桥、不结算(盈利币未卖回时曾误冻亏损)
|
||||
try:
|
||||
left = fetch_trading_coin_available(ex, underlying)
|
||||
except Exception:
|
||||
left = None
|
||||
if left is not None and is_sellable_spot_coin(ex, underlying, left):
|
||||
left_msg = f"卖回未完成,仍有可卖 {str(underlying or '').upper()}({float(left):g})"
|
||||
update_bridge(
|
||||
c,
|
||||
bid,
|
||||
status=BRIDGE_PENDING_SELL,
|
||||
sell_ord_id=str(sell.get("ord_id") or ""),
|
||||
coin_sold=_safe_float(sell.get("coin_sold")),
|
||||
inst_id=inst_id,
|
||||
message=left_msg,
|
||||
)
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": left_msg,
|
||||
"incomplete": True,
|
||||
"sell_complete": False,
|
||||
"sell": sell,
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_PENDING_SELL,
|
||||
}
|
||||
update_bridge(
|
||||
c,
|
||||
bid,
|
||||
@@ -609,7 +645,13 @@ def sell_residual_after_option_flat(
|
||||
message="期权已平,币已卖回 USDT",
|
||||
close=True,
|
||||
)
|
||||
return {"ok": True, "sell": sell, "bridge_id": bid, "bridge_status": BRIDGE_CLOSED}
|
||||
return {
|
||||
"ok": True,
|
||||
"sell": sell,
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_CLOSED,
|
||||
"sell_complete": True,
|
||||
}
|
||||
|
||||
if get_db is not None:
|
||||
conn2 = get_db()
|
||||
|
||||
Reference in New Issue
Block a user