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:
@@ -777,6 +777,16 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
||||
if q.get("ct_mult") is not None:
|
||||
body["ct_mult"] = float(q.get("ct_mult") or 0.01)
|
||||
if is_option_primary(body):
|
||||
from lib.hedge_plan.hedge_plan_option_primary_lib import contracts_from_option_sheets
|
||||
from lib.hedge_plan.hedge_po_mode_lib import (
|
||||
apply_mode_to_start_body,
|
||||
get_hedge_po_mode,
|
||||
is_cross_asset,
|
||||
option_underlying,
|
||||
perp_underlying,
|
||||
)
|
||||
|
||||
body.update(apply_mode_to_start_body(body))
|
||||
cs = float(body.get("contract_size") or 0.01)
|
||||
get_cs = cfg.get("get_contract_size")
|
||||
sym = str(body.get("exchange_symbol") or "")
|
||||
@@ -789,20 +799,41 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
||||
ct = float(body.get("ct_mult") or q.get("ct_mult") or 0.01)
|
||||
ratio = float(body.get("option_perp_ratio") or 2)
|
||||
body["contract_size"] = cs
|
||||
opt_idx = None
|
||||
perp_idx = None
|
||||
try:
|
||||
from lib.exchange.okx_public_http_lib import fetch_index_px
|
||||
|
||||
mode = str(body.get("po_mode") or get_hedge_po_mode())
|
||||
opt_idx = float(fetch_index_px(option_underlying(mode)) or 0) or None
|
||||
if is_cross_asset(mode):
|
||||
perp_idx = float(fetch_index_px(perp_underlying(mode)) or 0) or None
|
||||
else:
|
||||
perp_idx = opt_idx
|
||||
except Exception:
|
||||
opt_idx = perp_idx = None
|
||||
# 页面传入的 entry/index 作回退
|
||||
if opt_idx is None:
|
||||
try:
|
||||
opt_idx = float(body.get("index_px") or body.get("entry") or 0) or None
|
||||
except (TypeError, ValueError):
|
||||
opt_idx = None
|
||||
if perp_idx is None:
|
||||
perp_idx = opt_idx
|
||||
if market and float(body.get("sheets") or 0) > 0:
|
||||
# 手动下单:以张数为准,永续按比例
|
||||
sheets_i = float(int(float(body["sheets"])))
|
||||
eth = round(sheets_i * ct, 2)
|
||||
body["sheets"] = sheets_i
|
||||
body["eth_qty"] = eth
|
||||
body["contracts"] = (eth / ratio) / cs if ratio > 0 and cs > 0 else 0
|
||||
sized = {
|
||||
"ok": True,
|
||||
"sheets": sheets_i,
|
||||
"eth_qty": eth,
|
||||
"contracts": body["contracts"],
|
||||
"ratio": ratio,
|
||||
}
|
||||
sized = contracts_from_option_sheets(
|
||||
sheets=float(body["sheets"]),
|
||||
ct_mult=ct,
|
||||
ratio=ratio,
|
||||
contract_size=cs,
|
||||
option_index=opt_idx,
|
||||
perp_index=perp_idx,
|
||||
)
|
||||
if not sized.get("ok"):
|
||||
return {"ok": False, "msg": sized.get("msg") or "定仓失败", "quote": q, "sizing": sized}
|
||||
body["sheets"] = sized["sheets"]
|
||||
body["eth_qty"] = sized.get("coin_qty") or sized.get("eth_qty")
|
||||
body["contracts"] = sized["contracts"]
|
||||
else:
|
||||
sized = size_from_premium(
|
||||
premium_budget=float(body.get("premium_budget") or 0),
|
||||
@@ -810,21 +841,39 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
||||
ct_mult=ct,
|
||||
ratio=ratio,
|
||||
contract_size=cs,
|
||||
option_index=opt_idx,
|
||||
perp_index=perp_idx,
|
||||
)
|
||||
if not sized.get("ok"):
|
||||
return {"ok": False, "msg": sized.get("msg") or "定仓失败", "quote": q, "sizing": sized}
|
||||
body["sheets"] = sized["sheets"]
|
||||
body["contracts"] = sized["contracts"]
|
||||
body["eth_qty"] = sized["eth_qty"]
|
||||
body["eth_qty"] = sized.get("coin_qty") or sized["eth_qty"]
|
||||
# 深度不足则缩量
|
||||
ask_sz = float(q.get("ask_sz") or 0)
|
||||
if ask_sz > 0 and float(body["sheets"]) > ask_sz:
|
||||
body["sheets"] = float(int(ask_sz))
|
||||
if body["sheets"] <= 0:
|
||||
return {"ok": False, "msg": "卖一深度不足 1 张", "quote": q, "sizing": sized}
|
||||
eth = round(float(body["sheets"]) * ct, 2)
|
||||
body["eth_qty"] = eth
|
||||
body["contracts"] = (eth / ratio) / cs if ratio > 0 and cs > 0 else 0
|
||||
resized = contracts_from_option_sheets(
|
||||
sheets=float(body["sheets"]),
|
||||
ct_mult=ct,
|
||||
ratio=ratio,
|
||||
contract_size=cs,
|
||||
option_index=opt_idx,
|
||||
perp_index=perp_idx,
|
||||
)
|
||||
if not resized.get("ok"):
|
||||
return {"ok": False, "msg": resized.get("msg") or "缩量定仓失败", "quote": q}
|
||||
body["eth_qty"] = resized.get("coin_qty") or resized.get("eth_qty")
|
||||
body["contracts"] = resized["contracts"]
|
||||
sized = resized
|
||||
cross_note = ""
|
||||
if sized.get("cross_asset") or sized.get("option_notional_usdt"):
|
||||
cross_note = (
|
||||
f" · 名义U 期权{float(sized.get('option_notional_usdt') or 0):.0f}"
|
||||
f"/永续{float(sized.get('perp_notional_usdt') or 0):.0f}"
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"ask": float(q["ask"]),
|
||||
@@ -837,10 +886,12 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
||||
"msg": (
|
||||
f"手动定仓: {body.get('eth_qty')}币 / "
|
||||
f"{body.get('sheets')}张期权 / {float(body.get('contracts') or 0):.4f}张永续 @{q['ask']}"
|
||||
f"{cross_note}"
|
||||
if market
|
||||
else (
|
||||
f"期权为主定仓: 权利金×0.95→{body.get('eth_qty')}ETH / "
|
||||
f"期权为主定仓: 权利金×0.95→{body.get('eth_qty')}币 / "
|
||||
f"{body.get('sheets')}张期权 / {float(body.get('contracts') or 0):.4f}张永续 @{q['ask']}"
|
||||
f"{cross_note}"
|
||||
)
|
||||
),
|
||||
}
|
||||
@@ -1184,7 +1235,16 @@ def validate_start_body(plan_type: str, body: dict[str, Any]) -> Optional[str]:
|
||||
is_option_primary,
|
||||
validate_option_primary_start,
|
||||
)
|
||||
from lib.hedge_plan.hedge_po_mode_lib import (
|
||||
apply_mode_to_start_body,
|
||||
validate_body_matches_mode,
|
||||
)
|
||||
|
||||
# 模拟/实盘同路径:启动前按实例模式钳制
|
||||
body.update(apply_mode_to_start_body(body))
|
||||
mode_err = validate_body_matches_mode(body)
|
||||
if mode_err:
|
||||
return mode_err
|
||||
if is_option_primary(body):
|
||||
from lib.hedge_plan.hedge_plan_option_primary_lib import validate_option_primary_watch
|
||||
|
||||
|
||||
Reference in New Issue
Block a user