增加资金费率
This commit is contained in:
+16
-4
@@ -26,6 +26,7 @@ const SORT_KEYS = {
|
||||
if (r.is_high_change) score += 1;
|
||||
return score;
|
||||
},
|
||||
funding_rate: (r) => Number(r.funding_rate_pct) || 0,
|
||||
};
|
||||
|
||||
function formatPeriod(start, end) {
|
||||
@@ -88,7 +89,7 @@ function renderTable(tableId, tbody) {
|
||||
const items = sortItems(state.items, state.sortKey, state.sortDir);
|
||||
|
||||
if (!items.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="loading">暂无数据</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="loading">暂无数据</td></tr>';
|
||||
updateSortHeaders(tableId);
|
||||
return;
|
||||
}
|
||||
@@ -113,6 +114,14 @@ function renderTable(tableId, tbody) {
|
||||
</td>
|
||||
<td data-value="${row.quote_volume ?? 0}">${row.quote_volume_fmt || row.quote_volume}</td>
|
||||
<td class="${pctClass(pct)}" data-value="${pct}">${row.price_change_pct_fmt || pct.toFixed(2) + "%"}</td>
|
||||
<td class="funding-cell" data-value="${row.funding_rate_pct ?? 0}">
|
||||
<div class="funding-cell-inner">
|
||||
<span class="funding-rate-label ${pctClass(row.funding_rate_pct ?? 0)}">${row.funding_rate_fmt || "—"}</span>
|
||||
<div class="mini-funding-chart" data-symbol="${row.symbol}">
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td data-value="${tagText(row)}">${renderTags(row)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
@@ -120,6 +129,7 @@ function renderTable(tableId, tbody) {
|
||||
|
||||
updateSortHeaders(tableId);
|
||||
enqueueCharts(tbody);
|
||||
if (typeof enqueueFundingCharts === "function") enqueueFundingCharts(tbody);
|
||||
}
|
||||
|
||||
function setTableData(tableId, data) {
|
||||
@@ -165,6 +175,7 @@ function exportCsv(tableId) {
|
||||
"成交额显示",
|
||||
"成交额USDT",
|
||||
"涨跌幅%",
|
||||
"资金费率%",
|
||||
"千万+",
|
||||
"涨跌5%+",
|
||||
"标记",
|
||||
@@ -175,6 +186,7 @@ function exportCsv(tableId) {
|
||||
r.quote_volume_fmt || "",
|
||||
r.quote_volume ?? "",
|
||||
r.price_change_pct ?? "",
|
||||
r.funding_rate_pct ?? "",
|
||||
r.is_high_volume ? "是" : "否",
|
||||
r.is_high_change ? "是" : "否",
|
||||
tagText(r),
|
||||
@@ -213,7 +225,7 @@ document.querySelectorAll("[data-reset]").forEach((btn) => {
|
||||
|
||||
async function loadYesterday() {
|
||||
const body = document.getElementById("yesterday-body");
|
||||
body.innerHTML = '<tr><td colspan="6" class="loading">加载中…</td></tr>';
|
||||
body.innerHTML = '<tr><td colspan="7" class="loading">加载中…</td></tr>';
|
||||
try {
|
||||
const res = await fetch("/api/yesterday/top30");
|
||||
const data = await res.json();
|
||||
@@ -225,7 +237,7 @@ async function loadYesterday() {
|
||||
"更新: " + (data.updated_at || "").replace("T", " ").slice(0, 19);
|
||||
setTableData("yesterday", data);
|
||||
} catch (e) {
|
||||
body.innerHTML = `<tr><td colspan="6" class="error">加载失败: ${e.message}</td></tr>`;
|
||||
body.innerHTML = `<tr><td colspan="7" class="error">加载失败: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +255,7 @@ async function loadToday() {
|
||||
setTableData("today", data);
|
||||
document.getElementById("status").textContent = "今日数据已刷新";
|
||||
} catch (e) {
|
||||
body.innerHTML = `<tr><td colspan="6" class="error">加载失败: ${e.message}</td></tr>`;
|
||||
body.innerHTML = `<tr><td colspan="7" class="error">加载失败: ${e.message}</td></tr>`;
|
||||
document.getElementById("status").textContent = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/** 资金费率历史曲线 */
|
||||
|
||||
const fundingCache = new Map();
|
||||
const fundingQueue = [];
|
||||
let fundingQueueRunning = false;
|
||||
const FUNDING_FETCH_GAP_MS = 200;
|
||||
|
||||
const FUNDING_MINI = { w: 200, h: 56 };
|
||||
const FUNDING_MODAL = { w: 960, h: 320 };
|
||||
|
||||
function enqueueFundingCharts(root) {
|
||||
root.querySelectorAll(".mini-funding-chart[data-symbol]").forEach((box) => {
|
||||
if (box.dataset.loaded === "1" || box.dataset.loading === "1") return;
|
||||
fundingQueue.push(box);
|
||||
});
|
||||
runFundingQueue();
|
||||
}
|
||||
|
||||
async function runFundingQueue() {
|
||||
if (fundingQueueRunning) return;
|
||||
fundingQueueRunning = true;
|
||||
while (fundingQueue.length) {
|
||||
const box = fundingQueue.shift();
|
||||
if (!box?.isConnected) continue;
|
||||
await loadMiniFundingChart(box);
|
||||
await new Promise((r) => setTimeout(r, FUNDING_FETCH_GAP_MS));
|
||||
}
|
||||
fundingQueueRunning = false;
|
||||
}
|
||||
|
||||
function setupCanvas(canvas, w, h) {
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||
canvas.style.width = `${w}px`;
|
||||
canvas.style.height = `${h}px`;
|
||||
canvas.width = Math.floor(w * dpr);
|
||||
canvas.height = Math.floor(h * dpr);
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
return { ctx, w, h };
|
||||
}
|
||||
|
||||
function drawFundingChart(canvas, history, current, large = false) {
|
||||
if (!canvas || !history?.length) return;
|
||||
const size = large ? FUNDING_MODAL : FUNDING_MINI;
|
||||
const { ctx, w, h } = setupCanvas(canvas, size.w, size.h);
|
||||
const pad = large ? { t: 20, r: 16, b: 24, l: 48 } : { t: 6, r: 4, b: 8, l: 4 };
|
||||
const plotW = w - pad.l - pad.r;
|
||||
const plotH = h - pad.t - pad.b;
|
||||
|
||||
const rates = history.map((p) => p.rate_pct);
|
||||
let min = Math.min(...rates, 0);
|
||||
let max = Math.max(...rates, 0);
|
||||
const margin = Math.max(0.005, (max - min) * 0.15);
|
||||
min -= margin;
|
||||
max += margin;
|
||||
const range = max - min || 1;
|
||||
const n = history.length;
|
||||
const step = plotW / Math.max(n - 1, 1);
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.fillStyle = "#0d1118";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
const y0 = pad.t + plotH * (1 - (0 - min) / range);
|
||||
ctx.strokeStyle = "#3a4558";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([4, 4]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pad.l, y0);
|
||||
ctx.lineTo(w - pad.r, y0);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
if (large) {
|
||||
ctx.fillStyle = "#8b9cb3";
|
||||
ctx.font = "11px system-ui,sans-serif";
|
||||
ctx.textAlign = "right";
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const v = max - (range * i) / 4;
|
||||
const y = pad.t + (plotH * i) / 4;
|
||||
ctx.fillText(`${v.toFixed(3)}%`, pad.l - 6, y + 4);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.lineWidth = large ? 2 : 1.2;
|
||||
ctx.beginPath();
|
||||
history.forEach((p, i) => {
|
||||
const x = pad.l + i * step;
|
||||
const y = pad.t + plotH * (1 - (p.rate_pct - min) / range);
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
});
|
||||
ctx.strokeStyle = "#5c7cfa";
|
||||
ctx.stroke();
|
||||
|
||||
history.forEach((p, i) => {
|
||||
const x = pad.l + i * step;
|
||||
const y = pad.t + plotH * (1 - (p.rate_pct - min) / range);
|
||||
ctx.fillStyle = p.rate_pct >= 0 ? "#0ecb81" : "#f6465d";
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, large ? 3 : 1.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
if (current != null) {
|
||||
const lx = w - pad.r - 2;
|
||||
const ly = pad.t + 2;
|
||||
ctx.fillStyle = current >= 0 ? "#0ecb81" : "#f6465d";
|
||||
ctx.font = `${large ? 13 : 10}px system-ui,sans-serif`;
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(`当前 ${current.toFixed(4)}%`, lx, ly + (large ? 12 : 10));
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMiniFundingChart(box) {
|
||||
const symbol = box.dataset.symbol;
|
||||
if (!symbol) return;
|
||||
box.dataset.loading = "1";
|
||||
const canvas = box.querySelector("canvas");
|
||||
const label = box.querySelector(".funding-rate-label");
|
||||
|
||||
try {
|
||||
let bundle = fundingCache.get(symbol);
|
||||
if (!bundle) {
|
||||
const res = await fetch(`/api/funding/${symbol}/history?limit=90`);
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).detail || res.statusText);
|
||||
bundle = await res.json();
|
||||
fundingCache.set(symbol, bundle);
|
||||
}
|
||||
const history = bundle.history || [];
|
||||
const curPct = bundle.current?.rate_pct ?? 0;
|
||||
if (label) {
|
||||
label.textContent = `${curPct >= 0 ? "+" : ""}${curPct.toFixed(4)}%`;
|
||||
label.className = `funding-rate-label ${curPct >= 0 ? "pct-up" : "pct-down"}`;
|
||||
}
|
||||
drawFundingChart(canvas, history, curPct, false);
|
||||
box.dataset.loaded = "1";
|
||||
box.title = `${symbol} 资金费率历史 ${history.length} 点,点击放大`;
|
||||
} catch (e) {
|
||||
if (label) label.textContent = "—";
|
||||
box.title = e.message;
|
||||
} finally {
|
||||
box.dataset.loading = "0";
|
||||
}
|
||||
}
|
||||
|
||||
function setupFundingModal() {
|
||||
let modal = document.getElementById("funding-modal");
|
||||
if (!modal) {
|
||||
modal = document.createElement("div");
|
||||
modal.id = "funding-modal";
|
||||
modal.className = "chart-modal hidden";
|
||||
modal.innerHTML = `
|
||||
<div class="chart-modal-inner">
|
||||
<button type="button" class="chart-modal-close funding-modal-close">×</button>
|
||||
<h3 id="funding-modal-title"></h3>
|
||||
<p class="chart-modal-hint">资金费率历史(约 90 次结算,8h/次)· 虚线为零轴</p>
|
||||
<div class="chart-modal-canvas-wrap">
|
||||
<canvas id="funding-modal-canvas"></canvas>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
modal.querySelector(".funding-modal-close").onclick = () => modal.classList.add("hidden");
|
||||
modal.addEventListener("click", (e) => {
|
||||
if (e.target === modal) modal.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
document.body.addEventListener("click", (e) => {
|
||||
const box = e.target.closest(".mini-funding-chart[data-symbol]");
|
||||
if (!box || box.dataset.loaded !== "1") return;
|
||||
const symbol = box.dataset.symbol;
|
||||
const bundle = fundingCache.get(symbol);
|
||||
if (!bundle) return;
|
||||
modal.classList.remove("hidden");
|
||||
document.getElementById("funding-modal-title").textContent =
|
||||
`${symbol} · 资金费率`;
|
||||
drawFundingChart(
|
||||
document.getElementById("funding-modal-canvas"),
|
||||
bundle.history,
|
||||
bundle.current?.rate_pct,
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setupFundingModal();
|
||||
+4
-1
@@ -9,7 +9,7 @@
|
||||
<body>
|
||||
<header>
|
||||
<h1>币安 U本位合约 · 成交额排名</h1>
|
||||
<p class="subtitle">北京时间 08:00 切日 · Top30 · 合约右侧 300 日K+成交量 · 点击图表放大查看</p>
|
||||
<p class="subtitle">Top30 · 日K+成交量 · 资金费率当前+历史曲线 · 点击图表放大</p>
|
||||
</header>
|
||||
|
||||
<section class="panel" id="panel-yesterday">
|
||||
@@ -31,6 +31,7 @@
|
||||
<th class="chart-col">日线图</th>
|
||||
<th class="sortable" data-sort="quote_volume">成交额 (USDT)</th>
|
||||
<th class="sortable" data-sort="price_change_pct">涨跌幅</th>
|
||||
<th class="funding-col">资金费率</th>
|
||||
<th class="sortable" data-sort="tags">标记</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -58,6 +59,7 @@
|
||||
<th class="chart-col">日线图</th>
|
||||
<th class="sortable" data-sort="quote_volume">成交额 (USDT)</th>
|
||||
<th class="sortable" data-sort="price_change_pct">涨跌幅</th>
|
||||
<th class="funding-col">资金费率</th>
|
||||
<th class="sortable" data-sort="tags">标记</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -72,6 +74,7 @@
|
||||
</footer>
|
||||
|
||||
<script src="/static/charts.js"></script>
|
||||
<script src="/static/funding.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+40
-1
@@ -311,7 +311,46 @@ button:hover {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#chart-modal-canvas {
|
||||
#chart-modal-canvas,
|
||||
#funding-modal-canvas {
|
||||
display: block;
|
||||
background: #0d1118;
|
||||
}
|
||||
|
||||
.funding-col {
|
||||
min-width: 220px;
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.funding-cell {
|
||||
vertical-align: middle;
|
||||
padding: 0.4rem 0.5rem !important;
|
||||
}
|
||||
|
||||
.funding-cell-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.funding-rate-label {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mini-funding-chart {
|
||||
width: 200px;
|
||||
height: 56px;
|
||||
cursor: zoom-in;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #0d1118;
|
||||
}
|
||||
|
||||
.mini-funding-chart canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user