feat: show ops-map summaries as detailed period tables

Replace paragraph text with day-hit stats (days/days_hit/pct) so leverage and move charts list each hour with counts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-05 17:12:23 +08:00
parent e213db8d2d
commit 270e114658
8 changed files with 513 additions and 210 deletions
+107 -8
View File
@@ -10,7 +10,12 @@ import AppNav from "../components/AppNav";
import LeverageChart from "../components/LeverageChart";
import LoginGate from "../components/LoginGate";
import MovePointsChart from "../components/MovePointsChart";
import { leverageSummary, moveSummary } from "../lib/opsSummary";
import {
fmtNum,
fmtPct,
leverageSummaryTable,
moveSummaryTable,
} from "../lib/opsSummary";
type RangeKey = "day" | "week" | "month";
type SideKey = "both" | "C" | "P";
@@ -215,9 +220,49 @@ export default function OpsMapPage() {
minLeverage={lev.min_leverage}
title={`上图 · 时段杠杆均值(${rangeLabel}`}
/>
<p className="chart-summary">
{leverageSummary(range, lev.buckets, lev.min_leverage)}
</p>
{(() => {
const levTable = leverageSummaryTable(
range,
lev.buckets,
lev.min_leverage
);
if (levTable.kind === "empty") {
return <p className="chart-summary">{levTable.message}</p>;
}
return (
<div className="chart-summary chart-summary-table">
{levTable.note && (
<div className="summary-note">{levTable.note}</div>
)}
<div className="summary-table-wrap">
<table className="summary-table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{levTable.rows.map((r) => (
<tr key={r.label}>
<td>{r.label}</td>
<td>{r.days}</td>
<td>{r.daysHit}</td>
<td>{r.pct != null ? fmtPct(r.pct) : "—"}</td>
<td>{r.mean != null ? fmtNum(r.mean) : "—"}</td>
<td>{r.n}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
})()}
<div className="toolbar" style={{ marginTop: "1.25rem" }}>
<div className="seg">
@@ -250,14 +295,68 @@ export default function OpsMapPage() {
moveMode === "abs" ? "abs" : "signed"
}`}
/>
<p className="chart-summary">
{moveSummary(range, mov?.buckets ?? [], {
{(() => {
const movTable = moveSummaryTable(range, mov?.buckets ?? [], {
pendingExpiry: mov?.pending_expiry,
pendingCount: mov?.pending_count,
mode: moveMode,
apiMessage: mov?.message,
})}
</p>
});
if (movTable.kind === "empty") {
return <p className="chart-summary">{movTable.message}</p>;
}
return (
<div className="chart-summary chart-summary-table">
{movTable.note && (
<div className="summary-note">{movTable.note}</div>
)}
<div className="summary-table-wrap">
<table className="summary-table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th>
{moveMode === "abs" ? "绝对波动均值" : "带符号均值"}
</th>
<th>
{moveMode === "abs" ? "带符号均值" : "绝对波动均值"}
</th>
</tr>
</thead>
<tbody>
{movTable.rows.map((r) => (
<tr key={r.label}>
<td>{r.label}</td>
<td>{r.days}</td>
<td>{r.n}</td>
<td>
{moveMode === "abs"
? r.meanAbs != null
? fmtNum(r.meanAbs, 2)
: "—"
: r.meanSigned != null
? fmtNum(r.meanSigned, 2)
: "—"}
</td>
<td>
{moveMode === "abs"
? r.meanSigned != null
? fmtNum(r.meanSigned, 2)
: "—"
: r.meanAbs != null
? fmtNum(r.meanAbs, 2)
: "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
})()}
</>
)}
</div>