Show trade open/close times and hold period by target exit.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,10 @@ type Group = {
|
||||
close_reason: string | null;
|
||||
open_at_ms: number | null;
|
||||
close_at_ms: number | null;
|
||||
hold_open_at_ms?: number | null;
|
||||
hold_close_at_ms?: number | null;
|
||||
hold_ms?: number | null;
|
||||
hold_basis?: string | null;
|
||||
pnl_summary?: PnlSummary;
|
||||
};
|
||||
|
||||
@@ -50,9 +54,37 @@ function fmt(n: number | null | undefined, digits = 2) {
|
||||
return Number(n).toFixed(digits);
|
||||
}
|
||||
|
||||
/** 上海时区开/平仓时间 */
|
||||
function fmtTime(ms: number | null | undefined) {
|
||||
if (ms == null || !Number.isFinite(ms) || ms <= 0) return "—";
|
||||
return new Date(ms).toLocaleString("zh-CN", {
|
||||
timeZone: "Asia/Shanghai",
|
||||
hour12: false,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
/** 持仓周期:x时y分z秒 */
|
||||
function fmtHold(ms: number | null | undefined) {
|
||||
if (ms == null || !Number.isFinite(ms) || ms < 0) return "—";
|
||||
const totalSec = Math.floor(ms / 1000);
|
||||
const h = Math.floor(totalSec / 3600);
|
||||
const m = Math.floor((totalSec % 3600) / 60);
|
||||
const s = totalSec % 60;
|
||||
if (h > 0) return `${h}时${m}分${s}秒`;
|
||||
if (m > 0) return `${m}分${s}秒`;
|
||||
return `${s}秒`;
|
||||
}
|
||||
|
||||
export default function TradesPage() {
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
const [selectedGroup, setSelectedGroup] = useState<Group | null>(null);
|
||||
const [fills, setFills] = useState<Fill[]>([]);
|
||||
const [summary, setSummary] = useState<PnlSummary | null>(null);
|
||||
const [err, setErr] = useState("");
|
||||
@@ -66,12 +98,16 @@ export default function TradesPage() {
|
||||
async function openGroup(id: string) {
|
||||
setSelected(id);
|
||||
setSummary(null);
|
||||
setSelectedGroup(null);
|
||||
try {
|
||||
const r = await apiFetch<{ fills: Fill[]; pnl_summary: PnlSummary }>(
|
||||
`/api/trades/groups/${id}`
|
||||
);
|
||||
const r = await apiFetch<{
|
||||
group: Group;
|
||||
fills: Fill[];
|
||||
pnl_summary: PnlSummary;
|
||||
}>(`/api/trades/groups/${id}`);
|
||||
setFills(r.fills);
|
||||
setSummary(r.pnl_summary);
|
||||
setSelectedGroup(r.group);
|
||||
} catch (e) {
|
||||
setErr(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
@@ -80,6 +116,9 @@ export default function TradesPage() {
|
||||
return (
|
||||
<div className="trades-page">
|
||||
<h2 style={{ marginTop: 0 }}>交易记录</h2>
|
||||
<p className="trade-hold-note">
|
||||
持仓周期按目标平仓计时;净盈利达标且只平永续时,以永续平仓时间为准(不含期权残留至到期)。
|
||||
</p>
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
<div className="card trade-list" style={{ marginBottom: 12 }}>
|
||||
{groups.length === 0 ? (
|
||||
@@ -88,6 +127,8 @@ export default function TradesPage() {
|
||||
groups.map((g) => {
|
||||
const listPnl =
|
||||
g.net_pnl ?? g.pnl_summary?.net_pnl ?? g.realized_pnl;
|
||||
const openMs = g.hold_open_at_ms ?? g.open_at_ms;
|
||||
const closeMs = g.hold_close_at_ms ?? g.close_at_ms;
|
||||
return (
|
||||
<button
|
||||
key={g.group_id}
|
||||
@@ -101,6 +142,10 @@ export default function TradesPage() {
|
||||
{statusZh(g.status)} ·{" "}
|
||||
{positionSidesZh(g.perp_side, g.option_side)}
|
||||
</span>
|
||||
<span className="trade-row-times">
|
||||
开 {fmtTime(openMs)} · 平 {fmtTime(closeMs)} · 周期{" "}
|
||||
{fmtHold(g.hold_ms)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={`trade-row-pnl mono ${pnlClass(listPnl)}`}>
|
||||
<span>净盈亏 {fmt(listPnl)}</span>
|
||||
@@ -118,6 +163,31 @@ export default function TradesPage() {
|
||||
{selected ? (
|
||||
<div className="card trade-detail">
|
||||
<h3 style={{ marginTop: 0 }}>{selected} 成交明细</h3>
|
||||
{selectedGroup ? (
|
||||
<div className="trade-hold-summary">
|
||||
<div className="kv">
|
||||
<span>开仓时间</span>
|
||||
<span className="mono">
|
||||
{fmtTime(selectedGroup.hold_open_at_ms ?? selectedGroup.open_at_ms)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="kv">
|
||||
<span>平仓时间</span>
|
||||
<span className="mono">
|
||||
{fmtTime(
|
||||
selectedGroup.hold_close_at_ms ?? selectedGroup.close_at_ms,
|
||||
)}
|
||||
{selectedGroup.hold_basis === "perp" ? (
|
||||
<span className="trade-hold-basis">(永续)</span>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
<div className="kv">
|
||||
<span>持仓周期</span>
|
||||
<span className="mono">{fmtHold(selectedGroup.hold_ms)}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<p style={{ color: "var(--muted)", fontSize: 13, marginTop: 0 }}>
|
||||
成交价为成交均价(未预先扣费);手续费单独列出。净盈亏 = 期权盈亏 + 永续盈亏 −
|
||||
全部手续费。
|
||||
|
||||
Reference in New Issue
Block a user