From 6607d0952685f65ee39e9899e1ee54d5046752c9 Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 14 Oct 2026 18:37:46 +0800 Subject: [PATCH] Make this repo hedge-only: skip standalone options UI by default. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nav/env only expose 永期/期期; set STANDALONE_OPTIONS_UI=true for emergency opt-in. Co-authored-by: Cursor --- README.md | 10 +++-- app.py | 28 +++++++++--- lib/env/env_schema.py | 1 - lib/env/env_ui_manifest.py | 4 +- lib/hedge_plan/okx_trade_mode_lib.py | 48 ++++++++++++-------- tests/test_okx_trade_mode.py | 66 ++++++++++++++++++++-------- 6 files changed, 106 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 0aecf8e..516d6c4 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,25 @@ OKX **币本位期权** + **U 本位永续对冲** 独立实例。 ## 产品范围 +本仓**仅对冲**。单独期权(含中控)请用专用期权仓库;此处默认不装期权页/复盘/独立盯盘。 + | 保留 | 说明 | |------|------| -| 单独期权 | 仅币本位(USDT→买币→开期权) | | 永期对冲 | 币本位期权腿 + USDT 永续腿 | | 期期对冲 | 双腿币本位期权 | | 模拟资金 | **默认 `sim`**,本地钱包撮合 | +| 期权执行层 | 报价/下单/买币桥,供对冲腿共用 | | 不提供 | 说明 | |--------|------| +| 单独期权 UI | 默认关闭;紧急可 `STANDALONE_OPTIONS_UI=true`(不推荐) | | 单独永续下单 | `/trade` 已重定向 | | 关键位自动开仓 | `/key_monitor` 已重定向 | | 策略趋势/滚仓 | 未安装 | | USDC 期权 | 已固定币本位 | | Windows 部署 | **仅 Ubuntu 一键部署** | -源仓 `crypto_monitor` **只读复制**,不回改源仓。中控二期再做。 +源仓 `crypto_monitor` **只读复制**,不回改源仓。 ## Ubuntu 一键部署(推荐) @@ -57,7 +60,8 @@ python app.py - `SIM_DEFAULT_MODE=sim`(默认模拟盘) - `OKX_OPTIONS_MARGIN_MODE=coin`(固定) -- `OKX_TRADE_MODE=options|perp_options|options_options` +- `OKX_TRADE_MODE=perp_options|options_options`(本仓仅对冲) +- `STANDALONE_OPTIONS_UI=false`(默认;勿开,除非临时救急) - `SIM_INITIAL_EQUITY_USDT=10000` - `APP_PORT=5010` diff --git a/app.py b/app.py index e1a63ce..6af91ff 100644 --- a/app.py +++ b/app.py @@ -6869,9 +6869,13 @@ def render_main_page(page="trade", embed_mode=None): from lib.instance.instance_display_prefs_lib import display_prefs_template_context _display_ctx = display_prefs_template_context(get_db) - from lib.hedge_plan.okx_trade_mode_lib import get_okx_trade_mode + from lib.hedge_plan.okx_trade_mode_lib import ( + get_okx_trade_mode, + standalone_options_ui_enabled, + ) _okx_trade_mode = get_okx_trade_mode() + _standalone_options_ui = standalone_options_ui_enabled() _hedge_mode_on = _okx_trade_mode in ("perp_options", "options_options") _show_perp_funds = show_perp_funds_enabled(exchange_key="okx") template_ctx = dict( @@ -6969,9 +6973,9 @@ def render_main_page(page="trade", embed_mode=None): exchange_display=EXCHANGE_DISPLAY_NAME, options_enabled=OKX_OPTIONS_ENABLED, show_perp_funds=_show_perp_funds or (options_margin_mode == "coin"), - options_nav_visible=True, + options_nav_visible=_standalone_options_ui and _okx_trade_mode == "options", okx_trade_mode=_okx_trade_mode, - options_open_allowed=_okx_trade_mode == "options", + options_open_allowed=_standalone_options_ui and _okx_trade_mode == "options", hedge_plan_enabled=_hedge_mode_on, hedge_plan_nav_visible=_hedge_mode_on, hedge_plan_show_perp_options=_okx_trade_mode == "perp_options", @@ -9937,13 +9941,23 @@ from lib.sim.register import install_sim_trading install_sim_trading(app, _REPO_ROOT, app_module=sys.modules[__name__]) -from lib.options.options_register import install_options_trading +# 本仓默认仅对冲:不装单独期权页/复盘/独立盯盘(执行层由 hedge 共用 okx_options_lib). +# 紧急需要单独期权 UI 时设 STANDALONE_OPTIONS_UI=true(不推荐;专用期权仓更完整). +from lib.hedge_plan.okx_trade_mode_lib import standalone_options_ui_enabled as _standalone_opt_ui -install_options_trading(app, _REPO_ROOT, app_module=sys.modules[__name__]) +if _standalone_opt_ui(): + from lib.options.options_register import install_options_trading -from lib.options.options_review_register import install_options_review + install_options_trading(app, _REPO_ROOT, app_module=sys.modules[__name__]) -install_options_review(app, _REPO_ROOT, app_module=sys.modules[__name__]) + from lib.options.options_review_register import install_options_review + + install_options_review(app, _REPO_ROOT, app_module=sys.modules[__name__]) +else: + print( + "[boot] STANDALONE_OPTIONS_UI=false → skip options/review UI (hedge-only)", + flush=True, + ) from lib.hedge_plan.hedge_plan_register import install_hedge_plan diff --git a/lib/env/env_schema.py b/lib/env/env_schema.py index 9d4e5f3..0d3afaf 100644 --- a/lib/env/env_schema.py +++ b/lib/env/env_schema.py @@ -163,7 +163,6 @@ SELECT_OPTIONS: dict[str, tuple[tuple[str, str], ...]] = { ("sheets", "张数"), ), "OKX_TRADE_MODE": ( - ("options", "单独期权"), ("perp_options", "永期对冲"), ("options_options", "期期对冲"), ), diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py index 079a6de..52d03f4 100644 --- a/lib/env/env_ui_manifest.py +++ b/lib/env/env_ui_manifest.py @@ -160,7 +160,7 @@ _MODE_SECTION: dict[str, Any] = { ( "OKX_TRADE_MODE", "交易模式", - "三选一:单独期权 / 永期对冲 / 期期对冲.选单独期权时隐藏对冲导航与对冲配置;选对冲时不可单独开期权", + "本仓仅对冲二选一:永期对冲 / 期期对冲.单独期权请用专用期权仓库", ), ], } @@ -351,7 +351,7 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = { "OKX_OPTIONS_CHAIN_MAX_DTE_DAYS": "14", "OKX_OPTIONS_MAX_DTE_DAYS": "2", "OKX_OPTIONS_MAX_ACTIVE_POSITIONS": "0", - "OKX_TRADE_MODE": "options", + "OKX_TRADE_MODE": "perp_options", "MAX_ACTIVE_HEDGE_PLANS": "1", "HEDGE_PLAN_OO_CLOSE_MODE_ENABLED": "true", "HEDGE_PLAN_OO_BIAS_SPLIT_BY": "budget", diff --git a/lib/hedge_plan/okx_trade_mode_lib.py b/lib/hedge_plan/okx_trade_mode_lib.py index 2da692c..ed9d3a8 100644 --- a/lib/hedge_plan/okx_trade_mode_lib.py +++ b/lib/hedge_plan/okx_trade_mode_lib.py @@ -1,8 +1,11 @@ -"""OKX 期权/对冲三选一模式(env: OKX_TRADE_MODE). +"""OKX 对冲交易模式(env: OKX_TRADE_MODE). -options → 仅单独期权(隐藏对冲导航与对冲 env 配置) -perp_options → 仅永期对冲(不可单独开期权;对冲组数上限 MAX_ACTIVE_HEDGE_PLANS) -options_options → 仅期期对冲(同上) +本仓定位:仅对冲(永期/期期).单独期权产品面已迁出,默认不安装其 UI/复盘/独立盯盘. +期权执行层(报价/下单/买币桥)仍由对冲腿共用. + +perp_options → 永期对冲 +options_options → 期期对冲 +options → 遗留值;未开 STANDALONE_OPTIONS_UI 时回退为永期 """ from __future__ import annotations @@ -13,6 +16,8 @@ MODE_OPTIONS = "options" MODE_PERP = "perp_options" MODE_OO = "options_options" VALID_MODES = frozenset({MODE_OPTIONS, MODE_PERP, MODE_OO}) +# env 可选值(本仓默认只暴露对冲两种) +HEDGE_MODES = frozenset({MODE_PERP, MODE_OO}) _ALIASES = { "option": MODE_OPTIONS, @@ -36,6 +41,11 @@ def _env_bool(name: str, default: bool = False) -> bool: return str(raw).strip().lower() in ("1", "true", "yes", "on") +def standalone_options_ui_enabled() -> bool: + """是否安装单独期权页/复盘/独立盯盘.本仓默认 false.""" + return _env_bool("STANDALONE_OPTIONS_UI", False) + + def normalize_okx_trade_mode(raw: Optional[str]) -> str: s = str(raw or "").strip().lower() if s in VALID_MODES: @@ -46,27 +56,25 @@ def normalize_okx_trade_mode(raw: Optional[str]) -> str: def legacy_infer_okx_trade_mode() -> str: - """未配置 OKX_TRADE_MODE 时,按旧开关推断,避免已有部署行为突变.""" - if not _env_bool("HEDGE_PLAN_ENABLED", False): - return MODE_OPTIONS + """未配置 OKX_TRADE_MODE 时推断;本仓默认永期对冲.""" + if not _env_bool("HEDGE_PLAN_ENABLED", True): + # 旧开关关对冲时,本仓仍回退永期(不再默认单独期权) + return MODE_PERP show_po = _env_bool("HEDGE_PLAN_SHOW_PERP_OPTIONS", True) show_oo = _env_bool("HEDGE_PLAN_SHOW_OPTIONS_OPTIONS", True) - if show_po and not show_oo: - return MODE_PERP if show_oo and not show_po: return MODE_OO - if show_po: - return MODE_PERP - if show_oo: - return MODE_OO - return MODE_OPTIONS + return MODE_PERP def get_okx_trade_mode() -> str: m = normalize_okx_trade_mode(os.getenv("OKX_TRADE_MODE")) - if m: - return m - return legacy_infer_okx_trade_mode() + if not m: + m = legacy_infer_okx_trade_mode() + # 未显式打开单独期权 UI 时,options 模式回退永期,避免空导航 + if m == MODE_OPTIONS and not standalone_options_ui_enabled(): + return MODE_PERP + return m def hedge_module_enabled() -> bool: @@ -82,7 +90,7 @@ def show_options_options() -> bool: def standalone_options_open_allowed() -> bool: - return get_okx_trade_mode() == MODE_OPTIONS + return standalone_options_ui_enabled() and get_okx_trade_mode() == MODE_OPTIONS def mode_label(mode: Optional[str] = None) -> str: @@ -97,7 +105,9 @@ def mode_label(mode: Optional[str] = None) -> str: def block_standalone_open_by_mode_msg() -> Optional[str]: if standalone_options_open_allowed(): return None + if not standalone_options_ui_enabled(): + return "本实例仅对冲;单独期权请使用专用期权仓库" return ( f"当前交易模式为「{mode_label()}」,不可单独开期权;" - "请在 env「交易模式」切换为「单独期权」" + "请在 env「交易模式」切换为「单独期权」(并开启 STANDALONE_OPTIONS_UI)" ) diff --git a/tests/test_okx_trade_mode.py b/tests/test_okx_trade_mode.py index bb1e20e..c4cd25d 100644 --- a/tests/test_okx_trade_mode.py +++ b/tests/test_okx_trade_mode.py @@ -1,4 +1,4 @@ -"""OKX_TRADE_MODE 三选一.""" +"""OKX_TRADE_MODE 对冲二选一(本仓默认无单独期权 UI).""" import os import unittest @@ -14,31 +14,56 @@ from lib.hedge_plan.okx_trade_mode_lib import ( show_options_options, show_perp_options, standalone_options_open_allowed, + standalone_options_ui_enabled, ) class OkxTradeModeTests(unittest.TestCase): - def test_explicit_modes(self): + def test_hedge_modes(self): for mode, hedge, po, oo in ( - (MODE_OPTIONS, False, False, False), (MODE_PERP, True, True, False), (MODE_OO, True, False, True), ): - with patch.dict(os.environ, {"OKX_TRADE_MODE": mode}, clear=False): + with patch.dict( + os.environ, + {"OKX_TRADE_MODE": mode, "STANDALONE_OPTIONS_UI": "false"}, + clear=False, + ): self.assertEqual(get_okx_trade_mode(), mode) self.assertEqual(hedge_module_enabled(), hedge) self.assertEqual(show_perp_options(), po) self.assertEqual(show_options_options(), oo) - self.assertEqual(standalone_options_open_allowed(), mode == MODE_OPTIONS) + self.assertFalse(standalone_options_open_allowed()) - def test_legacy_infer_options(self): + def test_options_mode_coerces_to_perp_without_ui_flag(self): with patch.dict( os.environ, - {"HEDGE_PLAN_ENABLED": "false"}, + {"OKX_TRADE_MODE": MODE_OPTIONS, "STANDALONE_OPTIONS_UI": "false"}, + clear=False, + ): + self.assertEqual(get_okx_trade_mode(), MODE_PERP) + self.assertTrue(hedge_module_enabled()) + self.assertFalse(standalone_options_open_allowed()) + + def test_options_mode_with_ui_flag(self): + with patch.dict( + os.environ, + {"OKX_TRADE_MODE": MODE_OPTIONS, "STANDALONE_OPTIONS_UI": "true"}, + clear=False, + ): + self.assertEqual(get_okx_trade_mode(), MODE_OPTIONS) + self.assertFalse(hedge_module_enabled()) + self.assertTrue(standalone_options_ui_enabled()) + self.assertTrue(standalone_options_open_allowed()) + + def test_legacy_infer_defaults_perp(self): + with patch.dict( + os.environ, + {"HEDGE_PLAN_ENABLED": "false", "STANDALONE_OPTIONS_UI": "false"}, clear=False, ): os.environ.pop("OKX_TRADE_MODE", None) - self.assertEqual(get_okx_trade_mode(), MODE_OPTIONS) + self.assertEqual(get_okx_trade_mode(), MODE_PERP) def test_legacy_infer_perp(self): with patch.dict( @@ -47,28 +72,25 @@ class OkxTradeModeTests(unittest.TestCase): "HEDGE_PLAN_ENABLED": "true", "HEDGE_PLAN_SHOW_PERP_OPTIONS": "true", "HEDGE_PLAN_SHOW_OPTIONS_OPTIONS": "false", + "STANDALONE_OPTIONS_UI": "false", }, clear=False, ): os.environ.pop("OKX_TRADE_MODE", None) self.assertEqual(get_okx_trade_mode(), MODE_PERP) - def test_block_standalone_in_hedge_mode(self): - with patch.dict(os.environ, {"OKX_TRADE_MODE": MODE_OO}): + def test_block_standalone_hedge_only_msg(self): + with patch.dict( + os.environ, + {"OKX_TRADE_MODE": MODE_OO, "STANDALONE_OPTIONS_UI": "false"}, + clear=False, + ): msg = block_standalone_open_by_mode_msg() self.assertIsNotNone(msg) - self.assertIn("期期", msg or "") + self.assertIn("仅对冲", msg or "") class EnvUiModeSectionsTests(unittest.TestCase): - def test_options_mode_hides_hedge_section(self): - from lib.env.env_ui_manifest import ui_sections_for_exchange - - titles = [s["title"] for s in ui_sections_for_exchange("okx", mode="options")] - self.assertIn("期权/对冲模式", titles) - self.assertIn("期权账户", titles) - self.assertTrue(all("对冲" not in t for t in titles if t != "期权/对冲模式")) - def test_perp_mode_shows_po_fields(self): from lib.env.env_ui_manifest import ui_sections_for_exchange @@ -90,6 +112,12 @@ class EnvUiModeSectionsTests(unittest.TestCase): self.assertIn("HEDGE_PLAN_OO_BIAS_RATIO", keys) self.assertNotIn("HEDGE_PLAN_OPEN_ORDER", keys) + def test_trade_mode_choices_are_hedge_only(self): + from lib.env.env_schema import SELECT_OPTIONS + + modes = [k for k, _ in SELECT_OPTIONS["OKX_TRADE_MODE"]] + self.assertEqual(modes, ["perp_options", "options_options"]) + if __name__ == "__main__": unittest.main()