Fix post-close auto sell: restore missing spot min-size helpers used by sim sell.
Sell was silently failing on ImportError; surface sell result in close alert notes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2940,7 +2940,11 @@
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({}),
|
body: JSON.stringify({}),
|
||||||
});
|
});
|
||||||
alert(d.msg || "已平仓 #" + (d.plan_id || planId));
|
var tip = d.msg || "已平仓 #" + (d.plan_id || planId);
|
||||||
|
if (Array.isArray(d.notes) && d.notes.length) {
|
||||||
|
tip += "\n" + d.notes.join("\n");
|
||||||
|
}
|
||||||
|
alert(tip);
|
||||||
void loadLivePositions();
|
void loadLivePositions();
|
||||||
void loadActivePlans();
|
void loadActivePlans();
|
||||||
void loadHistory();
|
void loadHistory();
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ def _sell_all_coin_to_usdt(cfg: dict[str, Any], underlying: str) -> dict[str, An
|
|||||||
from lib.options.options_spot_bridge_lib import spot_market_sell_coin_to_usdt
|
from lib.options.options_spot_bridge_lib import spot_market_sell_coin_to_usdt
|
||||||
|
|
||||||
ex = cfg.get("exchange_options") or cfg.get("exchange")
|
ex = cfg.get("exchange_options") or cfg.get("exchange")
|
||||||
|
if callable(ex):
|
||||||
|
ex = ex()
|
||||||
if ex is None:
|
if ex is None:
|
||||||
return {"ok": False, "msg": "无交易所实例,跳过卖币"}
|
return {"ok": False, "msg": "无交易所实例,跳过卖币"}
|
||||||
return spot_market_sell_coin_to_usdt(ex, underlying=str(underlying or "ETH").upper())
|
return spot_market_sell_coin_to_usdt(ex, underlying=str(underlying or "ETH").upper())
|
||||||
|
|||||||
@@ -1681,18 +1681,36 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
|
|||||||
notes.append(f"永续已平 {symbol}")
|
notes.append(f"永续已平 {symbol}")
|
||||||
_release_db_write(conn)
|
_release_db_write(conn)
|
||||||
|
|
||||||
# 3) 卖币回 USDT
|
# 3) 卖币回 USDT(期权平仓后交易账户应有标的币)
|
||||||
sell_r: dict[str, Any] = {"ok": True, "skipped": True}
|
sell_r: dict[str, Any] = {"ok": False, "msg": "未执行卖币"}
|
||||||
try:
|
try:
|
||||||
_release_db_write(conn)
|
_release_db_write(conn)
|
||||||
sell_ex = cfg.get("exchange_options") or cfg.get("exchange")
|
sell_ex = cfg.get("exchange_options") or cfg.get("exchange")
|
||||||
if sell_ex is not None:
|
if callable(sell_ex):
|
||||||
|
sell_ex = sell_ex()
|
||||||
|
if sell_ex is None:
|
||||||
|
sell_r = {"ok": False, "msg": "无交易所实例,无法卖币"}
|
||||||
|
else:
|
||||||
sell_r = spot_market_sell_coin_to_usdt(sell_ex, underlying=uly)
|
sell_r = spot_market_sell_coin_to_usdt(sell_ex, underlying=uly)
|
||||||
|
# 无可用币视为已无需卖(非失败),其它失败要暴露
|
||||||
|
if not sell_r.get("ok"):
|
||||||
|
msg_l = str(sell_r.get("msg") or "")
|
||||||
|
if "无可用" in msg_l or "可卖数量过小" in msg_l:
|
||||||
|
sell_r = {**sell_r, "ok": True, "skipped": True, "soft": True}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sell_r = {"ok": False, "msg": str(e)}
|
sell_r = {"ok": False, "msg": str(e)}
|
||||||
close_detail["sell_coin"] = sell_r
|
close_detail["sell_coin"] = sell_r
|
||||||
if sell_r.get("ok"):
|
if sell_r.get("ok") and not sell_r.get("skipped"):
|
||||||
notes.append("已卖币回 USDT" if not sell_r.get("skipped") else "卖币跳过")
|
notes.append(
|
||||||
|
"已卖币回 USDT"
|
||||||
|
+ (
|
||||||
|
f" {sell_r.get('coin_sold')}→{sell_r.get('usdt_recovered')}U"
|
||||||
|
if sell_r.get("coin_sold") is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif sell_r.get("skipped"):
|
||||||
|
notes.append("卖币跳过: " + str(sell_r.get("msg") or "无可用币"))
|
||||||
else:
|
else:
|
||||||
notes.append("卖币失败: " + str(sell_r.get("msg") or ""))
|
notes.append("卖币失败: " + str(sell_r.get("msg") or ""))
|
||||||
|
|
||||||
@@ -1730,11 +1748,18 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
|
|||||||
notify_plan_end(cfg, conn, plan2)
|
notify_plan_end(cfg, conn, plan2)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
sell_ok = bool(sell_r.get("ok"))
|
||||||
|
msg = "已平仓并结束计划"
|
||||||
|
if sell_ok and not sell_r.get("skipped"):
|
||||||
|
msg = "已平仓并卖币回 USDT"
|
||||||
|
elif not sell_ok:
|
||||||
|
msg = "已平仓,但卖币失败: " + str(sell_r.get("msg") or "")
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"plan_id": int(plan_id),
|
"plan_id": int(plan_id),
|
||||||
"msg": "已平仓并结束计划",
|
"msg": msg,
|
||||||
"notes": notes,
|
"notes": notes,
|
||||||
"close": close_detail,
|
"close": close_detail,
|
||||||
|
"sell_coin": sell_r,
|
||||||
"realized_pnl_total": plan2.get("realized_pnl_total") if plan2 else total,
|
"realized_pnl_total": plan2.get("realized_pnl_total") if plan2 else total,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,4 +431,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/hedge_plan.js?v=57"></script>
|
<script src="/static/hedge_plan.js?v=58"></script>
|
||||||
|
|||||||
@@ -177,6 +177,50 @@ def fetch_trading_coin_available(ex: Any, ccy: str) -> float | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def spot_min_sell_sz(ex: Any, ccy: str) -> float:
|
||||||
|
"""OKX 现货最小卖出量(base ccy);读不到则用保守默认."""
|
||||||
|
ccy_u = (ccy or "").strip().upper()
|
||||||
|
defaults = {"BTC": 1e-5, "ETH": 1e-4}
|
||||||
|
default = float(defaults.get(ccy_u, 1e-4))
|
||||||
|
inst_id = spot_quote_inst_id(ccy_u)
|
||||||
|
if not inst_id:
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
if ex is not None and hasattr(ex, "public_get_public_instruments"):
|
||||||
|
resp = ex.public_get_public_instruments({"instType": "SPOT", "instId": inst_id})
|
||||||
|
data = (resp or {}).get("data") or []
|
||||||
|
if data:
|
||||||
|
raw = data[0].get("minSz")
|
||||||
|
if raw is not None and str(raw).strip() != "":
|
||||||
|
return float(raw)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
if ex is not None and hasattr(ex, "market"):
|
||||||
|
m = ex.market(inst_id.replace("-", "/"))
|
||||||
|
lim = (m or {}).get("limits") or {}
|
||||||
|
amt = (lim.get("amount") or {}).get("min")
|
||||||
|
if amt is not None:
|
||||||
|
return float(amt)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def is_sellable_spot_coin(ex: Any, ccy: str, amount: float) -> bool:
|
||||||
|
"""数量是否达到现货最小卖出门槛."""
|
||||||
|
try:
|
||||||
|
amt = float(amount or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
if amt <= 0:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return amt + 1e-15 >= float(spot_min_sell_sz(ex, ccy))
|
||||||
|
except Exception:
|
||||||
|
return amt > 0
|
||||||
|
|
||||||
|
|
||||||
def spot_market_buy_coin_with_usdt(
|
def spot_market_buy_coin_with_usdt(
|
||||||
ex: Any,
|
ex: Any,
|
||||||
*,
|
*,
|
||||||
|
|||||||
Reference in New Issue
Block a user