"""Gate:资金账户(spot account_book) + 交易账户(futures account_book),USDT.""" from __future__ import annotations from typing import Any, Callable, Optional from lib.account_ledger.account_ledger_normalize import ( ACCOUNT_FUNDING, ACCOUNT_TRADING, from_ccxt_ledger_entry, kind_from_raw, make_ref_id, normalize_row, ) def _sec(ms: int) -> int: return max(0, int(int(ms) // 1000)) def _paginate_spot_book(exchange, *, start_ms: int, end_ms: int, max_pages: int = 10) -> list[dict]: out: list[dict] = [] # Gate spot account_book: from/to 为秒 cursor = _sec(start_ms) end = _sec(end_ms) for _ in range(max_pages): try: batch = exchange.privateSpotGetAccountBook( { "currency": "USDT", "from": cursor, "to": end, "limit": 100, } ) except Exception: break if not batch: break if isinstance(batch, dict): batch = batch.get("data") or batch.get("result") or [] if not isinstance(batch, list) or not batch: break out.extend(batch) if len(batch) < 100: break last_t = batch[-1].get("time") or batch[-1].get("create_time") try: last_i = int(float(last_t)) except Exception: break # spot 返回秒 if last_i > 1e12: last_i = last_i // 1000 if last_i >= end: break cursor = last_i + 1 return out def _paginate_swap_book(exchange, *, start_ms: int, end_ms: int, max_pages: int = 10) -> list[dict]: out: list[dict] = [] cursor = _sec(start_ms) end = _sec(end_ms) for _ in range(max_pages): try: batch = exchange.privateFuturesGetSettleAccountBook( { "settle": "usdt", "from": cursor, "to": end, "limit": 100, } ) except Exception: break if not batch: break if isinstance(batch, dict): batch = batch.get("data") or batch.get("result") or [] if not isinstance(batch, list) or not batch: break out.extend(batch) if len(batch) < 100: break last_t = batch[-1].get("time") try: last_i = int(float(last_t)) except Exception: break if last_i > 1e12: last_i = last_i // 1000 if last_i >= end: break cursor = last_i + 1 return out def _spot_row(raw: dict) -> Optional[dict[str, Any]]: if not isinstance(raw, dict): return None amt = raw.get("change") ts = raw.get("time") or raw.get("create_time") # 秒 → 毫秒 try: t = float(ts) if t < 1e12: t = t * 1000.0 ts = t except Exception: pass raw_type = str(raw.get("type") or raw.get("change_type") or "") bal = raw.get("balance") ref = str(raw.get("id") or raw.get("txid") or "") return normalize_row( account=ACCOUNT_FUNDING, ccy="USDT", amount=amt, ts_ms=ts, ref_id=ref or make_ref_id("funding", raw_type, ts, amt), raw_type=raw_type, balance_after=bal, note=str(raw.get("text") or ""), kind=kind_from_raw(raw_type, float(amt) if amt is not None else None), ) def _swap_row(raw: dict) -> Optional[dict[str, Any]]: if not isinstance(raw, dict): return None # futures account_book: change, balance, type, text, time, contract... amt = raw.get("change") ts = raw.get("time") try: t = float(ts) if t < 1e12: t = t * 1000.0 ts = t except Exception: pass raw_type = str(raw.get("type") or "") bal = raw.get("balance") ref = str(raw.get("id") or "") return normalize_row( account=ACCOUNT_TRADING, ccy="USDT", amount=amt, ts_ms=ts, ref_id=ref or make_ref_id("trading", raw_type, ts, amt, raw.get("contract")), raw_type=raw_type, balance_after=bal, symbol=str(raw.get("contract") or ""), note=str(raw.get("text") or ""), kind=kind_from_raw(raw_type, float(amt) if amt is not None else None), ) def fetch_gate_account_ledger( exchange, *, start_ms: int, end_ms: int, ensure_markets: Optional[Callable[[], None]] = None, ) -> tuple[list[dict[str, Any]], list[str]]: errors: list[str] = [] rows: list[dict[str, Any]] = [] if ensure_markets: try: ensure_markets() except Exception as e: errors.append(f"markets:{e}") try: for e in _paginate_spot_book(exchange, start_ms=start_ms, end_ms=end_ms): n = _spot_row(e) if n: rows.append(n) except Exception as e: errors.append(f"funding:{e}") # 回退 ccxt fetch_ledger try: batch = exchange.fetch_ledger( "USDT", int(start_ms), 100, {"type": "spot", "until": int(end_ms)} ) or [] for e in batch: n = from_ccxt_ledger_entry(e, account=ACCOUNT_FUNDING) if n: rows.append(n) except Exception as e2: errors.append(f"funding_fallback:{e2}") try: for e in _paginate_swap_book(exchange, start_ms=start_ms, end_ms=end_ms): n = _swap_row(e) if n: rows.append(n) except Exception as e: errors.append(f"trading:{e}") try: batch = exchange.fetch_ledger( "USDT", int(start_ms), 100, {"type": "swap", "settle": "usdt", "until": int(end_ms)}, ) or [] for e in batch: n = from_ccxt_ledger_entry(e, account=ACCOUNT_TRADING) if n: rows.append(n) except Exception as e2: errors.append(f"trading_fallback:{e2}") return rows, errors