Show open time and hold duration on position cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-29 15:41:56 +08:00
parent 195e63fb2e
commit 3df64aeb2d
4 changed files with 58 additions and 0 deletions
+2
View File
@@ -1039,6 +1039,7 @@ class Matcher:
)
strike = float(g["strike"]) if g and g["strike"] is not None else None
expiry_ymd = str(g["expiry_ymd"]) if g and g["expiry_ymd"] else None
open_at_ms = int(g["open_at_ms"]) if g and g["open_at_ms"] else None
expiry_ms = None
if expiry_ymd and len(expiry_ymd) == 6:
try:
@@ -1056,6 +1057,7 @@ class Matcher:
return {
"has_position": True,
"group_id": group_id,
"open_at_ms": open_at_ms,
"perp_side": perp_side,
"option_side": option_side,
"perp_inst_id": perp_inst_id,
+9
View File
@@ -5,6 +5,15 @@
---
## 2026-07-29 — 持仓卡显示开仓时间 / 持仓时长
### 变更
1. 监控页永续、期权持仓卡增加 **开仓时间**(上海时区)与 **持仓时长**(每秒刷新)。
2. 接口 `position.open_at_ms` 取自当前组成交组开仓时刻。
---
## 2026-07-27 — 残留期权一行一条
### 变更
+1
View File
@@ -238,6 +238,7 @@ export type PlanState = {
position: {
has_position: boolean;
group_id?: string;
open_at_ms?: number | null;
perp_side?: string;
option_side?: string;
perp_inst_id?: string;
+46
View File
@@ -41,6 +41,33 @@ function formatCountdown(msLeft: number): string {
return `${m}${String(sec).padStart(2, "0")}`;
}
/** 上海时区开仓时间 */
function fmtOpenTime(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,
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}
/** 持仓时长(实时) */
function fmtHoldDuration(openAtMs: number | null | undefined, now: number) {
if (openAtMs == null || !Number.isFinite(openAtMs) || openAtMs <= 0) return "—";
const ms = Math.max(0, now - openAtMs);
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}`;
}
const PHASE_ZH: Record<string, string> = {
idle: "空闲",
wait_signal: "等待信号",
@@ -132,6 +159,9 @@ export default function PlanPage() {
plan?.mode === "LIVE"
? `实盘·${(snap?.exchange || "okx").toUpperCase()}`
: "模拟盘";
const openAtMs = pos?.open_at_ms ?? null;
const openTimeLabel = fmtOpenTime(openAtMs);
const holdDurationLabel = open ? fmtHoldDuration(openAtMs, nowMs) : "—";
return (
<div className="plan-page">
@@ -429,6 +459,14 @@ export default function PlanPage() {
<span className="pos-label"></span>
<span className="pos-value mono">{fmt(movePct, 2)}%</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{openTimeLabel}</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{holdDurationLabel}</span>
</div>
</div>
</div>
@@ -482,6 +520,14 @@ export default function PlanPage() {
<span className="pos-label"></span>
<span className="pos-value mono">{fmt(pos?.initial_premium)}</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{openTimeLabel}</span>
</div>
<div className="pos-cell">
<span className="pos-label"></span>
<span className="pos-value mono">{holdDurationLabel}</span>
</div>
</div>
</div>
</>