Align strategy UI prices with exchange tick sizes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 12:34:42 +08:00
parent 0c12d76212
commit 921e96321f
3 changed files with 87 additions and 31 deletions
+43
View File
@@ -0,0 +1,43 @@
/** 价格按交易所常见精度:永续/指数 0.01;期权权利金 0.1 */
export type PxKind = "perp" | "option" | "index";
export function fmtExPx(
kind: PxKind,
px: number | null | undefined,
): string {
if (px == null || Number.isNaN(Number(px))) return "—";
const n = Number(px);
if (kind === "option") {
return (Math.round(n * 10) / 10).toFixed(1);
}
return (Math.round(n * 100) / 100).toFixed(2);
}
export function fmtTopEx(
kind: PxKind,
px: number | null | undefined,
sz: number | null | undefined,
): string {
if (px == null || Number.isNaN(Number(px))) return "—";
const p = fmtExPx(kind, px);
if (sz == null || Number.isNaN(Number(sz))) return p;
const n = Number(sz);
const s =
n >= 100 ? n.toFixed(0) : n >= 10 ? n.toFixed(1) : n.toFixed(2);
return `${p}(${s})`;
}
export function fmtBidLiqEx(
kind: PxKind,
px: number | null | undefined,
sz: number | null | undefined,
): string {
if (px == null || Number.isNaN(Number(px))) return "—";
const p = fmtExPx(kind, px);
if (sz == null || Number.isNaN(Number(sz))) return p;
const n = Number(sz);
const s =
n >= 100 ? n.toFixed(0) : n >= 10 ? n.toFixed(1) : n.toFixed(2);
return `${p}/${s}`;
}
+35 -28
View File
@@ -1,27 +1,12 @@
import { useEffect, useState } from "react";
import { apiFetch, MarketSnapshot, PlanState } from "../api/client";
import { fmtBidLiqEx, fmtExPx, fmtTopEx } from "../format";
function fmt(n: number | null | undefined, d = 2) {
if (n == null || Number.isNaN(n)) return "—";
return n.toFixed(d);
}
/** 卖一/买一 + 对应档量:8.80(12.5) / 7.20(8.0) */
function fmtTop(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})`;
}
/** 持仓买一:价格/流动性张数,如 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 optionLevLabel(
underlying: number | null | undefined,
@@ -607,11 +592,15 @@ export default function PlanPage() {
<div className="pos-grid">
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{fmt(pos?.perp_entry_px)}</span>
<span className="pos-value mono">
{fmtExPx("perp", pos?.perp_entry_px)}
</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{fmt(pos?.perp_mark_px)}</span>
<span className="pos-value mono">
{fmtExPx("perp", pos?.perp_mark_px)}
</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
@@ -666,7 +655,12 @@ export default function PlanPage() {
<div className="pos-meta">
<span className="pos-meta-item"></span>
<span className="pos-meta-item"> {pos?.expiry_ymd || "—"}</span>
<span className="pos-meta-item"> {fmt(pos?.strike, 0)}</span>
<span className="pos-meta-item">
{" "}
{pos?.strike != null
? Math.round(Number(pos.strike))
: "—"}
</span>
<span className="pos-meta-item mono">
{fmt(pos?.option_qty_eth, 2)} ETH · {fmt(pos?.option_qty_contracts, 0)}
</span>
@@ -677,12 +671,18 @@ export default function PlanPage() {
<div className="pos-grid">
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{fmt(pos?.option_entry_px)}</span>
<span className="pos-value mono">
{fmtExPx("option", pos?.option_entry_px)}
</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">
{fmtBidLiq(pos?.option_mark_px, pos?.option_bid_sz)}
{fmtBidLiqEx(
"option",
pos?.option_mark_px,
pos?.option_bid_sz,
)}
</span>
</div>
<div className="pos-cell">
@@ -722,16 +722,17 @@ export default function PlanPage() {
<h3 className="plan-panel-title"></h3>
<div className="kv">
<span></span>
<span className="mono">{fmt(snap?.perp?.bid)}</span>
<span className="mono">{fmtExPx("perp", snap?.perp?.bid)}</span>
</div>
<div className="kv">
<span></span>
<span className="mono">{fmt(snap?.perp?.ask)}</span>
<span className="mono">{fmtExPx("perp", snap?.perp?.ask)}</span>
</div>
<div className="kv">
<span></span>
<span className="mono">
{fmt(
{fmtExPx(
"perp",
snap?.perp?.mark_px ??
(snap?.perp?.bid != null && snap?.perp?.ask != null
? (snap.perp.bid + snap.perp.ask) / 2
@@ -741,13 +742,19 @@ export default function PlanPage() {
</div>
<div className="kv">
<span></span>
<span className="mono">{fmt(snap?.index_px)}</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}` : ""}
{snap?.pair
? ` @ ${
snap.pair.strike != null
? Math.round(Number(snap.pair.strike))
: "—"
}`
: ""}
</h3>
{(() => {
const under =
@@ -776,14 +783,14 @@ export default function PlanPage() {
<div className="kv">
<span>Call {sideLabel}</span>
<span className="mono">
{fmtTop(callPx, callSz)}
{fmtTopEx("option", callPx, callSz)}
{!open ? ` · 杠杆 ${callLev}` : ""}
</span>
</div>
<div className="kv">
<span>Put {sideLabel}</span>
<span className="mono">
{fmtTop(putPx, putSz)}
{fmtTopEx("option", putPx, putSz)}
{!open ? ` · 杠杆 ${putLev}` : ""}
</span>
</div>
+9 -3
View File
@@ -1,5 +1,6 @@
import { useEffect, useId, useMemo, useState } from "react";
import { apiFetch } from "../api/client";
import { fmtExPx } from "../format";
import {
closeReasonZh,
fillDescZh,
@@ -495,7 +496,7 @@ export default function TradesPage() {
{fmtMovePoints(selectedGroup.move_points)}
{selectedGroup.entry_index_px != null &&
selectedGroup.close_index_px != null
? `${fmt(selectedGroup.entry_index_px, 1)}${fmt(selectedGroup.close_index_px, 1)}`
? `${fmtExPx("index", selectedGroup.entry_index_px)}${fmtExPx("index", selectedGroup.close_index_px)}`
: ""}
</span>
</div>
@@ -512,7 +513,10 @@ export default function TradesPage() {
<div className="kv">
<span></span>
<span className="mono">
{fmt(selectedGroup.expiry_settle.settle_index_px)}
{fmtExPx(
"index",
selectedGroup.expiry_settle.settle_index_px,
)}
</span>
</div>
<div className="kv">
@@ -554,7 +558,9 @@ export default function TradesPage() {
) : null}
</span>
<span className="mono">
{pxLabel} {f.fill_px.toFixed(4)} · {f.qty_eth}
{pxLabel}{" "}
{fmtExPx(isOpt ? "option" : "perp", f.fill_px)} · {" "}
{f.qty_eth}
{notional != null && Number.isFinite(notional)
? ` · 合计 ${notional.toFixed(2)}`
: ""}{" "}