"""TBX Markets REST client. Running this file only reads balances and orders.""" import hashlib import hmac import json import os import threading import time import urllib.error import urllib.parse import urllib.request class TBXError(Exception): def __init__(self, status, payload): self.status, self.payload = status, payload super().__init__(f"TBX HTTP {status}: {payload}") class TBXClient: base_url = "https://tbxmarkets.com/api/v2/peatio" def __init__(self, key=None, secret=None): self.key = key or os.environ["TBX_API_KEY"] self.secret = secret or os.environ["TBX_API_SECRET"] self._nonce = 0 self._lock = threading.Lock() def request(self, method, path, params=None, private=True): if not path.startswith("/") or "://" in path or "?" in path: raise ValueError("Use a relative API path and the params argument") method = method.upper() headers = {"Accept": "application/json", "Content-Type": "application/json"} if private: with self._lock: self._nonce = max(time.time_ns() // 1_000_000, self._nonce + 1) nonce = str(self._nonce) signature = hmac.new(self.secret.encode(), (nonce + self.key).encode(), hashlib.sha256).hexdigest() headers.update({"X-Auth-Apikey": self.key, "X-Auth-Nonce": nonce, "X-Auth-Signature": signature}) url, body = self.base_url + path, None if method == "GET" and params: url += "?" + urllib.parse.urlencode(params, doseq=True) elif params is not None: body = json.dumps(params, separators=(",", ":")).encode() request = urllib.request.Request(url, data=body, headers=headers, method=method) # Writes are never retried automatically: a timeout can follow acceptance. try: with urllib.request.urlopen(request, timeout=15) as response: raw = response.read() return json.loads(raw) if raw else None except urllib.error.HTTPError as error: raw = error.read().decode("utf-8", errors="replace") try: payload = json.loads(raw) except ValueError: payload = {"errors": ["non_json_response"]} raise TBXError(error.code, payload) from None if __name__ == "__main__": client = TBXClient() print(json.dumps(client.request("GET", "/account/balances"), ensure_ascii=False, indent=2)) print(json.dumps(client.request("GET", "/market/orders", {"market": "usdtrub", "state": "wait"}), ensure_ascii=False, indent=2))