Add exponential retry case for history client
parent
ac31bca181
commit
7bdebd47d1
|
@ -19,6 +19,7 @@ Kucoin broker backend
|
|||
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
from typing import Any, Callable, Optional, Literal, AsyncGenerator
|
||||
from contextlib import asynccontextmanager as acm
|
||||
from datetime import datetime
|
||||
|
@ -62,17 +63,6 @@ _ohlc_dtype = [
|
|||
]
|
||||
|
||||
|
||||
def get_config() -> dict[str, dict]:
|
||||
conf, path = config.load()
|
||||
|
||||
section = conf.get("kucoin")
|
||||
|
||||
if section is None:
|
||||
log.warning("No config section found for kucoin in config")
|
||||
|
||||
return section
|
||||
|
||||
|
||||
class KucoinMktPair(Struct, frozen=True):
|
||||
'''
|
||||
Kucoin's pair format
|
||||
|
@ -137,6 +127,12 @@ class KucoinTrade(Struct, frozen=True):
|
|||
time: float
|
||||
|
||||
|
||||
class BrokerConfig(Struct, frozen=True):
|
||||
key_id: str
|
||||
key_secret: str
|
||||
key_passphrase: str
|
||||
|
||||
|
||||
class KucoinTradeMsg(Struct, frozen=True):
|
||||
type: str
|
||||
topic: str
|
||||
|
@ -144,6 +140,18 @@ class KucoinTradeMsg(Struct, frozen=True):
|
|||
data: list[KucoinTrade]
|
||||
|
||||
|
||||
def get_config() -> BrokerConfig | None:
|
||||
conf, path = config.load()
|
||||
|
||||
section = conf.get("kucoin")
|
||||
|
||||
if section is None:
|
||||
log.warning("No config section found for kucoin in config")
|
||||
return None
|
||||
|
||||
return BrokerConfig(**section)
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self) -> None:
|
||||
self._pairs: dict[str, KucoinMktPair] = {}
|
||||
|
@ -153,25 +161,25 @@ class Client:
|
|||
self._key_passphrase: str
|
||||
self._authenticated: bool = False
|
||||
|
||||
config = get_config()
|
||||
config: BrokerConfig | None = get_config()
|
||||
|
||||
if (
|
||||
config
|
||||
and float("key_id" in config)
|
||||
and ("key_secret" in config)
|
||||
and ("key_passphrase" in config)
|
||||
and float(config.key_id)
|
||||
and config.key_secret
|
||||
and config.key_passphrase
|
||||
):
|
||||
self._authenticated = True
|
||||
self._key_id = config["key_id"]
|
||||
self._key_secret = config["key_secret"]
|
||||
self._key_passphrase = config["key_passphrase"]
|
||||
self._key_id = config.key_id
|
||||
self._key_secret = config.key_secret
|
||||
self._key_passphrase = config.key_passphrase
|
||||
|
||||
def _gen_auth_req_headers(
|
||||
self,
|
||||
action: Literal["POST", "GET"],
|
||||
endpoint: str,
|
||||
api_v: str = "v2",
|
||||
) -> dict[str, str]:
|
||||
) -> dict[str, str | bytes]:
|
||||
'''
|
||||
Generate authenticated request headers
|
||||
https://docs.kucoin.com/#authentication
|
||||
|
@ -212,7 +220,7 @@ class Client:
|
|||
endpoint: str,
|
||||
api_v: str = "v2",
|
||||
headers: dict = {},
|
||||
) -> dict[str, Any]:
|
||||
) -> Any:
|
||||
'''
|
||||
Generic request wrapper for Kucoin API
|
||||
|
||||
|
@ -221,12 +229,14 @@ class Client:
|
|||
headers = self._gen_auth_req_headers(action, endpoint, api_v)
|
||||
|
||||
api_url = f"https://api.kucoin.com/api/{api_v}{endpoint}"
|
||||
|
||||
res = await asks.request(action, api_url, headers=headers)
|
||||
|
||||
if "data" in res.json():
|
||||
return res.json()["data"]
|
||||
else:
|
||||
log.error(f'Error making request to {api_url} -> {res.json()["msg"]}')
|
||||
return res.json()["msg"]
|
||||
|
||||
async def _get_ws_token(
|
||||
self,
|
||||
|
@ -237,14 +247,18 @@ class Client:
|
|||
|
||||
'''
|
||||
token_type = "private" if private else "public"
|
||||
data = await self._request("POST", f"/bullet-{token_type}", "v1")
|
||||
data: dict[str, Any] | None = await self._request(
|
||||
"POST",
|
||||
f"/bullet-{token_type}",
|
||||
"v1"
|
||||
)
|
||||
|
||||
if "token" in data:
|
||||
ping_interval = data["instanceServers"][0]["pingInterval"]
|
||||
if data and "token" in data:
|
||||
ping_interval: int = data["instanceServers"][0]["pingInterval"]
|
||||
return data["token"], ping_interval
|
||||
else:
|
||||
elif data:
|
||||
log.error(
|
||||
f'Error making request for Kucoin ws token -> {res.json()["msg"]}'
|
||||
f'Error making request for Kucoin ws token -> {data.json()["msg"]}'
|
||||
)
|
||||
|
||||
async def _get_pairs(
|
||||
|
@ -297,8 +311,9 @@ class Client:
|
|||
# repack in dict form
|
||||
return {kucoin_sym_to_fqsn(item[0].name): item[0] for item in matches}
|
||||
|
||||
async def last_trades(self, sym: str) -> AccountResponse:
|
||||
async def last_trades(self, sym: str) -> list[AccountTrade]:
|
||||
trades = await self._request("GET", f"/accounts/ledgers?currency={sym}", "v1")
|
||||
trades = AccountResponse(**trades)
|
||||
return trades.items
|
||||
|
||||
async def _get_bars(
|
||||
|
@ -327,12 +342,19 @@ class Client:
|
|||
kucoin_sym = fqsn_to_kucoin_sym(fqsn, self._pairs)
|
||||
|
||||
url = f"/market/candles?type={type}&symbol={kucoin_sym}&startAt={start_dt}&endAt={end_dt}"
|
||||
bars = []
|
||||
for i in range(10):
|
||||
|
||||
bars = await self._request(
|
||||
res = await self._request(
|
||||
"GET",
|
||||
url,
|
||||
api_v="v1",
|
||||
)
|
||||
if not isinstance(res, list):
|
||||
await trio.sleep(i + (randint(0, 1000) / 1000))
|
||||
else:
|
||||
bars = res
|
||||
break
|
||||
|
||||
# Map to OHLC values to dict then to np array
|
||||
new_bars = []
|
||||
|
|
Loading…
Reference in New Issue