8563e4ea53
Signed-off-by: Dinger <quantdinger@gmail.com>
154 lines
5.1 KiB
Python
154 lines
5.1 KiB
Python
"""
|
|
Base REST client helpers for direct exchange connections.
|
|
|
|
Notes:
|
|
- Keep this minimal and dependency-light (requests only).
|
|
- All secrets must be excluded from logs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import time
|
|
from dataclasses import dataclass
|
|
from typing import Any, Dict, Optional, Tuple, Union
|
|
|
|
import requests
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Cached SSL verify setting for all live-trading REST calls (requests + SOCKS proxy).
|
|
_requests_verify_value: Optional[Union[bool, str]] = None
|
|
_ssl_verify_disabled_logged = False
|
|
|
|
# OS CA bundles (Docker / slim images: install ``ca-certificates``; corporate roots often added here too).
|
|
_SYSTEM_CA_BUNDLE_CANDIDATES: Tuple[str, ...] = (
|
|
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu
|
|
"/etc/ssl/cert.pem", # Alpine, some slim images
|
|
"/etc/pki/tls/certs/ca-bundle.crt", # RHEL/Fedora
|
|
)
|
|
|
|
|
|
def _get_requests_verify() -> Union[bool, str]:
|
|
"""
|
|
Resolve ``verify`` for ``requests`` when calling exchanges through proxies (e.g. PROXY_URL=socks5h://...).
|
|
|
|
- LIVE_TRADING_SSL_VERIFY=0|false|no|off: disable verification (insecure; mitm risk).
|
|
- LIVE_TRADING_CA_BUNDLE / REQUESTS_CA_BUNDLE / SSL_CERT_FILE / CURL_CA_BUNDLE: path to a PEM CA bundle
|
|
(needed for corporate TLS inspection or custom roots).
|
|
- Else a non-empty OS CA file if present (helps Gate/HTX/hbdm etc. in minimal images).
|
|
- Otherwise certifi's bundle when available.
|
|
"""
|
|
global _requests_verify_value, _ssl_verify_disabled_logged
|
|
if _requests_verify_value is not None:
|
|
return _requests_verify_value
|
|
|
|
flag = (os.environ.get("LIVE_TRADING_SSL_VERIFY") or "").strip().lower()
|
|
if flag in ("0", "false", "no", "off"):
|
|
if not _ssl_verify_disabled_logged:
|
|
logger.warning(
|
|
"LIVE_TRADING_SSL_VERIFY is disabled: HTTPS certificate verification is OFF for live trading "
|
|
"requests (MITM risk). Fix CA trust or set LIVE_TRADING_CA_BUNDLE instead for production."
|
|
)
|
|
_ssl_verify_disabled_logged = True
|
|
_requests_verify_value = False
|
|
return _requests_verify_value
|
|
|
|
for key in ("LIVE_TRADING_CA_BUNDLE", "REQUESTS_CA_BUNDLE", "SSL_CERT_FILE", "CURL_CA_BUNDLE"):
|
|
path = (os.environ.get(key) or "").strip()
|
|
if path and os.path.isfile(path):
|
|
_requests_verify_value = path
|
|
return _requests_verify_value
|
|
|
|
for path in _SYSTEM_CA_BUNDLE_CANDIDATES:
|
|
try:
|
|
if path and os.path.isfile(path) and os.path.getsize(path) >= 256:
|
|
_requests_verify_value = path
|
|
return _requests_verify_value
|
|
except OSError:
|
|
continue
|
|
|
|
try:
|
|
import certifi
|
|
|
|
_requests_verify_value = certifi.where()
|
|
except ImportError:
|
|
_requests_verify_value = True
|
|
return _requests_verify_value
|
|
|
|
|
|
@dataclass
|
|
class LiveOrderResult:
|
|
exchange_id: str
|
|
exchange_order_id: str
|
|
filled: float
|
|
avg_price: float
|
|
raw: Dict[str, Any]
|
|
|
|
|
|
class LiveTradingError(Exception):
|
|
pass
|
|
|
|
|
|
class BaseRestClient:
|
|
def __init__(self, base_url: str, timeout_sec: float = 15.0):
|
|
self.base_url = (base_url or "").rstrip("/")
|
|
self.timeout_sec = float(timeout_sec)
|
|
|
|
def _url(self, path: str) -> str:
|
|
p = str(path or "")
|
|
if not p.startswith("/"):
|
|
p = "/" + p
|
|
return f"{self.base_url}{p}"
|
|
|
|
def _request(
|
|
self,
|
|
method: str,
|
|
path: str,
|
|
*,
|
|
params: Optional[Dict[str, Any]] = None,
|
|
json_body: Optional[Dict[str, Any]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
data: Optional[Any] = None,
|
|
) -> Tuple[int, Dict[str, Any], str]:
|
|
url = self._url(path)
|
|
try:
|
|
resp = requests.request(
|
|
method=str(method or "GET").upper(),
|
|
url=url,
|
|
params=params or None,
|
|
json=json_body if json_body is not None else None,
|
|
data=data,
|
|
headers=headers or None,
|
|
timeout=self.timeout_sec,
|
|
verify=_get_requests_verify(),
|
|
)
|
|
except requests.exceptions.SSLError as e:
|
|
logger.warning(
|
|
"Exchange HTTPS TLS verify failed (%s). Same setting applies to all REST exchanges (Gate, HTX/hbdm, etc.). "
|
|
"Behind PROXY_URL/SOCKS or TLS inspection: set LIVE_TRADING_CA_BUNDLE to a PEM bundle (or REQUESTS_CA_BUNDLE), "
|
|
"ensure ca-certificates in the image, or dev-only LIVE_TRADING_SSL_VERIFY=false. %s",
|
|
url,
|
|
e,
|
|
)
|
|
raise
|
|
text = resp.text or ""
|
|
parsed: Dict[str, Any] = {}
|
|
try:
|
|
parsed = resp.json() if text else {}
|
|
except Exception:
|
|
parsed = {"raw_text": text[:2000]}
|
|
return int(resp.status_code), parsed, text
|
|
|
|
@staticmethod
|
|
def _now_ms() -> int:
|
|
return int(time.time() * 1000)
|
|
|
|
@staticmethod
|
|
def _json_dumps(obj: Any) -> str:
|
|
return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
|
|
|
|
|