Add spot sell retry UI; sell all trading-account coin on sell-back.

Fix coin-margin sell-back to use full available balance instead of bridge record; show retry banner and button on the options positions panel.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-23 08:01:06 +08:00
parent 6215d0975d
commit a18f1f6713
6 changed files with 152 additions and 42 deletions
+27 -1
View File
@@ -1423,7 +1423,33 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
rows.append(row)
finally:
conn.close()
return jsonify({"ok": True, "positions": rows})
spot_sell_hint = None
try:
from lib.options.options_margin_mode_lib import is_coin_margin_mode
if is_coin_margin_mode():
from lib.options.options_spot_bridge_lib import list_open_bridges
conn_b = cfg["get_db"]()
try:
from lib.options.options_db import init_options_tables
init_options_tables(conn_b)
open_bridges = list_open_bridges(conn_b)
bal = cfg["fetch_options_balances"](ex, scope="main")
bridge = open_bridges[0] if open_bridges else None
spot_sell_hint = {
"bridge_status": str(bridge.get("status") or "") if bridge else None,
"bridge_underlying": str(bridge.get("underlying") or "").upper() if bridge else None,
"trading_eth": bal.get("trading_eth_avail") or bal.get("trading_eth"),
"trading_btc": bal.get("trading_btc_avail") or bal.get("trading_btc"),
"default_underly": (cfg.get("default_underly") or "ETH").strip().upper(),
}
finally:
conn_b.close()
except Exception:
spot_sell_hint = None
return jsonify({"ok": True, "positions": rows, "spot_sell": spot_sell_hint})
@app.route("/api/options/targets")
@lr
+11 -32
View File
@@ -221,20 +221,16 @@ def spot_market_sell_coin_to_usdt(
underlying: str,
coin_amount: float | None = None,
) -> dict[str, Any]:
"""交易账户:市价卖出标的币换 USDT.coin_amount 空则尽量卖光可用."""
"""交易账户:市价卖出标的币换 USDT.始终卖光交易户可用余额(coin_amount 仅兼容旧调用,不参与定量)."""
ccy = (underlying or "ETH").upper()
amt = coin_amount
if amt is None or float(amt) <= 0:
avail = fetch_trading_coin_available(ex, ccy)
if avail is None or float(avail) <= 0:
return {"ok": False, "msg": f"交易账户无可用 {ccy}"}
amt = float(avail)
if float(amt) <= 0:
avail = fetch_trading_coin_available(ex, ccy)
if avail is None or float(avail) <= 0:
return {"ok": False, "msg": f"交易账户无可用 {ccy}"}
amt = float(avail)
if amt <= 0:
return {"ok": False, "msg": f"{ccy} 数量须大于 0"}
# 留一点粉尘避免精度拒单
sell_sz = float(amt)
if sell_sz > 1e-8:
sell_sz = max(0.0, sell_sz * 0.999)
sell_sz = max(0.0, amt * 0.999)
inst_id = spot_quote_inst_id(ccy)
try:
# 现货卖出数量精度:截到 8 位
@@ -278,20 +274,9 @@ def rollback_bought_coin_to_usdt(
reason: str = "",
coin_amount: float | None = None,
) -> dict[str, Any]:
"""买币后开期权失败:卖回 USDT 并关闭桥.优先卖 bridge 记录的买入量."""
amt = coin_amount
if amt is None or float(amt) <= 0:
ensure_bridge_table(conn)
row = conn.execute(
"SELECT coin_bought FROM options_spot_bridge WHERE id=?",
(int(bridge_id),),
).fetchone()
if row:
try:
amt = float(row[0] if not isinstance(row, dict) else row.get("coin_bought") or 0)
except (TypeError, ValueError, KeyError, IndexError):
amt = None
sell = spot_market_sell_coin_to_usdt(ex, underlying=underlying, coin_amount=amt)
"""买币后开期权失败:卖回 USDT 并关闭桥.卖光交易户可用标的币."""
_ = coin_amount # 兼容旧签名;定量以交易户可用为准
sell = spot_market_sell_coin_to_usdt(ex, underlying=underlying)
if not sell.get("ok"):
update_bridge(
conn,
@@ -328,13 +313,7 @@ def sell_residual_after_option_flat(
if not underlying or str(b.get("underlying") or "").upper() == underlying.upper():
target = b
break
coin_amt = None
if target is not None:
try:
coin_amt = float(target.get("coin_bought") or 0) or None
except (TypeError, ValueError):
coin_amt = None
sell = spot_market_sell_coin_to_usdt(ex, underlying=underlying, coin_amount=coin_amt)
sell = spot_market_sell_coin_to_usdt(ex, underlying=underlying)
if target is None:
if not sell.get("ok"):
msg = str(sell.get("msg") or "")
+4
View File
@@ -204,6 +204,10 @@
</div>
<div class="options-pos-tab-body">
<div class="options-pos-pane is-active" data-opt-pos-pane="live" role="tabpanel" aria-labelledby="opt-pos-tab-live">
<div id="opt-spot-sell-banner" class="opt-spot-sell-banner" hidden>
<div class="opt-spot-sell-banner-text" id="opt-spot-sell-banner-text"></div>
<button type="button" class="btn-primary" id="opt-spot-sell-retry-btn">重试卖回</button>
</div>
<div id="opt-target-monitors" class="opt-target-monitors" hidden>
<div class="opt-target-monitors-head">目标监控</div>
<div id="opt-target-monitors-list"></div>