Add cumulative P&L banner to fund overview for at-a-glance profit/loss.
Expose period delta from equity curve start and surface it in the toolbar and summary cards with green/red cues. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -74,6 +74,30 @@ def compute_drawdown(values: list[float]) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def compute_period_delta(series: list[dict]) -> dict[str, Any]:
|
||||||
|
"""相对曲线起点的资金变动(U 与 %);含出入金影响,口径同权益曲线."""
|
||||||
|
pts = [
|
||||||
|
p
|
||||||
|
for p in (series or [])
|
||||||
|
if isinstance(p, dict) and isinstance(p.get("total_usdt"), (int, float))
|
||||||
|
]
|
||||||
|
if not pts:
|
||||||
|
return {
|
||||||
|
"start_usdt": None,
|
||||||
|
"period_delta_usdt": None,
|
||||||
|
"period_delta_pct": None,
|
||||||
|
}
|
||||||
|
start = round(float(pts[0]["total_usdt"]), 4)
|
||||||
|
end = round(float(pts[-1]["total_usdt"]), 4)
|
||||||
|
delta = round(end - start, 4)
|
||||||
|
pct = round((delta / start) * 100, 2) if start > 0 else None
|
||||||
|
return {
|
||||||
|
"start_usdt": start,
|
||||||
|
"period_delta_usdt": delta,
|
||||||
|
"period_delta_pct": pct,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _atomic_write(path: Path, data: dict) -> None:
|
def _atomic_write(path: Path, data: dict) -> None:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
tmp = path.with_suffix(path.suffix + ".tmp")
|
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||||
@@ -343,6 +367,7 @@ def build_fund_overview(
|
|||||||
day_delta = round(series[-1]["total_usdt"] - series[-2]["total_usdt"], 4)
|
day_delta = round(series[-1]["total_usdt"] - series[-2]["total_usdt"], 4)
|
||||||
elif data_ok and total is not None:
|
elif data_ok and total is not None:
|
||||||
day_delta = round(total - series[-1]["total_usdt"], 4)
|
day_delta = round(total - series[-1]["total_usdt"], 4)
|
||||||
|
period = compute_period_delta(series)
|
||||||
|
|
||||||
accounts_out.append(
|
accounts_out.append(
|
||||||
{
|
{
|
||||||
@@ -361,6 +386,9 @@ def build_fund_overview(
|
|||||||
"series": series,
|
"series": series,
|
||||||
"drawdown": dd,
|
"drawdown": dd,
|
||||||
"day_delta_usdt": day_delta,
|
"day_delta_usdt": day_delta,
|
||||||
|
"start_usdt": period["start_usdt"],
|
||||||
|
"period_delta_usdt": period["period_delta_usdt"],
|
||||||
|
"period_delta_pct": period["period_delta_pct"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if key:
|
if key:
|
||||||
@@ -387,6 +415,7 @@ def build_fund_overview(
|
|||||||
total_day_delta = round(
|
total_day_delta = round(
|
||||||
total_series[-1]["total_usdt"] - total_series[-2]["total_usdt"], 4
|
total_series[-1]["total_usdt"] - total_series[-2]["total_usdt"], 4
|
||||||
)
|
)
|
||||||
|
total_period = compute_period_delta(total_series)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
@@ -400,6 +429,9 @@ def build_fund_overview(
|
|||||||
"live_known_count": live_known,
|
"live_known_count": live_known,
|
||||||
"total_usdt": round(live_total, 4) if live_known > 0 else None,
|
"total_usdt": round(live_total, 4) if live_known > 0 else None,
|
||||||
"day_delta_usdt": total_day_delta,
|
"day_delta_usdt": total_day_delta,
|
||||||
|
"start_usdt": total_period["start_usdt"],
|
||||||
|
"period_delta_usdt": total_period["period_delta_usdt"],
|
||||||
|
"period_delta_pct": total_period["period_delta_pct"],
|
||||||
"series": total_series,
|
"series": total_series,
|
||||||
"drawdown": total_dd,
|
"drawdown": total_dd,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7266,6 +7266,10 @@ html[data-theme="light"] .funds-stage {
|
|||||||
50% { opacity: 0.55; transform: scale(0.88); }
|
50% { opacity: 0.55; transform: scale(0.88); }
|
||||||
}
|
}
|
||||||
.funds-toolbar {
|
.funds-toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px 12px;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
.funds-btn-refresh {
|
.funds-btn-refresh {
|
||||||
@@ -7273,15 +7277,115 @@ html[data-theme="light"] .funds-stage {
|
|||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
}
|
}
|
||||||
|
.funds-pnl-banner {
|
||||||
|
flex: 1 1 240px;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px 16px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
html[data-theme="light"] .funds-pnl-banner {
|
||||||
|
background: rgba(255, 255, 255, 0.82);
|
||||||
|
}
|
||||||
|
.funds-pnl-banner.is-pos {
|
||||||
|
border-color: color-mix(in srgb, var(--green) 45%, var(--border-soft));
|
||||||
|
background: color-mix(in srgb, var(--green) 10%, rgba(0, 0, 0, 0.28));
|
||||||
|
box-shadow: inset 0 0 28px color-mix(in srgb, var(--green) 8%, transparent);
|
||||||
|
}
|
||||||
|
.funds-pnl-banner.is-neg {
|
||||||
|
border-color: color-mix(in srgb, var(--red) 45%, var(--border-soft));
|
||||||
|
background: color-mix(in srgb, var(--red) 10%, rgba(0, 0, 0, 0.28));
|
||||||
|
box-shadow: inset 0 0 28px color-mix(in srgb, var(--red) 8%, transparent);
|
||||||
|
}
|
||||||
|
html[data-theme="light"] .funds-pnl-banner.is-pos {
|
||||||
|
background: color-mix(in srgb, var(--green) 8%, rgba(255, 255, 255, 0.9));
|
||||||
|
}
|
||||||
|
html[data-theme="light"] .funds-pnl-banner.is-neg {
|
||||||
|
background: color-mix(in srgb, var(--red) 8%, rgba(255, 255, 255, 0.9));
|
||||||
|
}
|
||||||
|
.funds-pnl-main {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.funds-pnl-label {
|
||||||
|
font-family: var(--display);
|
||||||
|
font-size: 0.62rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.funds-pnl-value {
|
||||||
|
font-family: var(--font);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
.funds-pnl-pct {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
.funds-pnl-value.pos,
|
||||||
|
.funds-pnl-pct.pos,
|
||||||
|
.funds-pnl-side-val.pos {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
.funds-pnl-value.neg,
|
||||||
|
.funds-pnl-pct.neg,
|
||||||
|
.funds-pnl-side-val.neg {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
.funds-pnl-side {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 2px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border-left: 1px solid var(--border-soft);
|
||||||
|
}
|
||||||
|
.funds-pnl-side-label {
|
||||||
|
font-family: var(--display);
|
||||||
|
font-size: 0.58rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.funds-pnl-side-val {
|
||||||
|
font-family: var(--font);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
.funds-status.err {
|
.funds-status.err {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
}
|
}
|
||||||
.funds-summary {
|
.funds-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
.funds-stat-card-pnl .funds-stat-val {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
}
|
||||||
|
.funds-stat-sub {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-family: var(--mono);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
.funds-stat-card {
|
.funds-stat-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: rgba(0, 0, 0, 0.28);
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
const elDdU = document.getElementById("funds-total-dd-u");
|
const elDdU = document.getElementById("funds-total-dd-u");
|
||||||
const elDdPct = document.getElementById("funds-total-dd-pct");
|
const elDdPct = document.getElementById("funds-total-dd-pct");
|
||||||
const elDelta = document.getElementById("funds-total-delta");
|
const elDelta = document.getElementById("funds-total-delta");
|
||||||
|
const elPeriod = document.getElementById("funds-total-period");
|
||||||
|
const elPeriodSub = document.getElementById("funds-total-period-sub");
|
||||||
|
const elPeriodBanner = document.getElementById("funds-period-delta");
|
||||||
|
const elPeriodPct = document.getElementById("funds-period-pct");
|
||||||
|
const elDayChip = document.getElementById("funds-day-chip");
|
||||||
|
const elPnlBanner = document.getElementById("funds-pnl-banner");
|
||||||
const elMeta = document.getElementById("funds-meta");
|
const elMeta = document.getElementById("funds-meta");
|
||||||
const elDescBody = document.getElementById("funds-desc-body");
|
const elDescBody = document.getElementById("funds-desc-body");
|
||||||
const elChartSub = document.getElementById("funds-chart-sub");
|
const elChartSub = document.getElementById("funds-chart-sub");
|
||||||
@@ -50,6 +56,13 @@
|
|||||||
return sign + v.toFixed(2) + " U";
|
return sign + v.toFixed(2) + " U";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtPct(n) {
|
||||||
|
if (n == null || !Number.isFinite(Number(n))) return "—";
|
||||||
|
const v = Number(n);
|
||||||
|
const sign = v > 0 ? "+" : "";
|
||||||
|
return sign + v.toFixed(2) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
function deltaClass(n) {
|
function deltaClass(n) {
|
||||||
if (!Number.isFinite(Number(n))) return "";
|
if (!Number.isFinite(Number(n))) return "";
|
||||||
if (Number(n) > 0) return "pos";
|
if (Number(n) > 0) return "pos";
|
||||||
@@ -193,6 +206,8 @@
|
|||||||
const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—";
|
const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—";
|
||||||
const deltaCls = deltaClass(ac.day_delta_usdt);
|
const deltaCls = deltaClass(ac.day_delta_usdt);
|
||||||
const deltaText = monitored ? fmtDelta(ac.day_delta_usdt) : "—";
|
const deltaText = monitored ? fmtDelta(ac.day_delta_usdt) : "—";
|
||||||
|
const periodCls = deltaClass(ac.period_delta_usdt);
|
||||||
|
const periodText = monitored ? fmtDelta(ac.period_delta_usdt) : "—";
|
||||||
return (
|
return (
|
||||||
'<button type="button" class="funds-ac-card' +
|
'<button type="button" class="funds-ac-card' +
|
||||||
offCls +
|
offCls +
|
||||||
@@ -227,6 +242,11 @@
|
|||||||
trading +
|
trading +
|
||||||
"</span></div>" +
|
"</span></div>" +
|
||||||
optLine +
|
optLine +
|
||||||
|
'<div><span class="k">累计盈亏</span><span class="v ' +
|
||||||
|
periodCls +
|
||||||
|
'">' +
|
||||||
|
periodText +
|
||||||
|
"</span></div>" +
|
||||||
'<div><span class="k">较昨日</span><span class="v ' +
|
'<div><span class="k">较昨日</span><span class="v ' +
|
||||||
deltaCls +
|
deltaCls +
|
||||||
'">' +
|
'">' +
|
||||||
@@ -370,6 +390,35 @@
|
|||||||
elDelta.textContent = fmtDelta(totals.day_delta_usdt);
|
elDelta.textContent = fmtDelta(totals.day_delta_usdt);
|
||||||
elDelta.className = "funds-stat-val " + deltaClass(totals.day_delta_usdt);
|
elDelta.className = "funds-stat-val " + deltaClass(totals.day_delta_usdt);
|
||||||
}
|
}
|
||||||
|
const periodCls = deltaClass(totals.period_delta_usdt);
|
||||||
|
if (elPeriod) {
|
||||||
|
elPeriod.textContent = fmtDelta(totals.period_delta_usdt);
|
||||||
|
elPeriod.className = "funds-stat-val " + periodCls;
|
||||||
|
}
|
||||||
|
if (elPeriodSub) {
|
||||||
|
const startDay = data.history_start_day || "—";
|
||||||
|
const pct = fmtPct(totals.period_delta_pct);
|
||||||
|
elPeriodSub.textContent =
|
||||||
|
pct !== "—"
|
||||||
|
? "自 " + startDay + " · " + pct
|
||||||
|
: "自 " + startDay + " 起相对起点";
|
||||||
|
}
|
||||||
|
if (elPeriodBanner) {
|
||||||
|
elPeriodBanner.textContent = fmtDelta(totals.period_delta_usdt);
|
||||||
|
elPeriodBanner.className = "funds-pnl-value " + periodCls;
|
||||||
|
}
|
||||||
|
if (elPeriodPct) {
|
||||||
|
elPeriodPct.textContent = fmtPct(totals.period_delta_pct);
|
||||||
|
elPeriodPct.className = "funds-pnl-pct " + periodCls;
|
||||||
|
}
|
||||||
|
if (elDayChip) {
|
||||||
|
elDayChip.textContent = fmtDelta(totals.day_delta_usdt);
|
||||||
|
elDayChip.className = "funds-pnl-side-val " + deltaClass(totals.day_delta_usdt);
|
||||||
|
}
|
||||||
|
if (elPnlBanner) {
|
||||||
|
elPnlBanner.className =
|
||||||
|
"funds-pnl-banner" + (periodCls ? " is-" + periodCls : "");
|
||||||
|
}
|
||||||
if (elMeta) {
|
if (elMeta) {
|
||||||
const parts = [
|
const parts = [
|
||||||
"交易日 " + (data.trading_day || "—"),
|
"交易日 " + (data.trading_day || "—"),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||||
<link rel="stylesheet" href="/assets/app.css?v=20260715-hub-phone-folds" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260716-funds-pnl-banner" />
|
||||||
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
@@ -625,6 +625,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="funds-toolbar toolbar">
|
<div class="funds-toolbar toolbar">
|
||||||
<button type="button" id="funds-btn-refresh" class="primary funds-btn-refresh">同步快照</button>
|
<button type="button" id="funds-btn-refresh" class="primary funds-btn-refresh">同步快照</button>
|
||||||
|
<div id="funds-pnl-banner" class="funds-pnl-banner" aria-live="polite">
|
||||||
|
<div class="funds-pnl-main">
|
||||||
|
<span class="funds-pnl-label">累计盈亏 · P&L</span>
|
||||||
|
<span id="funds-period-delta" class="funds-pnl-value">—</span>
|
||||||
|
<span id="funds-period-pct" class="funds-pnl-pct">—</span>
|
||||||
|
</div>
|
||||||
|
<div class="funds-pnl-side">
|
||||||
|
<span class="funds-pnl-side-label">较昨日</span>
|
||||||
|
<span id="funds-day-chip" class="funds-pnl-side-val">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<span id="funds-status" class="toolbar-meta funds-status"></span>
|
<span id="funds-status" class="toolbar-meta funds-status"></span>
|
||||||
</div>
|
</div>
|
||||||
<section class="funds-summary">
|
<section class="funds-summary">
|
||||||
@@ -632,6 +643,11 @@
|
|||||||
<div class="funds-stat-label">总资金 · TOTAL</div>
|
<div class="funds-stat-label">总资金 · TOTAL</div>
|
||||||
<div id="funds-total-usdt" class="funds-stat-value">—</div>
|
<div id="funds-total-usdt" class="funds-stat-value">—</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="funds-stat-card funds-stat-card-pnl">
|
||||||
|
<div class="funds-stat-label">累计盈亏 · SINCE START</div>
|
||||||
|
<div id="funds-total-period" class="funds-stat-val">—</div>
|
||||||
|
<div id="funds-total-period-sub" class="funds-stat-sub">相对统计起点</div>
|
||||||
|
</div>
|
||||||
<div class="funds-stat-card">
|
<div class="funds-stat-card">
|
||||||
<div class="funds-stat-label">较昨日 · Δ24H</div>
|
<div class="funds-stat-label">较昨日 · Δ24H</div>
|
||||||
<div id="funds-total-delta" class="funds-stat-val">—</div>
|
<div id="funds-total-delta" class="funds-stat-val">—</div>
|
||||||
@@ -1301,7 +1317,7 @@
|
|||||||
<script src="/assets/calculator.js?v=20260715-calc-tabs"></script>
|
<script src="/assets/calculator.js?v=20260715-calc-tabs"></script>
|
||||||
<script src="/assets/trade_stats_calendar.js?v=3"></script>
|
<script src="/assets/trade_stats_calendar.js?v=3"></script>
|
||||||
<script src="/assets/archive.js?v=20260710-archive-cum2"></script>
|
<script src="/assets/archive.js?v=20260710-archive-cum2"></script>
|
||||||
<script src="/assets/funds.js?v=20260707-hub-options-funds"></script>
|
<script src="/assets/funds.js?v=20260716-funds-pnl-banner"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260708-options-expiry-cd"></script>
|
<script src="/assets/dashboard.js?v=20260708-options-expiry-cd"></script>
|
||||||
<script src="/assets/strategy.js?v=3"></script>
|
<script src="/assets/strategy.js?v=3"></script>
|
||||||
<script src="/assets/help.js?v=1"></script>
|
<script src="/assets/help.js?v=1"></script>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
### 总览
|
### 总览
|
||||||
|
|
||||||
- **总资金**:当前监控板最新一轮聚合的实时合计(资金户+交易户齐全才计入)
|
- **总资金**:当前监控板最新一轮聚合的实时合计(资金户+交易户齐全才计入)
|
||||||
|
- **累计盈亏**:相对统计起点(`HUB_FUND_HISTORY_START_DAY`)首个快照的总资金变动(U / %);顶栏大字绿涨红跌,一眼可看盈亏。含出入金影响,口径同权益曲线,不含浮盈
|
||||||
- **较昨日**:相对上一交易日快照点的变动(U)
|
- **较昨日**:相对上一交易日快照点的变动(U)
|
||||||
- **最大回撤**:总资金历史曲线的峰值回撤(U / %)
|
- **最大回撤**:总资金历史曲线的峰值回撤(U / %)
|
||||||
- **总资金曲线**:近 180 交易日
|
- **总资金曲线**:近 180 交易日
|
||||||
@@ -78,9 +79,11 @@
|
|||||||
返回字段概要:
|
返回字段概要:
|
||||||
|
|
||||||
- `totals.total_usdt` — 当前总资金
|
- `totals.total_usdt` — 当前总资金
|
||||||
|
- `totals.day_delta_usdt` — 较昨日变动
|
||||||
|
- `totals.period_delta_usdt` / `period_delta_pct` / `start_usdt` — 相对曲线起点累计盈亏
|
||||||
- `totals.series[]` — `{ day, total_usdt }` 总曲线
|
- `totals.series[]` — `{ day, total_usdt }` 总曲线
|
||||||
- `totals.drawdown` — `{ peak_usdt, max_drawdown_u, max_drawdown_pct }`
|
- `totals.drawdown` — `{ peak_usdt, max_drawdown_u, max_drawdown_pct }`
|
||||||
- `accounts[]` — 分户实时余额,曲线,回撤,`monitored` 标记
|
- `accounts[]` — 分户实时余额,曲线,回撤,日/累计变动,`monitored` 标记
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from lib.hub.hub_fund_history_lib import (
|
|||||||
account_total_usdt,
|
account_total_usdt,
|
||||||
build_fund_overview,
|
build_fund_overview,
|
||||||
compute_drawdown,
|
compute_drawdown,
|
||||||
|
compute_period_delta,
|
||||||
get_fund_history,
|
get_fund_history,
|
||||||
record_fund_snapshot,
|
record_fund_snapshot,
|
||||||
)
|
)
|
||||||
@@ -23,6 +24,20 @@ def test_compute_drawdown():
|
|||||||
assert dd["max_drawdown_pct"] == 25.0
|
assert dd["max_drawdown_pct"] == 25.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_period_delta():
|
||||||
|
out = compute_period_delta(
|
||||||
|
[
|
||||||
|
{"day": "2026-06-09", "total_usdt": 100},
|
||||||
|
{"day": "2026-06-10", "total_usdt": 112.5},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert out["start_usdt"] == 100.0
|
||||||
|
assert out["period_delta_usdt"] == 12.5
|
||||||
|
assert out["period_delta_pct"] == 12.5
|
||||||
|
empty = compute_period_delta([])
|
||||||
|
assert empty["period_delta_usdt"] is None
|
||||||
|
|
||||||
|
|
||||||
def test_build_fund_overview_skips_unmonitored(tmp_path, monkeypatch):
|
def test_build_fund_overview_skips_unmonitored(tmp_path, monkeypatch):
|
||||||
hist_path = tmp_path / "hub_fund_history.json"
|
hist_path = tmp_path / "hub_fund_history.json"
|
||||||
monkeypatch.setattr("hub_fund_history_lib.FUND_HISTORY_PATH", hist_path)
|
monkeypatch.setattr("hub_fund_history_lib.FUND_HISTORY_PATH", hist_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user