diff --git a/backend/app/api/trades.py b/backend/app/api/trades.py index 217d3d8..08d37ef 100644 --- a/backend/app/api/trades.py +++ b/backend/app/api/trades.py @@ -16,35 +16,62 @@ def _row(r: Any) -> dict: return dict(r) +def _is_oo_group(g: dict) -> bool: + return ( + str(g.get("hedge_mode") or "") == "option_option" + or bool(g.get("option2_inst_id")) + or str(g.get("bias") or "") == "option_option" + ) + + +def _infer_settle_index(g: dict, fills: list) -> float | None: + settle_index = g.get("settle_index_px") + if settle_index is not None: + try: + v = float(settle_index) + if v > 0: + return v + except (TypeError, ValueError): + pass + strike = g.get("strike") + side = str(g.get("option_side") or "").lower() + if strike is None: + return None + for raw in fills: + f = dict(raw) if not isinstance(raw, dict) else raw + if str(f.get("leg")) != "option" or str(f.get("action")) != "close": + continue + if abs(float(f.get("slip") or 0)) > 1e-12: + continue + px = float(f.get("fill_px") or 0) + k = float(strike) + if side in ("call", "c"): + return k + px + if side in ("put", "p"): + return k - px + break + return None + + +def _intrinsic(side: str, settle_index: float, strike: float) -> float: + s = str(side or "").lower() + if s in ("call", "c"): + return max(settle_index - strike, 0.0) + if s in ("put", "p"): + return max(strike - settle_index, 0.0) + return 0.0 + + def _expiry_settle_info(g: dict, fills: list) -> dict | None: """到期结算口径:期权价 = 内在价值(指数 vs 行权价),非盘口。""" if str(g.get("close_reason") or "") != "expiry": return None + settle_index = _infer_settle_index(g, fills) strike = g.get("strike") side = str(g.get("option_side") or "").lower() - settle_index = g.get("settle_index_px") - if settle_index is None and strike is not None: - for raw in fills: - f = dict(raw) if not isinstance(raw, dict) else raw - if str(f.get("leg")) != "option" or str(f.get("action")) != "close": - continue - if abs(float(f.get("slip") or 0)) > 1e-12: - continue - px = float(f.get("fill_px") or 0) - k = float(strike) - if side in ("call", "c"): - settle_index = k + px - elif side in ("put", "p"): - settle_index = k - px - break intrinsic = None if settle_index is not None and strike is not None: - s = float(settle_index) - k = float(strike) - if side in ("call", "c"): - intrinsic = max(s - k, 0.0) - elif side in ("put", "p"): - intrinsic = max(k - s, 0.0) + intrinsic = _intrinsic(side, float(settle_index), float(strike)) formula = ( "Call: max(指数−行权价, 0)" if side in ("call", "c") @@ -52,13 +79,36 @@ def _expiry_settle_info(g: dict, fills: list) -> dict | None: if side in ("put", "p") else "" ) - return { + is_oo = _is_oo_group(g) + out: dict[str, Any] = { "settle_index_px": float(settle_index) if settle_index is not None else None, "strike": float(strike) if strike is not None else None, "intrinsic": intrinsic, "formula": formula, - "perp_note": "永续仍按市价平仓(非指数交割)", + "perp_note": ( + "期期无永续腿;两腿均按内在价值结算" + if is_oo + else "永续仍按市价平仓(非指数交割)" + ), + "is_oo": is_oo, } + if is_oo: + strike2 = g.get("strike2") + side2 = str(g.get("option2_side") or "put").lower() + intrinsic2 = None + if settle_index is not None and strike2 is not None: + intrinsic2 = _intrinsic(side2, float(settle_index), float(strike2)) + out["strike2"] = float(strike2) if strike2 is not None else None + out["intrinsic2"] = intrinsic2 + out["formula2"] = ( + "Put: max(行权价−指数, 0)" + if side2 in ("put", "p") + else "Call: max(指数−行权价, 0)" + if side2 in ("call", "c") + else "" + ) + out["option2_side"] = side2 + return out def _close_index_px(g: dict, fills: list) -> float | None: @@ -101,10 +151,10 @@ def _move_points(g: dict, fills: list) -> float | None: return round(float(close_px) - e, 2) -def _option_entry_px(fills: list) -> float | None: +def _option_entry_px(fills: list, *, leg: str = "option") -> 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": + if str(f.get("leg") or "") != leg or str(f.get("action") or "") != "open": continue try: v = float(f.get("fill_px") or 0) @@ -116,7 +166,9 @@ def _option_entry_px(fills: list) -> float | None: return None -def _option_leverage(g: dict, fills: list) -> float | None: +def _option_leverage_for_leg( + g: dict, fills: list, *, leg: str = "option" +) -> float | None: """开仓期权杠杆 = 开仓指数 ÷ 期权开仓均价(与选约门限口径一致)。""" from ..strategy.selection import option_leverage @@ -124,7 +176,7 @@ def _option_leverage(g: dict, fills: list) -> float | None: entry = float(g.get("entry_index_px") or 0) except (TypeError, ValueError): return None - opt_px = _option_entry_px(fills) + opt_px = _option_entry_px(fills, leg=leg) if entry <= 0 or opt_px is None: return None lev = option_leverage(entry, opt_px) @@ -132,6 +184,8 @@ def _option_leverage(g: dict, fills: list) -> float | None: def _enrich_group(g: dict, fills: list) -> dict: + is_oo = _is_oo_group(g) + g["is_oo"] = is_oo summary = summarize_fills_pnl(fills) # LIVE:优先 groups.realized_pnl(已按交易所回写,含资金费) if str(g.get("exec_mode") or "").upper() == "LIVE" and g.get("realized_pnl") is not None: @@ -140,11 +194,26 @@ def _enrich_group(g: dict, fills: list) -> dict: if g.get("funding_usdt") is not None: summary["funding_usdt"] = float(g["funding_usdt"]) summary["pnl_source"] = "live_exchange" + # 期期 SIM:若成交汇总缺腿但组上已有 realized_pnl,用组值兜底 + elif ( + is_oo + and g.get("realized_pnl") is not None + and ( + summary.get("option_pnl") is None + or summary.get("option2_pnl") is None + ) + ): + summary = dict(summary) + summary["net_pnl"] = float(g["realized_pnl"]) + summary["pnl_source"] = "group_realized" g["pnl_summary"] = summary if summary.get("net_pnl") is not None: g["net_pnl"] = summary["net_pnl"] elif g.get("realized_pnl") is not None: g["net_pnl"] = float(g["realized_pnl"]) + prem1 = float(g.get("initial_premium") or 0) + prem2 = float(g.get("initial_premium2") or 0) if is_oo else 0.0 + g["total_initial_premium"] = prem1 + prem2 if is_oo else prem1 g.update(hold_timing(g, fills)) info = _expiry_settle_info(g, fills) if info: @@ -154,7 +223,9 @@ 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) + g["option_leverage"] = _option_leverage_for_leg(g, fills, leg="option") + if is_oo: + g["option2_leverage"] = _option_leverage_for_leg(g, fills, leg="option2") return g diff --git a/backend/app/sim/pnl.py b/backend/app/sim/pnl.py index dbc7201..4ad9c53 100644 --- a/backend/app/sim/pnl.py +++ b/backend/app/sim/pnl.py @@ -14,23 +14,35 @@ def _as_map(x: Any) -> dict[str, Any]: return {} -def summarize_fills_pnl(fills: list[Any]) -> dict[str, float | None]: - """ - 价差盈亏按 fill_px;手续费另扣。 - 净盈亏 = 期权盈亏 + 永续盈亏 − 全部手续费(开+平)。 - 允许只有永续已平、期权尚未结算的半组。 - - 手续费拆:fees_perp / fees_option;滑点合计 slip_total(SIM 记账;LIVE 应为 0)。 - """ - rows = [_as_map(x) for x in fills] +def _leg_option_pnl(rows: list[dict[str, Any]], leg: str) -> float | None: opt_open = next( - (f for f in rows if f.get("leg") == "option" and f.get("action") == "open"), + (f for f in rows if f.get("leg") == leg and f.get("action") == "open"), None, ) opt_close = next( - (f for f in rows if f.get("leg") == "option" and f.get("action") == "close"), + (f for f in rows if f.get("leg") == leg and f.get("action") == "close"), None, ) + if not opt_open or not opt_close: + return None + qty = float(opt_open.get("qty_eth") or opt_close.get("qty_eth") or 0) + return (float(opt_close["fill_px"]) - float(opt_open["fill_px"])) * qty + + +def summarize_fills_pnl(fills: list[Any]) -> dict[str, float | None]: + """ + 价差盈亏按 fill_px;手续费另扣。 + 净盈亏 = 各腿盈亏之和 − 全部手续费(开+平)。 + 支持永期(option+perp)与期期(option+option2)。 + + 手续费拆:fees_perp / fees_option(含 option2)/ fees_option2; + 滑点合计 slip_total(SIM 记账;LIVE 应为 0)。 + """ + rows = [_as_map(x) for x in fills] + + option_pnl = _leg_option_pnl(rows, "option") + option2_pnl = _leg_option_pnl(rows, "option2") + perp_open = next( (f for f in rows if f.get("leg") == "perp" and f.get("action") == "open"), None, @@ -40,11 +52,6 @@ def summarize_fills_pnl(fills: list[Any]) -> dict[str, float | None]: None, ) - option_pnl: float | None = None - if opt_open and opt_close: - qty = float(opt_open.get("qty_eth") or opt_close.get("qty_eth") or 0) - option_pnl = (float(opt_close["fill_px"]) - float(opt_open["fill_px"])) * qty - perp_pnl: float | None = None if perp_open and perp_close: qty = float(perp_open.get("qty_eth") or perp_close.get("qty_eth") or 0) @@ -60,27 +67,34 @@ def summarize_fills_pnl(fills: list[Any]) -> dict[str, float | None]: float(f.get("fee") or 0) for f in rows if str(f.get("leg") or "") == "perp" ) fees_option = sum( - float(f.get("fee") or 0) for f in rows if str(f.get("leg") or "") == "option" + float(f.get("fee") or 0) + for f in rows + if str(f.get("leg") or "") in ("option", "option2") + ) + fees_option2 = sum( + float(f.get("fee") or 0) for f in rows if str(f.get("leg") or "") == "option2" ) fees_total = fees_perp + fees_option slip_total = sum(float(f.get("slip") or 0) for f in rows) - gross = None - net = None - if option_pnl is not None and perp_pnl is not None: - gross = option_pnl + perp_pnl - net = gross - fees_total - elif option_pnl is not None: - gross = option_pnl - net = option_pnl - fees_total - elif perp_pnl is not None: - gross = perp_pnl - net = perp_pnl - fees_total + + parts: list[float] = [] + if option_pnl is not None: + parts.append(option_pnl) + if option2_pnl is not None: + parts.append(option2_pnl) + if perp_pnl is not None: + parts.append(perp_pnl) + + gross = sum(parts) if parts else None + net = (gross - fees_total) if gross is not None else None return { "option_pnl": option_pnl, + "option2_pnl": option2_pnl, "perp_pnl": perp_pnl, "fees_perp": fees_perp, "fees_option": fees_option, + "fees_option2": fees_option2, "fees_total": fees_total, "slip_total": slip_total, "gross_pnl": gross, diff --git a/backend/tests/test_oo_fills_pnl.py b/backend/tests/test_oo_fills_pnl.py new file mode 100644 index 0000000..01d6883 --- /dev/null +++ b/backend/tests/test_oo_fills_pnl.py @@ -0,0 +1,67 @@ +"""期期成交盈亏汇总:须计入 option2(Put)腿。""" + +from __future__ import annotations + +from app.sim.pnl import summarize_fills_pnl + + +def test_summarize_oo_both_legs() -> None: + fills = [ + { + "leg": "option", + "action": "open", + "side": "long", + "fill_px": 12.6, + "qty_eth": 3.5, + "fee": 0.0221, + "slip": 0, + }, + { + "leg": "option2", + "action": "open", + "side": "long", + "fill_px": 9.0, + "qty_eth": 5.0, + "fee": 0.0225, + "slip": 0, + }, + { + "leg": "option", + "action": "close", + "side": "sell", + "fill_px": 0.0, + "qty_eth": 3.5, + "fee": 0.0, + "slip": 0, + }, + { + "leg": "option2", + "action": "close", + "side": "sell", + "fill_px": 0.0, + "qty_eth": 5.0, + "fee": 0.0, + "slip": 0, + }, + ] + s = summarize_fills_pnl(fills) + assert s["option_pnl"] == -12.6 * 3.5 + assert s["option2_pnl"] == -9.0 * 5.0 + assert s["perp_pnl"] is None + assert abs(float(s["fees_option"] or 0) - 0.0446) < 1e-9 + assert abs(float(s["gross_pnl"] or 0) - (-44.1 - 45.0)) < 1e-9 + assert abs(float(s["net_pnl"] or 0) - (-89.1 - 0.0446)) < 1e-9 + + +def test_summarize_perp_option_unchanged() -> None: + fills = [ + {"leg": "option", "action": "open", "fill_px": 10, "qty_eth": 2, "fee": 0.1, "slip": 0}, + {"leg": "perp", "action": "open", "side": "short", "fill_px": 100, "qty_eth": 1, "fee": 0.2, "slip": 0}, + {"leg": "option", "action": "close", "fill_px": 12, "qty_eth": 2, "fee": 0.1, "slip": 0}, + {"leg": "perp", "action": "close", "side": "short", "fill_px": 98, "qty_eth": 1, "fee": 0.2, "slip": 0}, + ] + s = summarize_fills_pnl(fills) + assert s["option_pnl"] == 4.0 + assert s["perp_pnl"] == 2.0 + assert s["option2_pnl"] is None + assert abs(float(s["net_pnl"] or 0) - (6.0 - 0.6)) < 1e-9 diff --git a/docs/更新说明.md b/docs/更新说明.md index cde0e3a..be9052c 100644 --- a/docs/更新说明.md +++ b/docs/更新说明.md @@ -5,6 +5,20 @@ --- +## 2026-08-11 — 期期交易记录展示与盈亏汇总 + +### 变更 + +1. `summarize_fills_pnl` 计入 `option2`(Put)盈亏与手续费,期期净盈亏不再漏腿。 +2. 交易记录列表/详情:期期显示「期期·看涨+看跌」、Call/Put 合约与权利金、双腿结算与 Call/Put 盈亏;成交文案不再出现 `option2平仓sell`。 +3. 到期结算信息补充 Put 行权价/内在价值。 + +### 审计 + +截图 G-20260807-01:详情按永期模板只显 Call,初始权利金漏 Put,成交腿标签乱码,净盈亏汇总缺 Put。 + +--- + ## 2026-08-08 — 策略格式三页文档 ### 变更 diff --git a/frontend/src/labels.ts b/frontend/src/labels.ts index 75e6dbe..f29bf8d 100644 --- a/frontend/src/labels.ts +++ b/frontend/src/labels.ts @@ -11,11 +11,14 @@ const SIDE_ZH: Record = { short: "空", call: "看涨", put: "看跌", + buy: "多", + sell: "平", }; const LEG_ZH: Record = { perp: "永续", option: "期权", + option2: "Put", }; const ACTION_ZH: Record = { @@ -27,6 +30,7 @@ const CLOSE_REASON_ZH: Record = { fixed_usdt: "固定净盈利达标·双腿全平", premium_multiple: "权利金倍数达标·双腿全平", target_perp_only: "净盈利达标·只平永续(期权归档)", + target_oo_win: "期期达标·平盈利腿(亏损腿残留)", residual_premium_close: "残留期权·权利金回收中途平", semi_target_points: "半自动·指数到行权价±波动点且净利>0·双腿全平", semi_perp_exit: "半自动·永续净利锁定达标·双腿全平", @@ -47,11 +51,20 @@ export function sideZh(v: string | null | undefined): string { return SIDE_ZH[v] || v; } -/** 永续方向 / 期权方向,如「多/看跌」 */ +/** 永续方向 / 期权方向,如「多/看跌」;期期为「期期·看涨+看跌」 */ export function positionSidesZh( perp: string | null | undefined, option: string | null | undefined, + opts?: { + isOo?: boolean; + option2?: string | null; + }, ): string { + if (opts?.isOo) { + const a = sideZh(option || "call"); + const b = sideZh(opts.option2 || "put"); + return `期期·${a}+${b}`; + } return `${sideZh(perp)}/${sideZh(option)}`; } @@ -60,18 +73,33 @@ export function fillDescZh( action: string, side: string, closeReason?: string | null, + isOo?: boolean, ): string { + const isExpiry = closeReason === "expiry"; + if (leg === "option2") { + if (action === "open") return "Put开多"; + if (action === "close" && isExpiry) return "Put到期结算"; + if (action === "close") return "Put平多"; + } + if (leg === "option") { + if (isOo) { + if (action === "open") return "Call开多"; + if (action === "close" && isExpiry) return "Call到期结算"; + if (action === "close") return "Call平多"; + } + if (action === "open") return "期权开多"; + if (action === "close" && isExpiry) return "期权到期结算"; + if (action === "close") return "期权平多"; + } + if (leg === "perp" && action === "open") + return sideZh(side) === "多" ? "永续开多" : "永续开空"; + if (leg === "perp" && action === "close") { + const s = sideZh(side); + return s === "多" || side === "long" ? "永续平多" : "永续平空"; + } const l = LEG_ZH[leg] || leg; const a = ACTION_ZH[action] || action; const s = SIDE_ZH[side] || side; - if (leg === "option" && action === "close" && closeReason === "expiry") { - return "期权到期结算"; - } - // 期权买入开仓:「期权开多」;永续:「永续开多/开空」 - if (leg === "option" && action === "open") return "期权开多"; - if (leg === "option" && action === "close") return "期权平多"; - if (leg === "perp" && action === "open") return s === "多" ? "永续开多" : "永续开空"; - if (leg === "perp" && action === "close") return s === "多" ? "永续平多" : "永续平空"; return `${l}${a}${s}`; } diff --git a/frontend/src/pages/Trades.tsx b/frontend/src/pages/Trades.tsx index 7711e79..1115169 100644 --- a/frontend/src/pages/Trades.tsx +++ b/frontend/src/pages/Trades.tsx @@ -10,9 +10,11 @@ import { type PnlSummary = { option_pnl: number | null; + option2_pnl?: number | null; perp_pnl: number | null; fees_perp?: number; fees_option?: number; + fees_option2?: number; fees_total: number; slip_total?: number; gross_pnl: number | null; @@ -25,6 +27,11 @@ type ExpirySettle = { intrinsic: number | null; formula: string; perp_note: string; + is_oo?: boolean; + strike2?: number | null; + intrinsic2?: number | null; + formula2?: string; + option2_side?: string | null; }; type Group = { @@ -34,9 +41,13 @@ type Group = { option_side: string | null; perp_side: string | null; option_inst_id?: string | null; + option2_inst_id?: string | null; + option2_side?: string | null; perp_inst_id?: string | null; expiry_ymd?: string | null; initial_premium: number; + initial_premium2?: number | null; + total_initial_premium?: number | null; realized_pnl: number; net_pnl?: number | null; close_reason: string | null; @@ -47,16 +58,30 @@ type Group = { hold_ms?: number | null; hold_basis?: string | null; strike?: number | null; + strike2?: number | null; settle_index_px?: number | null; entry_index_px?: number | null; close_index_px?: number | null; move_points?: number | null; option_leverage?: number | null; + option2_leverage?: number | null; exec_mode?: string | null; + hedge_mode?: string | null; + is_oo?: boolean; expiry_settle?: ExpirySettle | null; pnl_summary?: PnlSummary; }; +function isOoGroup(g: Group | null | undefined): boolean { + if (!g) return false; + return ( + g.is_oo === true || + g.hedge_mode === "option_option" || + !!g.option2_inst_id || + g.bias === "option_option" + ); +} + type Fill = { id: number; leg: string; @@ -111,6 +136,24 @@ function groupPnl(g: Group) { return g.net_pnl ?? g.pnl_summary?.net_pnl ?? g.realized_pnl; } +function groupDirectionZh(g: Group) { + return positionSidesZh(g.perp_side, g.option_side, { + isOo: isOoGroup(g), + option2: g.option2_side, + }); +} + +function groupLeverageZh(g: Group) { + if (isOoGroup(g)) { + const a = + g.option_leverage != null ? `${fmt(g.option_leverage, 0)}x` : "—"; + const b = + g.option2_leverage != null ? `${fmt(g.option2_leverage, 0)}x` : "—"; + return `${a}/${b}`; + } + return g.option_leverage != null ? `${fmt(g.option_leverage, 0)}x` : "—"; +} + /** 开仓→平仓指数点数(带符号) */ function fmtMovePoints(n: number | null | undefined) { if (n == null || Number.isNaN(n)) return "—"; @@ -272,8 +315,7 @@ export default function TradesPage() {
{g.group_id} - {statusZh(g.status)} ·{" "} - {positionSidesZh(g.perp_side, g.option_side)} + {statusZh(g.status)} · {groupDirectionZh(g)} 开 {fmtTime(openMs)} · 平 {fmtTime(closeMs)} · 周期{" "} @@ -281,8 +323,8 @@ export default function TradesPage() { {g.move_points != null ? ` · 波动 ${fmtMovePoints(g.move_points)}` : ""} - {g.option_leverage != null - ? ` · 期权杠杆 ${fmt(g.option_leverage, 0)}x` + {isOoGroup(g) || g.option_leverage != null + ? ` · ${isOoGroup(g) ? "杠杆" : "期权杠杆"} ${groupLeverageZh(g)}` : ""}
@@ -343,7 +385,7 @@ export default function TradesPage() { 开仓时间 平仓时间 持仓时长 - 期权杠杆 + 杠杆 波动点数 盈亏金额 平仓方式 @@ -366,18 +408,12 @@ export default function TradesPage() { > {seq} {g.group_id} - - {positionSidesZh(g.perp_side, g.option_side)} - + {groupDirectionZh(g)} {statusZh(g.status)} {fmtTime(openMs)} {fmtTime(closeMs)} {fmtHold(g.hold_ms)} - - {g.option_leverage != null - ? `${fmt(g.option_leverage, 0)}x` - : "—"} - + {groupLeverageZh(g)} {fmtMovePoints(g.move_points)} {fmt(listPnl)} @@ -487,6 +523,19 @@ export default function TradesPage() { {!detailLoading && !detailErr && selectedGroup ? ( <> + {(() => { + const oo = isOoGroup(selectedGroup); + const callInst = + selectedGroup.option_inst_id || + optionInstFromFills(fills) || + "—"; + const putInst = selectedGroup.option2_inst_id || "—"; + const totalPrem = + selectedGroup.total_initial_premium ?? + (Number(selectedGroup.initial_premium || 0) + + Number(selectedGroup.initial_premium2 || 0)); + return ( + <>
状态 @@ -498,22 +547,32 @@ export default function TradesPage() {
- 方向 - - {positionSidesZh( - selectedGroup.perp_side, - selectedGroup.option_side, - )} - + 策略 + {oo ? "期期对冲" : "永期对冲"}
- 期权合约 + 方向 - {selectedGroup.option_inst_id || - optionInstFromFills(fills) || - "—"} + {groupDirectionZh(selectedGroup)}
+ {oo ? ( + <> +
+ Call合约 + {callInst} +
+
+ Put合约 + {putInst} +
+ + ) : ( +
+ 期权合约 + {callInst} +
+ )}
期权到期日 @@ -526,12 +585,15 @@ export default function TradesPage() { )}
- {selectedGroup.strike != null && - !selectedGroup.expiry_settle ? ( + {!selectedGroup.expiry_settle && + (selectedGroup.strike != null || + (oo && selectedGroup.strike2 != null)) ? (
行权价 - {fmt(selectedGroup.strike, 0)} + {oo + ? `Call ${fmt(selectedGroup.strike, 0)} / Put ${fmt(selectedGroup.strike2, 0)}` + : fmt(selectedGroup.strike, 0)}
) : null} @@ -563,21 +625,42 @@ export default function TradesPage() {
- 期权杠杆 + {oo ? "杠杆(Call/Put)" : "期权杠杆"} - {selectedGroup.option_leverage != null - ? `${fmt(selectedGroup.option_leverage, 0)}x` - : "—"} - -
-
- 初始权利金 - - {selectedGroup.initial_premium != null - ? `${fmt(selectedGroup.initial_premium)} USDT` - : "—"} + {groupLeverageZh(selectedGroup)}
+ {oo ? ( + <> +
+ Call权利金 + + {fmt(selectedGroup.initial_premium)} USDT + +
+
+ Put权利金 + + {fmt(selectedGroup.initial_premium2)} USDT + +
+
+ 初始权利金合计 + + {fmt(totalPrem)} USDT + +
+ + ) : ( +
+ 初始权利金 + + {selectedGroup.initial_premium != null + ? `${fmt(selectedGroup.initial_premium)} USDT` + : "—"} + +
+ )}
波动点数 @@ -591,9 +674,13 @@ export default function TradesPage() {

- {selectedGroup.close_reason === "expiry" - ? "到期结算:期权按「指数 vs 行权价」的内在价值入账(非盘口);永续仍按市价平。净盈亏 = 期权盈亏 + 永续盈亏 − 全部手续费。" - : "成交价为成交均价(未预先扣费);手续费单独列出。净盈亏 = 期权盈亏 + 永续盈亏 − 全部手续费。"} + {oo + ? selectedGroup.close_reason === "expiry" + ? "期期到期:Call/Put 均按「指数 vs 行权价」内在价值结算(非盘口)。净盈亏 = Call盈亏 + Put盈亏 − 全部手续费。" + : "期期对冲:双腿均为期权,无永续。成交价为成交均价;手续费单独列出。净盈亏 = Call盈亏 + Put盈亏 − 全部手续费。" + : selectedGroup.close_reason === "expiry" + ? "到期结算:期权按「指数 vs 行权价」的内在价值入账(非盘口);永续仍按市价平。净盈亏 = 期权盈亏 + 永续盈亏 − 全部手续费。" + : "成交价为成交均价(未预先扣费);手续费单独列出。净盈亏 = 期权盈亏 + 永续盈亏 − 全部手续费。"}

{selectedGroup.expiry_settle ? ( @@ -607,26 +694,54 @@ export default function TradesPage() { )} -
- 行权价 - - {fmt(selectedGroup.expiry_settle.strike, 0)} - -
-
- 内在价值 - - {fmt(selectedGroup.expiry_settle.intrinsic)} - {selectedGroup.expiry_settle.formula - ? ` · ${selectedGroup.expiry_settle.formula}` - : ""} - -
+ {oo ? ( + <> +
+ Call行权价 / 内在价值 + + {fmt(selectedGroup.expiry_settle.strike, 0)} /{" "} + {fmt(selectedGroup.expiry_settle.intrinsic)} + {selectedGroup.expiry_settle.formula + ? ` · ${selectedGroup.expiry_settle.formula}` + : ""} + +
+
+ Put行权价 / 内在价值 + + {fmt(selectedGroup.expiry_settle.strike2, 0)} /{" "} + {fmt(selectedGroup.expiry_settle.intrinsic2)} + {selectedGroup.expiry_settle.formula2 + ? ` · ${selectedGroup.expiry_settle.formula2}` + : ""} + +
+ + ) : ( + <> +
+ 行权价 + + {fmt(selectedGroup.expiry_settle.strike, 0)} + +
+
+ 内在价值 + + {fmt(selectedGroup.expiry_settle.intrinsic)} + {selectedGroup.expiry_settle.formula + ? ` · ${selectedGroup.expiry_settle.formula}` + : ""} + +
+ + )} ) : null} {fills.map((f) => { - const isOpt = f.leg === "option"; + const isOpt = + f.leg === "option" || f.leg === "option2"; const pxLabel = isOpt ? "权利金" : "价"; const notional = isOpt && f.action === "open" @@ -640,6 +755,7 @@ export default function TradesPage() { f.action, f.side, selectedGroup.close_reason, + oo, )} {f.inst_id ? ( · {f.inst_id} @@ -660,24 +776,51 @@ export default function TradesPage() { {summary ? (
-
- 期权盈亏 - - {fmt(summary.option_pnl)} - -
-
- 永续盈亏 - - {fmt(summary.perp_pnl)} - -
-
- 永续手续费 - - {fmt(summary.fees_perp ?? 0, 4)} - -
+ {oo ? ( + <> +
+ Call盈亏 + + {fmt(summary.option_pnl)} + +
+
+ Put盈亏 + + {fmt(summary.option2_pnl)} + +
+ + ) : ( + <> +
+ 期权盈亏 + + {fmt(summary.option_pnl)} + +
+
+ 永续盈亏 + + {fmt(summary.perp_pnl)} + +
+
+ 永续手续费 + + {fmt(summary.fees_perp ?? 0, 4)} + +
+ + )}
期权手续费 @@ -707,6 +850,9 @@ export default function TradesPage() {
) : null} + + ); + })()} ) : null}