Signed-off-by: Dinger <quantdinger@gmail.com>
This commit is contained in:
Dinger
2026-04-06 23:19:41 +08:00
parent 3ca291a346
commit de3fd0945b
91 changed files with 1504 additions and 405 deletions
@@ -31,6 +31,9 @@ class BinanceSpotClient(BaseRestClient):
self._sym_filter_cache: Dict[str, Tuple[float, Dict[str, Any]]] = {}
self._sym_filter_cache_ttl_sec = 300.0
self._time_offset_ms: int = 0
self._time_sync_monotonic: float = 0.0
@staticmethod
def _to_dec(x: Any) -> Decimal:
try:
@@ -158,6 +161,24 @@ class BinanceSpotClient(BaseRestClient):
def _signed_headers(self) -> Dict[str, str]:
return {"X-MBX-APIKEY": self.api_key}
def _ensure_server_time(self, *, force: bool = False) -> None:
"""Align signed request timestamps with Binance (GET /api/v3/time)."""
now_m = time.monotonic()
if not force and (now_m - float(self._time_sync_monotonic or 0.0)) < 300.0:
return
try:
code, data, _ = self._request("GET", "/api/v3/time")
if code != 200 or not isinstance(data, dict):
return
server_ms = int(data.get("serverTime") or 0)
if server_ms <= 0:
return
local_ms = int(time.time() * 1000)
self._time_offset_ms = server_ms - local_ms
self._time_sync_monotonic = now_m
except Exception:
pass
def _format_client_order_id(self, client_order_id: Optional[str]) -> str:
raw = str(client_order_id or "").strip()
broker_id = str(self.broker_id or "").strip()
@@ -174,16 +195,34 @@ class BinanceSpotClient(BaseRestClient):
return f"{prefix}{raw[:suffix_budget]}"
def _signed_request(self, method: str, path: str, *, params: Dict[str, Any]) -> Dict[str, Any]:
p = dict(params or {})
p["timestamp"] = int(time.time() * 1000)
qs = urlencode(p, doseq=True)
p["signature"] = self._sign(qs)
code, data, text = self._request(method, path, params=p, headers=self._signed_headers())
if code >= 400:
raise LiveTradingError(f"BinanceSpot HTTP {code}: {text[:500]}")
if isinstance(data, dict) and data.get("code") and int(data.get("code")) < 0:
raise LiveTradingError(f"BinanceSpot error: {data}")
return data if isinstance(data, dict) else {"raw": data}
self._ensure_server_time()
last_err: Optional[LiveTradingError] = None
for attempt in range(2):
p = dict(params or {})
p["timestamp"] = int(time.time() * 1000) + int(self._time_offset_ms)
if "recvWindow" not in p:
p["recvWindow"] = 10000
qs = urlencode(p, doseq=True)
p["signature"] = self._sign(qs)
code, data, text = self._request(method, path, params=p, headers=self._signed_headers())
if code >= 400:
err = LiveTradingError(f"BinanceSpot HTTP {code}: {text[:500]}")
if attempt == 0 and ("-1021" in text or "1021" in text):
self._ensure_server_time(force=True)
last_err = err
continue
raise err
if isinstance(data, dict) and data.get("code") and int(data.get("code")) < 0:
err = LiveTradingError(f"BinanceSpot error: {data}")
if attempt == 0 and int(data.get("code") or 0) == -1021:
self._ensure_server_time(force=True)
last_err = err
continue
raise err
return data if isinstance(data, dict) else {"raw": data}
if last_err:
raise last_err
raise LiveTradingError("BinanceSpot signed request failed")
def ping(self) -> bool:
"""