Show trade open/close times and hold period by target exit.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
"""交易组持仓周期:目标出场以策略平仓时刻为准;永续先平则以永续平仓为准。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Mapping, Sequence
|
||||||
|
|
||||||
|
# 目标平仓(含 15U / 权利金倍数 / 只平永续)
|
||||||
|
_TARGET_REASONS = frozenset({"fixed_usdt", "premium_multiple", "target_perp_only"})
|
||||||
|
|
||||||
|
|
||||||
|
def _ts(v: Any) -> int | None:
|
||||||
|
if v is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
n = int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
return n if n > 0 else None
|
||||||
|
|
||||||
|
|
||||||
|
def first_perp_close_ts_ms(fills: Sequence[Mapping[str, Any]]) -> int | None:
|
||||||
|
"""永续平仓成交时间(目标只平永续时作为持仓结束时刻)。"""
|
||||||
|
best: int | None = None
|
||||||
|
for f in fills:
|
||||||
|
if str(f.get("leg") or "") != "perp":
|
||||||
|
continue
|
||||||
|
if str(f.get("action") or "") != "close":
|
||||||
|
continue
|
||||||
|
ts = _ts(f.get("ts_ms"))
|
||||||
|
if ts is None:
|
||||||
|
continue
|
||||||
|
if best is None or ts < best:
|
||||||
|
best = ts
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def hold_timing(
|
||||||
|
group: Mapping[str, Any], fills: Sequence[Mapping[str, Any]]
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
返回展示用开仓/平仓/持仓时长。
|
||||||
|
|
||||||
|
- 开仓:groups.open_at_ms
|
||||||
|
- 平仓(策略持仓周期):
|
||||||
|
- `target_perp_only` / `option_residual`:永续平仓 fill 时间
|
||||||
|
- 其它已平:groups.close_at_ms(缺则回退成交)
|
||||||
|
"""
|
||||||
|
open_ms = _ts(group.get("open_at_ms"))
|
||||||
|
status = str(group.get("status") or "")
|
||||||
|
reason = str(group.get("close_reason") or "")
|
||||||
|
group_close = _ts(group.get("close_at_ms"))
|
||||||
|
perp_close = first_perp_close_ts_ms(fills)
|
||||||
|
|
||||||
|
use_perp = reason == "target_perp_only" or status == "option_residual"
|
||||||
|
if use_perp:
|
||||||
|
close_ms = perp_close or group_close
|
||||||
|
basis = "perp"
|
||||||
|
elif status == "open":
|
||||||
|
close_ms = None
|
||||||
|
basis = "open"
|
||||||
|
else:
|
||||||
|
close_ms = group_close
|
||||||
|
if close_ms is None and reason in _TARGET_REASONS:
|
||||||
|
close_ms = perp_close
|
||||||
|
basis = "group"
|
||||||
|
|
||||||
|
hold_ms: int | None = None
|
||||||
|
if open_ms is not None and close_ms is not None and close_ms >= open_ms:
|
||||||
|
hold_ms = close_ms - open_ms
|
||||||
|
|
||||||
|
return {
|
||||||
|
"hold_open_at_ms": open_ms,
|
||||||
|
"hold_close_at_ms": close_ms,
|
||||||
|
"hold_ms": hold_ms,
|
||||||
|
"hold_basis": basis,
|
||||||
|
}
|
||||||
+20
-21
@@ -7,6 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||||||
from ..models.db import get_db
|
from ..models.db import get_db
|
||||||
from ..sim.pnl import summarize_fills_pnl
|
from ..sim.pnl import summarize_fills_pnl
|
||||||
from .auth import require_user
|
from .auth import require_user
|
||||||
|
from .hold_timing import hold_timing
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/trades", tags=["trades"])
|
router = APIRouter(prefix="/api/trades", tags=["trades"])
|
||||||
|
|
||||||
@@ -15,17 +16,7 @@ def _row(r: Any) -> dict:
|
|||||||
return dict(r)
|
return dict(r)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/groups")
|
def _enrich_group(g: dict, fills: list) -> dict:
|
||||||
async def list_groups(_user: Annotated[str, Depends(require_user)]) -> dict:
|
|
||||||
db = get_db()
|
|
||||||
rows = db.fetchall("SELECT * FROM groups ORDER BY open_at_ms DESC LIMIT 200")
|
|
||||||
groups = []
|
|
||||||
for r in rows:
|
|
||||||
g = _row(r)
|
|
||||||
fills = db.fetchall(
|
|
||||||
"SELECT * FROM fills WHERE group_id=? ORDER BY id ASC",
|
|
||||||
(g["group_id"],),
|
|
||||||
)
|
|
||||||
summary = summarize_fills_pnl(fills)
|
summary = summarize_fills_pnl(fills)
|
||||||
# LIVE:优先 groups.realized_pnl(已按交易所回写,含资金费)
|
# LIVE:优先 groups.realized_pnl(已按交易所回写,含资金费)
|
||||||
if str(g.get("exec_mode") or "").upper() == "LIVE" and g.get("realized_pnl") is not None:
|
if str(g.get("exec_mode") or "").upper() == "LIVE" and g.get("realized_pnl") is not None:
|
||||||
@@ -39,7 +30,22 @@ async def list_groups(_user: Annotated[str, Depends(require_user)]) -> dict:
|
|||||||
g["net_pnl"] = summary["net_pnl"]
|
g["net_pnl"] = summary["net_pnl"]
|
||||||
elif g.get("realized_pnl") is not None:
|
elif g.get("realized_pnl") is not None:
|
||||||
g["net_pnl"] = float(g["realized_pnl"])
|
g["net_pnl"] = float(g["realized_pnl"])
|
||||||
groups.append(g)
|
g.update(hold_timing(g, fills))
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/groups")
|
||||||
|
async def list_groups(_user: Annotated[str, Depends(require_user)]) -> dict:
|
||||||
|
db = get_db()
|
||||||
|
rows = db.fetchall("SELECT * FROM groups ORDER BY open_at_ms DESC LIMIT 200")
|
||||||
|
groups = []
|
||||||
|
for r in rows:
|
||||||
|
g = _row(r)
|
||||||
|
fills = db.fetchall(
|
||||||
|
"SELECT * FROM fills WHERE group_id=? ORDER BY id ASC",
|
||||||
|
(g["group_id"],),
|
||||||
|
)
|
||||||
|
groups.append(_enrich_group(g, fills))
|
||||||
return {"groups": groups}
|
return {"groups": groups}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,16 +60,9 @@ async def group_detail(
|
|||||||
fills = db.fetchall(
|
fills = db.fetchall(
|
||||||
"SELECT * FROM fills WHERE group_id=? ORDER BY id ASC", (group_id,)
|
"SELECT * FROM fills WHERE group_id=? ORDER BY id ASC", (group_id,)
|
||||||
)
|
)
|
||||||
summary = summarize_fills_pnl(fills)
|
gr = _enrich_group(_row(g), fills)
|
||||||
gr = _row(g)
|
|
||||||
if str(gr.get("exec_mode") or "").upper() == "LIVE" and gr.get("realized_pnl") is not None:
|
|
||||||
summary = dict(summary)
|
|
||||||
summary["net_pnl"] = float(gr["realized_pnl"])
|
|
||||||
if gr.get("funding_usdt") is not None:
|
|
||||||
summary["funding_usdt"] = float(gr["funding_usdt"])
|
|
||||||
summary["pnl_source"] = "live_exchange"
|
|
||||||
return {
|
return {
|
||||||
"group": gr,
|
"group": gr,
|
||||||
"fills": [_row(x) for x in fills],
|
"fills": [_row(x) for x in fills],
|
||||||
"pnl_summary": summary,
|
"pnl_summary": gr.get("pnl_summary"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1017,10 +1017,11 @@ class BinanceLiveExecutor(Matcher):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.db._conn.execute(
|
self.db._conn.execute(
|
||||||
"""UPDATE groups SET status=?, close_reason=?, realized_pnl=?,
|
"""UPDATE groups SET status=?, close_at_ms=?, close_reason=?, realized_pnl=?,
|
||||||
fees=?, slip_cost=?, note=?, exec_mode=? WHERE group_id=?""",
|
fees=?, slip_cost=?, note=?, exec_mode=? WHERE group_id=?""",
|
||||||
(
|
(
|
||||||
"option_residual",
|
"option_residual",
|
||||||
|
now,
|
||||||
reason,
|
reason,
|
||||||
interim_net,
|
interim_net,
|
||||||
fees,
|
fees,
|
||||||
|
|||||||
@@ -1017,10 +1017,11 @@ class OkxLiveExecutor(Matcher):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.db._conn.execute(
|
self.db._conn.execute(
|
||||||
"""UPDATE groups SET status=?, close_reason=?, realized_pnl=?,
|
"""UPDATE groups SET status=?, close_at_ms=?, close_reason=?, realized_pnl=?,
|
||||||
fees=?, slip_cost=?, note=?, exec_mode=? WHERE group_id=?""",
|
fees=?, slip_cost=?, note=?, exec_mode=? WHERE group_id=?""",
|
||||||
(
|
(
|
||||||
"option_residual",
|
"option_residual",
|
||||||
|
now,
|
||||||
reason,
|
reason,
|
||||||
interim_net,
|
interim_net,
|
||||||
fees,
|
fees,
|
||||||
|
|||||||
@@ -729,10 +729,11 @@ class Matcher:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.db._conn.execute(
|
self.db._conn.execute(
|
||||||
"""UPDATE groups SET status=?, close_reason=?, realized_pnl=?,
|
"""UPDATE groups SET status=?, close_at_ms=?, close_reason=?, realized_pnl=?,
|
||||||
fees=?, slip_cost=?, note=? WHERE group_id=?""",
|
fees=?, slip_cost=?, note=? WHERE group_id=?""",
|
||||||
(
|
(
|
||||||
"option_residual",
|
"option_residual",
|
||||||
|
now,
|
||||||
reason,
|
reason,
|
||||||
interim_net,
|
interim_net,
|
||||||
fees,
|
fees,
|
||||||
@@ -898,7 +899,8 @@ class Matcher:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.db._conn.execute(
|
self.db._conn.execute(
|
||||||
"""UPDATE groups SET status=?, close_at_ms=?, realized_pnl=?, fees=?, slip_cost=?
|
"""UPDATE groups SET status=?, close_at_ms=COALESCE(close_at_ms, ?),
|
||||||
|
realized_pnl=?, fees=?, slip_cost=?
|
||||||
WHERE group_id=?""",
|
WHERE group_id=?""",
|
||||||
("closed", now_ms, float(net), fees, slip, group_id),
|
("closed", now_ms, float(net), fees, slip, group_id),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""hold_timing unit tests."""
|
||||||
|
|
||||||
|
from backend.app.api.hold_timing import hold_timing
|
||||||
|
|
||||||
|
|
||||||
|
def test_hold_target_perp_only_uses_perp_close():
|
||||||
|
g = {
|
||||||
|
"open_at_ms": 1_000,
|
||||||
|
"close_at_ms": 9_000, # later residual settle would have overwritten
|
||||||
|
"status": "closed",
|
||||||
|
"close_reason": "target_perp_only",
|
||||||
|
}
|
||||||
|
fills = [
|
||||||
|
{"leg": "option", "action": "open", "ts_ms": 1_000},
|
||||||
|
{"leg": "perp", "action": "open", "ts_ms": 1_100},
|
||||||
|
{"leg": "perp", "action": "close", "ts_ms": 5_000},
|
||||||
|
{"leg": "option", "action": "close", "ts_ms": 9_000},
|
||||||
|
]
|
||||||
|
h = hold_timing(g, fills)
|
||||||
|
assert h["hold_open_at_ms"] == 1_000
|
||||||
|
assert h["hold_close_at_ms"] == 5_000
|
||||||
|
assert h["hold_ms"] == 4_000
|
||||||
|
assert h["hold_basis"] == "perp"
|
||||||
|
|
||||||
|
|
||||||
|
def test_hold_option_residual_uses_perp():
|
||||||
|
g = {
|
||||||
|
"open_at_ms": 100,
|
||||||
|
"close_at_ms": 500,
|
||||||
|
"status": "option_residual",
|
||||||
|
"close_reason": "target_perp_only",
|
||||||
|
}
|
||||||
|
fills = [
|
||||||
|
{"leg": "perp", "action": "close", "ts_ms": 500},
|
||||||
|
]
|
||||||
|
h = hold_timing(g, fills)
|
||||||
|
assert h["hold_close_at_ms"] == 500
|
||||||
|
assert h["hold_ms"] == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_hold_dual_leg_uses_group_close():
|
||||||
|
g = {
|
||||||
|
"open_at_ms": 100,
|
||||||
|
"close_at_ms": 800,
|
||||||
|
"status": "closed",
|
||||||
|
"close_reason": "fixed_usdt",
|
||||||
|
}
|
||||||
|
fills = [
|
||||||
|
{"leg": "option", "action": "close", "ts_ms": 790},
|
||||||
|
{"leg": "perp", "action": "close", "ts_ms": 800},
|
||||||
|
]
|
||||||
|
h = hold_timing(g, fills)
|
||||||
|
assert h["hold_close_at_ms"] == 800
|
||||||
|
assert h["hold_ms"] == 700
|
||||||
|
assert h["hold_basis"] == "group"
|
||||||
|
|
||||||
|
|
||||||
|
def test_hold_open_no_close():
|
||||||
|
g = {"open_at_ms": 100, "close_at_ms": None, "status": "open", "close_reason": None}
|
||||||
|
h = hold_timing(g, [])
|
||||||
|
assert h["hold_close_at_ms"] is None
|
||||||
|
assert h["hold_ms"] is None
|
||||||
|
assert h["hold_basis"] == "open"
|
||||||
@@ -5,6 +5,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-27 — 交易记录:开仓/平仓时间与持仓周期
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
1. 交易记录列表与明细展示:**开仓时间、平仓时间、持仓周期**(上海时区)。
|
||||||
|
2. **持仓周期按目标平仓计时**:净盈利达标(含默认 15U)出场为准。
|
||||||
|
3. **只平永续 / 期权残留**:平仓时间与周期以**永续平仓**时刻为准,不含期权拖到到期。
|
||||||
|
4. 只平永续时写入 `close_at_ms`;残留期权到期结算不再覆盖该策略平仓时间。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-27 — 安卓竖屏:全屏后系统锁定
|
## 2026-07-27 — 安卓竖屏:全屏后系统锁定
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ type Group = {
|
|||||||
close_reason: string | null;
|
close_reason: string | null;
|
||||||
open_at_ms: number | null;
|
open_at_ms: number | null;
|
||||||
close_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;
|
pnl_summary?: PnlSummary;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,9 +54,37 @@ function fmt(n: number | null | undefined, digits = 2) {
|
|||||||
return Number(n).toFixed(digits);
|
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() {
|
export default function TradesPage() {
|
||||||
const [groups, setGroups] = useState<Group[]>([]);
|
const [groups, setGroups] = useState<Group[]>([]);
|
||||||
const [selected, setSelected] = useState<string | null>(null);
|
const [selected, setSelected] = useState<string | null>(null);
|
||||||
|
const [selectedGroup, setSelectedGroup] = useState<Group | null>(null);
|
||||||
const [fills, setFills] = useState<Fill[]>([]);
|
const [fills, setFills] = useState<Fill[]>([]);
|
||||||
const [summary, setSummary] = useState<PnlSummary | null>(null);
|
const [summary, setSummary] = useState<PnlSummary | null>(null);
|
||||||
const [err, setErr] = useState("");
|
const [err, setErr] = useState("");
|
||||||
@@ -66,12 +98,16 @@ export default function TradesPage() {
|
|||||||
async function openGroup(id: string) {
|
async function openGroup(id: string) {
|
||||||
setSelected(id);
|
setSelected(id);
|
||||||
setSummary(null);
|
setSummary(null);
|
||||||
|
setSelectedGroup(null);
|
||||||
try {
|
try {
|
||||||
const r = await apiFetch<{ fills: Fill[]; pnl_summary: PnlSummary }>(
|
const r = await apiFetch<{
|
||||||
`/api/trades/groups/${id}`
|
group: Group;
|
||||||
);
|
fills: Fill[];
|
||||||
|
pnl_summary: PnlSummary;
|
||||||
|
}>(`/api/trades/groups/${id}`);
|
||||||
setFills(r.fills);
|
setFills(r.fills);
|
||||||
setSummary(r.pnl_summary);
|
setSummary(r.pnl_summary);
|
||||||
|
setSelectedGroup(r.group);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setErr(e instanceof Error ? e.message : String(e));
|
setErr(e instanceof Error ? e.message : String(e));
|
||||||
}
|
}
|
||||||
@@ -80,6 +116,9 @@ export default function TradesPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="trades-page">
|
<div className="trades-page">
|
||||||
<h2 style={{ marginTop: 0 }}>交易记录</h2>
|
<h2 style={{ marginTop: 0 }}>交易记录</h2>
|
||||||
|
<p className="trade-hold-note">
|
||||||
|
持仓周期按目标平仓计时;净盈利达标且只平永续时,以永续平仓时间为准(不含期权残留至到期)。
|
||||||
|
</p>
|
||||||
{err ? <div className="err">{err}</div> : null}
|
{err ? <div className="err">{err}</div> : null}
|
||||||
<div className="card trade-list" style={{ marginBottom: 12 }}>
|
<div className="card trade-list" style={{ marginBottom: 12 }}>
|
||||||
{groups.length === 0 ? (
|
{groups.length === 0 ? (
|
||||||
@@ -88,6 +127,8 @@ export default function TradesPage() {
|
|||||||
groups.map((g) => {
|
groups.map((g) => {
|
||||||
const listPnl =
|
const listPnl =
|
||||||
g.net_pnl ?? g.pnl_summary?.net_pnl ?? g.realized_pnl;
|
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 (
|
return (
|
||||||
<button
|
<button
|
||||||
key={g.group_id}
|
key={g.group_id}
|
||||||
@@ -101,6 +142,10 @@ export default function TradesPage() {
|
|||||||
{statusZh(g.status)} ·{" "}
|
{statusZh(g.status)} ·{" "}
|
||||||
{positionSidesZh(g.perp_side, g.option_side)}
|
{positionSidesZh(g.perp_side, g.option_side)}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="trade-row-times">
|
||||||
|
开 {fmtTime(openMs)} · 平 {fmtTime(closeMs)} · 周期{" "}
|
||||||
|
{fmtHold(g.hold_ms)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={`trade-row-pnl mono ${pnlClass(listPnl)}`}>
|
<div className={`trade-row-pnl mono ${pnlClass(listPnl)}`}>
|
||||||
<span>净盈亏 {fmt(listPnl)}</span>
|
<span>净盈亏 {fmt(listPnl)}</span>
|
||||||
@@ -118,6 +163,31 @@ export default function TradesPage() {
|
|||||||
{selected ? (
|
{selected ? (
|
||||||
<div className="card trade-detail">
|
<div className="card trade-detail">
|
||||||
<h3 style={{ marginTop: 0 }}>{selected} 成交明细</h3>
|
<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 }}>
|
<p style={{ color: "var(--muted)", fontSize: 13, marginTop: 0 }}>
|
||||||
成交价为成交均价(未预先扣费);手续费单独列出。净盈亏 = 期权盈亏 + 永续盈亏 −
|
成交价为成交均价(未预先扣费);手续费单独列出。净盈亏 = 期权盈亏 + 永续盈亏 −
|
||||||
全部手续费。
|
全部手续费。
|
||||||
|
|||||||
@@ -558,6 +558,32 @@ input {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trade-row-times {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-hold-note {
|
||||||
|
margin: -4px 0 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-hold-summary {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-hold-basis {
|
||||||
|
margin-left: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.trade-row-pnl {
|
.trade-row-pnl {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user