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:
@@ -695,6 +695,7 @@ def _tick_po_option_primary(
|
||||
target_hit,
|
||||
)
|
||||
from lib.hedge_plan.hedge_plan_orders_lib import _close_perp
|
||||
from lib.hedge_plan.hedge_po_mode_lib import combo_net_usdt
|
||||
|
||||
perp = next((x for x in legs if x.get("leg_role") == "perp"), None)
|
||||
opt = next((x for x in legs if x.get("leg_role") == "option_hedge"), None)
|
||||
@@ -714,7 +715,9 @@ def _tick_po_option_primary(
|
||||
profit_target = _plan_profit_target(plan)
|
||||
if strike is None or strike <= 0:
|
||||
return None
|
||||
idx = _index_px(cfg, str(plan.get("underlying") or "ETH"))
|
||||
# 期权指数:计划 underlying(跨币=BTC);目标位同口径
|
||||
opt_uly = str(plan.get("underlying") or plan.get("option_underlying") or "ETH").upper()
|
||||
idx = _index_px(cfg, opt_uly)
|
||||
if idx is None:
|
||||
return None
|
||||
|
||||
@@ -743,6 +746,13 @@ def _tick_po_option_primary(
|
||||
mark = _sf((t.get("info") or {}).get("markPx")) or _sf(t.get("last"))
|
||||
except Exception:
|
||||
mark = None
|
||||
# 跨币:永续标记用 ETH,不可回退到 BTC 指数
|
||||
if mark is None:
|
||||
perp_uly = str(plan.get("perp_underlying") or "").upper()
|
||||
if not perp_uly and symbol:
|
||||
perp_uly = symbol.split("/")[0].upper()
|
||||
if perp_uly and perp_uly != opt_uly:
|
||||
mark = _index_px(cfg, perp_uly)
|
||||
mark = mark or idx
|
||||
entry = _sf(plan.get("entry_mark")) or _sf(perp.get("avg_open")) or mark
|
||||
cs = float(cfg.get("default_contract_size") or 0.01)
|
||||
@@ -787,10 +797,11 @@ def _tick_po_option_primary(
|
||||
)
|
||||
perp_px = _sf(book.get("vwap")) or mark or entry
|
||||
|
||||
# 优先期权腿平仓(须买一流动性);盈利金额模式要求扣双边费后净利≥目标
|
||||
# 优先期权腿平仓(须买一流动性);盈利金额模式要求扣双边费后组合净利(U)≥目标
|
||||
if hit_opt:
|
||||
liq_ok, liq_msg = option_bid_liquidity_ok(bid, bid_sz, need_sheets=sheets)
|
||||
net = None
|
||||
net_usdt = None
|
||||
if liq_ok:
|
||||
net = estimate_combo_net_pnl(
|
||||
view_side=view,
|
||||
@@ -806,17 +817,19 @@ def _tick_po_option_primary(
|
||||
contracts=contracts,
|
||||
contract_size=cs,
|
||||
)
|
||||
net_usdt = combo_net_usdt(net, option_index=float(idx))
|
||||
if exit_mode == "profit":
|
||||
can_opt_exit = bool(
|
||||
liq_ok and net is not None and float(net.get("net") or 0) + 1e-9 >= profit_target
|
||||
liq_ok and net_usdt is not None and float(net_usdt) + 1e-9 >= profit_target
|
||||
)
|
||||
else:
|
||||
can_opt_exit = bool(liq_ok and net is not None and float(net.get("net") or 0) > 0)
|
||||
# 目标位模式:组合 U 净利须 > 0(与持仓卡同口径)
|
||||
can_opt_exit = bool(liq_ok and net_usdt is not None and float(net_usdt) > 0)
|
||||
if can_opt_exit:
|
||||
reason = "profit_target_usdt" if exit_mode == "profit" else "opt_target_points"
|
||||
close_r = _sell_option(cfg, inst_id=str(opt.get("inst_id") or ""), sheets=sheets)
|
||||
if close_r.get("already_flat"):
|
||||
opt_pnl = _option_leg_pnl_after_close(cfg, opt, fallback=net["opt_net"])
|
||||
opt_pnl_coin = _option_leg_pnl_after_close(cfg, opt, fallback=float(net["opt_net"]))
|
||||
elif not close_r.get("ok") or not close_r.get("fully_closed", True):
|
||||
notify_hedge(
|
||||
cfg,
|
||||
@@ -829,10 +842,12 @@ def _tick_po_option_primary(
|
||||
update_plan(conn, int(plan["id"]), close_reason="opt_target_pending")
|
||||
return {"plan_id": plan["id"], "retry": True, "close": close_r}
|
||||
else:
|
||||
opt_pnl = _option_leg_pnl_after_close(cfg, opt, fallback=net["opt_net"])
|
||||
opt_pnl_coin = _option_leg_pnl_after_close(cfg, opt, fallback=float(net["opt_net"]))
|
||||
# 币本位期权盈亏折 U(×期权指数),与持仓卡/出场门控同口径
|
||||
opt_pnl_u = round(float(opt_pnl_coin) * float(idx), 4)
|
||||
conn.execute(
|
||||
"UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
|
||||
("closed", reason, _now(), round(opt_pnl, 4), opt["id"]),
|
||||
("closed", reason, _now(), opt_pnl_u, opt["id"]),
|
||||
)
|
||||
perp_close = _close_perp(
|
||||
cfg, symbol=symbol, direction=perp_dir, contracts=contracts, dry_run=False
|
||||
@@ -853,14 +868,14 @@ def _tick_po_option_primary(
|
||||
"UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
|
||||
("closed", reason, _now(), round(perp_pnl, 4), perp["id"]),
|
||||
)
|
||||
total = float(opt_pnl) + float(perp_pnl)
|
||||
total = float(net_usdt) if net_usdt is not None else (opt_pnl_u + perp_pnl)
|
||||
update_plan(
|
||||
conn,
|
||||
int(plan["id"]),
|
||||
status="closed",
|
||||
close_reason=reason,
|
||||
realized_pnl_perp=round(perp_pnl, 4),
|
||||
realized_pnl_options=round(opt_pnl, 4),
|
||||
realized_pnl_options=opt_pnl_u,
|
||||
realized_pnl_total=round(total, 4),
|
||||
stats_bucket="opt_primary",
|
||||
closed_at=_now(),
|
||||
@@ -872,6 +887,7 @@ def _tick_po_option_primary(
|
||||
"close_reason": reason,
|
||||
"total": total,
|
||||
"net": net,
|
||||
"net_usdt": net_usdt,
|
||||
"sell_coin": sell_r,
|
||||
}
|
||||
if exit_mode == "profit":
|
||||
@@ -881,16 +897,18 @@ def _tick_po_option_primary(
|
||||
"msg": (
|
||||
liq_msg
|
||||
if not liq_ok
|
||||
else f"净利未达目标 {profit_target:g}(当前 {float((net or {}).get('net') or 0):.2f})"
|
||||
else f"组合净利(U)未达目标 {profit_target:g}(当前 {float(net_usdt or 0):.2f})"
|
||||
),
|
||||
"net": net,
|
||||
"net_usdt": net_usdt,
|
||||
}
|
||||
if not hit_perp:
|
||||
return {
|
||||
"plan_id": plan["id"],
|
||||
"skip": True,
|
||||
"msg": (liq_msg if not liq_ok else "净利≤0,继续持有"),
|
||||
"msg": (liq_msg if not liq_ok else "组合净利(U)≤0,继续持有"),
|
||||
"net": net,
|
||||
"net_usdt": net_usdt,
|
||||
}
|
||||
|
||||
if not hit_perp:
|
||||
|
||||
Reference in New Issue
Block a user