From e6af97c596761e42dc0b3097f6ae977c732736a1 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 20 May 2024 11:09:30 -0400 Subject: [PATCH] Port `kucoin` backend to `httpx` --- piker/brokers/kucoin.py | 75 ++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/piker/brokers/kucoin.py b/piker/brokers/kucoin.py index 28524a22..8bb36f33 100755 --- a/piker/brokers/kucoin.py +++ b/piker/brokers/kucoin.py @@ -16,10 +16,9 @@ # along with this program. If not, see . ''' -Kucoin broker backend +Kucoin cex API backend. ''' - from contextlib import ( asynccontextmanager as acm, aclosing, @@ -42,7 +41,7 @@ import wsproto from uuid import uuid4 from trio_typing import TaskStatus -import asks +import httpx from bidict import bidict import numpy as np import pendulum @@ -212,8 +211,12 @@ def get_config() -> BrokerConfig | None: class Client: - def __init__(self) -> None: - self._config: BrokerConfig | None = get_config() + def __init__( + self, + httpx_client: httpx.AsyncClient, + ) -> None: + self._http: httpx.AsyncClient = httpx_client + self._config: BrokerConfig|None = get_config() self._pairs: dict[str, KucoinMktPair] = {} self._fqmes2mktids: bidict[str, str] = bidict() self._bars: list[list[float]] = [] @@ -227,18 +230,24 @@ class Client: ) -> dict[str, str | bytes]: ''' - Generate authenticated request headers + Generate authenticated request headers: + https://docs.kucoin.com/#authentication + https://www.kucoin.com/docs/basic-info/connection-method/authentication/creating-a-request + https://www.kucoin.com/docs/basic-info/connection-method/authentication/signing-a-message ''' - if not self._config: raise ValueError( - 'No config found when trying to send authenticated request') + 'No config found when trying to send authenticated request' + ) str_to_sign = ( str(int(time.time() * 1000)) - + action + f'/api/{api}/{endpoint.lstrip("/")}' + + + action + + + f'/api/{api}/{endpoint.lstrip("/")}' ) signature = base64.b64encode( @@ -249,6 +258,7 @@ class Client: ).digest() ) + # TODO: can we cache this between calls? passphrase = base64.b64encode( hmac.new( self._config.key_secret.encode('utf-8'), @@ -270,8 +280,10 @@ class Client: self, action: Literal['POST', 'GET'], endpoint: str, + api: str = 'v2', headers: dict = {}, + ) -> Any: ''' Generic request wrapper for Kucoin API @@ -284,13 +296,17 @@ class Client: api, ) - api_url = f'https://api.kucoin.com/api/{api}/{endpoint}' - - res = await asks.request(action, api_url, headers=headers) - - json = res.json() - if 'data' in json: - return json['data'] + req_meth: Callable = getattr( + self._http, + action.lower(), + ) + res = await req_meth( + url=f'/{api}/{endpoint}', + headers=headers, + ) + json: dict = res.json() + if data := json.get('data'): + return data else: log.error( f'Error making request to {api_url} ->\n' @@ -311,7 +327,7 @@ class Client: ''' token_type = 'private' if private else 'public' try: - data: dict[str, Any] | None = await self._request( + data: dict[str, Any]|None = await self._request( 'POST', endpoint=f'bullet-{token_type}', api='v1' @@ -349,8 +365,8 @@ class Client: currencies: dict[str, Currency] = {} entries: list[dict] = await self._request( 'GET', - api='v1', endpoint='currencies', + api='v1', ) for entry in entries: curr = Currency(**entry).copy() @@ -366,7 +382,10 @@ class Client: dict[str, KucoinMktPair], bidict[str, KucoinMktPair], ]: - entries = await self._request('GET', 'symbols') + entries = await self._request( + 'GET', + endpoint='symbols', + ) log.info(f' {len(entries)} Kucoin market pairs fetched') pairs: dict[str, KucoinMktPair] = {} @@ -567,13 +586,21 @@ def fqme_to_kucoin_sym( @acm async def get_client() -> AsyncGenerator[Client, None]: - client = Client() + ''' + Load an API `Client` preconfigured from user settings - async with trio.open_nursery() as n: - n.start_soon(client.get_mkt_pairs) - await client.get_currencies() + ''' + async with ( + httpx.AsyncClient( + base_url=f'https://api.kucoin.com/api', + ) as trio_client, + ): + client = Client(httpx_client=trio_client) + async with trio.open_nursery() as tn: + tn.start_soon(client.get_mkt_pairs) + await client.get_currencies() - yield client + yield client @tractor.context