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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user