fix(hub): archive chart perpetual label and trade row highlight

Label archive K-lines as USDT swap in title and API; highlight the full trade row when opening its chart.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-11 19:56:55 +08:00
parent a9637fafb2
commit a87234f627
5 changed files with 52 additions and 6 deletions
+14
View File
@@ -67,6 +67,20 @@ def normalize_chart_timeframe(raw: str | None, default: str = "5m") -> str:
return tf if tf in CHART_TIMEFRAMES else default return tf if tf in CHART_TIMEFRAMES else default
def normalize_perpetual_symbol(symbol: str) -> str:
"""BTC/USDT → BTC/USDT:USDT(与四所 ccxt swap 行情一致)。"""
sym = (symbol or "").strip().upper()
if not sym:
return ""
if ":" in sym:
return sym
if "/" in sym:
base, quote = sym.split("/", 1)
quote_clean = quote.split(":")[0]
return f"{base}/{quote_clean}:{quote_clean}"
return sym
def sync_timeframe_for_display(timeframe: str) -> str: def sync_timeframe_for_display(timeframe: str) -> str:
"""展示周期对应的入库 / 同步周期。""" """展示周期对应的入库 / 同步周期。"""
tf = normalize_chart_timeframe(timeframe) tf = normalize_chart_timeframe(timeframe)
+4
View File
@@ -17,6 +17,7 @@ from hub_ohlcv_lib import (
TIMEFRAME_MS, TIMEFRAME_MS,
aggregate_ohlcv_bars, aggregate_ohlcv_bars,
normalize_chart_timeframe, normalize_chart_timeframe,
normalize_perpetual_symbol,
) )
from hub_trades_lib import ( from hub_trades_lib import (
display_entry_type_label, display_entry_type_label,
@@ -927,10 +928,13 @@ def resolve_archive_chart(
if not candles: if not candles:
return {"ok": False, "msg": "视窗内无 K 线"} return {"ok": False, "msg": "视窗内无 K 线"}
ex_sym = normalize_perpetual_symbol(sym)
return { return {
"ok": True, "ok": True,
"exchange_key": ex_k, "exchange_key": ex_k,
"symbol": sym, "symbol": sym,
"exchange_symbol": ex_sym,
"market_type": "swap",
"timeframe": tf, "timeframe": tf,
"mode": (mode or "hold").strip().lower(), "mode": (mode or "hold").strip().lower(),
"range_mode": rm, "range_mode": rm,
+4 -2
View File
@@ -5800,13 +5800,15 @@ body.funds-fullscreen-open {
cursor: pointer; cursor: pointer;
} }
.archive-trade-row.is-active { .archive-trade-row.is-active {
background: var(--inset-surface); background: color-mix(in srgb, var(--accent) 16%, var(--inset-surface));
box-shadow: inset 3px 0 0 var(--accent);
} }
.archive-trade-row.archive-trade-sick td { .archive-trade-row.archive-trade-sick td {
color: var(--red); color: var(--red);
} }
.archive-trade-row.archive-trade-sick.is-active { .archive-trade-row.archive-trade-sick.is-active {
background: var(--inset-surface); background: color-mix(in srgb, var(--accent) 12%, color-mix(in srgb, var(--red) 8%, var(--panel)));
box-shadow: inset 3px 0 0 var(--accent);
} }
.archive-trade-row.archive-trade-sick .archive-tag-select, .archive-trade-row.archive-trade-sick .archive-tag-select,
.archive-trade-row.archive-trade-sick .archive-note-input { .archive-trade-row.archive-trade-sick .archive-note-input {
+28 -2
View File
@@ -66,6 +66,8 @@
let inited = false; let inited = false;
let markAuto = true; let markAuto = true;
let lastCandles = []; let lastCandles = [];
let chartExchangeSymbol = "";
let chartMarketType = "swap";
let searchTimer = null; let searchTimer = null;
function esc(s) { function esc(s) {
@@ -309,13 +311,27 @@
scheduleChartResize(); scheduleChartResize();
} }
function formatChartContractLabel(sym, exchangeSymbol, marketType) {
const base = String(sym || "—");
const mt = String(marketType || "").toLowerCase();
if (mt === "swap" || (exchangeSymbol && String(exchangeSymbol).indexOf(":") >= 0)) {
return base + " 永续";
}
return base;
}
function updateChartTitle() { function updateChartTitle() {
if (!elChartTitle) return; if (!elChartTitle) return;
if (!selected) { if (!selected) {
elChartTitle.textContent = "—"; elChartTitle.textContent = "—";
return; return;
} }
elChartTitle.textContent = selected.symbol + " · " + exchangeLabel(selected.exchange_key); const label = formatChartContractLabel(
selected.symbol,
chartExchangeSymbol,
chartMarketType
);
elChartTitle.textContent = label + " · " + exchangeLabel(selected.exchange_key);
} }
async function apiFetch(url, opts) { async function apiFetch(url, opts) {
@@ -871,6 +887,8 @@
setStatus(j.detail || "K 线加载失败"); setStatus(j.detail || "K 线加载失败");
return; return;
} }
chartExchangeSymbol = j.exchange_symbol || "";
chartMarketType = j.market_type || "swap";
if (chart) { if (chart) {
destroyChart(); destroyChart();
} }
@@ -900,7 +918,14 @@
} }
updateChartTitle(); updateChartTitle();
scheduleChartResize(); scheduleChartResize();
setStatus("K 线 " + candles.length + " 根 · " + timeframe); setStatus(
"K 线 " +
candles.length +
" 根 · " +
timeframe +
" · " +
formatChartContractLabel(selected.symbol, chartExchangeSymbol, chartMarketType)
);
} }
async function openTradeChart(tr) { async function openTradeChart(tr) {
@@ -913,6 +938,7 @@
} }
selected = { exchange_key: exKey, symbol: sym }; selected = { exchange_key: exKey, symbol: sym };
selectedTradeId = String(tr.trade_id || tr.id); selectedTradeId = String(tr.trade_id || tr.id);
renderTrades();
setChartOpen(true); setChartOpen(true);
await loadSymbolTradesForChart(exKey, sym); await loadSymbolTradesForChart(exKey, sym);
await loadChart(); await loadChart();
+2 -2
View File
@@ -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=20260612-trade-symbol-col" /> <link rel="stylesheet" href="/assets/app.css?v=20260612-archive-chart-row" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" /> <link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" />
</head> </head>
<body> <body>
@@ -584,7 +584,7 @@
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script> <script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script src="/assets/chart_draw.js?v=20260609-market-day-split"></script> <script src="/assets/chart_draw.js?v=20260609-market-day-split"></script>
<script src="/assets/chart.js?v=20260609-market-day-split"></script> <script src="/assets/chart.js?v=20260609-market-day-split"></script>
<script src="/assets/archive.js?v=20260612-trade-symbol-col"></script> <script src="/assets/archive.js?v=20260612-archive-chart-row"></script>
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script> <script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script> <script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
<script src="/assets/ai_review_render.js?v=3"></script> <script src="/assets/ai_review_render.js?v=3"></script>