diff --git a/backend/app/api/trades.py b/backend/app/api/trades.py index a25a6f5..62a0f19 100644 --- a/backend/app/api/trades.py +++ b/backend/app/api/trades.py @@ -101,6 +101,36 @@ def _move_points(g: dict, fills: list) -> float | None: return round(float(close_px) - e, 2) +def _option_entry_px(fills: list) -> float | None: + for row in fills: + f = dict(row) if not isinstance(row, dict) else row + if str(f.get("leg") or "") != "option" or str(f.get("action") or "") != "open": + continue + try: + v = float(f.get("fill_px") or 0) + if v > 0: + return v + except (TypeError, ValueError): + pass + break + return None + + +def _option_leverage(g: dict, fills: list) -> float | None: + """开仓期权杠杆 = 开仓指数 ÷ 期权开仓均价(与选约门限口径一致)。""" + from ..strategy.selection import option_leverage + + try: + entry = float(g.get("entry_index_px") or 0) + except (TypeError, ValueError): + return None + opt_px = _option_entry_px(fills) + if entry <= 0 or opt_px is None: + return None + lev = option_leverage(entry, opt_px) + return round(float(lev), 1) if lev is not None else None + + def _enrich_group(g: dict, fills: list) -> dict: summary = summarize_fills_pnl(fills) # LIVE:优先 groups.realized_pnl(已按交易所回写,含资金费) @@ -124,6 +154,7 @@ def _enrich_group(g: dict, fills: list) -> dict: mp = _move_points(g, fills) g["move_points"] = mp g["close_index_px"] = _close_index_px(g, fills) + g["option_leverage"] = _option_leverage(g, fills) return g diff --git a/backend/app/sim/matcher.py b/backend/app/sim/matcher.py index eaeb858..ca94ca8 100644 --- a/backend/app/sim/matcher.py +++ b/backend/app/sim/matcher.py @@ -1034,6 +1034,7 @@ class Matcher: option_upl = 0.0 est_opt_close_fee = 0.0 opt_mark = None + opt_bid_sz = None if oq and oq.bid is not None: bid = float(oq.bid) of = option_fill( @@ -1045,10 +1046,12 @@ class Matcher: ) est_opt_close_fee = of.fee opt_mark = bid + opt_bid_sz = float(oq.bid_sz) if oq.bid_sz is not None else None # 浮盈亏:买一×数量 − 初始权利金(对齐可市价卖出) option_upl = bid * opt_qty - initial_premium elif oq: opt_mark = oq.bid or oq.mark_px + opt_bid_sz = float(oq.bid_sz) if oq.bid_sz is not None else None if opt_mark is not None: option_upl = float(opt_mark) * opt_qty - initial_premium @@ -1063,6 +1066,11 @@ class Matcher: leverage = self.ledger.get_setting_float("leverage", s.leverage) notional = abs(perp_entry * perp_qty) margin = notional / leverage if leverage > 0 else None + from ..strategy.selection import option_leverage as _opt_lev + + opt_lev = _opt_lev(entry_idx, opt_entry) if entry_idx > 0 and opt_entry > 0 else None + if opt_lev is not None: + opt_lev = round(float(opt_lev), 1) group_id = pos.get("group_id") g = ( @@ -1105,6 +1113,8 @@ class Matcher: "option_qty_eth": opt_qty, "option_qty_contracts": float(pos["option_qty_contracts"] or 0), "option_mark_px": float(opt_mark) if opt_mark is not None else None, + "option_bid_sz": float(opt_bid_sz) if opt_bid_sz is not None else None, + "option_leverage": float(opt_lev) if opt_lev is not None else None, "strike": strike, "expiry_ymd": expiry_ymd, "expiry_ms": expiry_ms, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 9a21eb8..10f5c11 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -273,6 +273,8 @@ export type PlanState = { option_qty_eth?: number; option_qty_contracts?: number; option_mark_px?: number | null; + option_bid_sz?: number | null; + option_leverage?: number | null; strike?: number | null; expiry_ymd?: string | null; expiry_ms?: number | null; diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx index 8966c46..7cba7a4 100644 --- a/frontend/src/pages/Plan.tsx +++ b/frontend/src/pages/Plan.tsx @@ -14,6 +14,14 @@ function fmtTop(px: number | null | undefined, sz: number | null | undefined) { return `${fmt(px)}(${s})`; } +/** 持仓买一:价格/流动性张数,如 17.00/4000 */ +function fmtBidLiq(px: number | null | undefined, sz: number | null | undefined) { + if (px == null || Number.isNaN(px)) return "—"; + if (sz == null || Number.isNaN(sz)) return fmt(px); + const s = sz >= 100 ? sz.toFixed(0) : sz >= 10 ? sz.toFixed(1) : sz.toFixed(2); + return `${fmt(px)}/${s}`; +} + function pnlClass(n: number | null | undefined) { if (n == null || Number.isNaN(n) || n === 0) return ""; return n > 0 ? "pos-pnl-profit" : "pos-pnl-loss"; @@ -638,6 +646,9 @@ export default function PlanPage() { {fmt(pos?.option_qty_eth, 2)} ETH · {fmt(pos?.option_qty_contracts, 0)} 张 + + 杠杆 {fmt(pos?.option_leverage, 0)}x +