中控可下发买一全平:客户端新增 /api/hub/options/close,由本地执行并拒绝无效买一。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-02 16:03:10 +08:00
parent 34322d1731
commit fcef3b3d37
2 changed files with 107 additions and 59 deletions
+78 -58
View File
@@ -76,8 +76,13 @@ def _register_options_hub_bridge(app: Flask, cfg: dict[str, Any]) -> None:
def snapshot_fn():
return build_options_hub_snapshot(cfg)
def close_fn(inst_id: str) -> dict[str, Any]:
# 中控总览只全平(不改张数);无效买一在执行层拒绝
return _run_manual_bid_close(cfg, inst_id, sheets=None, reason="中控平仓")
hub_ctx = dict(app.config.get("HUB_CTX") or {})
hub_ctx["options_snapshot_fn"] = snapshot_fn
hub_ctx["options_close_fn"] = close_fn
app.config["HUB_CTX"] = hub_ctx
@@ -169,6 +174,78 @@ def _require_options_ex(cfg: dict[str, Any]):
return ex, ""
def _run_manual_bid_close(
cfg: dict[str, Any],
inst_id: str,
*,
sheets: int | None = None,
reason: str = "手动平仓",
) -> dict[str, Any]:
"""手动买一限价平仓(全平或指定张数);残档/无效买一由 close_option_by_bid1 拒绝."""
ex, err = _require_options_ex(cfg)
if ex is None:
return {"ok": False, "msg": err}
inst_id = (inst_id or "").strip()
if not inst_id:
return {"ok": False, "msg": "缺少 inst_id"}
from lib.options.options_close_exec_lib import close_option_by_bid1
result = close_option_by_bid1(
cfg,
ex,
inst_id,
sheets=sheets,
require_recycle_gate=False,
)
if not result.get("ok"):
return result
from lib.exchange.okx_options_lib import invalidate_option_positions_cache
invalidate_option_positions_cache()
_sync_options_trades(cfg, force=True)
try:
from lib.options.options_notify_lib import notify_options_close
from lib.options.options_target_lib import cancel_target_monitor
conn2 = cfg["get_db"]()
try:
if result.get("fully_closed"):
cancel_target_monitor(conn2, inst_id=inst_id)
conn2.commit()
note = reason if result.get("fully_closed") else f"{reason}(部分)"
notify_options_close(
cfg,
conn2,
inst_id=inst_id,
reason=note,
sheets=result.get("submitted_sheets"),
premium_received=result.get("premium_received"),
close_quote=result.get("locked_bid_px") or result.get("bid"),
)
finally:
conn2.close()
except Exception:
pass
if result.get("fully_closed"):
try:
from lib.options.options_coin_open_lib import maybe_sell_spot_after_close
spot_sell = maybe_sell_spot_after_close(cfg, ex, inst_id=inst_id, close_result=result)
if spot_sell is not None:
result = dict(result)
result["spot_sell"] = spot_sell
if spot_sell.get("bridge_status") == "pending_sell_spot":
result["msg"] = (
str(result.get("msg") or "平仓成功")
+ ";但卖回 USDT 失败,请点「重试卖回」"
)
except Exception as e:
result = dict(result)
result["spot_sell"] = {"ok": False, "msg": str(e)}
_mark_balances_stale(cfg)
return result
def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
"""打满可用额度 = min(交易户可用 USDC, 单笔预算);calc_order_size 再乘 budget_buffer."""
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
@@ -1538,9 +1615,6 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
@app.route("/api/options/close", methods=["POST"])
@lr
def api_options_close():
ex, err = _require_options_ex(cfg)
if ex is None:
return jsonify({"ok": False, "msg": err})
data = request.get_json(silent=True) or {}
inst_id = (data.get("inst_id") or "").strip()
if not inst_id:
@@ -1552,62 +1626,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
sheets_i = int(sheets) if sheets is not None and str(sheets).strip() != "" else None
except (TypeError, ValueError):
return jsonify({"ok": False, "msg": "张数无效"})
from lib.options.options_close_exec_lib import close_option_by_bid1
# 手动买一平仓:只验有效流动性;2×门控仅用于自动/目标位平仓
result = close_option_by_bid1(
cfg,
ex,
inst_id,
sheets=sheets_i,
require_recycle_gate=False,
)
if result.get("ok"):
from lib.exchange.okx_options_lib import invalidate_option_positions_cache
invalidate_option_positions_cache()
_sync_options_trades(cfg, force=True)
try:
from lib.options.options_target_lib import cancel_target_monitor
from lib.options.options_notify_lib import notify_options_close
conn2 = cfg["get_db"]()
try:
if result.get("fully_closed"):
cancel_target_monitor(conn2, inst_id=inst_id)
conn2.commit()
reason = "手动平仓" if result.get("fully_closed") else "手动平仓(部分)"
notify_options_close(
cfg,
conn2,
inst_id=inst_id,
reason=reason,
sheets=result.get("submitted_sheets"),
premium_received=result.get("premium_received"),
close_quote=result.get("locked_bid_px") or result.get("bid"),
)
finally:
conn2.close()
except Exception:
pass
if result.get("fully_closed"):
try:
from lib.options.options_coin_open_lib import maybe_sell_spot_after_close
spot_sell = maybe_sell_spot_after_close(cfg, ex, inst_id=inst_id, close_result=result)
if spot_sell is not None:
result = dict(result)
result["spot_sell"] = spot_sell
if spot_sell.get("bridge_status") == "pending_sell_spot":
result["msg"] = (
str(result.get("msg") or "平仓成功")
+ ";但卖回 USDT 失败,请点「重试卖回」"
)
except Exception as e:
result = dict(result)
result["spot_sell"] = {"ok": False, "msg": str(e)}
_mark_balances_stale(cfg)
return jsonify(result)
return jsonify(_run_manual_bid_close(cfg, inst_id, sheets=sheets_i, reason="手动平仓"))
@app.route("/api/options/spot-bridge/retry-sell", methods=["POST"])
@lr