From 3f53ec7518d1be32281cbcdb7ece2be5beb3fd59 Mon Sep 17 00:00:00 2001 From: "Benjamin T. Schwertfeger" Date: Fri, 8 Mar 2024 21:44:34 +0100 Subject: [PATCH] Resolve "The POST and query parameters of KrakenSpotBaseAPI and KrakenFuturesBaseAPI are not proper encoded in some case" (#189) --- .pre-commit-config.yaml | 37 +++++++----- MANIFEST.in | 3 + README.md | 2 +- doc/src/issues.rst | 2 + examples/futures_trading_bot_template.py | 2 +- examples/market_client_example.ipynb | 6 +- examples/spot_trading_bot_template_v1.py | 1 + examples/spot_trading_bot_template_v2.py | 2 +- kraken/base_api/__init__.py | 74 ++++++++++-------------- kraken/futures/__init__.py | 2 +- kraken/futures/trade.py | 2 +- kraken/futures/user.py | 2 +- kraken/futures/websocket/__init__.py | 2 +- kraken/futures/ws_client.py | 8 +-- kraken/spot/__init__.py | 8 +-- kraken/spot/funding.py | 20 +++---- kraken/spot/market.py | 16 ++--- kraken/spot/orderbook_v1.py | 2 +- kraken/spot/orderbook_v2.py | 2 +- kraken/spot/staking.py | 10 ++-- kraken/spot/trade.py | 26 +++++---- kraken/spot/user.py | 38 ++++++------ kraken/spot/websocket/__init__.py | 18 +++--- kraken/spot/websocket/connectors.py | 14 +++-- kraken/spot/websocket_v1.py | 10 ++-- pyproject.toml | 39 ++++++++----- tests/futures/conftest.py | 46 +++++++++++---- tests/futures/test_futures_funding.py | 3 +- tests/futures/test_futures_trade.py | 59 +++++++++++-------- tests/futures/test_futures_user.py | 4 +- tests/spot/helper.py | 2 +- tests/spot/test_spot_base_api.py | 13 +++-- tests/spot/test_spot_market.py | 2 +- tests/spot/test_spot_orderbook_v1.py | 2 +- tests/spot/test_spot_orderbook_v2.py | 2 +- tests/spot/test_spot_websocket_v2.py | 16 ++--- 36 files changed, 277 insertions(+), 220 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72f63813..60819ebc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.2.2 hooks: - id: ruff args: @@ -13,7 +13,7 @@ repos: - --fix - --exit-non-zero-on-fix - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 args: @@ -21,7 +21,7 @@ repos: - --show-source - --statistics - repo: https://github.com/pycqa/pylint - rev: v3.0.2 + rev: v3.0.3 hooks: - id: pylint name: pylint @@ -31,16 +31,23 @@ repos: - --rcfile=pyproject.toml - -d=R0801 # ignore duplicate code - -j=4 - # - repo: https://github.com/pre-commit/mirrors-mypy # FIXME - # rev: v1.8.0 - # hooks: - # - id: mypy - # name: mypy - # additional_dependencies: ["types-requests"] - # pass_filenames: false - # args: - # - --config-file=pyproject.toml - # - kraken + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy + name: mypy + additional_dependencies: ["types-requests"] + pass_filenames: false + args: + - --config-file=pyproject.toml + - --install-types + - --non-interactive + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + additional_dependencies: + - tomli - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: @@ -75,7 +82,7 @@ repos: - id: rst-inline-touching-normal - id: text-unicode-replacement-char - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 24.2.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-prettier @@ -83,7 +90,7 @@ repos: hooks: - id: prettier - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: diff --git a/MANIFEST.in b/MANIFEST.in index 73803ea6..a51b874d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,3 +10,6 @@ graft kraken prune doc prune examples prune tests +prune venv +prune .github +prune .cache diff --git a/README.md b/README.md index accb58c2..0e14e85b 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ The Kraken Futures API documentation can be found here: ## Futures REST API As the Spot API, Kraken also offers a REST API for Futures. Examples on how to -use the python-kraken-sdk fot Futures are shown in +use the python-kraken-sdk for Futures are shown in `examples/futures_examples.py` and listed in a shorter ways below. ```python diff --git a/doc/src/issues.rst b/doc/src/issues.rst index 169c03b5..49e8989d 100644 --- a/doc/src/issues.rst +++ b/doc/src/issues.rst @@ -10,6 +10,8 @@ Issues listed here: `python-kraken-sdk/issues`_ Futures Trading --------------- +- The Kraken API returns 500 - INTERNAL_SERVER_ERROR for some endpoints if + ``order_id`` or ``orderId``, ``cliOrdId`` seems to work in all cases. - Kraken's API doesn't seem to know the ``trailing_stop`` order type and raises an error if this type is part of an order. This order type is documented here https://docs.futures.kraken.com/#http-api-trading-v3-api-order-management-send-order diff --git a/examples/futures_trading_bot_template.py b/examples/futures_trading_bot_template.py index c69f034d..9a1df10b 100644 --- a/examples/futures_trading_bot_template.py +++ b/examples/futures_trading_bot_template.py @@ -135,7 +135,7 @@ async def __main(self: ManagedBot) -> None: Instantiates the trading strategy/algorithm and subscribes to the desired websocket feeds. Run the loop while no exception occur. - Thi variable `exception_occur` which is an attribute of the + The variable `exception_occur` which is an attribute of the KrakenFuturesWSClient can be set individually but is also being set to `True` if the websocket connection has some fatal error. This is used to exit the asyncio loop - but you can also apply your own reconnect rules. diff --git a/examples/market_client_example.ipynb b/examples/market_client_example.ipynb index 1187a036..e61146ea 100644 --- a/examples/market_client_example.ipynb +++ b/examples/market_client_example.ipynb @@ -45,7 +45,7 @@ "metadata": {}, "outputs": [], "source": [ - "market = Market() " + "market = Market()" ] }, { @@ -280,7 +280,7 @@ "# compute ema\n", "df['ema21'] = df['close'].ewm(span=21, adjust=False, min_periods=21).mean()\n", "df['ema50'] = df['close'].ewm(span=50, adjust=False, min_periods=50).mean()\n", - "df['ema200'] = df['close'].ewm(span=200, adjust=False, min_periods=200).mean() " + "df['ema200'] = df['close'].ewm(span=200, adjust=False, min_periods=200).mean()" ] }, { @@ -619,7 +619,7 @@ " df.at[row, 's2'] = np.NaN\n", " df.at[row, 'r2'] = np.NaN\n", "\n", - "# isnt that a beauty?:\n", + "# isn't that a beauty?:\n", "df" ] }, diff --git a/examples/spot_trading_bot_template_v1.py b/examples/spot_trading_bot_template_v1.py index edb093c5..34dd346d 100644 --- a/examples/spot_trading_bot_template_v1.py +++ b/examples/spot_trading_bot_template_v1.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (C) 2023 Benjamin Thomas Schwertfeger # GitHub: https://github.com/btschwertfeger +# ruff: noqa: RUF027 """ Module that provides a template to build a Spot trading algorithm using the diff --git a/examples/spot_trading_bot_template_v2.py b/examples/spot_trading_bot_template_v2.py index 42a40c1a..3fa34f66 100644 --- a/examples/spot_trading_bot_template_v2.py +++ b/examples/spot_trading_bot_template_v2.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (C) 2023 Benjamin Thomas Schwertfeger # GitHub: https://github.com/btschwertfeger - +# ruff: noqa: RUF027 """ Module that provides a template to build a Spot trading algorithm using the python-kraken-sdk and Kraken Spot websocket API v2. diff --git a/kraken/base_api/__init__.py b/kraken/base_api/__init__.py index 040bfd3e..d5159b07 100644 --- a/kraken/base_api/__init__.py +++ b/kraken/base_api/__init__.py @@ -12,10 +12,9 @@ import hmac import json import time -import urllib.parse from functools import wraps -from typing import Any, Callable, Optional, Type, TypeVar -from urllib.parse import urljoin +from typing import Any, Callable, Final, Optional, Type, TypeVar +from urllib.parse import urlencode, urljoin from uuid import uuid1 import requests @@ -183,7 +182,6 @@ class KrakenSpotBaseAPI: """ URL: str = "https://api.kraken.com" - API_V: str = "/0" TIMEOUT: int = 10 def __init__( @@ -198,9 +196,7 @@ def __init__( if sandbox: raise ValueError("Sandbox not available for Kraken Spot trading.") if url: - self.url = url - else: - self.url = urljoin(self.URL, self.API_V) + self.URL = url self.__key: str = key self.__secret: str = secret @@ -254,6 +250,9 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments :rtype: dict[str, Any] | list[str] | list[dict[str, Any]] | requests.Response """ + METHOD: str = method.upper() + URL: str = urljoin(self.URL, uri) + if not defined(params): params = {} if defined(extra_params): @@ -263,12 +262,11 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments else extra_params ) - METHOD: str = method.upper() - if METHOD in {"GET", "DELETE"} and params: - data_json: str = "&".join( - [f"{key}={params[key]}" for key in sorted(params)], - ) - uri += f"?{data_json}".replace(" ", "%20") + query_params: str = ( + urlencode(params, doseq=True) + if METHOD in {"GET", "DELETE"} and params + else "" + ) TIMEOUT: int = self.TIMEOUT if timeout != 10 else timeout HEADERS: dict = {} @@ -286,26 +284,25 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments sign_data = json.dumps(params) else: content_type = "application/x-www-form-urlencoded; charset=utf-8" - sign_data = urllib.parse.urlencode(params) + sign_data = urlencode(params, doseq=True) HEADERS.update( { "Content-Type": content_type, "API-Key": self.__key, "API-Sign": self._get_kraken_signature( - url_path=f"{self.API_V}{uri}", + url_path=f"{uri}{query_params}", data=sign_data, nonce=params["nonce"], ), }, ) - URL: str = f"{self.url}{uri}" if METHOD in {"GET", "DELETE"}: return self.__check_response_data( response=self.__session.request( method=METHOD, - url=URL, + url=f"{URL}?{query_params}", headers=HEADERS, timeout=TIMEOUT, ), @@ -506,15 +503,14 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments This is used for example when requesting an export of the trade history as .zip archive. :type return_raw: bool, optional - :raise kraken.exceptions.KrakenException.*: If the response contains + :raise kraken.exceptions.*: If the response contains errors :return: The response :rtype: dict[str, Any] | list[dict[str, Any]] | list[str] | requests.Response """ - METHOD: str = method.upper() + METHOD: Final[str] = method.upper() + URL: Final[str] = urljoin(self.url, uri) - post_string: str = "" - listed_params: list[str] if defined(extra_params): extra_params = ( json.loads(extra_params) @@ -524,22 +520,16 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments else: extra_params = {} - if defined(post_params): - post_params |= extra_params - listed_params = [f"{key}={post_params[key]}" for key in sorted(post_params)] - post_string = "&".join(listed_params) - else: + if post_params is None: post_params = {} post_params |= extra_params - query_string: str = "" - if query_params is not None: - listed_params = [ - f"{key}={query_params[key]}" for key in sorted(query_params) - ] - query_string = "&".join(listed_params).replace(" ", "%20") - else: - query_params = {} + encoded_payload: Final[str] = urlencode(post_params, doseq=True) + + # post_string: Final[str] = json.dumps(post_params) if post_params else "" + query_string = ( + "" if query_params is None else urlencode(query_params, doseq=True) + ) TIMEOUT: int = self.TIMEOUT if timeout == 10 else timeout HEADERS: dict = {} @@ -554,19 +544,17 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments "APIKey": self.__key, "Authent": self._get_kraken_futures_signature( uri, - query_string + post_string, + query_string + encoded_payload, nonce, ), }, ) - if METHOD in {"GET", "DELETE"}: return self.__check_response_data( response=self.__session.request( method=METHOD, - url=f"{self.url}{uri}" - if not query_string - else f"{self.url}{uri}?{query_string}", + url=URL, + params=query_string, headers=HEADERS, timeout=TIMEOUT, ), @@ -577,8 +565,8 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments return self.__check_response_data( response=self.__session.request( method=METHOD, - url=f"{self.url}{uri}", - params=str.encode(post_string), + url=URL, + params=encoded_payload, headers=HEADERS, timeout=TIMEOUT, ), @@ -588,8 +576,8 @@ def _request( # noqa: PLR0913 # pylint: disable=too-many-arguments return self.__check_response_data( response=self.__session.request( method=METHOD, - url=f"{self.url}{uri}?{post_string}", - data=str.encode(post_string), + url=URL, + data=encoded_payload, headers=HEADERS, timeout=TIMEOUT, ), diff --git a/kraken/futures/__init__.py b/kraken/futures/__init__.py index 4e5a7071..385ec3f6 100644 --- a/kraken/futures/__init__.py +++ b/kraken/futures/__init__.py @@ -12,4 +12,4 @@ from kraken.futures.user import User from kraken.futures.ws_client import KrakenFuturesWSClient -__all__ = ["Funding", "Market", "Trade", "User", "KrakenFuturesWSClient"] +__all__ = ["Funding", "KrakenFuturesWSClient", "Market", "Trade", "User"] diff --git a/kraken/futures/trade.py b/kraken/futures/trade.py index 449171e0..2018af3b 100644 --- a/kraken/futures/trade.py +++ b/kraken/futures/trade.py @@ -475,7 +475,7 @@ def get_orders_status( Requires at least the ``General API - Read Only`` permission in the API key settings. - - https://docs.futures.kraken.com/#http-api-trading-v3-api-order-management-get-the-current-status-for-specific-orders + - https://docs.futures.kraken.com/#http-api-trading-v3-api-order-management-get-specific-orders-39-status :param orderIds: The order ids to cancel :type orderIds: str | list[str], optional diff --git a/kraken/futures/user.py b/kraken/futures/user.py index 605f827b..f4f3b9dc 100644 --- a/kraken/futures/user.py +++ b/kraken/futures/user.py @@ -605,7 +605,7 @@ def get_order_events( extra_params: Optional[dict] = None, ) -> dict: """ - Retriev information about the user-specific order events including + Retrieve information about the user-specific order events including opened, closed, filled, etc. The returned ``continuation_token`` can be used to request more data. diff --git a/kraken/futures/websocket/__init__.py b/kraken/futures/websocket/__init__.py index 1cc860ee..48a91614 100644 --- a/kraken/futures/websocket/__init__.py +++ b/kraken/futures/websocket/__init__.py @@ -75,7 +75,7 @@ async def __run(self: ConnectFuturesWebsocket, event: asyncio.Event) -> None: self.__new_challenge = None self.__last_challenge = None - async with websockets.connect( # pylint: disable=no-member + async with websockets.connect( # pylint: disable=no-member # noqa: PLR1702 f"wss://{self.__ws_endpoint}", ping_interval=30, ) as socket: diff --git a/kraken/futures/ws_client.py b/kraken/futures/ws_client.py index dc3657b4..0dab6e2f 100644 --- a/kraken/futures/ws_client.py +++ b/kraken/futures/ws_client.py @@ -128,11 +128,9 @@ def __init__( self.__callback: Any = callback self._conn: ConnectFuturesWebsocket = ConnectFuturesWebsocket( client=self, - endpoint=url - if url - else self.DEMO_ENV_URL - if sandbox - else self.PROD_ENV_URL, + endpoint=( + url if url else self.DEMO_ENV_URL if sandbox else self.PROD_ENV_URL + ), callback=self.on_message, ) diff --git a/kraken/spot/__init__.py b/kraken/spot/__init__.py index 13d68033..20fad592 100644 --- a/kraken/spot/__init__.py +++ b/kraken/spot/__init__.py @@ -18,12 +18,12 @@ __all__ = [ "Funding", + "KrakenSpotWSClientV1", + "KrakenSpotWSClientV2", "Market", + "OrderbookClientV1", + "OrderbookClientV2", "Staking", "Trade", "User", - "OrderbookClientV1", - "OrderbookClientV2", - "KrakenSpotWSClientV1", - "KrakenSpotWSClientV2", ] diff --git a/kraken/spot/funding.py b/kraken/spot/funding.py index c397a853..87fa1e13 100644 --- a/kraken/spot/funding.py +++ b/kraken/spot/funding.py @@ -97,7 +97,7 @@ def get_deposit_methods( """ return self._request( method="POST", - uri="/private/DepositMethods", + uri="/0/private/DepositMethods", params={"asset": asset}, # type: ignore[return-value] extra_params=extra_params, ) @@ -150,7 +150,7 @@ def get_deposit_address( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/DepositAddresses", + uri="/0/private/DepositAddresses", params={"asset": asset, "method": method, "new": new}, extra_params=extra_params, ) @@ -243,7 +243,7 @@ def get_recent_deposits_status( return self._request( # type: ignore[return-value] method="POST", - uri="/private/DepositStatus", + uri="/0/private/DepositStatus", params=params, extra_params=extra_params, ) @@ -296,7 +296,7 @@ def get_withdrawal_info( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/WithdrawInfo", + uri="/0/private/WithdrawInfo", params={"asset": asset, "key": str(key), "amount": str(amount)}, extra_params=extra_params, ) @@ -349,7 +349,7 @@ def withdraw_funds( return self._request( # type: ignore[return-value] method="POST", - uri="/private/Withdraw", + uri="/0/private/Withdraw", params=params, extra_params=extra_params, ) @@ -419,7 +419,7 @@ def get_recent_withdraw_status( params["cursor"] = cursor return self._request( # type: ignore[return-value] method="POST", - uri="/private/WithdrawStatus", + uri="/0/private/WithdrawStatus", params=params, extra_params=extra_params, ) @@ -457,7 +457,7 @@ def cancel_withdraw( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/WithdrawCancel", + uri="/0/private/WithdrawCancel", params={"asset": asset, "refid": str(refid)}, extra_params=extra_params, ) @@ -505,7 +505,7 @@ def wallet_transfer( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/WalletTransfer", + uri="/0/private/WalletTransfer", params={"asset": asset, "from": from_, "to": to_, "amount": str(amount)}, extra_params=extra_params, ) @@ -542,7 +542,7 @@ def withdraw_methods( params["network"] = network return self._request( # type: ignore[return-value] method="POST", - uri="/private/WithdrawMethods", + uri="/0/private/WithdrawMethods", params=params, extra_params=extra_params, ) @@ -589,7 +589,7 @@ def withdraw_addresses( params["verified"] = verified return self._request( # type: ignore[return-value] method="POST", - uri="/private/WithdrawMethods", + uri="/0/private/WithdrawMethods", params=params, extra_params=extra_params, ) diff --git a/kraken/spot/market.py b/kraken/spot/market.py index f2cf213f..54047666 100644 --- a/kraken/spot/market.py +++ b/kraken/spot/market.py @@ -129,7 +129,7 @@ def get_assets( params["aclass"] = aclass return self._request( # type: ignore[return-value] method="GET", - uri="/public/Assets", + uri="/0/public/Assets", params=params, auth=False, extra_params=extra_params, @@ -213,7 +213,7 @@ def get_asset_pairs( params["info"] = info return self._request( # type: ignore[return-value] method="GET", - uri="/public/AssetPairs", + uri="/0/public/AssetPairs", params=params, auth=False, extra_params=extra_params, @@ -263,7 +263,7 @@ def get_ticker( params["pair"] = pair return self._request( # type: ignore[return-value] method="GET", - uri="/public/Ticker", + uri="/0/public/Ticker", params=params, auth=False, extra_params=extra_params, @@ -318,7 +318,7 @@ def get_ohlc( params["since"] = since return self._request( # type: ignore[return-value] method="GET", - uri="/public/OHLC", + uri="/0/public/OHLC", params=params, auth=False, extra_params=extra_params, @@ -366,7 +366,7 @@ def get_order_book( """ return self._request( # type: ignore[return-value] method="GET", - uri="/public/Depth", + uri="/0/public/Depth", params={"pair": pair, "count": count}, auth=False, extra_params=extra_params, @@ -417,7 +417,7 @@ def get_recent_trades( params["count"] = count return self._request( # type: ignore[return-value] method="GET", - uri="/public/Trades", + uri="/0/public/Trades", params=params, auth=False, extra_params=extra_params, @@ -462,7 +462,7 @@ def get_recent_spreads( params["since"] = since return self._request( # type: ignore[return-value] method="GET", - uri="/public/Spread", + uri="/0/public/Spread", params=params, auth=False, extra_params=extra_params, @@ -491,7 +491,7 @@ def get_system_status( """ return self._request( # type: ignore[return-value] method="GET", - uri="/public/SystemStatus", + uri="/0/public/SystemStatus", auth=False, extra_params=extra_params, ) diff --git a/kraken/spot/orderbook_v1.py b/kraken/spot/orderbook_v1.py index 695cb73a..44f968ca 100644 --- a/kraken/spot/orderbook_v1.py +++ b/kraken/spot/orderbook_v1.py @@ -259,7 +259,7 @@ def exception_occur(self: OrderbookClientV1) -> bool: """ Can be used to determine if any critical error occurred within the websocket connection. If so, the function will return ``True`` and the - client instance is most likely not useable anymore. So this is the + client instance is most likely not usable anymore. So this is the switch lets the user know, when to delete the current one and create a new one. diff --git a/kraken/spot/orderbook_v2.py b/kraken/spot/orderbook_v2.py index c347fe3c..d57ccb2a 100644 --- a/kraken/spot/orderbook_v2.py +++ b/kraken/spot/orderbook_v2.py @@ -280,7 +280,7 @@ def exception_occur(self: OrderbookClientV2) -> bool: """ Can be used to determine if any critical error occurred within the websocket connection. If so, the function will return ``True`` and the - client instance is most likely not useable anymore. So this is the + client instance is most likely not usable anymore. So this is the switch lets the user know, when to delete the current one and create a new one. diff --git a/kraken/spot/staking.py b/kraken/spot/staking.py index a07860a3..b6f91b6a 100644 --- a/kraken/spot/staking.py +++ b/kraken/spot/staking.py @@ -102,7 +102,7 @@ def stake_asset( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/Stake", + uri="/0/private/Stake", params={"asset": asset, "amount": amount, "method": method}, auth=True, extra_params=extra_params, @@ -154,7 +154,7 @@ def unstake_asset( return self._request( # type: ignore[return-value] method="POST", - uri="/private/Unstake", + uri="/0/private/Unstake", params=params, auth=True, extra_params=extra_params, @@ -219,7 +219,7 @@ def list_stakeable_assets( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/Staking/Assets", + uri="/0/private/Staking/Assets", auth=True, extra_params=extra_params, ) @@ -262,7 +262,7 @@ def get_pending_staking_transactions( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/Staking/Pending", + uri="/0/private/Staking/Pending", auth=True, extra_params=extra_params, ) @@ -308,7 +308,7 @@ def list_staking_transactions( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/Staking/Transactions", + uri="/0/private/Staking/Transactions", auth=True, extra_params=extra_params, ) diff --git a/kraken/spot/trade.py b/kraken/spot/trade.py index 738b5d38..5cf0df74 100644 --- a/kraken/spot/trade.py +++ b/kraken/spot/trade.py @@ -115,7 +115,7 @@ def create_order( # pylint: disable=too-many-branches,too-many-arguments # noqa ``stop-loss-limit``, ``take-profit``, and ``take-profit-limit`` :type price: str | float, optional :param price2: The limit price for ``stop-loss-limit`` and - ``take-profit-limit`` orders The price2 can also be set to absolut + ``take-profit-limit`` orders The price2 can also be set to absolute or relative changes. * Prefixed using ``+`` or ``-`` defines the change in the quote asset @@ -272,7 +272,7 @@ def create_order( # pylint: disable=too-many-branches,too-many-arguments # noqa } } - ''' The price2 and close_price2 can also be set to absolut or + ''' The price2 and close_price2 can also be set to absolute or relative changes. * Prefixed using "+" or "-" defines the change in the quote asset @@ -297,9 +297,11 @@ def create_order( # pylint: disable=too-many-branches,too-many-arguments # noqa "ordertype": ordertype, "type": side, "pair": pair, - "volume": volume - if not truncate - else self.truncate(amount=volume, amount_type="volume", pair=pair), + "volume": ( + volume + if not truncate + else self.truncate(amount=volume, amount_type="volume", pair=pair) + ), "stp_type": stptype, "starttm": starttm, "validate": validate, @@ -356,7 +358,7 @@ def create_order( # pylint: disable=too-many-branches,too-many-arguments # noqa return self._request( # type: ignore[return-value] method="POST", - uri="/private/AddOrder", + uri="/0/private/AddOrder", params=params, extra_params=extra_params, ) @@ -436,7 +438,7 @@ def create_order_batch( params["deadline"] = deadline return self._request( # type: ignore[return-value] method="POST", - uri="/private/AddOrderBatch", + uri="/0/private/AddOrderBatch", params=params, do_json=True, extra_params=extra_params, @@ -543,7 +545,7 @@ def edit_order( # pylint: disable=too-many-arguments # noqa: PLR0913, PLR0917 params["deadline"] = deadline return self._request( # type: ignore[return-value] "POST", - uri="/private/EditOrder", + uri="/0/private/EditOrder", params=params, extra_params=extra_params, ) @@ -580,7 +582,7 @@ def cancel_order( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/CancelOrder", + uri="/0/private/CancelOrder", params={"txid": txid}, extra_params=extra_params, ) @@ -612,7 +614,7 @@ def cancel_all_orders( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/CancelAll", + uri="/0/private/CancelAll", extra_params=extra_params, ) @@ -649,7 +651,7 @@ def cancel_all_orders_after_x( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/CancelAllOrdersAfter", + uri="/0/private/CancelAllOrdersAfter", params={"timeout": timeout}, extra_params=extra_params, ) @@ -686,7 +688,7 @@ def cancel_order_batch( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/CancelOrderBatch", + uri="/0/private/CancelOrderBatch", params={"orders": orders}, do_json=True, extra_params=extra_params, diff --git a/kraken/spot/user.py b/kraken/spot/user.py index a588e900..120357c7 100644 --- a/kraken/spot/user.py +++ b/kraken/spot/user.py @@ -96,7 +96,7 @@ def get_account_balance( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/Balance", + uri="/0/private/Balance", extra_params=extra_params, ) @@ -142,7 +142,7 @@ def get_balances( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/BalanceEx", + uri="/0/private/BalanceEx", extra_params=extra_params, ) @@ -231,7 +231,7 @@ def get_trade_balance( params["asset"] = asset return self._request( # type: ignore[return-value] method="POST", - uri="/private/TradeBalance", + uri="/0/private/TradeBalance", params=params, extra_params=extra_params, ) @@ -305,7 +305,7 @@ def get_open_orders( params["userref"] = userref return self._request( # type: ignore[return-value] method="POST", - uri="/private/OpenOrders", + uri="/0/private/OpenOrders", params=params, extra_params=extra_params, ) @@ -400,7 +400,7 @@ def get_closed_orders( return self._request( # type: ignore[return-value] method="POST", - uri="/private/ClosedOrders", + uri="/0/private/ClosedOrders", params=params, extra_params=extra_params, ) @@ -513,7 +513,7 @@ def get_orders_info( params["userref"] = userref return self._request( # type: ignore[return-value] method="POST", - uri="/private/QueryOrders", + uri="/0/private/QueryOrders", params=params, extra_params=extra_params, ) @@ -596,7 +596,7 @@ def get_trades_history( params["ofs"] = ofs return self._request( # type: ignore[return-value] method="POST", - uri="/private/TradesHistory", + uri="/0/private/TradesHistory", params=params, extra_params=extra_params, ) @@ -653,7 +653,7 @@ def get_trades_info( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/QueryTrades", + uri="/0/private/QueryTrades", params={ "trades": trades, "txid": txid, @@ -724,7 +724,7 @@ def get_open_positions( params["txid"] = txid return self._request( # type: ignore[return-value] method="POST", - uri="/private/OpenPositions", + uri="/0/private/OpenPositions", params=params, extra_params=extra_params, ) @@ -800,7 +800,7 @@ def get_ledgers_info( params["ofs"] = ofs return self._request( # type: ignore[return-value] method="POST", - uri="/private/Ledgers", + uri="/0/private/Ledgers", params=params, extra_params=extra_params, ) @@ -851,7 +851,7 @@ def get_ledgers( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/QueryLedgers", + uri="/0/private/QueryLedgers", params={"trades": trades, "id": id_}, extra_params=extra_params, ) @@ -922,7 +922,7 @@ def get_trade_volume( params["pair"] = pair return self._request( # type: ignore[return-value] method="POST", - uri="/private/TradeVolume", + uri="/0/private/TradeVolume", params=params, extra_params=extra_params, ) @@ -992,7 +992,7 @@ def request_export_report( # noqa: PLR0913 # pylint: disable=too-many-arguments params["endtm"] = endtm return self._request( # type: ignore[return-value] method="POST", - uri="/private/AddExport", + uri="/0/private/AddExport", params=params, extra_params=extra_params, timeout=timeout, @@ -1056,7 +1056,7 @@ def get_export_report_status( raise ValueError('report must be one of "trades", "ledgers"') return self._request( # type: ignore[return-value] method="POST", - uri="/private/ExportStatus", + uri="/0/private/ExportStatus", params={"report": report}, extra_params=extra_params, ) @@ -1082,7 +1082,7 @@ def retrieve_export( :type id_: str :param timeout: Timeout for that request, default: ``10`` seconds :type timeout: int, optional - :return: The reponse - a zipped report + :return: The response - a zipped report :rtype: requests.Response .. code-block:: python @@ -1104,7 +1104,7 @@ def retrieve_export( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/RetrieveExport", + uri="/0/private/RetrieveExport", params={"id": id_}, timeout=timeout, return_raw=True, @@ -1147,7 +1147,7 @@ def delete_export_report( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/RemoveExport", + uri="/0/private/RemoveExport", params={"id": id_, "type": type_}, extra_params=extra_params, ) @@ -1183,7 +1183,7 @@ def create_subaccount( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/CreateSubaccount", + uri="/0/private/CreateSubaccount", params={"username": username, "email": email}, extra_params=extra_params, ) @@ -1236,7 +1236,7 @@ def account_transfer( """ return self._request( # type: ignore[return-value] method="POST", - uri="/private/AccountTransfer", + uri="/0/private/AccountTransfer", params={"asset": asset, "amount": amount, "from": from_, "to": to_}, extra_params=extra_params, ) diff --git a/kraken/spot/websocket/__init__.py b/kraken/spot/websocket/__init__.py index e83016d7..c6ed7334 100644 --- a/kraken/spot/websocket/__init__.py +++ b/kraken/spot/websocket/__init__.py @@ -69,9 +69,9 @@ def __init__( self.__callback: Optional[Callable] = callback self.exception_occur: bool = False self._pub_conn: Optional[ConnectSpotWebsocketV1 | ConnectSpotWebsocketV2] = None - self._priv_conn: Optional[ - ConnectSpotWebsocketV1 | ConnectSpotWebsocketV2 - ] = None + self._priv_conn: Optional[ConnectSpotWebsocketV1 | ConnectSpotWebsocketV2] = ( + None + ) self.__connect(version=api_version, beta=beta, no_public=no_public) @@ -90,9 +90,9 @@ def __connect( :param version: The Websocket API version to use (one of ``v1``, ``v2``) :type version: str """ - ConnectSpotWebsocket: Type[ConnectSpotWebsocketV1] | Type[ - ConnectSpotWebsocketV2 - ] + ConnectSpotWebsocket: ( + Type[ConnectSpotWebsocketV1] | Type[ConnectSpotWebsocketV2] + ) if version == "v1": ConnectSpotWebsocket = ConnectSpotWebsocketV1 @@ -171,7 +171,7 @@ async def __aexit__( def get_ws_token(self: KrakenSpotWSClientBase) -> dict: """ Get the authentication token to establish the authenticated - websocket connection. This is used internally ond in moste cases not + websocket connection. This is used internally and in most cases not needed outside. - https://docs.kraken.com/rest/#tag/Websockets-Authentication @@ -180,8 +180,8 @@ def get_ws_token(self: KrakenSpotWSClientBase) -> dict: :rtype: dict """ return self._request( # type: ignore[return-value] - "POST", - "/private/GetWebSocketsToken", + method="POST", + uri="/0/private/GetWebSocketsToken", ) def _get_socket(self: KrakenSpotWSClientBase, *, private: bool) -> Any: diff --git a/kraken/spot/websocket/connectors.py b/kraken/spot/websocket/connectors.py index 33f616db..f3c1e971 100644 --- a/kraken/spot/websocket/connectors.py +++ b/kraken/spot/websocket/connectors.py @@ -220,7 +220,9 @@ async def __reconnect(self: ConnectSpotWebsocketBase) -> None: if task.exception(): exception_occur = True traceback.print_stack() - message: str = f"{task} got an exception {task.exception()}\n {task.get_stack()}" + message: str = ( + f"{task} got an exception {task.exception()}\n {task.get_stack()}" + ) self.LOG.warning(message) for process in pending: self.LOG.warning("pending %s", process) @@ -348,7 +350,9 @@ async def _recover_subscriptions( it is set to ``True`` - which is when the connection is ready) :type event: asyncio.Event """ - log_msg: str = f'Recover {"authenticated" if self.is_auth else "public"} subscriptions {self._subscriptions}' + log_msg: str = ( + f'Recover {"authenticated" if self.is_auth else "public"} subscriptions {self._subscriptions}' + ) self.LOG.info("%s: waiting", log_msg) await event.wait() @@ -504,7 +508,9 @@ async def _recover_subscriptions( it is set to ``True`` - which is when the connection is ready) :type event: asyncio.Event """ - log_msg: str = f'Recover {"authenticated" if self.is_auth else "public"} subscriptions {self._subscriptions}' + log_msg: str = ( + f'Recover {"authenticated" if self.is_auth else "public"} subscriptions {self._subscriptions}' + ) self.LOG.info("%s: waiting", log_msg) await event.wait() @@ -598,7 +604,7 @@ def __transform_subscription( # Kraken somehow responds with this key - but this is not # accepted when subscribing (Dec 2023). if ( - subscription_copy["method"] == "unsubscribe" + subscription_copy["method"] in {"subscribe", "unsubscribe"} and "maxratecount" in subscription["result"] ): del subscription_copy["result"]["maxratecount"] diff --git a/kraken/spot/websocket_v1.py b/kraken/spot/websocket_v1.py index 7a9f825b..f745bee7 100644 --- a/kraken/spot/websocket_v1.py +++ b/kraken/spot/websocket_v1.py @@ -505,9 +505,11 @@ async def create_order( # pylint: disable=too-many-arguments # noqa: PLR0913, P "ordertype": str(ordertype), "type": str(side), "pair": str(pair), - "volume": str(volume) - if not truncate - else Trade().truncate(amount=volume, amount_type="volume", pair=pair), + "volume": ( + str(volume) + if not truncate + else Trade().truncate(amount=volume, amount_type="volume", pair=pair) + ), "validate": str(validate), } if defined(price): @@ -547,7 +549,7 @@ async def create_order( # pylint: disable=too-many-arguments # noqa: PLR0913, P await self.send_message(message=payload, private=True) @ensure_string("oflags") - async def edit_order( # pylint: disable=too-many-arguments # noqa: PLR0913, PLR0917 + async def edit_order( # pylint: disable=too-many-arguments # noqa: PLR0913 self: KrakenSpotWSClientV1, orderid: str, reqid: Optional[str | int] = None, diff --git a/pyproject.toml b/pyproject.toml index ac3301c1..e82bed20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,9 @@ dynamic = ["version"] authors = [ { name = "Benjamin Thomas Schwertfeger", email = "contact@b-schwertfeger.de" }, ] +maintainers = [ + { name = "Benjamin Thomas Schwertfeger", email = "contact@b-schwertfeger.de" }, +] description = "Collection of REST and websocket clients to interact with the Kraken cryptocurrency exchange." readme = "README.md" license = { file = "LICENSE" } @@ -29,6 +32,7 @@ classifiers = [ "Topic :: Utilities", "Programming Language :: Python", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Framework :: AsyncIO", "Natural Language :: English", "Operating System :: MacOS", @@ -111,6 +115,10 @@ omit = ["*tests*"] exclude_lines = ["coverage: disable", "if TYPE_CHECKING:"] skip_empty = true +[tool.codespell] +skip = "CHANGELOG.md,examples/market_client_example.ipynb" +check-filenames = true + # ========= T Y P I N G ======================================================== # [tool.mypy] @@ -179,7 +187,13 @@ strict = true # https://beta.ruff.rs/docs/rules/ # https://beta.ruff.rs/docs/settings/ # src = ["kraken"] +cache-dir = ".cache/ruff" + +respect-gitignore = true +exclude = [] +line-length = 130 +[tool.ruff.lint] select = [ "A", # flake8-builtins "AIR", # Airflow @@ -245,16 +259,12 @@ ignore = [ "PLR2004", # magic value in comparison "E203", # Whitespace before ':' # false positive on list slices "PLR6301", # Method `…` could be a function or static method # false positive for no-self-use + "RUF022", # `__all__` is not sorted ] -respect-gitignore = true -exclude = [] - -line-length = 130 -cache-dir = ".cache/ruff" task-tags = ["todo", "TODO"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "examples/*.py" = [ "ASYNC101", # no open call on async function "BLE001", # blind exception @@ -262,6 +272,9 @@ task-tags = ["todo", "TODO"] "S110", # try-catch-pass without logging "T201", # print ] +"examples/spot_trading_bot_template_v2.py" = [ + "RUF027", # Possible f-string without an `f` prefix +] "tests/*.py" = [ "ASYNC101", # no open call on async function "E501", # line to long @@ -275,24 +288,24 @@ task-tags = ["todo", "TODO"] "TID252", # ban relative imports ] -[tool.ruff.flake8-copyright] +[tool.ruff.lint.flake8-copyright] author = "Benjamin Thomas Schwertfeger" notice-rgx = "(?i)Copyright \\(C\\) \\d{4}" min-file-size = 1024 -[tool.ruff.flake8-quotes] +[tool.ruff.lint.flake8-quotes] docstring-quotes = "double" -[tool.ruff.flake8-tidy-imports] +[tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" -[tool.ruff.flake8-bandit] +[tool.ruff.lint.flake8-bandit] check-typed-exception = true -[tool.ruff.flake8-type-checking] +[tool.ruff.lint.flake8-type-checking] strict = true -[tool.ruff.pep8-naming] +[tool.ruff.lint.pep8-naming] ignore-names = [ "i", "j", @@ -328,7 +341,7 @@ ignore-names = [ "HEADERS", ] -[tool.ruff.pylint] +[tool.ruff.lint.pylint] max-args = 8 max-branches = 15 max-returns = 6 diff --git a/tests/futures/conftest.py b/tests/futures/conftest.py index 37130638..4c38dd11 100644 --- a/tests/futures/conftest.py +++ b/tests/futures/conftest.py @@ -15,6 +15,8 @@ FUTURES_SANDBOX_KEY: str = os.getenv("FUTURES_SANDBOX_KEY") FUTURES_SANDBOX_SECRET_KEY: str = os.getenv("FUTURES_SANDBOX_SECRET") +FUTURES_EXTENDED_TIMEOUT: int = 30 + @pytest.fixture() def futures_api_key() -> str: @@ -33,7 +35,9 @@ def futures_market() -> Market: """ Fixture providing an unauthenticated Futures Market client """ - return Market() + market: Market = Market() + market.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return market @pytest.fixture() @@ -41,7 +45,9 @@ def futures_auth_market() -> Market: """ Fixture providing an authenticated Futures Market client. """ - return Market(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) + market: Market = Market(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) + market.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return market @pytest.fixture() @@ -50,11 +56,13 @@ def futures_demo_market() -> Market: Fixture providing an authenticated Futures Market client that uses the demo/sandbox environment. """ - return Market( + market: Market = Market( key=FUTURES_SANDBOX_KEY, secret=FUTURES_SANDBOX_SECRET_KEY, sandbox=True, ) + market.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return market @pytest.fixture() @@ -63,7 +71,7 @@ def futures_user() -> User: Fixture providing an unauthenticated Futures User client. """ user: User = User() - user.TIMEOUT = 30 + user.TIMEOUT = FUTURES_EXTENDED_TIMEOUT return user @@ -73,7 +81,7 @@ def futures_auth_user() -> User: Fixture providing an authenticated Futures User client. """ user: User = User(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) - User.TIMEOUT = 30 + User.TIMEOUT = FUTURES_EXTENDED_TIMEOUT return user @@ -83,11 +91,13 @@ def futures_demo_user() -> User: Fixture providing an authenticated Futures User client that uses the demo/sandbox environment. """ - return User( + user: User = User( key=FUTURES_SANDBOX_KEY, secret=FUTURES_SANDBOX_SECRET_KEY, sandbox=True, ) + User.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return user @pytest.fixture() @@ -95,7 +105,9 @@ def futures_trade() -> Trade: """ Fixture providing an unauthenticated Futures Trade client. """ - return Trade() + trade: Trade = Trade() + trade.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return trade @pytest.fixture() @@ -103,7 +115,9 @@ def futures_auth_trade() -> Trade: """ Fixture providing an authenticated Futures Trade client. """ - return Trade(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) + trade: Trade = Trade(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) + trade.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return trade @pytest.fixture() @@ -112,11 +126,13 @@ def futures_demo_trade() -> Trade: Fixture providing an authenticated Futures Trade client that uses the demo/sandbox environment. """ - return Trade( + trade: Trade = Trade( key=FUTURES_SANDBOX_KEY, secret=FUTURES_SANDBOX_SECRET_KEY, sandbox=True, ) + trade.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return trade @pytest.fixture() @@ -124,7 +140,9 @@ def futures_funding() -> Funding: """ Fixture providing an unauthenticated Futures Funding client. """ - return Funding() + funding: Funding = Funding() + funding.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return funding @pytest.fixture() @@ -132,7 +150,9 @@ def futures_auth_funding() -> Funding: """ Fixture providing an authenticated Futures Funding client. """ - return Funding(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) + funding: Funding = Funding(key=FUTURES_API_KEY, secret=FUTURES_SECRET_KEY) + funding.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return funding @pytest.fixture() @@ -141,8 +161,10 @@ def futures_demo_funding() -> Funding: Fixture providing an authenticated Futures Funding client that uses the demo/sandbox environment. """ - return Funding( + funding: Funding = Funding( key=FUTURES_SANDBOX_KEY, secret=FUTURES_SANDBOX_SECRET_KEY, sandbox=True, ) + funding.TIMEOUT = FUTURES_EXTENDED_TIMEOUT + return funding diff --git a/tests/futures/test_futures_funding.py b/tests/futures/test_futures_funding.py index 5612d659..03206f11 100644 --- a/tests/futures/test_futures_funding.py +++ b/tests/futures/test_futures_funding.py @@ -18,12 +18,13 @@ @pytest.mark.futures() @pytest.mark.futures_auth() @pytest.mark.futures_funding() +@pytest.mark.skip(reason="Too much data, read time out") def test_get_historical_funding_rates(futures_demo_funding: Funding) -> None: """ Checks the ``get_historical_funding_rates`` function. """ assert is_success( - futures_demo_funding.get_historical_funding_rates(symbol="PF_SOLUSD"), + futures_demo_funding.get_historical_funding_rates(symbol="PF_XBTUSD"), ) diff --git a/tests/futures/test_futures_trade.py b/tests/futures/test_futures_trade.py index 93a948ec..803937ae 100644 --- a/tests/futures/test_futures_trade.py +++ b/tests/futures/test_futures_trade.py @@ -8,6 +8,7 @@ from contextlib import suppress from time import sleep +from typing import Generator import pytest @@ -17,7 +18,7 @@ @pytest.fixture(autouse=True) -def _run_before_and_after_tests(futures_demo_trade) -> None: +def _run_before_and_after_tests(futures_demo_trade) -> Generator: """ Fixture that ensures all orders are cancelled after test. """ @@ -66,16 +67,17 @@ def test_get_orders_status(futures_demo_trade) -> None: assert is_success( futures_demo_trade.get_orders_status( orderIds=[ - "d47e7fb4-aed0-4f3d-987b-9e3ca78ba74e", - "fc589be9-5095-48f0-b6f1-a2dfad6d9677", + "bcaaefce-27a3-44b4-b13a-19df21e3f087", + "685d5a1a-23eb-450c-bf17-1e4ab5c6fe8a", ], ), ) + assert is_success( futures_demo_trade.get_orders_status( cliOrdIds=[ - "2c611222-bfe6-42d1-9f55-77bddc01a313", - "fc589be9-5095-48f0-b6f1-a2dfad6d9677", + "bcaaefce-27a3-44b4-b13a-19df21e3f087", + "685d5a1a-23eb-450c-bf17-1e4ab5c6fe8a", ], ), ) @@ -99,16 +101,17 @@ def test_create_order(futures_demo_trade) -> None: reduceOnly=True, ) - with suppress(KrakenInsufficientAvailableFundsError): - futures_demo_trade.create_order( - orderType="take_profit", - size=10, - side="buy", - symbol="PI_XBTUSD", - limitPrice=12000, - triggerSignal="last", - stopPrice=13000, - ) + # FIXME: why are these commented out? + # with suppress(KrakenInsufficientAvailableFundsError): + # futures_demo_trade.create_order( + # orderType="take_profit", + # size=10, + # side="buy", + # symbol="PI_XBTUSD", + # limitPrice=12000, + # triggerSignal="last", + # stopPrice=13000, + # ) # try: # # does not work, 400 response "invalid order type" @@ -133,8 +136,7 @@ def test_create_order(futures_demo_trade) -> None: @pytest.mark.futures_trade() def test_create_order_failing(futures_demo_trade) -> None: """ - Checks ``create_order`` endpoint to fail when using invalid - parameters. + Checks ``create_order`` endpoint to fail when using invalid parameters. """ with pytest.raises( ValueError, @@ -213,14 +215,16 @@ def test_edit_order(futures_demo_trade) -> None: """ Checks the ``edit_order`` endpoint. """ - # success, because kraken received the correct message, even if the id is invalid assert is_success( - futures_demo_trade.edit_order(orderId="my_another_client_id", limitPrice=3), + futures_demo_trade.edit_order( + orderId="685d5a1a-23eb-450c-bf17-1e4ab5c6fe8a", + limitPrice=3, + ), ) assert is_success( futures_demo_trade.edit_order( - cliOrdId="myclientorderid", + cliOrdId="685d5a1a-23eb-450c-bf17-1e4ab5c6fe8a", size=111.0, stopPrice=1000, ), @@ -232,8 +236,7 @@ def test_edit_order(futures_demo_trade) -> None: @pytest.mark.futures_trade() def test_edit_order_failing(futures_demo_trade) -> None: """ - Checks if the ``edit_order`` endpoint fails when using invalid - parameters. + Checks if the ``edit_order`` endpoint fails when using invalid parameters. """ with pytest.raises(ValueError, match=r"Either orderId or cliOrdId must be set!"): futures_demo_trade.edit_order() @@ -246,8 +249,16 @@ def test_cancel_order(futures_demo_trade) -> None: """ Checks the ``cancel_order`` endpoint. """ - assert is_success(futures_demo_trade.cancel_order(cliOrdId="my_another_client_id")) - assert is_success(futures_demo_trade.cancel_order(order_id="1234")) + assert is_success( + futures_demo_trade.cancel_order( + cliOrdId="685d5a1a-23eb-450c-bf17-1e4ab5c6fe8a", + ), + ) + assert is_success( + futures_demo_trade.cancel_order( + order_id="685d5a1a-23eb-450c-bf17-1e4ab5c6fe8a", + ), + ) @pytest.mark.futures() diff --git a/tests/futures/test_futures_user.py b/tests/futures/test_futures_user.py index b22df888..52b8238b 100644 --- a/tests/futures/test_futures_user.py +++ b/tests/futures/test_futures_user.py @@ -178,7 +178,7 @@ def test_get_trigger_events(futures_auth_user: User) -> None: @pytest.mark.futures() @pytest.mark.futures_auth() @pytest.mark.futures_user() -@pytest.mark.skip("Subaccount actions are only available for insitutional clients") +@pytest.mark.skip("Subaccount actions are only available for institutional clients") def test_check_trading_enabled_on_subaccount(futures_auth_user: User) -> None: """ Checks the ``check_trading_enabled_on_subaccount`` function. @@ -195,7 +195,7 @@ def test_check_trading_enabled_on_subaccount(futures_auth_user: User) -> None: @pytest.mark.futures() @pytest.mark.futures_auth() @pytest.mark.futures_user() -@pytest.mark.skip("Subaccount actions are only available for insitutional clients") +@pytest.mark.skip("Subaccount actions are only available for institutional clients") def test_set_trading_on_subaccount(futures_auth_user: User) -> None: """ Checks the ``set_trading_on_subaccount`` function. diff --git a/tests/spot/helper.py b/tests/spot/helper.py index 0e7138e1..96886112 100644 --- a/tests/spot/helper.py +++ b/tests/spot/helper.py @@ -36,7 +36,7 @@ def is_not_error(value: Any) -> bool: async def async_wait(seconds: float = 1.0) -> None: - """Function that waits for ``seconds`` - asynchron.""" + """Function that waits for ``seconds`` - asynchronous.""" start: float = time() while time() - seconds < start: await sleep(0.2) diff --git a/tests/spot/test_spot_base_api.py b/tests/spot/test_spot_base_api.py index 4d59f395..1b13a012 100644 --- a/tests/spot/test_spot_base_api.py +++ b/tests/spot/test_spot_base_api.py @@ -10,7 +10,7 @@ from kraken.base_api import KrakenSpotBaseAPI from kraken.exceptions import KrakenInvalidAPIKeyError, KrakenPermissionDeniedError -from kraken.spot import Funding, Market, Staking, Trade, User +from kraken.spot import Funding, Market, Trade, User from .helper import is_not_error @@ -27,13 +27,13 @@ def test_KrakenSpotBaseAPI_without_exception() -> None: KrakenSpotBaseAPI( key="fake", secret="fake", - )._request(method="POST", uri="/private/AddOrder", auth=True) + )._request(method="POST", uri="/0/private/AddOrder", auth=True) assert KrakenSpotBaseAPI( key="fake", secret="fake", use_custom_exceptions=False, - )._request(method="POST", uri="/private/AddOrder", auth=True).json() == { + )._request(method="POST", uri="/0/private/AddOrder", auth=True).json() == { "error": ["EAPI:Invalid key"], } @@ -45,7 +45,7 @@ def test_spot_rest_contextmanager( spot_auth_funding: Funding, spot_auth_trade: Trade, spot_auth_user: User, - spot_auth_staking: Staking, + # spot_auth_staking: Staking, ) -> None: """ Checks if the clients can be used as context manager. @@ -60,8 +60,9 @@ def test_spot_rest_contextmanager( with spot_auth_user as user: assert is_not_error(user.get_account_balance()) - with spot_auth_staking as staking: - assert isinstance(staking.get_pending_staking_transactions(), list) + # FIXME: does not work; deprecated + # with spot_auth_staking as staking: + # assert isinstance(staking.get_pending_staking_transactions(), list) with spot_auth_trade as trade, pytest.raises(KrakenPermissionDeniedError): trade.cancel_order(txid="OB6JJR-7NZ5P-N5SKCB") diff --git a/tests/spot/test_spot_market.py b/tests/spot/test_spot_market.py index 920d9474..e7a32b03 100644 --- a/tests/spot/test_spot_market.py +++ b/tests/spot/test_spot_market.py @@ -67,7 +67,7 @@ def test_get_asset_pairs(spot_market: Market) -> None: def test_get_ticker(spot_market: Market) -> None: """ Checks the ``get_ticker`` endpoint by performing multiple - requests with different paramaters and validating that the response + requests with different parameters and validating that the response does not contain the error key. """ assert is_not_error(spot_market.get_ticker()) diff --git a/tests/spot/test_spot_orderbook_v1.py b/tests/spot/test_spot_orderbook_v1.py index 802502f7..c4ba8a6f 100644 --- a/tests/spot/test_spot_orderbook_v1.py +++ b/tests/spot/test_spot_orderbook_v1.py @@ -36,7 +36,7 @@ def test_create_public_bot(caplog: Any) -> None: async def create_bot() -> None: orderbook: OrderbookClientV1Wrapper = OrderbookClientV1Wrapper() - await async_wait(seconds=4) + await async_wait(seconds=10) assert orderbook.depth == 10 diff --git a/tests/spot/test_spot_orderbook_v2.py b/tests/spot/test_spot_orderbook_v2.py index ca746467..820ff2b4 100644 --- a/tests/spot/test_spot_orderbook_v2.py +++ b/tests/spot/test_spot_orderbook_v2.py @@ -36,7 +36,7 @@ def test_create_public_bot(caplog: Any) -> None: async def create_bot() -> None: orderbook: OrderbookClientV2Wrapper = OrderbookClientV2Wrapper() - await async_wait(seconds=4) + await async_wait(seconds=10) assert orderbook.depth == 10 diff --git a/tests/spot/test_spot_websocket_v2.py b/tests/spot/test_spot_websocket_v2.py index b9ff5d98..d085d326 100644 --- a/tests/spot/test_spot_websocket_v2.py +++ b/tests/spot/test_spot_websocket_v2.py @@ -234,7 +234,7 @@ async def test_subscription() -> None: asyncio_run(test_subscription()) assert ( - '{"method": "subscribe", "req_id": 12345678, "result": {"channel": "ticker", "snapshot": true, "symbol": "BTC/USD"}, "success": true, "time_in":' + '{"method": "subscribe", "req_id": 12345678, "result": {"channel": "ticker", "event_trigger": "trades", "snapshot": true, "symbol": "BTC/USD"}, "success": true, "time_in":' in caplog.text ) @@ -313,9 +313,9 @@ async def test_unsubscribe() -> None: asyncio_run(test_unsubscribe()) for expected in ( - '{"method": "subscribe", "req_id": 123456789, "result": {"channel": "ticker", "snapshot": true, "symbol": "BTC/USD"}, "success": true, "time_in": ', + '{"method": "subscribe", "req_id": 123456789, "result": {"channel": "ticker", "event_trigger": "trades", "snapshot": true, "symbol": "BTC/USD"}, "success": true, "time_in": ', '{"channel": "ticker", "type": "snapshot", "data": [{"symbol": "BTC/USD", ', - '{"method": "unsubscribe", "req_id": 987654321, "result": {"channel": "ticker", "symbol": "BTC/USD"}, "success": true, "time_in": ', + '{"method": "unsubscribe", "req_id": 987654321, "result": {"channel": "ticker", "event_trigger": "trades", "symbol": "BTC/USD"}, "success": true, "time_in": ', ): assert expected in caplog.text @@ -509,13 +509,13 @@ async def check_reconnect() -> None: '{"channel": "status", "data": [{"api_version": "v2", "connection_id": ', '"system": "online", "version": ', # "2.0.x" '"type": "update"}', - '{"method": "subscribe", "result": {"channel": "ticker", "snapshot": true, "symbol": "BTC/USD"}, "success": true,', + '{"method": "subscribe", "result": {"channel": "ticker", "event_trigger": "trades", "snapshot": true, "symbol": "BTC/USD"}, "success": true,', '"channel": "ticker", "type": "snapshot", "data": [{"symbol": "BTC/USD", ', "got an exception sent 1000 (OK); then received 1000 (OK)", - "Recover public subscriptions [{'channel': 'ticker', 'snapshot': True, 'symbol': ['BTC/USD']}]: waiting", - "Recover public subscriptions [{'channel': 'ticker', 'snapshot': True, 'symbol': ['BTC/USD']}]: done", - "Recover authenticated subscriptions [{'channel': 'executions', 'maxratecount': 180, 'snapshot': True}]: waiting", - "Recover authenticated subscriptions [{'channel': 'executions', 'maxratecount': 180, 'snapshot': True}]: done", + "Recover public subscriptions [{'channel': 'ticker', 'event_trigger': 'trades', 'snapshot': True, 'symbol': ['BTC/USD']}]: waiting", + "Recover public subscriptions [{'channel': 'ticker', 'event_trigger': 'trades', 'snapshot': True, 'symbol': ['BTC/USD']}]: done", + "Recover authenticated subscriptions [{'channel': 'executions', 'snapshot': True}]: waiting", + "Recover authenticated subscriptions [{'channel': 'executions', 'snapshot': True}]: done", ): assert phrase in caplog.text