修复全仓复利关闭后仍无法开仓:前端不再残留选中全仓,后端强制改指定张数。

审计:开关关闭时隐藏全仓芯片并自动勾选指定张数;报价/余额热同步 compound_full_enabled;API 将 compound_full 归一为 sheets(缺张数默认1);单测覆盖开关开关两种归一路径。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-12 16:14:53 +08:00
parent 9c19afc8d4
commit 271865fa3d
8 changed files with 168 additions and 28 deletions
+61 -10
View File
@@ -200,6 +200,16 @@ def _size_mode_budget_cap(
return None
def _normalize_size_mode(mode: str) -> tuple[str, str | None]:
"""全仓复利关闭时强制离开 compound_full,避免前端残留选中导致无法开仓."""
m = (mode or "sheets").strip() or "sheets"
if m == "compound_full" and not _compound_full_enabled():
return "sheets", "全仓复利已关闭,已改用指定张数"
if m == "budget_full" and _compound_full_enabled():
return "compound_full", None
return m, None
def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
"""全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer."""
if not _compound_full_enabled():
@@ -411,7 +421,16 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
return jsonify({"ok": False, "msg": err})
force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
bal = cfg["fetch_options_balances"](ex, force=force, scope="main")
return jsonify({"ok": True, **bal, "trade_budget": cfg["trade_budget"]})
return jsonify(
{
"ok": True,
**bal,
"trade_budget": _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", float(cfg.get("trade_budget") or 10)),
"compound_full_enabled": _compound_full_enabled(),
"compound_full_cap_enabled": _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False),
"compound_full_cap_usdc": _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0),
}
)
@app.route("/api/options/chain")
@lr
@@ -475,7 +494,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
ask = q.get("ask")
ct_mult = q.get("ct_mult") or 0.01
min_sz = q.get("min_sz") or 1
mode = (request.args.get("mode") or "budget_full").strip()
mode = (request.args.get("mode") or "sheets").strip()
sheet_count = None
try:
if request.args.get("sheets"):
@@ -486,26 +505,39 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
paid = _open_premium_paid(cfg, inst_id)
target = sheet_count if sheet_count is not None else 0
return jsonify(_attach_close_preview(cfg, ex, {**q, "pos": target, "premium_paid": paid}, sheets=target, premium_paid=paid))
mode, mode_note = _normalize_size_mode(mode)
budget = cfg["trade_budget"]
budget_cap = cfg["trade_budget"]
available_usdc = None
if mode == "budget_full":
blocked = _budget_full_blocked_by_compound_msg()
if blocked:
return jsonify({"ok": False, "msg": blocked})
return jsonify(
{
"ok": False,
"msg": blocked,
"compound_full_enabled": _compound_full_enabled(),
}
)
budget, budget_err = _budget_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
return jsonify({"ok": False, "msg": budget_err, "compound_full_enabled": _compound_full_enabled()})
budget_cap = budget
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
available_usdc = fetch_options_trading_usdc(ex)
elif mode == "compound_full":
if not _compound_full_enabled():
return jsonify({"ok": False, "msg": "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)"})
return jsonify(
{
"ok": False,
"msg": "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)",
"compound_full_enabled": False,
}
)
budget, budget_err = _compound_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
return jsonify({"ok": False, "msg": budget_err, "compound_full_enabled": True})
budget_cap = budget
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
@@ -721,6 +753,9 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
"compound_full_usdc": budget if mode == "compound_full" else None,
"mode": mode,
"mode_note": mode_note,
"compound_full_enabled": _compound_full_enabled(),
}
)
@@ -752,8 +787,12 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
return jsonify({"ok": False, "msg": f"互斥校验失败: {e}"})
data = request.get_json(silent=True) or {}
inst_id = (data.get("inst_id") or "").strip()
mode = (data.get("mode") or "budget_full").strip()
mode = (data.get("mode") or "sheets").strip()
mode, mode_note = _normalize_size_mode(mode)
signal_note = (data.get("signal_note") or "").strip()
if mode_note and mode == "sheets" and (data.get("mode") or "").strip() == "compound_full":
# 前端残留全仓复利选中时,已自动改指定张数;继续开仓
pass
target_index = None
raw_target = data.get("target_index")
if raw_target is not None and str(raw_target).strip() != "":
@@ -819,20 +858,32 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
try:
sheet_count = int(data.get("sheets"))
except (TypeError, ValueError):
return jsonify({"ok": False, "msg": "张数无效"})
sheet_count = None
if sheet_count is None or int(sheet_count) < 1:
# 全仓复利关闭后前端可能仍带着旧 mode 过来,归一后缺张数则默认 1
if (data.get("mode") or "").strip() == "compound_full":
sheet_count = 1
else:
return jsonify({"ok": False, "msg": "张数无效"})
budget = cfg["trade_budget"]
budget_cap = cfg["trade_budget"]
if mode == "budget_full":
blocked = _budget_full_blocked_by_compound_msg()
if blocked:
return jsonify({"ok": False, "msg": blocked})
return jsonify({"ok": False, "msg": blocked, "compound_full_enabled": _compound_full_enabled()})
budget, budget_err = _budget_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
budget_cap = budget
elif mode == "compound_full":
if not _compound_full_enabled():
return jsonify({"ok": False, "msg": "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)"})
return jsonify(
{
"ok": False,
"msg": "全仓复利未开启,请改用指定张数或先开启全仓复利",
"compound_full_enabled": False,
}
)
budget, budget_err = _compound_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
+1 -1
View File
@@ -350,4 +350,4 @@
</div>
</div>
<script src="/static/options_expiry_countdown.js?v=1"></script>
<script src="/static/options_panel.js?v=61"></script>
<script src="/static/options_panel.js?v=62"></script>