fix: invalidate stale 12-item volume rank cache and force full top20 refresh

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 16:01:27 +08:00
parent 89a58c7323
commit 93b84da72e
5 changed files with 94 additions and 24 deletions
+26 -6
View File
@@ -26,6 +26,7 @@ from hub_ohlcv_lib import (
)
from hub_volume_rank_lib import (
TOP_N_DEFAULT,
_exchange_rank_row_stale,
cache_needs_refresh,
format_volume_quote,
get_cached_rank,
@@ -343,7 +344,7 @@ def _fetch_instance_volume_rank_sync(ex: dict, *, top_n: int = TOP_N_DEFAULT) ->
params = {"top": str(int(top_n))}
url = f"{base}/api/hub/volume-rank?{urlencode(params)}"
try:
with httpx.Client(timeout=max(HUB_FLASK_TIMEOUT, 60.0)) as client:
with httpx.Client(timeout=max(HUB_FLASK_TIMEOUT, 120.0)) as client:
r = client.get(url, headers=_hub_headers())
if r.status_code >= 400:
parsed = _parse_http_json_body(r)
@@ -367,14 +368,21 @@ def _refresh_volume_ranks(*, force: bool = False) -> dict:
global _volume_rank_cache
expected = rank_date_label()
cache = _get_volume_rank_cache()
if not force and not cache_needs_refresh(cache, expected_rank_date=expected):
targets = enabled_exchanges(load_settings())
required_keys = [
str(ex.get("key") or "").strip().lower()
for ex in targets
if ex.get("enabled") and str(ex.get("key") or "").strip()
]
if not force and not cache_needs_refresh(
cache, expected_rank_date=expected, required_keys=required_keys
):
return {
"ok": True,
"skipped": True,
"rank_date": cache.get("rank_date"),
"updated_at": cache.get("updated_at"),
}
targets = enabled_exchanges(load_settings())
errors: list[str] = []
for ex in targets:
ex_key = str(ex.get("key") or "").strip().lower()
@@ -782,10 +790,22 @@ def api_chart_volume_rank(exchange_key: str = "", refresh: str = ""):
if not result.get("ok"):
raise HTTPException(status_code=502, detail=result.get("msg") or "刷新失败")
cache = _get_volume_rank_cache()
if cache_needs_refresh(cache):
_refresh_volume_ranks(force=False)
cache = _get_volume_rank_cache()
ex_k = (exchange_key or "").strip().lower()
targets = enabled_exchanges(load_settings())
required_keys = [
str(ex.get("key") or "").strip().lower()
for ex in targets
if ex.get("enabled") and str(ex.get("key") or "").strip()
]
need_keys = [ex_k] if ex_k else required_keys
if cache_needs_refresh(cache, required_keys=need_keys):
_refresh_volume_ranks(force=True)
cache = _get_volume_rank_cache()
elif ex_k:
row = (cache.get("exchanges") or {}).get(ex_k) or {}
if _exchange_rank_row_stale(row):
_refresh_volume_ranks(force=True)
cache = _get_volume_rank_cache()
if ex_k:
ex = _find_exchange_by_key(ex_k)
if not ex:
+23 -11
View File
@@ -2629,15 +2629,24 @@
const rankDate = data.rank_date || "—";
const updated = data.updated_at || "—";
const total = data.total_symbols != null ? data.total_symbols : "";
elVolRankMeta.textContent =
"昨日成交 Top20 · 交易日 " +
const count = data.items.length;
const expect = data.expected_count != null ? data.expected_count : 20;
let meta =
"昨日成交 Top" +
expect +
" · 交易日 " +
rankDate +
" · 每早 " +
resetHour +
":00 更新" +
(total ? " · 全市场 " + total + " 个" : "") +
" · " +
updated;
":00 更新 · 显示 " +
count +
"/" +
expect +
" 条";
if (total) meta += " · 全市场 " + total + " 个";
if (data.stale) meta += " · 数据不完整,正在重拉…";
meta += " · " + updated;
elVolRankMeta.textContent = meta;
const curSym = (elSymbol && elSymbol.value.trim().toUpperCase()) || "";
data.items.forEach(function (row) {
const li = document.createElement("li");
@@ -2667,21 +2676,24 @@
});
}
async function loadVolumeRank() {
async function loadVolumeRank(forceRefresh) {
const exKey = (elExchange && elExchange.value) || "";
if (!exKey || !elVolRankMeta) return;
elVolRankMeta.textContent = "加载排名…";
if (elVolRankList) elVolRankList.innerHTML = "";
try {
const r = await fetch(
"/api/chart/volume-rank?exchange_key=" + encodeURIComponent(exKey),
{ credentials: "same-origin" }
);
let url = "/api/chart/volume-rank?exchange_key=" + encodeURIComponent(exKey);
if (forceRefresh) url += "&refresh=1";
const r = await fetch(url, { credentials: "same-origin" });
const data = await r.json();
if (!r.ok) {
throw new Error((data && data.detail) || (data && data.msg) || "加载失败");
}
renderVolumeRank(data);
const expect = data.expected_count != null ? data.expected_count : 20;
if (!forceRefresh && data.ok && data.items && data.items.length < expect) {
void loadVolumeRank(true);
}
} catch (e) {
renderVolumeRank({ ok: false, msg: String(e.message || e) });
}
+1 -1
View File
@@ -395,7 +395,7 @@
<div id="toast"></div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script src="/assets/chart_draw.js?v=20260608-market-vol-rank"></script>
<script src="/assets/chart.js?v=20260608-market-vol-rank"></script>
<script src="/assets/chart.js?v=20260608-market-vol-rank-v2"></script>
<script src="/assets/archive.js?v=20260607-hub-archive-v6"></script>
<script src="/assets/ai_review_render.js?v=2"></script>
<script src="/assets/app.js?v=20260607-hub-archive-v1"></script>