Add HEDGE_PO_MODE instance modes with shared sim/live path.

Lock one mode per instance/subaccount, cross-asset BTC option/ETH perp sizing, combo-U exit, and ship docs/tests/audit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 20:05:25 +08:00
parent ab7a2309ad
commit 5d161b4220
18 changed files with 1215 additions and 115 deletions
+86 -28
View File
@@ -39,8 +39,13 @@ def attach_hedge_plan_templates(app: Flask, repo_root: str) -> None:
def install_hedge_plan(app: Flask, repo_root: str, app_module: Any) -> None:
from lib.hedge_plan.hedge_po_mode_lib import clamp_hedge_po_mode_on_startup, public_mode_payload
attach_hedge_plan_templates(app, repo_root)
mode_summary = clamp_hedge_po_mode_on_startup()
cfg = _build_cfg(app_module)
cfg["po_mode"] = mode_summary.get("mode")
cfg["po_mode_payload"] = public_mode_payload(mode_summary.get("mode"))
app.extensions["hedge_plan_cfg"] = cfg
register_hedge_plan_routes(app, cfg)
_maybe_start_monitor(cfg)
@@ -98,6 +103,7 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
"chain_max_dte": float(os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS") or os.getenv("OKX_OPTIONS_MAX_DTE_DAYS") or "14"),
"perp_account_label": (os.getenv("OKX_ACCOUNT_LABEL") or "合约账户").strip(),
"options_account_label": (os.getenv("OKX_OPTIONS_ACCOUNT_LABEL") or "期权账户").strip(),
"po_mode": (os.getenv("HEDGE_PO_MODE") or "").strip().lower(),
"trade_budget_usdc": float(
os.getenv("OKX_OPTIONS_COIN_BUDGET_USDT")
or os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC")
@@ -318,16 +324,25 @@ def _persist_po(cfg: dict[str, Any], result: dict[str, Any], body: dict[str, Any
perp_direction_for_view,
)
from lib.hedge_plan.hedge_po_mode_lib import (
apply_mode_to_start_body,
get_hedge_po_mode,
option_underlying,
perp_underlying,
)
body = apply_mode_to_start_body(body)
opt_primary = is_option_primary(body)
view = str(body.get("direction") or "long")
perp_dir = (
str((perp or {}).get("direction") or "")
or (perp_direction_for_view(view) if opt_primary else view)
)
po_mode = str(body.get("po_mode") or get_hedge_po_mode())
plan_row = {
"plan_type": "perp_options",
"status": "partial" if is_partial else "active",
"underlying": str(body.get("underlying") or "ETH").upper(),
"underlying": str(body.get("underlying") or option_underlying(po_mode)).upper(),
"direction": view,
"entry_mark": float(body.get("entry") or 0),
"tp": float(body.get("tp") or 0) if not opt_primary else 0,
@@ -343,11 +358,12 @@ def _persist_po(cfg: dict[str, Any], result: dict[str, Any], body: dict[str, Any
"note": (result.get("msg") or "")[:500] if is_partial else None,
"option_primary": 1 if opt_primary else 0,
"perp_direction": perp_dir,
"po_mode": po_mode,
"perp_underlying": str(body.get("perp_underlying") or perp_underlying(po_mode)).upper(),
}
if opt_primary:
exit_mode = str(body.get("exit_mode") or "target").strip().lower()
if exit_mode not in ("profit", "target"):
exit_mode = "target"
# 实例模式强制组合盈利金额出场
exit_mode = "profit"
plan_row.update(
{
"option_target_points": float(body.get("option_target_points") or 0),
@@ -410,18 +426,26 @@ def _persist_po_watching(cfg: dict[str, Any], body: dict[str, Any]) -> int:
"""以期权为主:只落库盯盘计划,不下单."""
from lib.hedge_plan.hedge_plan_db import init_hedge_plan_tables, insert_plan
from lib.hedge_plan.hedge_plan_option_primary_lib import perp_direction_for_view
from lib.hedge_plan.hedge_po_mode_lib import (
apply_mode_to_start_body,
get_hedge_po_mode,
option_underlying,
perp_underlying,
)
body = apply_mode_to_start_body(body)
conn = cfg["get_db"]()
try:
init_hedge_plan_tables(conn)
view = str(body.get("direction") or "long")
money = str(body.get("moneyness") or body.get("option_moneyness") or "otm").strip().lower()
po_mode = str(body.get("po_mode") or get_hedge_po_mode())
plan_id = insert_plan(
conn,
{
"plan_type": "perp_options",
"status": "watching",
"underlying": str(body.get("underlying") or "ETH").upper(),
"underlying": str(body.get("underlying") or option_underlying(po_mode)).upper(),
"direction": view,
"entry_mark": float(body.get("index_px") or body.get("entry") or 0) or None,
"tp": 0,
@@ -445,6 +469,10 @@ def _persist_po_watching(cfg: dict[str, Any], body: dict[str, Any]) -> int:
"min_option_hours": float(body.get("min_option_hours") or 36),
"option_moneyness": money,
"option_leverage": float(body.get("option_leverage") or 0),
"exit_mode": "profit",
"profit_target_usdt": float(body.get("profit_target_usdt") or 0),
"po_mode": po_mode,
"perp_underlying": str(body.get("perp_underlying") or perp_underlying(po_mode)).upper(),
},
)
conn.commit()
@@ -650,41 +678,51 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
@app.route("/api/hedge-plan/market")
@lr
def api_hedge_market():
base = (request.args.get("base") or cfg.get("default_underly") or "ETH").strip().upper()
if base not in ("BTC", "ETH"):
return jsonify({"ok": False, "msg": "对冲计划仅支持 BTC/ETH"}), 400
from lib.hedge_plan.hedge_po_mode_lib import (
get_hedge_po_mode,
option_underlying,
perp_underlying,
public_mode_payload,
)
mode = get_hedge_po_mode()
mode_payload = public_mode_payload(mode)
# 永续行情按实例模式的永续标的;跨币忽略前端乱传的 base
base = perp_underlying(mode)
req_base = (request.args.get("base") or "").strip().upper()
if req_base in ("BTC", "ETH") and req_base == base:
pass
elif req_base and req_base != base:
# 兼容旧前端:仍返回正确永续,并提示
pass
direction = (request.args.get("direction") or "long").strip().lower()
if direction not in ("long", "short"):
direction = "long"
option_primary = (request.args.get("option_primary") or "").strip().lower() in (
"1",
"true",
"yes",
"on",
)
# 实例模式强制以期权为主
option_primary = True
data, err = _fetch_perp_market(cfg, base)
if err:
return jsonify({"ok": False, "msg": err}), 400
sizing_mode = load_position_sizing_mode()
gates = _gates_dict(cfg, "perp_options")
if option_primary:
from lib.hedge_plan.hedge_plan_option_primary_lib import (
opt_type_for_view,
perp_direction_for_view,
)
from lib.hedge_plan.hedge_plan_option_primary_lib import (
opt_type_for_view,
perp_direction_for_view,
)
suggested = opt_type_for_view(direction)
perp_dir = perp_direction_for_view(direction)
acct_note = "以期权为主:看法腿买期权,永续反向对冲"
else:
suggested = "P" if direction == "long" else "C"
perp_dir = direction
acct_note = "永续腿使用合约(交易)账户可用 USDT"
suggested = opt_type_for_view(direction)
perp_dir = perp_direction_for_view(direction)
acct_note = (
f"实例模式 {mode_payload['po_mode_label']}:期权 {option_underlying(mode)}"
f" + 永续 {base};一子账户互不干涉"
)
out = {
"ok": True,
"base": base,
"option_underlying": option_underlying(mode),
"perp_underlying": base,
"direction": direction,
"option_primary": option_primary,
"option_primary": True,
"suggested_opt_type": suggested,
"perp_direction": perp_dir,
**data,
@@ -693,9 +731,17 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
"account_kind": "perp",
"account_label": cfg.get("perp_account_label") or "合约账户",
"account_note": acct_note,
**mode_payload,
}
return jsonify(out)
@app.route("/api/hedge-plan/po-mode")
@lr
def api_hedge_po_mode():
from lib.hedge_plan.hedge_po_mode_lib import public_mode_payload
return jsonify({"ok": True, **public_mode_payload()})
@app.route("/api/hedge-plan/options-chain")
@lr
def api_hedge_options_chain():
@@ -704,7 +750,19 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
ex = cfg.get("exchange_options")
if ex is None:
return jsonify({"ok": False, "msg": "期权交易所未初始化"}), 400
u = (request.args.get("underlying") or cfg.get("default_underly") or "ETH").upper()
from lib.hedge_plan.hedge_po_mode_lib import get_hedge_po_mode, option_underlying
# 期权链锁定实例模式的期权标的(跨币=BTC)
u = option_underlying(get_hedge_po_mode())
req_u = (request.args.get("underlying") or "").strip().upper()
if req_u in ("BTC", "ETH") and req_u != u:
return jsonify(
{
"ok": False,
"msg": f"本实例模式期权标的为 {u},不可拉 {req_u} 链",
"option_underlying": u,
}
), 400
# 热更新:链展示天数每次读 env
chain_max_dte = float(
os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS")