Show OTM option quotes in option-option mode.

Align session to amplitude Call/Put pair and replace perp/ATM market panels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-07 16:30:29 +08:00
parent cd88fa5d83
commit 0659611d82
4 changed files with 388 additions and 70 deletions
+12
View File
@@ -204,9 +204,21 @@ export type MarketSnapshot = {
index_px: number | null;
exchange?: string;
perp_inst_id?: string;
hedge_mode?: "perp_option" | "option_option";
oo_amplitude?: {
high?: number;
low?: number;
mid?: number;
range_pct?: number;
hours?: number;
min_pct?: number;
ok?: boolean;
} | null;
pair: {
expiry_ymd: string;
strike: number;
call_strike?: number;
put_strike?: number;
call_inst_id: string;
put_inst_id: string;
} | null;
+158 -59
View File
@@ -187,6 +187,7 @@ export default function PlanPage() {
const pos = plan?.position;
const isOo =
plan?.hedge_mode === "option_option" ||
snap?.hedge_mode === "option_option" ||
pos?.hedge_mode === "option_option" ||
!!pos?.option2_inst_id;
const open = !!pos?.has_position;
@@ -851,6 +852,11 @@ export default function PlanPage() {
</div>
</div>
</>
) : isOo ? (
<>
<div className="pos-empty">Call · </div>
<div className="pos-empty">Put · </div>
</>
) : (
<>
<div className="pos-empty"> · </div>
@@ -861,43 +867,102 @@ export default function PlanPage() {
</div>
<div className="plan-market">
<div className="card">
<h3 className="plan-panel-title"></h3>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("perp", snap?.perp?.bid)}</span>
{isOo ? (
<div className="card">
<h3 className="plan-panel-title"> / </h3>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("index", snap?.index_px)}</span>
</div>
<div className="kv">
<span>
{snap?.oo_amplitude?.hours != null
? `${fmt(snap.oo_amplitude.hours, 0)}h`
: ""}
</span>
<span className="mono">
{fmtExPx("index", snap?.oo_amplitude?.high)}
</span>
</div>
<div className="kv">
<span></span>
<span className="mono">
{fmtExPx("index", snap?.oo_amplitude?.low)}
</span>
</div>
<div className="kv">
<span></span>
<span className="mono">
{snap?.oo_amplitude?.range_pct != null
? `${fmt(snap.oo_amplitude.range_pct, 2)}%`
: "—"}
{snap?.oo_amplitude?.min_pct != null
? ` · 门限≥${fmt(snap.oo_amplitude.min_pct, 1)}%`
: ""}
{snap?.oo_amplitude?.ok === false
? " · 不足"
: snap?.oo_amplitude?.ok === true
? " · 达标"
: ""}
</span>
</div>
</div>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("perp", snap?.perp?.ask)}</span>
) : (
<div className="card">
<h3 className="plan-panel-title"></h3>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("perp", snap?.perp?.bid)}</span>
</div>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("perp", snap?.perp?.ask)}</span>
</div>
<div className="kv">
<span></span>
<span className="mono">
{fmtExPx(
"perp",
snap?.perp?.mark_px ??
(snap?.perp?.bid != null && snap?.perp?.ask != null
? (snap.perp.bid + snap.perp.ask) / 2
: null),
)}
</span>
</div>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("index", snap?.index_px)}</span>
</div>
</div>
<div className="kv">
<span></span>
<span className="mono">
{fmtExPx(
"perp",
snap?.perp?.mark_px ??
(snap?.perp?.bid != null && snap?.perp?.ask != null
? (snap.perp.bid + snap.perp.ask) / 2
: null),
)}
</span>
</div>
<div className="kv">
<span></span>
<span className="mono">{fmtExPx("index", snap?.index_px)}</span>
</div>
</div>
)}
<div className="card">
<h3 className="plan-panel-title">
{open ? "持仓期权" : "期权 ATM"}
{snap?.pair
? ` @ ${
snap.pair.strike != null
? Math.round(Number(snap.pair.strike))
: "—"
}`
: ""}
{isOo
? open
? "持仓虚值"
: "期期虚值"
: open
? "持仓期权"
: "期权 ATM"}
{isOo && snap?.pair
? ` · C@${Math.round(
Number(
snap.pair.call_strike ?? snap.pair.strike ?? 0,
),
)} / P@${Math.round(
Number(
snap.pair.put_strike ?? snap.pair.strike ?? 0,
),
)}`
: snap?.pair
? ` @ ${
snap.pair.strike != null
? Math.round(Number(snap.pair.strike))
: "—"
}`
: ""}
</h3>
{(() => {
const under =
@@ -921,17 +986,29 @@ export default function PlanPage() {
: open && heldSide === "put"
? putLev
: null;
const callK = snap?.pair?.call_strike ?? snap?.pair?.strike;
const putK = snap?.pair?.put_strike ?? snap?.pair?.strike;
return (
<>
<div className="kv">
<span>Call {sideLabel}</span>
<span>
Call {sideLabel}
{isOo && callK != null
? ` @${Math.round(Number(callK))}`
: ""}
</span>
<span className="mono">
{fmtTopEx("option", callPx, callSz)}
{!open ? ` · 杠杆 ${callLev}` : ""}
</span>
</div>
<div className="kv">
<span>Put {sideLabel}</span>
<span>
Put {sideLabel}
{isOo && putK != null
? ` @${Math.round(Number(putK))}`
: ""}
</span>
<span className="mono">
{fmtTopEx("option", putPx, putSz)}
{!open ? ` · 杠杆 ${putLev}` : ""}
@@ -940,7 +1017,7 @@ export default function PlanPage() {
<div className="kv">
<span></span>
<span className="mono">
{open
{open && !isOo
? heldLev ||
(heldSide
? "—"
@@ -951,29 +1028,51 @@ export default function PlanPage() {
</>
);
})()}
<div className="kv">
<span></span>
<span className="mono">
{(() => {
const strike = snap?.pair?.strike;
const px =
snap?.index_px ??
(snap?.perp?.bid != null && snap?.perp?.ask != null
? (snap.perp.bid + snap.perp.ask) / 2
: null);
if (strike == null || px == null) return "—";
const abs = Math.abs(strike - px);
const sign = strike - px > 0 ? "+" : "";
const delta = `${sign}${(strike - px).toFixed(0)}`;
if (!plan?.atm_open_offset_enabled) {
return `${delta} · 偏差限制关`;
}
const lim = plan?.max_atm_open_offset ?? 3;
const ok = abs <= lim + 1e-9;
return `${delta} · 开仓${ok ? "可" : "不可"}(|Δ|≤${lim})`;
})()}
</span>
</div>
{isOo ? (
<div className="kv">
<span></span>
<span className="mono">
{(() => {
const px = snap?.index_px;
const ck = snap?.pair?.call_strike ?? snap?.pair?.strike;
const pk = snap?.pair?.put_strike;
if (px == null || ck == null) return "—";
const cOff = Number(ck) - Number(px);
const pOff =
pk != null ? Number(pk) - Number(px) : null;
const fmtOff = (n: number) =>
`${n > 0 ? "+" : ""}${n.toFixed(0)}`;
return pOff != null
? `C ${fmtOff(cOff)} · P ${fmtOff(pOff)}`
: `C ${fmtOff(cOff)}`;
})()}
</span>
</div>
) : (
<div className="kv">
<span></span>
<span className="mono">
{(() => {
const strike = snap?.pair?.strike;
const px =
snap?.index_px ??
(snap?.perp?.bid != null && snap?.perp?.ask != null
? (snap.perp.bid + snap.perp.ask) / 2
: null);
if (strike == null || px == null) return "—";
const abs = Math.abs(strike - px);
const sign = strike - px > 0 ? "+" : "";
const delta = `${sign}${(strike - px).toFixed(0)}`;
if (!plan?.atm_open_offset_enabled) {
return `${delta} · 偏差限制关`;
}
const lim = plan?.max_atm_open_offset ?? 3;
const ok = abs <= lim + 1e-9;
return `${delta} · 开仓${ok ? "可" : "不可"}(|Δ|≤${lim})`;
})()}
</span>
</div>
)}
<div className="kv">
<span></span>
<span className="mono">{snap?.pair?.expiry_ymd || "—"}</span>