From bd7640e36ac941faf75ca7b2b06c12ad3669fff0 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 7 Aug 2026 17:40:45 +0800 Subject: [PATCH] Show both Call and Put legs for OO positions in control. Co-authored-by: Cursor --- backend/app/api/fleet.py | 32 ++++- control/frontend/src/pages/Monitor.tsx | 182 ++++++++++++++++--------- 2 files changed, 146 insertions(+), 68 deletions(-) diff --git a/backend/app/api/fleet.py b/backend/app/api/fleet.py index ee3148a..a753667 100644 --- a/backend/app/api/fleet.py +++ b/backend/app/api/fleet.py @@ -169,6 +169,12 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di except Exception: st = {} pos = st.get("position") if isinstance(st.get("position"), dict) else {} + hedge_mode = str( + pos.get("hedge_mode") + or st.get("hedge_mode") + or "" + ).strip().lower() + is_oo = hedge_mode == "option_option" or bool(pos.get("option2_inst_id")) legs: list[dict] = [] if pos.get("has_position") or str(pos.get("status") or "") in ( "open", @@ -176,7 +182,8 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di "option_closed_perp_pending", "opening", ): - if pos.get("perp_side") or pos.get("perp_inst_id"): + # 期期无永续腿;有 perp 才展示 + if not is_oo and (pos.get("perp_side") or pos.get("perp_inst_id")): legs.append( { "kind": "perp", @@ -194,7 +201,7 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di legs.append( { "kind": "option", - "side": pos.get("option_side"), + "side": pos.get("option_side") or ("call" if is_oo else None), "inst_id": pos.get("option_inst_id"), "qty": pos.get("option_qty_eth"), "avg_px": pos.get("option_entry_px"), @@ -202,6 +209,22 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di "upl": pos.get("option_upl"), "margin": None, "premium": pos.get("initial_premium"), + "strike": pos.get("strike"), + } + ) + if pos.get("option2_inst_id") or pos.get("option2_side"): + legs.append( + { + "kind": "option", + "side": pos.get("option2_side") or "put", + "inst_id": pos.get("option2_inst_id"), + "qty": pos.get("option2_qty_eth"), + "avg_px": pos.get("option2_entry_px"), + "mark_px": pos.get("option2_mark_px"), + "upl": pos.get("option2_upl"), + "margin": None, + "premium": pos.get("initial_premium2"), + "strike": pos.get("strike2"), } ) @@ -292,6 +315,7 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di if ( hm := str( st.get("hedge_mode") + or ("option_option" if is_oo else None) or db.get_setting("hedge_mode", settings.hedge_mode) or settings.hedge_mode or "perp_option" @@ -306,15 +330,19 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di "position": { "status": pos.get("status") or ("open" if pos.get("has_position") else "flat"), "has_position": bool(pos.get("has_position")), + "hedge_mode": "option_option" if is_oo else "perp_option", "group_id": pos.get("group_id"), "open_at_ms": pos.get("open_at_ms"), "initial_premium": pos.get("initial_premium"), + "initial_premium2": pos.get("initial_premium2"), "perp_margin": pos.get("perp_margin"), "exit_target_usdt": pos.get("exit_target_usdt"), "net_pnl": pos.get("net_pnl"), "perp_upl": pos.get("perp_upl"), "option_upl": pos.get("option_upl"), + "option2_upl": pos.get("option2_upl"), "strike": pos.get("strike"), + "strike2": pos.get("strike2"), "expiry_ymd": pos.get("expiry_ymd"), "legs": legs, }, diff --git a/control/frontend/src/pages/Monitor.tsx b/control/frontend/src/pages/Monitor.tsx index e3003e5..00bc09e 100644 --- a/control/frontend/src/pages/Monitor.tsx +++ b/control/frontend/src/pages/Monitor.tsx @@ -1196,40 +1196,74 @@ export default function MonitorPage() {

持仓

-

- 状态:{String(detail.position.status || "flat")} - {detail.position.net_pnl != null ? ( - <> - {" · 净浮盈 "} - - {fmt(detail.position.net_pnl, 2)} USDT - - - ) : null} - {detail.position.perp_margin != null ? ( - <> - {" · 保证金 "} - - {fmt(detail.position.perp_margin, 2)} U - - - ) : null} - {detail.position.initial_premium != null ? ( - <> - {" · 权利金 "} - - {fmt(detail.position.initial_premium, 2)} U - - - ) : null} - {detail.position.expiry_ymd - ? ` · 到期 ${String(detail.position.expiry_ymd)} @ ${ - detail.position.strike != null - ? Math.round(Number(detail.position.strike)) + {(() => { + const pos = detail.position; + const isOo = + String(pos.hedge_mode || detail.strat.hedge_mode || "") + .toLowerCase() === "option_option" || + detailLegs.some( + (lg) => + String(lg.side || "").toLowerCase() === "put" && + String(lg.kind || "") === "option", + ); + const prem1 = Number(pos.initial_premium); + const prem2 = Number(pos.initial_premium2); + const premTotal = + (Number.isFinite(prem1) ? prem1 : 0) + + (Number.isFinite(prem2) ? prem2 : 0); + const strikeLabel = isOo + ? `C@${ + pos.strike != null ? Math.round(Number(pos.strike)) : "—" + } / P@${ + pos.strike2 != null + ? Math.round(Number(pos.strike2)) : "—" }` - : ""} -

+ : pos.strike != null + ? String(Math.round(Number(pos.strike))) + : "—"; + return ( +

+ 状态:{String(pos.status || "flat")} + {isOo ? ( + <> + {" · "} + 期期 + + ) : null} + {pos.net_pnl != null ? ( + <> + {" · 净浮盈 "} + + {fmt(pos.net_pnl, 2)} USDT + + + ) : null} + {pos.perp_margin != null && !isOo ? ( + <> + {" · 保证金 "} + + {fmt(pos.perp_margin, 2)} U + + + ) : null} + {premTotal > 0 || pos.initial_premium != null ? ( + <> + {" · 权利金 "} + + {isOo && Number.isFinite(prem2) + ? `${fmt(prem1, 2)}+${fmt(prem2, 2)}=${fmt(premTotal, 2)}` + : fmt(pos.initial_premium, 2)}{" "} + U + + + ) : null} + {pos.expiry_ymd + ? ` · 到期 ${String(pos.expiry_ymd)} ${strikeLabel}` + : ""} +

+ ); + })()} {detailLegs.length ? (
@@ -1247,40 +1281,56 @@ export default function MonitorPage() { - {detailLegs.map((lg, i) => ( - - - - - - - - - - - - ))} + {detailLegs.map((lg, i) => { + const kind = String(lg.kind || ""); + const side = String(lg.side || "").toLowerCase(); + const legLabel = + kind === "perp" + ? "永续" + : side === "call" + ? "Call" + : side === "put" + ? "Put" + : kind || "—"; + return ( + + + + + + + + + + + + ); + })}
{String(lg.kind || "—")}{String(lg.side || "—")}{String(lg.inst_id || "—")}{fmt(lg.qty, 2)} - {fmtExPx(lg.kind, lg.avg_px)} - - {fmtExPx(lg.kind, lg.mark_px)} - - {lg.kind === "perp" - ? fmt( - lg.margin ?? detail.position.perp_margin, - 2, - ) - : "—"} - - {lg.kind === "option" - ? fmt( - lg.premium ?? - detail.position.initial_premium, - 2, - ) - : "—"} - - {fmt(lg.upl, 2)} -
+ {side === "put" ? ( + {legLabel} + ) : ( + legLabel + )} + {String(lg.side || "—")} + {String(lg.inst_id || "—")} + {fmt(lg.qty, 2)} + {fmtExPx(lg.kind, lg.avg_px)} + + {fmtExPx(lg.kind, lg.mark_px)} + + {kind === "perp" + ? fmt( + lg.margin ?? detail.position.perp_margin, + 2, + ) + : "—"} + + {kind === "option" || kind === "option2" + ? fmt(lg.premium, 2) + : "—"} + + {fmt(lg.upl, 2)} +