Make this repo hedge-only: skip standalone options UI by default.

Nav/env only expose 永期/期期; set STANDALONE_OPTIONS_UI=true for emergency opt-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 18:37:46 +08:00
parent 14cbbc300d
commit 6607d09526
6 changed files with 106 additions and 51 deletions
+7 -3
View File
@@ -6,22 +6,25 @@ OKX **币本位期权** + **U 本位永续对冲** 独立实例。
## 产品范围 ## 产品范围
本仓**仅对冲**。单独期权(含中控)请用专用期权仓库;此处默认不装期权页/复盘/独立盯盘。
| 保留 | 说明 | | 保留 | 说明 |
|------|------| |------|------|
| 单独期权 | 仅币本位(USDT→买币→开期权) |
| 永期对冲 | 币本位期权腿 + USDT 永续腿 | | 永期对冲 | 币本位期权腿 + USDT 永续腿 |
| 期期对冲 | 双腿币本位期权 | | 期期对冲 | 双腿币本位期权 |
| 模拟资金 | **默认 `sim`**,本地钱包撮合 | | 模拟资金 | **默认 `sim`**,本地钱包撮合 |
| 期权执行层 | 报价/下单/买币桥,供对冲腿共用 |
| 不提供 | 说明 | | 不提供 | 说明 |
|--------|------| |--------|------|
| 单独期权 UI | 默认关闭;紧急可 `STANDALONE_OPTIONS_UI=true`(不推荐) |
| 单独永续下单 | `/trade` 已重定向 | | 单独永续下单 | `/trade` 已重定向 |
| 关键位自动开仓 | `/key_monitor` 已重定向 | | 关键位自动开仓 | `/key_monitor` 已重定向 |
| 策略趋势/滚仓 | 未安装 | | 策略趋势/滚仓 | 未安装 |
| USDC 期权 | 已固定币本位 | | USDC 期权 | 已固定币本位 |
| Windows 部署 | **仅 Ubuntu 一键部署** | | Windows 部署 | **仅 Ubuntu 一键部署** |
源仓 `crypto_monitor` **只读复制**,不回改源仓。中控二期再做。 源仓 `crypto_monitor` **只读复制**,不回改源仓。
## Ubuntu 一键部署(推荐) ## Ubuntu 一键部署(推荐)
@@ -57,7 +60,8 @@ python app.py
- `SIM_DEFAULT_MODE=sim`(默认模拟盘) - `SIM_DEFAULT_MODE=sim`(默认模拟盘)
- `OKX_OPTIONS_MARGIN_MODE=coin`(固定) - `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` - `SIM_INITIAL_EQUITY_USDT=10000`
- `APP_PORT=5010` - `APP_PORT=5010`
+21 -7
View File
@@ -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 from lib.instance.instance_display_prefs_lib import display_prefs_template_context
_display_ctx = display_prefs_template_context(get_db) _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() _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") _hedge_mode_on = _okx_trade_mode in ("perp_options", "options_options")
_show_perp_funds = show_perp_funds_enabled(exchange_key="okx") _show_perp_funds = show_perp_funds_enabled(exchange_key="okx")
template_ctx = dict( template_ctx = dict(
@@ -6969,9 +6973,9 @@ def render_main_page(page="trade", embed_mode=None):
exchange_display=EXCHANGE_DISPLAY_NAME, exchange_display=EXCHANGE_DISPLAY_NAME,
options_enabled=OKX_OPTIONS_ENABLED, options_enabled=OKX_OPTIONS_ENABLED,
show_perp_funds=_show_perp_funds or (options_margin_mode == "coin"), 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, 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_enabled=_hedge_mode_on,
hedge_plan_nav_visible=_hedge_mode_on, hedge_plan_nav_visible=_hedge_mode_on,
hedge_plan_show_perp_options=_okx_trade_mode == "perp_options", 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__]) 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 from lib.hedge_plan.hedge_plan_register import install_hedge_plan
-1
View File
@@ -163,7 +163,6 @@ SELECT_OPTIONS: dict[str, tuple[tuple[str, str], ...]] = {
("sheets", "张数"), ("sheets", "张数"),
), ),
"OKX_TRADE_MODE": ( "OKX_TRADE_MODE": (
("options", "单独期权"),
("perp_options", "永期对冲"), ("perp_options", "永期对冲"),
("options_options", "期期对冲"), ("options_options", "期期对冲"),
), ),
+2 -2
View File
@@ -160,7 +160,7 @@ _MODE_SECTION: dict[str, Any] = {
( (
"OKX_TRADE_MODE", "OKX_TRADE_MODE",
"交易模式", "交易模式",
"三选一:单独期权 / 永期对冲 / 期期对冲.选单独期权时隐藏对冲导航与对冲配置;选对冲时不可单独开期权", "本仓仅对冲二选一:永期对冲 / 期期对冲.单独期权请用专用期权仓库",
), ),
], ],
} }
@@ -351,7 +351,7 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = {
"OKX_OPTIONS_CHAIN_MAX_DTE_DAYS": "14", "OKX_OPTIONS_CHAIN_MAX_DTE_DAYS": "14",
"OKX_OPTIONS_MAX_DTE_DAYS": "2", "OKX_OPTIONS_MAX_DTE_DAYS": "2",
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS": "0", "OKX_OPTIONS_MAX_ACTIVE_POSITIONS": "0",
"OKX_TRADE_MODE": "options", "OKX_TRADE_MODE": "perp_options",
"MAX_ACTIVE_HEDGE_PLANS": "1", "MAX_ACTIVE_HEDGE_PLANS": "1",
"HEDGE_PLAN_OO_CLOSE_MODE_ENABLED": "true", "HEDGE_PLAN_OO_CLOSE_MODE_ENABLED": "true",
"HEDGE_PLAN_OO_BIAS_SPLIT_BY": "budget", "HEDGE_PLAN_OO_BIAS_SPLIT_BY": "budget",
+27 -17
View File
@@ -1,8 +1,11 @@
"""OKX 期权/对冲三选一模式(env: OKX_TRADE_MODE). """OKX 对冲交易模式(env: OKX_TRADE_MODE).
options → 仅单独期权(隐藏对冲导航与对冲 env 配置) 本仓定位:仅对冲(永期/期期).单独期权产品面已迁出,默认不安装其 UI/复盘/独立盯盘.
perp_options → 仅永期对冲(不可单独开期权;对冲组数上限 MAX_ACTIVE_HEDGE_PLANS) 期权执行层(报价/下单/买币桥)仍由对冲腿共用.
options_options → 仅期期对冲(同上)
perp_options → 永期对冲
options_options → 期期对冲
options → 遗留值;未开 STANDALONE_OPTIONS_UI 时回退为永期
""" """
from __future__ import annotations from __future__ import annotations
@@ -13,6 +16,8 @@ MODE_OPTIONS = "options"
MODE_PERP = "perp_options" MODE_PERP = "perp_options"
MODE_OO = "options_options" MODE_OO = "options_options"
VALID_MODES = frozenset({MODE_OPTIONS, MODE_PERP, MODE_OO}) VALID_MODES = frozenset({MODE_OPTIONS, MODE_PERP, MODE_OO})
# env 可选值(本仓默认只暴露对冲两种)
HEDGE_MODES = frozenset({MODE_PERP, MODE_OO})
_ALIASES = { _ALIASES = {
"option": MODE_OPTIONS, "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") 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: def normalize_okx_trade_mode(raw: Optional[str]) -> str:
s = str(raw or "").strip().lower() s = str(raw or "").strip().lower()
if s in VALID_MODES: 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: def legacy_infer_okx_trade_mode() -> str:
"""未配置 OKX_TRADE_MODE 时,按旧开关推断,避免已有部署行为突变.""" """未配置 OKX_TRADE_MODE 时推断;本仓默认永期对冲."""
if not _env_bool("HEDGE_PLAN_ENABLED", False): if not _env_bool("HEDGE_PLAN_ENABLED", True):
return MODE_OPTIONS # 旧开关关对冲时,本仓仍回退永期(不再默认单独期权)
return MODE_PERP
show_po = _env_bool("HEDGE_PLAN_SHOW_PERP_OPTIONS", True) show_po = _env_bool("HEDGE_PLAN_SHOW_PERP_OPTIONS", True)
show_oo = _env_bool("HEDGE_PLAN_SHOW_OPTIONS_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: if show_oo and not show_po:
return MODE_OO return MODE_OO
if show_po:
return MODE_PERP return MODE_PERP
if show_oo:
return MODE_OO
return MODE_OPTIONS
def get_okx_trade_mode() -> str: def get_okx_trade_mode() -> str:
m = normalize_okx_trade_mode(os.getenv("OKX_TRADE_MODE")) m = normalize_okx_trade_mode(os.getenv("OKX_TRADE_MODE"))
if m: 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 return m
return legacy_infer_okx_trade_mode()
def hedge_module_enabled() -> bool: def hedge_module_enabled() -> bool:
@@ -82,7 +90,7 @@ def show_options_options() -> bool:
def standalone_options_open_allowed() -> 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: 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]: def block_standalone_open_by_mode_msg() -> Optional[str]:
if standalone_options_open_allowed(): if standalone_options_open_allowed():
return None return None
if not standalone_options_ui_enabled():
return "本实例仅对冲;单独期权请使用专用期权仓库"
return ( return (
f"当前交易模式为「{mode_label()}」,不可单独开期权;" f"当前交易模式为「{mode_label()}」,不可单独开期权;"
"请在 env「交易模式」切换为「单独期权」" "请在 env「交易模式」切换为「单独期权」(并开启 STANDALONE_OPTIONS_UI)"
) )
+47 -19
View File
@@ -1,4 +1,4 @@
"""OKX_TRADE_MODE 三选一.""" """OKX_TRADE_MODE 对冲二选一(本仓默认无单独期权 UI)."""
import os import os
import unittest import unittest
@@ -14,31 +14,56 @@ from lib.hedge_plan.okx_trade_mode_lib import (
show_options_options, show_options_options,
show_perp_options, show_perp_options,
standalone_options_open_allowed, standalone_options_open_allowed,
standalone_options_ui_enabled,
) )
class OkxTradeModeTests(unittest.TestCase): class OkxTradeModeTests(unittest.TestCase):
def test_explicit_modes(self): def test_hedge_modes(self):
for mode, hedge, po, oo in ( for mode, hedge, po, oo in (
(MODE_OPTIONS, False, False, False),
(MODE_PERP, True, True, False), (MODE_PERP, True, True, False),
(MODE_OO, True, False, True), (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(get_okx_trade_mode(), mode)
self.assertEqual(hedge_module_enabled(), hedge) self.assertEqual(hedge_module_enabled(), hedge)
self.assertEqual(show_perp_options(), po) self.assertEqual(show_perp_options(), po)
self.assertEqual(show_options_options(), oo) 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( with patch.dict(
os.environ, 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, clear=False,
): ):
os.environ.pop("OKX_TRADE_MODE", None) 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): def test_legacy_infer_perp(self):
with patch.dict( with patch.dict(
@@ -47,28 +72,25 @@ class OkxTradeModeTests(unittest.TestCase):
"HEDGE_PLAN_ENABLED": "true", "HEDGE_PLAN_ENABLED": "true",
"HEDGE_PLAN_SHOW_PERP_OPTIONS": "true", "HEDGE_PLAN_SHOW_PERP_OPTIONS": "true",
"HEDGE_PLAN_SHOW_OPTIONS_OPTIONS": "false", "HEDGE_PLAN_SHOW_OPTIONS_OPTIONS": "false",
"STANDALONE_OPTIONS_UI": "false",
}, },
clear=False, clear=False,
): ):
os.environ.pop("OKX_TRADE_MODE", None) os.environ.pop("OKX_TRADE_MODE", None)
self.assertEqual(get_okx_trade_mode(), MODE_PERP) self.assertEqual(get_okx_trade_mode(), MODE_PERP)
def test_block_standalone_in_hedge_mode(self): def test_block_standalone_hedge_only_msg(self):
with patch.dict(os.environ, {"OKX_TRADE_MODE": MODE_OO}): with patch.dict(
os.environ,
{"OKX_TRADE_MODE": MODE_OO, "STANDALONE_OPTIONS_UI": "false"},
clear=False,
):
msg = block_standalone_open_by_mode_msg() msg = block_standalone_open_by_mode_msg()
self.assertIsNotNone(msg) self.assertIsNotNone(msg)
self.assertIn("期期", msg or "") self.assertIn("仅对冲", msg or "")
class EnvUiModeSectionsTests(unittest.TestCase): 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): def test_perp_mode_shows_po_fields(self):
from lib.env.env_ui_manifest import ui_sections_for_exchange 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.assertIn("HEDGE_PLAN_OO_BIAS_RATIO", keys)
self.assertNotIn("HEDGE_PLAN_OPEN_ORDER", 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__": if __name__ == "__main__":
unittest.main() unittest.main()