Fix btc_eth live card option premium USDT conversion.

Use option (BTC) index for coin-to-U; never fall back to ETH perp mark. Also add no-server-ops Cursor rule.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 20:48:10 +08:00
parent e1fd77cd06
commit e8f33c3c52
4 changed files with 153 additions and 19 deletions
+70 -14
View File
@@ -104,9 +104,57 @@ def _has_open_leg(legs: list[dict[str, Any]]) -> bool:
return any(str(x.get("status") or "").lower() == "open" for x in legs)
def resolve_option_index_px(
cfg: dict[str, Any],
option_uly: str,
*,
ex_opt: Any = None,
cross_asset: bool = False,
perp_fallback: Optional[float] = None,
) -> Optional[float]:
"""期权币→U 必须用期权标的指数;跨币禁止回落永续价(否则 BTC 权利金会×ETH).
优先公共 HTTP(单参),再 cfg.fetch_index_price(ex, uly);同币才可用永续价兜底.
"""
uly = str(option_uly or "ETH").strip().upper() or "ETH"
try:
from lib.exchange.okx_public_http_lib import fetch_index_px
px = _sf(fetch_index_px(uly))
if px is not None and px > 0:
return px
except Exception:
pass
fetch_idx = cfg.get("fetch_index_price") if isinstance(cfg, dict) else None
if callable(fetch_idx):
try:
if ex_opt is not None:
px = _sf(fetch_idx(ex_opt, uly))
else:
# 兼容已绑定 ex 的单参包装;勿把 uly 当成 ex
px = _sf(fetch_idx(uly))
if px is not None and px > 0:
return px
except TypeError:
try:
px = _sf(fetch_idx(uly))
if px is not None and px > 0:
return px
except Exception:
pass
except Exception:
pass
if not cross_asset:
fb = _sf(perp_fallback)
if fb is not None and fb > 0:
return fb
return None
def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any]]:
"""当前持仓:未平完的永期仓(含计划已结束但腿仍 open)."""
from lib.hedge_plan.hedge_plan_db import attach_legs_to_plans, list_plans_for_live_positions
from lib.hedge_plan.hedge_po_mode_lib import combo_net_usdt, get_hedge_po_mode, is_cross_asset, option_underlying
rows = list_plans_for_live_positions(conn, plan_type="perp_options", limit=40)
if not rows:
@@ -129,7 +177,6 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
get_perp_metrics = cfg.get("get_live_position_exchange_metrics")
get_cs = cfg.get("get_contract_size")
fetch_idx = cfg.get("fetch_index_price")
ex_perp = None
try:
get_pex = cfg.get("exchange")
@@ -161,7 +208,13 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
continue
view = str(plan.get("direction") or "long").lower()
uly = str(plan.get("underlying") or "ETH").upper()
mode = str(plan.get("po_mode") or get_hedge_po_mode())
uly = str(
plan.get("option_underlying") or plan.get("underlying") or option_underlying(mode) or "ETH"
).upper()
cross = is_cross_asset(mode) or (
str(plan.get("perp_underlying") or "").upper() not in ("", uly)
)
inst_id = str((opt_leg or {}).get("inst_id") or "")
opt_raw = _pick_option_row(opt_rows, inst_id) if inst_id else None
@@ -230,14 +283,14 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
perp_top_sz = _sf(book.get("top_sz"))
book_side = str(book.get("book_side") or "")
idx = None
if callable(fetch_idx):
try:
idx = _sf(fetch_idx(uly))
except Exception:
idx = None
if idx is None or idx <= 0:
idx = perp_vwap or perp_mark or strike or 0.0
# 币→U:期权标的指数(btc_eth=BTC);禁止用 ETH 永续价折算
idx = resolve_option_index_px(
cfg,
uly,
ex_opt=ex_opt,
cross_asset=cross,
perp_fallback=(perp_vwap or perp_mark or strike),
)
net = estimate_combo_net_pnl(
view_side=view,
@@ -256,12 +309,15 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
option_fee=fr_opt,
)
opt_net_coin = float(net.get("opt_net") or 0)
# 币→U:用当前指数(与买币参考价同口径)
opt_net_usdt = round(opt_net_coin * float(idx or 0), 4) if idx else None
opt_net_usdt = (
round(opt_net_coin * float(idx), 4) if idx is not None and idx > 0 else None
)
perp_net = float(net.get("perp_net") or 0)
combo_usdt = round((opt_net_usdt or 0) + perp_net, 4) if opt_net_usdt is not None else round(perp_net, 4)
combo_usdt = combo_net_usdt(net, option_index=float(idx or 0))
if combo_usdt is None:
combo_usdt = round(perp_net, 4)
prem_coin = float(net.get("premium") or (float(avg_open or 0) * float(sheets or 0) * float(ct or 0.01)))
prem_usdt = round(prem_coin * float(idx or 0), 2) if idx else None
prem_usdt = round(prem_coin * float(idx), 2) if idx is not None and idx > 0 else None
opened_at = plan.get("opened_at")
opened_ms = _parse_opened_ms(opened_at)