diff --git a/backend/app/api/trades.py b/backend/app/api/trades.py index bb858ba..a25a6f5 100644 --- a/backend/app/api/trades.py +++ b/backend/app/api/trades.py @@ -61,6 +61,46 @@ def _expiry_settle_info(g: dict, fills: list) -> dict | None: } +def _close_index_px(g: dict, fills: list) -> float | None: + """平仓时标的指数:优先 settle_index_px,否则用永续平仓价近似。""" + raw = g.get("settle_index_px") + if raw is not None: + try: + v = float(raw) + if v > 0: + return v + except (TypeError, ValueError): + pass + for row in fills: + f = dict(row) if not isinstance(row, dict) else row + if str(f.get("leg") or "") == "perp" and str(f.get("action") or "") == "close": + try: + v = float(f.get("fill_px") or 0) + if v > 0: + return v + except (TypeError, ValueError): + pass + break + return None + + +def _move_points(g: dict, fills: list) -> float | None: + """开仓指数 → 平仓指数的点数(带符号:上涨为正)。持仓中无平仓价则空。""" + entry = g.get("entry_index_px") + if entry is None: + return None + try: + e = float(entry) + except (TypeError, ValueError): + return None + if e <= 0: + return None + close_px = _close_index_px(g, fills) + if close_px is None: + return None + return round(float(close_px) - e, 2) + + def _enrich_group(g: dict, fills: list) -> dict: summary = summarize_fills_pnl(fills) # LIVE:优先 groups.realized_pnl(已按交易所回写,含资金费) @@ -81,6 +121,9 @@ def _enrich_group(g: dict, fills: list) -> dict: g["expiry_settle"] = info if g.get("settle_index_px") is None and info.get("settle_index_px") is not None: g["settle_index_px"] = info["settle_index_px"] + mp = _move_points(g, fills) + g["move_points"] = mp + g["close_index_px"] = _close_index_px(g, fills) return g diff --git a/backend/app/live/executor.py b/backend/app/live/executor.py index 4994f82..b652302 100644 --- a/backend/app/live/executor.py +++ b/backend/app/live/executor.py @@ -1108,10 +1108,11 @@ class OkxLiveExecutor(Matcher): net = summary.get("net_pnl") if net is None: net = opt_pnl + perp_pnl - of_fee - pf_fee + close_index = float(spot) if spot is not None else None self.db._conn.execute( """UPDATE groups SET status=?, close_at_ms=?, close_reason=?, realized_pnl=?, - fees=?, slip_cost=? WHERE group_id=?""", - ("closed", now, reason, float(net), fees, slip, group_id), + fees=?, slip_cost=?, settle_index_px=COALESCE(?, settle_index_px) WHERE group_id=?""", + ("closed", now, reason, float(net), fees, slip, close_index, group_id), ) self.db._conn.execute( """UPDATE positions SET diff --git a/backend/app/sim/matcher.py b/backend/app/sim/matcher.py index c6e01cc..eaeb858 100644 --- a/backend/app/sim/matcher.py +++ b/backend/app/sim/matcher.py @@ -583,7 +583,7 @@ class Matcher: ).fetchone() fees = float(g["fees"] or 0) + pf.fee + of.fee slip = float(g["slip_cost"] or 0) + pf.slip + of.slip - settle_index = float(spot) if is_expiry and spot is not None else None + settle_index = float(spot) if spot is not None else None self.db._conn.execute( """UPDATE groups SET status=?, close_at_ms=?, close_reason=?, realized_pnl=?, fees=?, slip_cost=?, note=NULL, settle_index_px=? WHERE group_id=?""", @@ -1045,7 +1045,7 @@ class Matcher: ) est_opt_close_fee = of.fee opt_mark = bid - # 浮盈亏:买一×数量 − 初始权利金 + # 浮盈亏:买一×数量 − 初始权利金(对齐可市价卖出) option_upl = bid * opt_qty - initial_premium elif oq: opt_mark = oq.bid or oq.mark_px diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx index 219ba83..8966c46 100644 --- a/frontend/src/pages/Plan.tsx +++ b/frontend/src/pages/Plan.tsx @@ -645,7 +645,7 @@ export default function PlanPage() { {fmt(pos?.option_entry_px)}