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,
}