a1abe159fa
Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
213 lines
6.1 KiB
Python
213 lines
6.1 KiB
Python
"""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
|