fix(hub): merge strategy snapshots into archive for gate_bot

Include strategy_trade_snapshots when trade_records is empty, harden SQL for older schemas, and show per-exchange sync errors in the archive UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-07 23:02:46 +08:00
parent 6a56928d59
commit 3052607280
6 changed files with 396 additions and 34 deletions
+33 -3
View File
@@ -95,6 +95,7 @@ DIR = Path(__file__).resolve().parent
HUB_BUILD = "20260607-hub-archive"
_archive_sync_stop: asyncio.Event | None = None
_archive_sync_task: asyncio.Task | None = None
_last_archive_sync: dict | None = None
HUB_AGENT_TIMEOUT = float(os.getenv("HUB_AGENT_TIMEOUT", "8"))
HUB_FLASK_TIMEOUT = float(os.getenv("HUB_FLASK_TIMEOUT", "10"))
HUB_BOARD_TIMEOUT = float(os.getenv("HUB_BOARD_TIMEOUT", "45"))
@@ -239,6 +240,7 @@ def _schedule_board_refresh() -> None:
async def _run_archive_sync_once() -> dict:
global _last_archive_sync
init_archive_db()
settings = load_settings()
targets = enabled_exchanges(settings)
@@ -254,11 +256,25 @@ async def _run_archive_sync_once() -> dict:
limit=ARCHIVE_TRADE_LIMIT,
)
if not trades_resp.get("ok"):
st = trades_resp.get("status")
msg = (
trades_resp.get("msg")
or trades_resp.get("error")
or trades_resp.get("detail")
or "拉取交易失败"
)
if st == 404:
msg = (
"HTTP 404:该 Flask 未注册 /api/hub/trades/archive。"
"请在仓库根目录 git pull 后 pm2 restart crypto_gate crypto_gate_bot"
)
results.append(
{
"exchange_key": ex_key,
"name": ex.get("name"),
"ok": False,
"msg": trades_resp.get("msg") or trades_resp.get("error") or "拉取交易失败",
"status": st,
"msg": msg,
}
)
continue
@@ -282,8 +298,17 @@ async def _run_archive_sync_once() -> dict:
trades,
remote_fetch,
)
r["name"] = ex.get("name")
r["trade_count"] = len(trades)
results.append(r)
return {"ok": True, "exchanges": len(targets), "results": results}
out = {
"ok": True,
"exchanges": len(targets),
"results": results,
"updated_at": __import__("datetime").datetime.now().isoformat(timespec="seconds"),
}
_last_archive_sync = out
return out
async def _archive_sync_loop() -> None:
@@ -549,9 +574,13 @@ def _fetch_instance_trades_archive_sync(
if r.status_code >= 400:
parsed = _parse_http_json_body(r)
parsed.setdefault("ok", False)
parsed.setdefault("status", r.status_code)
return parsed
data = r.json() if r.content else {}
return data if isinstance(data, dict) else {"ok": False, "msg": "无效 JSON"}
if isinstance(data, dict):
data.setdefault("ok", True)
return data
return {"ok": False, "msg": "无效 JSON"}
except Exception as e:
return {"ok": False, "msg": str(e)}
@@ -1662,6 +1691,7 @@ def api_archive_meta():
"sync_interval_sec": ARCHIVE_SYNC_INTERVAL_SEC,
"visible_bars_default": ARCHIVE_VISIBLE_BARS_DEFAULT,
"exchanges": exchanges,
"last_sync": _last_archive_sync,
}
+21 -4
View File
@@ -410,6 +410,9 @@
const r = await apiFetch("/api/archive/meta");
meta = await r.json();
timeframe = (meta && meta.default_timeframe) || "15m";
if (meta && meta.last_sync && elStatus && !elStatus.textContent) {
setStatus(formatSyncSummary(meta.last_sync));
}
renderExchangeOptions();
if (elTfTabs) {
elTfTabs.querySelectorAll(".archive-tf-btn").forEach(function (btn) {
@@ -418,16 +421,30 @@
}
}
function formatSyncSummary(j) {
const results = j.results || [];
const okN = results.filter(function (x) {
return x.ok !== false;
}).length;
const parts = ["同步完成 · " + okN + "/" + (j.exchanges || 0) + " 所"];
results.forEach(function (row) {
const label = row.exchange_key || row.name || "?";
if (row.ok === false) {
parts.push(label + " 失败: " + (row.msg || "未知错误"));
} else {
parts.push(label + " " + (row.trade_count != null ? row.trade_count : row.trades || 0) + " 笔");
}
});
return parts.join(" · ");
}
async function syncAll() {
setStatus("同步中(可能需数分钟)…");
elBtnSync && (elBtnSync.disabled = true);
try {
const r = await apiFetch("/api/archive/sync", { method: "POST" });
const j = await r.json();
const okN = (j.results || []).filter(function (x) {
return x.ok !== false;
}).length;
setStatus("同步完成 · " + okN + "/" + (j.exchanges || 0) + " 所");
setStatus(formatSyncSummary(j));
await loadList();
if (selected) await openDetail(selected.exchange_key, selected.symbol);
} catch (e) {
+1 -1
View File
@@ -349,7 +349,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.js?v=20260604-upnl-contracts"></script>
<script src="/assets/archive.js?v=20260607-hub-archive-v1"></script>
<script src="/assets/archive.js?v=20260607-hub-archive-v2"></script>
<script src="/assets/ai_review_render.js?v=2"></script>
<script src="/assets/app.js?v=20260607-hub-archive-v1"></script>
</body>