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