Show open time and hold duration on position cards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1039,6 +1039,7 @@ class Matcher:
|
|||||||
)
|
)
|
||||||
strike = float(g["strike"]) if g and g["strike"] is not None else None
|
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
|
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
|
expiry_ms = None
|
||||||
if expiry_ymd and len(expiry_ymd) == 6:
|
if expiry_ymd and len(expiry_ymd) == 6:
|
||||||
try:
|
try:
|
||||||
@@ -1056,6 +1057,7 @@ class Matcher:
|
|||||||
return {
|
return {
|
||||||
"has_position": True,
|
"has_position": True,
|
||||||
"group_id": group_id,
|
"group_id": group_id,
|
||||||
|
"open_at_ms": open_at_ms,
|
||||||
"perp_side": perp_side,
|
"perp_side": perp_side,
|
||||||
"option_side": option_side,
|
"option_side": option_side,
|
||||||
"perp_inst_id": perp_inst_id,
|
"perp_inst_id": perp_inst_id,
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-29 — 持仓卡显示开仓时间 / 持仓时长
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
1. 监控页永续、期权持仓卡增加 **开仓时间**(上海时区)与 **持仓时长**(每秒刷新)。
|
||||||
|
2. 接口 `position.open_at_ms` 取自当前组成交组开仓时刻。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-27 — 残留期权一行一条
|
## 2026-07-27 — 残留期权一行一条
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ export type PlanState = {
|
|||||||
position: {
|
position: {
|
||||||
has_position: boolean;
|
has_position: boolean;
|
||||||
group_id?: string;
|
group_id?: string;
|
||||||
|
open_at_ms?: number | null;
|
||||||
perp_side?: string;
|
perp_side?: string;
|
||||||
option_side?: string;
|
option_side?: string;
|
||||||
perp_inst_id?: string;
|
perp_inst_id?: string;
|
||||||
|
|||||||
@@ -41,6 +41,33 @@ function formatCountdown(msLeft: number): string {
|
|||||||
return `${m}分 ${String(sec).padStart(2, "0")}秒`;
|
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> = {
|
const PHASE_ZH: Record<string, string> = {
|
||||||
idle: "空闲",
|
idle: "空闲",
|
||||||
wait_signal: "等待信号",
|
wait_signal: "等待信号",
|
||||||
@@ -132,6 +159,9 @@ export default function PlanPage() {
|
|||||||
plan?.mode === "LIVE"
|
plan?.mode === "LIVE"
|
||||||
? `实盘·${(snap?.exchange || "okx").toUpperCase()}`
|
? `实盘·${(snap?.exchange || "okx").toUpperCase()}`
|
||||||
: "模拟盘";
|
: "模拟盘";
|
||||||
|
const openAtMs = pos?.open_at_ms ?? null;
|
||||||
|
const openTimeLabel = fmtOpenTime(openAtMs);
|
||||||
|
const holdDurationLabel = open ? fmtHoldDuration(openAtMs, nowMs) : "—";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="plan-page">
|
<div className="plan-page">
|
||||||
@@ -429,6 +459,14 @@ export default function PlanPage() {
|
|||||||
<span className="pos-label">波动比例</span>
|
<span className="pos-label">波动比例</span>
|
||||||
<span className="pos-value mono">{fmt(movePct, 2)}%</span>
|
<span className="pos-value mono">{fmt(movePct, 2)}%</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -482,6 +520,14 @@ export default function PlanPage() {
|
|||||||
<span className="pos-label">初始权利金</span>
|
<span className="pos-label">初始权利金</span>
|
||||||
<span className="pos-value mono">{fmt(pos?.initial_premium)}</span>
|
<span className="pos-value mono">{fmt(pos?.initial_premium)}</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user