diff --git a/crypto_monitor_okx/.env.example b/crypto_monitor_okx/.env.example index 0fbe210..53a63ba 100644 --- a/crypto_monitor_okx/.env.example +++ b/crypto_monitor_okx/.env.example @@ -115,6 +115,10 @@ OKX_OPTIONS_ENABLED=false OKX_OPTIONS_ACCOUNT_LABEL=账户·期权 OKX_OPTIONS_TRADE_BUDGET_USDC=10 OKX_OPTIONS_BUDGET_BUFFER=0.95 +# 全仓复利:开关控制下单页是否出现该模式;上限开关另控封顶额度 +OKX_OPTIONS_COMPOUND_FULL_ENABLED=true +OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED=false +OKX_OPTIONS_COMPOUND_FULL_CAP_USDC=300 # 交易模式三选一(热更):options=单独期权 / perp_options=永期对冲 / options_options=期期对冲 # 选单独期权时隐藏对冲导航与对冲配置;选对冲时不可单独开期权,仓位按「对冲组数上限」 OKX_TRADE_MODE=options diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 2670968..d1b33a7 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6875,6 +6875,10 @@ def render_main_page(page="trade", embed_mode=None): hedge_plan_budget_buffer=float(os.getenv("HEDGE_PLAN_BUDGET_BUFFER") or "0.95"), options_trade_budget=OKX_OPTIONS_TRADE_BUDGET_USDC, options_budget_buffer=float(os.getenv("OKX_OPTIONS_BUDGET_BUFFER") or "0.95"), + options_compound_full_enabled=os.getenv( + "OKX_OPTIONS_COMPOUND_FULL_ENABLED", "true" + ).lower() + in ("1", "true", "yes", "on"), options_compound_full_cap_enabled=os.getenv( "OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", "false" ).lower() diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 797e83c..f1a6ca1 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -276,18 +276,36 @@ return el ? el.value : "sheets"; } + function compoundFullEnabled() { + return !!(root && String(root.dataset.compoundFullEnabled || "1") === "1"); + } + function updateSizeInputs() { const mode = currentSizeMode(); const sheetsEl = document.getElementById("opt-sheets-amount"); const ethEl = document.getElementById("opt-eth-amount"); const hint = document.getElementById("opt-budget-full-hint"); const compoundHint = document.getElementById("opt-compound-full-hint"); + const compoundWrap = document.getElementById("opt-size-mode-compound-wrap"); const capEl = document.getElementById("opt-budget-full-cap"); const compoundCapLine = document.getElementById("opt-compound-cap-line"); - if (sheetsEl) sheetsEl.style.display = mode === "sheets" ? "" : "none"; - if (ethEl) ethEl.style.display = mode === "eth_amount" ? "" : "none"; - if (hint) hint.style.display = mode === "budget_full" ? "" : "none"; - if (compoundHint) compoundHint.style.display = mode === "compound_full" ? "" : "none"; + const compoundOn = compoundFullEnabled(); + if (compoundWrap) { + compoundWrap.hidden = !compoundOn; + const radio = compoundWrap.querySelector('input[name="opt-size-mode"]'); + if (radio) radio.disabled = !compoundOn; + if (!compoundOn && mode === "compound_full") { + const sheetsRadio = document.querySelector('input[name="opt-size-mode"][value="sheets"]'); + if (sheetsRadio) { + sheetsRadio.checked = true; + } + } + } + const modeNow = currentSizeMode(); + if (sheetsEl) sheetsEl.style.display = modeNow === "sheets" ? "" : "none"; + if (ethEl) ethEl.style.display = modeNow === "eth_amount" ? "" : "none"; + if (hint) hint.style.display = modeNow === "budget_full" ? "" : "none"; + if (compoundHint) compoundHint.style.display = modeNow === "compound_full" && compoundOn ? "" : "none"; if (capEl && root && root.dataset.tradeBudget) { const n = Number(root.dataset.tradeBudget); if (Number.isFinite(n) && n > 0) capEl.textContent = n.toFixed(2); diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py index 739964e..b288608 100644 --- a/lib/env/env_ui_manifest.py +++ b/lib/env/env_ui_manifest.py @@ -145,15 +145,20 @@ _OPTIONS_SECTION: dict[str, Any] = { ("OKX_OPTIONS_ACCOUNT_LABEL", "期权账户备注", ""), ("OKX_OPTIONS_TRADE_BUDGET_USDC", "单笔预算(USDC)", ""), ("OKX_OPTIONS_BUDGET_BUFFER", "预算缓冲比例", "如 0.95;打满/全仓复利共用"), + ( + "OKX_OPTIONS_COMPOUND_FULL_ENABLED", + "全仓复利开关", + "默认 true;开启后下单页出现「全仓复利」模式;关闭则隐藏且拒绝该模式开仓", + ), ( "OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", "全仓复利上限开关", - "默认 false=关闭上限,用期权户全部可用;true 时按下方上限封顶", + "仅全仓复利开启时有意义;默认 false=不设上限用期权户全部可用;true 时按下方上限封顶", ), ( "OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", "全仓复利上限(USDC)", - "仅上限开关开启时生效;例如 300", + "仅「全仓复利」且「上限开关」都开启时生效;例如 300", ), ( "OKX_OPTIONS_MAX_ACTIVE_POSITIONS", diff --git a/lib/options/options_register.py b/lib/options/options_register.py index 4f175d4..3130237 100644 --- a/lib/options/options_register.py +++ b/lib/options/options_register.py @@ -103,6 +103,7 @@ def _build_cfg(app_module: Any) -> dict[str, Any]: "render_main_page": app_module.render_main_page, "trade_budget": _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", 10.0), "budget_buffer": _env_float("OKX_OPTIONS_BUDGET_BUFFER", 0.95), + "compound_full_enabled": _env_bool("OKX_OPTIONS_COMPOUND_FULL_ENABLED", True), "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), "default_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper(), @@ -176,8 +177,14 @@ def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]: return resolve_budget_full_usdc(trading, float(cap)), "" +def _compound_full_enabled() -> bool: + return _env_bool("OKX_OPTIONS_COMPOUND_FULL_ENABLED", True) + + def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]: """全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer.""" + if not _compound_full_enabled(): + return None, "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)" from lib.exchange.okx_options_lib import fetch_options_trading_usdc from lib.options.options_pricing_lib import resolve_compound_full_usdc @@ -472,6 +479,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: 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)"}) budget, budget_err = _compound_full_usdc(cfg, ex) if budget is None: return jsonify({"ok": False, "msg": budget_err}) @@ -785,6 +794,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> 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)"}) budget, budget_err = _compound_full_usdc(cfg, ex) if budget is None: return jsonify({"ok": False, "msg": budget_err}) diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index f59d771..bc98b4c 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -2,6 +2,7 @@ data-default-underly="{{ options_default_underly | default('ETH') }}" data-budget-buffer="{{ options_budget_buffer | default(0.95) }}" data-trade-budget="{{ options_trade_budget | default(10) }}" + data-compound-full-enabled="{% if options_compound_full_enabled %}1{% else %}0{% endif %}" data-compound-cap-enabled="{% if options_compound_full_cap_enabled %}1{% else %}0{% endif %}" data-compound-cap-usdc="{{ '%.2f'|format(options_compound_full_cap_usdc|default(300)|float) }}" data-ask-liq-filter="{% if options_chain_ask_liq_filter is defined %}{{ '1' if options_chain_ask_liq_filter else '0' }}{% else %}1{% endif %}"> @@ -134,8 +135,8 @@ 按可用余额打满 -