From 26dd5267b1fce78fdc2d7e4f5beb8a89834d9524 Mon Sep 17 00:00:00 2001 From: Benjamin Thomas Schwertfeger Date: Sat, 20 May 2023 14:22:15 +0200 Subject: [PATCH 1/4] removed spot utils --- kraken/spot/utils/__init__.py | 72 -------------------- tests/spot/test_spot_utils.py | 120 ---------------------------------- 2 files changed, 192 deletions(-) delete mode 100644 kraken/spot/utils/__init__.py delete mode 100644 tests/spot/test_spot_utils.py diff --git a/kraken/spot/utils/__init__.py b/kraken/spot/utils/__init__.py deleted file mode 100644 index e30f1a6f..00000000 --- a/kraken/spot/utils/__init__.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2023 Benjamin Thomas Schwertfeger -# GitHub: https://github.com/btschwertfeger - -"""Module that provides the Utils class that stores Spot related functions.""" - -from decimal import Decimal -from math import floor -from typing import Union - -from ..market import Market - - -class Utils: - """ - The Utils class provides utility functions for the Spot related clients. - """ - - @staticmethod - def truncate( - amount: Union[Decimal, float, int, str], amount_type: str, pair: str - ) -> str: - """ - Kraken only allows volume and price amounts to be specified with a specific number of - decimal places, and these varry depending on the currency pair used. - - This function converts an amount of a specific type and pair to a string that uses - the correct number of decimal places. - - - https://support.kraken.com/hc/en-us/articles/4521313131540 - - :param amount: The floating point number to represent - :type amount: Decimal | float | int | str - :param amount_type: What the amount represents. Either 'price' or 'volume' - :type amount_type: str - :param pair: The currency pair the amount is in reference to. - :type pair: str - :raises ValueError: If the ``amount_type`` is ``price`` and the price is less - than the costmin. - :raises ValueError: If the ``amount_type`` is ``volume`` and the volume is - less than the ordermin. - :raises ValueError: If no valid ``amount_type`` was passed. - :return: A string representation of the amount. - :rtype: str - """ - if amount_type not in ("price", "volume"): - raise ValueError("Amount type must be 'volume' or 'price'!") - - pair_data: dict = Market().get_asset_pairs(pair=pair) - data: dict = pair_data[list(pair_data)[0]] - - pair_decimals: int = int(data["pair_decimals"]) - lot_decimals: int = int(data["lot_decimals"]) - - ordermin: Decimal = Decimal(data["ordermin"]) - costmin: Decimal = Decimal(data["costmin"]) - - amount = Decimal(amount) - decimals: int - - if amount_type == "price": - if costmin > amount: - raise ValueError(f"Price is less than the costmin: {costmin}!") - decimals = pair_decimals - else: # amount_type == "volume": - if ordermin > amount: - raise ValueError(f"Volume is less than the ordermin: {ordermin}!") - decimals = lot_decimals - - amount_rounded: float = floor(float(amount) * 10**decimals) / 10**decimals - return f"{amount_rounded:.{decimals}f}" diff --git a/tests/spot/test_spot_utils.py b/tests/spot/test_spot_utils.py deleted file mode 100644 index f37767c2..00000000 --- a/tests/spot/test_spot_utils.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2023 Benjamin Thomas Schwertfeger -# GitHub: https://github.com/btschwertfeger -# - -"""Module that implements the unit tests for the Spot user client.""" - -from time import sleep - -import pytest - -from kraken.spot import Utils - - -@pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_price() -> None: - """ - Checks if the truncate function returns the expected results by - checking different inputs for price. - - NOTE: This test may break in the future since the lot_decimals, pair_decimals, - ordermin and costmin attributes could change. - """ - for price, expected in ( - (10000, "10000.0"), - (1000.1, "1000.1"), - (1000.01, "1000.0"), - (1000.001, "1000.0"), - ): - assert ( - Utils.truncate(amount=price, amount_type="price", pair="XBTUSD") == expected - ) - sleep(3) - - for price, expected in ( - (2, "2.0000"), - (12.1, "12.1000"), - (13.105, "13.1050"), - (4.32595, "4.3259"), - ): - assert ( - Utils.truncate(amount=price, amount_type="price", pair="DOTUSD") == expected - ) - sleep(3) - - -@pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_volume() -> None: - """ - Checks if the truncate function returns the expected results by - checking different inputs for volume. - - NOTE: This test may break in the future since the lot_decimals, pair_decimals, - ordermin and costmin attributes could change. - """ - for volume, expected in ( - (1, "1.00000000"), - (1.1, "1.10000000"), - (1.67, "1.67000000"), - (1.9328649837, "1.93286498"), - ): - assert ( - Utils.truncate(amount=volume, amount_type="volume", pair="XBTUSD") - == expected - ) - sleep(3) - - for volume, expected in ( - (2, "2.00000000"), - (12.158, "12.15800000"), - (13.1052093, "13.10520930"), - (4.32595342455, "4.32595342"), - ): - assert ( - Utils.truncate(amount=volume, amount_type="volume", pair="DOTUSD") - == expected - ) - sleep(3) - - -@pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_fail_price_costmin() -> None: - """ - Checks if the truncate function fails if the price is less than the costmin. - - NOTE: This test may break in the future since the lot_decimals, pair_decimals, - ordermin and costmin attributes could change. - """ - with pytest.raises(ValueError): - Utils.truncate(amount=0.001, amount_type="price", pair="XBTUSD") - - -@pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_fail_volume_ordermin() -> None: - """ - Checks if the truncate function fails if the volume is less than the ordermin. - - NOTE: This test may break in the future since the lot_decimals, pair_decimals, - ordermin and costmin attributes could change. - """ - with pytest.raises(ValueError): - Utils.truncate(amount=0.00001, amount_type="volume", pair="XBTUSD") - - -@pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_fail_invalid_amount_type() -> None: - """ - Checks if the truncate function fails when no valid ``amount_type`` was specified. - - NOTE: This test may break in the future since the lot_decimals, pair_decimals, - ordermin and costmin attributes could change. - """ - with pytest.raises(ValueError): - Utils.truncate(amount=1, amount_type="invalid", pair="XBTUSD") From 2b7311db8b7ea9326b88c8ae55cf619c676e97e0 Mon Sep 17 00:00:00 2001 From: Benjamin Thomas Schwertfeger Date: Sat, 20 May 2023 14:23:27 +0200 Subject: [PATCH 2/4] improved caching --- kraken/futures/funding/__init__.py | 8 +- kraken/futures/market/__init__.py | 17 +++- kraken/futures/trade/__init__.py | 8 +- kraken/futures/user/__init__.py | 8 +- kraken/spot/__init__.py | 1 - kraken/spot/funding/__init__.py | 6 +- kraken/spot/market/__init__.py | 123 ++++++++++++++++++++++++++++- kraken/spot/staking/__init__.py | 6 +- kraken/spot/trade/__init__.py | 81 ++++++++++++++++--- kraken/spot/user/__init__.py | 6 +- kraken/spot/websocket/__init__.py | 2 +- tests/spot/conftest.py | 5 ++ tests/spot/test_spot_market.py | 30 ++++++- tests/spot/test_spot_trade.py | 110 ++++++++++++++++++++++++++ 14 files changed, 368 insertions(+), 43 deletions(-) diff --git a/kraken/futures/funding/__init__.py b/kraken/futures/funding/__init__.py index 7a51c43e..3ea6eae5 100644 --- a/kraken/futures/funding/__init__.py +++ b/kraken/futures/funding/__init__.py @@ -45,10 +45,10 @@ class Funding(KrakenBaseFuturesAPI): def __init__( self, - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", - sandbox: Optional[bool] = False, + key: str = "", + secret: str = "", + url: str = "", + sandbox: bool = False, ) -> None: super().__init__(key=key, secret=secret, url=url, sandbox=sandbox) diff --git a/kraken/futures/market/__init__.py b/kraken/futures/market/__init__.py index ac07fdeb..d589862e 100644 --- a/kraken/futures/market/__init__.py +++ b/kraken/futures/market/__init__.py @@ -47,10 +47,10 @@ class Market(KrakenBaseFuturesAPI): def __init__( self: "Market", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", - sandbox: Optional[bool] = False, + key: str = "", + secret: str = "", + url: str = "", + sandbox: bool = False, ) -> None: super().__init__(key=key, secret=secret, url=url, sandbox=sandbox) @@ -134,6 +134,8 @@ def get_tick_types(self: "Market") -> List[str]: - https://docs.futures.kraken.com/#http-api-charts-ohlc-get-tick-types + This function uses caching. Run ``get_tick_types.cache_clear()`` to clear. + :return: List of available tick types :rtype: List[str] @@ -154,6 +156,8 @@ def get_tradeable_products(self: "Market", tick_type: str) -> List[str]: - https://docs.futures.kraken.com/#http-api-charts-ohlc-get-tradeable-products + This function uses caching. Run ``get_tradeable_products.cache_clear()`` to clear. + :param tick_type: The kind of data, based on ``mark``, ``spot``, or ``trade`` :type tick_type: str :return: List of tradeable assets @@ -178,6 +182,8 @@ def get_resolutions(self: "Market", tick_type: str, tradeable: str) -> List[str] - https://docs.futures.kraken.com/#http-api-charts-ohlc-get-resolutions + This function uses caching. Run ``get_resolutions.cache_clear()`` to clear. + :param tick_type: The kind of data, based on ``mark``, ``spot``, or ``trade`` :type tick_type: str :param tick_type: The asset of interest @@ -203,8 +209,11 @@ def get_fee_schedules(self: "Market") -> dict: Retrieve information about the current fees - https://docs.futures.kraken.com/#http-api-trading-v3-api-fee-schedules-get-fee-schedules + - https://support.kraken.com/hc/en-us/articles/360049269572-Fee-Schedules + This function uses caching. Run ``get_fee_schedules.cache_clear()`` to clear. + :return: Dictionary containing information about the fees for wide range of tradeable assets :rtype: dict diff --git a/kraken/futures/trade/__init__.py b/kraken/futures/trade/__init__.py index 9f6d43b7..dd5ced72 100644 --- a/kraken/futures/trade/__init__.py +++ b/kraken/futures/trade/__init__.py @@ -46,10 +46,10 @@ class Trade(KrakenBaseFuturesAPI): def __init__( self: "Trade", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", - sandbox: Optional[bool] = False, + key: str = "", + secret: str = "", + url: str = "", + sandbox: bool = False, ) -> None: super().__init__(key=key, secret=secret, url=url, sandbox=sandbox) diff --git a/kraken/futures/user/__init__.py b/kraken/futures/user/__init__.py index 90caf9c8..1ff4a61a 100644 --- a/kraken/futures/user/__init__.py +++ b/kraken/futures/user/__init__.py @@ -48,10 +48,10 @@ class User(KrakenBaseFuturesAPI): def __init__( self: "User", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", - sandbox: Optional[bool] = False, + key: str = "", + secret: str = "", + url: str = "", + sandbox: bool = False, ) -> None: super().__init__(key=key, secret=secret, url=url, sandbox=sandbox) diff --git a/kraken/spot/__init__.py b/kraken/spot/__init__.py index dcac8d9e..b0fe7f4f 100644 --- a/kraken/spot/__init__.py +++ b/kraken/spot/__init__.py @@ -11,5 +11,4 @@ from kraken.spot.staking import Staking from kraken.spot.trade import Trade from kraken.spot.user import User -from kraken.spot.utils import Utils from kraken.spot.websocket import KrakenSpotWSClient diff --git a/kraken/spot/funding/__init__.py b/kraken/spot/funding/__init__.py index 38493dda..153b98b0 100644 --- a/kraken/spot/funding/__init__.py +++ b/kraken/spot/funding/__init__.py @@ -44,9 +44,9 @@ class Funding(KrakenBaseSpotAPI): def __init__( self: "Funding", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", + key: str = "", + secret: str = "", + url: str = "", ) -> None: super().__init__(key=key, secret=secret, url=url) diff --git a/kraken/spot/market/__init__.py b/kraken/spot/market/__init__.py index ff4d9cc3..55d1e31d 100644 --- a/kraken/spot/market/__init__.py +++ b/kraken/spot/market/__init__.py @@ -6,6 +6,7 @@ """Module that implements the Kraken Spot market client""" +from functools import lru_cache from typing import List, Optional, Union from ...base_api import KrakenBaseSpotAPI @@ -44,9 +45,9 @@ class Market(KrakenBaseSpotAPI): def __init__( self: "Market", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", + key: str = "", + secret: str = "", + url: str = "", ) -> None: super().__init__(key=key, secret=secret, url=url) @@ -54,6 +55,52 @@ def __enter__(self: "Market") -> "Market": super().__enter__() return self + @lru_cache() + def get_asset( + self: "Market", + asset: str, + aclass: Optional[str] = None, + ) -> dict: + """ + Get information about a specific asset. Multiple assets + can be requested as follows: ``get_asset(asset="BTC,EUR")``. + + - https://docs.kraken.com/rest/#operation/getAssetInfo + + This function uses caching. Run ``get_asset.cache_clear()`` to clear. + + :param asset: The currency pair(s) of interest + :type asset: str + :param aclass: Filter by asset class + :type aclass: str, optional + :return: Information about the requested asset + :rtype: dict + + .. code-block:: python + :linenos: + :caption: Spot Market: Get information about a specific asset + + >>> from kraken.spot import Market + >>> market = Market() + >>> market.get_assets(asset="DOT") + { + 'DOT': { + 'aclass': 'currency', + 'altname': 'DOT', + 'decimals': 10, + 'display_decimals': 8, + 'collateral_value': 0.9, + 'status': 'enabled' + } + } + """ + params: dict = {"asset": asset} + if aclass is not None: + params["aclass"] = aclass + return self._request( # type: ignore[return-value] + method="GET", uri="/public/Assets", params=params, auth=False + ) + def get_assets( self: "Market", assets: Optional[Union[str, List[str]]] = None, @@ -118,13 +165,81 @@ def get_assets( method="GET", uri="/public/Assets", params=params, auth=False ) + @lru_cache() + def get_asset_pair(self: "Market", pair: str, info: Optional[str] = None) -> dict: + """ + Get information about a single asset/currency pair. Multiple currency pairs + can be requested as follows: ``get_asset_pair(asset="BTCUSD,BTCEUR")``. + + - https://docs.kraken.com/rest/#operation/getTradableAssetPairs + + This function uses caching. Run ``get_asset_pair.cache_clear()`` to to clear. + + :param asset: The asset of interest + :type asset: str + :param info: Filter by info, can be one of: ``info`` (all info), ``leverage`` + (leverage info), ``fees`` (fee info), and ``margin`` (margin info) + :type info: str, optional + :return: Information about the asset pair + :rtype: dict + + .. code-block:: python + :linenos: + :caption: Spot Market: Get information about a specific currency pair + + >>> from kraken.spot import Market + >>> Market().get_asset_pair(pair="XBTUSD") + { + 'XXBTZUSD': { + 'altname': 'XBTUSD', + 'wsname': 'XBT/USD', + 'aclass_base': 'currency', + 'base': 'XXBT', + 'aclass_quote': 'currency', + 'quote': 'ZUSD', + 'lot': 'unit', + 'cost_decimals': 5, + 'pair_decimals': 1, + 'lot_decimals': 8, + 'lot_multiplier': 1, + 'leverage_buy': [2, 3, 4, 5], + 'leverage_sell': [2, 3, 4, 5], + 'fees': [ + [0, 0.26], [50000, 0.24], [100000, 0.22], + [250000, 0.2], [500000, 0.18], [1000000, 0.16], + [2500000, 0.14], [5000000, 0.12], [10000000, 0.1] + ], + 'fees_maker': [ + [0, 0.16], [50000, 0.14], [100000, 0.12], + [250000, 0.1], [500000, 0.08], [1000000, 0.06], + [2500000, 0.04], [5000000, 0.02], [10000000, 0.0] + ], + 'fee_volume_currency': 'ZUSD', + 'margin_call': 80, + 'margin_stop': 40, + 'ordermin': '0.0001', + 'costmin': '0.5', + 'tick_size': '0.1', + 'status': 'online', + 'long_position_limit': 270, + 'short_position_limit': 180 + } + } + """ + params: dict = {"pair": pair} + if info is not None: + params["info"] = info + return self._request( # type: ignore[return-value] + method="GET", uri="/public/AssetPairs", params=params, auth=False + ) + def get_asset_pairs( self: "Market", pair: Optional[Union[str, List[str]]] = None, info: Optional[str] = None, ) -> dict: """ - Get information about the tradable asset pairs. Can be filtered by ``pair``. + Get information about the multiplle tradable asset pairs. Can be filtered by ``pair``. - https://docs.kraken.com/rest/#operation/getTradableAssetPairs diff --git a/kraken/spot/staking/__init__.py b/kraken/spot/staking/__init__.py index 93eff0aa..61b7abf7 100644 --- a/kraken/spot/staking/__init__.py +++ b/kraken/spot/staking/__init__.py @@ -44,9 +44,9 @@ class Staking(KrakenBaseSpotAPI): def __init__( self, - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", + key: str = "", + secret: str = "", + url: str = "", ) -> None: super().__init__(key=key, secret=secret, url=url) diff --git a/kraken/spot/trade/__init__.py b/kraken/spot/trade/__init__.py index 7437fe8c..269960eb 100644 --- a/kraken/spot/trade/__init__.py +++ b/kraken/spot/trade/__init__.py @@ -6,10 +6,13 @@ """Module that implements the Kraken Trade Spot client""" +from decimal import Decimal +from functools import lru_cache +from math import floor from typing import List, Optional, Union from ...base_api import KrakenBaseSpotAPI -from ..utils import Utils +from ...spot import Market class Trade(KrakenBaseSpotAPI): @@ -45,11 +48,12 @@ class Trade(KrakenBaseSpotAPI): def __init__( self: "Trade", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", + key: str = "", + secret: str = "", + url: str = "", ) -> None: super().__init__(key=key, secret=secret, url=url) + self.__market: Market = Market() def __enter__(self: "Trade") -> "Trade": super().__enter__() @@ -234,7 +238,7 @@ def create_order( >>> trade = Trade(key="api-key", secret="secret-key") >>> from datetime import datetime, timedelta, timezone >>> deadline = ( - ... datetime.now(timezone.utc) + timedelta(seconds=20) + ... datetime.now(timezone.utc) + timedelta(seconds=20) ... ).isoformat() >>> trade.create_order( ... ordertype="stop-loss-limit", @@ -292,7 +296,7 @@ def create_order( "pair": pair, "volume": volume if not truncate - else Utils.truncate(amount=volume, amount_type="volume", pair=pair), + else self.truncate(amount=volume, amount_type="volume", pair=pair), "stp_type": stptype, "starttm": starttm, "validate": validate, @@ -320,7 +324,7 @@ def create_order( params["price"] = ( price if not truncate - else Utils.truncate(amount=price, amount_type="price", pair=pair) + else self.truncate(amount=price, amount_type="price", pair=pair) ) if ordertype in ("stop-loss-limit", "take-profit-limit"): @@ -360,7 +364,7 @@ def create_order_batch( orders: List[dict], pair: str, deadline: Optional[str] = None, - validate: Optional[bool] = False, + validate: bool = False, ) -> dict: """ Create a batch of max 15 orders for a specifc asset pair. @@ -440,7 +444,7 @@ def edit_order( oflags: Optional[str] = None, deadline: Optional[str] = None, cancel_response: Optional[bool] = None, - validate: Optional[bool] = False, + validate: bool = False, userref: Optional[int] = None, ) -> dict: """ @@ -634,3 +638,62 @@ def cancel_order_batch(self: "Trade", orders: List[Union[str, int]]) -> dict: params={"orders": orders}, do_json=True, ) + + @lru_cache() + def truncate( + self: "Trade", + amount: Union[Decimal, float, int, str], + amount_type: str, + pair: str, + ) -> str: + """ + Kraken only allows volume and price amounts to be specified with a specific number of + decimal places, and these varry depending on the currency pair used. + + This function converts an amount of a specific type and pair to a string that uses + the correct number of decimal places. + + - https://support.kraken.com/hc/en-us/articles/4521313131540 + + This function uses caching. Run ``truncate.clear_cache()`` to clear. + + :param amount: The floating point number to represent + :type amount: Decimal | float | int | str + :param amount_type: What the amount represents. Either ``"price"`` or ``"volume"`` + :type amount_type: str + :param pair: The currency pair the amount is in reference to. + :type pair: str + :raises ValueError: If the ``amount_type`` is ``price`` and the price is less + than the costmin. + :raises ValueError: If the ``amount_type`` is ``volume`` and the volume is + less than the ordermin. + :raises ValueError: If no valid ``amount_type`` was passed. + :return: A string representation of the amount. + :rtype: str + """ + if amount_type not in ("price", "volume"): + raise ValueError("Amount type must be 'volume' or 'price'!") + + pair_data: dict = self.__market.get_asset_pair(pair=pair) + data: dict = pair_data[list(pair_data)[0]] + + pair_decimals: int = int(data["pair_decimals"]) + lot_decimals: int = int(data["lot_decimals"]) + + ordermin: Decimal = Decimal(data["ordermin"]) + costmin: Decimal = Decimal(data["costmin"]) + + amount = Decimal(amount) + decimals: int + + if amount_type == "price": + if costmin > amount: + raise ValueError(f"Price is less than the costmin: {costmin}!") + decimals = pair_decimals + else: # amount_type == "volume": + if ordermin > amount: + raise ValueError(f"Volume is less than the ordermin: {ordermin}!") + decimals = lot_decimals + + amount_rounded: float = floor(float(amount) * 10**decimals) / 10**decimals + return f"{amount_rounded:.{decimals}f}" diff --git a/kraken/spot/user/__init__.py b/kraken/spot/user/__init__.py index 045374b4..10484905 100644 --- a/kraken/spot/user/__init__.py +++ b/kraken/spot/user/__init__.py @@ -45,9 +45,9 @@ class User(KrakenBaseSpotAPI): def __init__( self: "User", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", + key: str = "", + secret: str = "", + url: str = "", ) -> None: super().__init__(key=key, secret=secret, url=url) diff --git a/kraken/spot/websocket/__init__.py b/kraken/spot/websocket/__init__.py index edcc8f55..9fe21d87 100644 --- a/kraken/spot/websocket/__init__.py +++ b/kraken/spot/websocket/__init__.py @@ -46,7 +46,7 @@ def __init__( client: KrakenSpotWSClient, endpoint: str, callback: Any, - is_auth: Optional[bool] = False, + is_auth: bool = False, ): self.__client: KrakenSpotWSClient = client self.__ws_endpoint: str = endpoint diff --git a/tests/spot/conftest.py b/tests/spot/conftest.py index 283f20fd..c6f83391 100644 --- a/tests/spot/conftest.py +++ b/tests/spot/conftest.py @@ -26,6 +26,11 @@ def spot_auth_market() -> Market: return Market(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) +@pytest.fixture +def spot_trade() -> Trade: + return Trade() + + @pytest.fixture def spot_auth_trade() -> Trade: return Trade(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) diff --git a/tests/spot/test_spot_market.py b/tests/spot/test_spot_market.py index 73740e97..6a7158dc 100644 --- a/tests/spot/test_spot_market.py +++ b/tests/spot/test_spot_market.py @@ -28,6 +28,18 @@ def test_get_system_status(spot_market: Market) -> None: @pytest.mark.spot @pytest.mark.spot_market +def test_get_asset(spot_market: Market) -> None: + """ + Checks the ``get_asset`` endpoint by requesting some asset info. + """ + assert is_not_error(spot_market.get_asset(asset="BTC")) + assert is_not_error(spot_market.get_asset(asset="BTC,EUR")) + assert is_not_error(spot_market.get_asset(asset="EUR", aclass="currency")) + + +@pytest.mark.spot +@pytest.mark.spot_market +@pytest.mark.selection def test_get_assets(spot_market: Market) -> None: """ Checks the ``get_assets`` endpoint by performing multiple @@ -38,18 +50,30 @@ def test_get_assets(spot_market: Market) -> None: {}, {"assets": "USD"}, {"assets": ["USD"]}, - {"assets": ["XBT", "USD"]}, + {"assets": ["XBT,USD"]}, {"assets": ["XBT", "USD"], "aclass": "currency"}, ): assert is_not_error(spot_market.get_assets(**params)) - sleep(1.5) + sleep(3) + + +@pytest.mark.spot +@pytest.mark.spot_market +def test_get_asset_pair(spot_market: Market) -> None: + """ + Checks the ``get_asset_pair`` endpoint by performing multiple + requests with different paramaters. + """ + assert is_not_error(spot_market.get_asset_pair(pair="BTCUSD")) + assert is_not_error(spot_market.get_asset_pair(pair="BTCUSD,ETHUSD")) + assert is_not_error(spot_market.get_asset_pair(pair="ETHUSD", info="info")) @pytest.mark.spot @pytest.mark.spot_market def test_get_asset_pairs(spot_market: Market) -> None: """ - Checks the ``get_tradable_asset_pair`` endpoint by performing multiple + Checks the ``get_asset_pairs`` endpoint by performing multiple requests with different paramaters and validating that the response does not contain the error key. """ diff --git a/tests/spot/test_spot_trade.py b/tests/spot/test_spot_trade.py index 3a143593..b172b1f0 100644 --- a/tests/spot/test_spot_trade.py +++ b/tests/spot/test_spot_trade.py @@ -7,6 +7,7 @@ """Module that implements the unit tests for the Spot trade client.""" from datetime import datetime, timedelta, timezone +from time import sleep import pytest @@ -246,3 +247,112 @@ def test_cancel_order_batch(spot_auth_trade: Trade) -> None: ), dict, ) + + +@pytest.mark.spot +@pytest.mark.spot_trade +def test_utils_truncate_price(spot_trade: Trade) -> None: + """ + Checks if the truncate function returns the expected results by + checking different inputs for price. + + NOTE: This test may break in the future since the lot_decimals, pair_decimals, + ordermin and costmin attributes could change. + """ + for price, expected in ( + (10000, "10000.0"), + (1000.1, "1000.1"), + (1000.01, "1000.0"), + (1000.001, "1000.0"), + ): + assert ( + spot_trade.truncate(amount=price, amount_type="price", pair="XBTUSD") + == expected + ) + sleep(3) + + for price, expected in ( + (2, "2.0000"), + (12.1, "12.1000"), + (13.105, "13.1050"), + (4.32595, "4.3259"), + ): + assert ( + spot_trade.truncate(amount=price, amount_type="price", pair="DOTUSD") + == expected + ) + sleep(3) + + +@pytest.mark.spot +@pytest.mark.spot_market +def test_utils_truncate_volume(spot_trade: Trade) -> None: + """ + Checks if the truncate function returns the expected results by + checking different inputs for volume. + + NOTE: This test may break in the future since the lot_decimals, pair_decimals, + ordermin and costmin attributes could change. + """ + for volume, expected in ( + (1, "1.00000000"), + (1.1, "1.10000000"), + (1.67, "1.67000000"), + (1.9328649837, "1.93286498"), + ): + assert ( + spot_trade.truncate(amount=volume, amount_type="volume", pair="XBTUSD") + == expected + ) + sleep(3) + + for volume, expected in ( + (2, "2.00000000"), + (12.158, "12.15800000"), + (13.1052093, "13.10520930"), + (4.32595342455, "4.32595342"), + ): + assert ( + spot_trade.truncate(amount=volume, amount_type="volume", pair="DOTUSD") + == expected + ) + sleep(3) + + +@pytest.mark.spot +@pytest.mark.spot_market +def test_utils_truncate_fail_price_costmin(spot_trade: Trade) -> None: + """ + Checks if the truncate function fails if the price is less than the costmin. + + NOTE: This test may break in the future since the lot_decimals, pair_decimals, + ordermin and costmin attributes could change. + """ + with pytest.raises(ValueError): + spot_trade.truncate(amount=0.001, amount_type="price", pair="XBTUSD") + + +@pytest.mark.spot +@pytest.mark.spot_market +def test_utils_truncate_fail_volume_ordermin(spot_trade: Trade) -> None: + """ + Checks if the truncate function fails if the volume is less than the ordermin. + + NOTE: This test may break in the future since the lot_decimals, pair_decimals, + ordermin and costmin attributes could change. + """ + with pytest.raises(ValueError): + spot_trade.truncate(amount=0.00001, amount_type="volume", pair="XBTUSD") + + +@pytest.mark.spot +@pytest.mark.spot_market +def test_utils_truncate_fail_invalid_amount_type(spot_trade: Trade) -> None: + """ + Checks if the truncate function fails when no valid ``amount_type`` was specified. + + NOTE: This test may break in the future since the lot_decimals, pair_decimals, + ordermin and costmin attributes could change. + """ + with pytest.raises(ValueError): + spot_trade.truncate(amount=1, amount_type="invalid", pair="XBTUSD") From 7c6f5695bb6b6a0c18be0d806e05631721086de3 Mon Sep 17 00:00:00 2001 From: Benjamin Thomas Schwertfeger Date: Sat, 20 May 2023 15:07:09 +0200 Subject: [PATCH 3/4] adusted caching --- kraken/spot/market/__init__.py | 140 +++++++++++++-------------------- kraken/spot/trade/__init__.py | 2 +- tests/spot/conftest.py | 23 ++++++ tests/spot/test_spot_market.py | 3 +- 4 files changed, 80 insertions(+), 88 deletions(-) diff --git a/kraken/spot/market/__init__.py b/kraken/spot/market/__init__.py index 55d1e31d..403cc738 100644 --- a/kraken/spot/market/__init__.py +++ b/kraken/spot/market/__init__.py @@ -58,19 +58,22 @@ def __enter__(self: "Market") -> "Market": @lru_cache() def get_asset( self: "Market", - asset: str, + asset: Optional[str] = None, aclass: Optional[str] = None, ) -> dict: """ - Get information about a specific asset. Multiple assets + Get information about one or more assets. Multiple assets can be requested as follows: ``get_asset(asset="BTC,EUR")``. + If left blank, all assets will be returned. + + For requesting a list of assets, use :func:`get_assets`. - https://docs.kraken.com/rest/#operation/getAssetInfo This function uses caching. Run ``get_asset.cache_clear()`` to clear. :param asset: The currency pair(s) of interest - :type asset: str + :type asset: str, optional :param aclass: Filter by asset class :type aclass: str, optional :return: Information about the requested asset @@ -82,7 +85,7 @@ def get_asset( >>> from kraken.spot import Market >>> market = Market() - >>> market.get_assets(asset="DOT") + >>> market.get_asset(asset="DOT") { 'DOT': { 'aclass': 'currency', @@ -93,8 +96,29 @@ def get_asset( 'status': 'enabled' } } + >>> market.get_asset(asset="MATIC,XBT") + { + 'MATIC': { + 'aclass': 'currency', + 'altname': 'MATIC', + 'decimals': 10, + 'display_decimals': 5, + 'collateral_value': 0.7, + 'status': 'enabled' + }, + 'XXBT': { + 'aclass': 'currency', + 'altname': 'XBT', + 'decimals': 10, + 'display_decimals': 5, + 'collateral_value': 1.0, + 'status': 'enabled' + } + } """ - params: dict = {"asset": asset} + params: dict = {} + if asset is not None: + params["asset"] = asset if aclass is not None: params["aclass"] = aclass return self._request( # type: ignore[return-value] @@ -107,13 +131,13 @@ def get_assets( aclass: Optional[str] = None, ) -> dict: """ - Get information about all available assets for trading, staking, deposit, + Get information about available assets for trading, staking, deposit, and withdraw. - https://docs.kraken.com/rest/#operation/getAssetInfo - :param asset: Filter by asset(s) - :type asset: str | List[str], optional + :param assets: Filter by asset(s) + :type assets: str | List[str], optional :param aclass: Filter by asset class :type aclass: str, optional :return: Information about the requested assets @@ -125,18 +149,7 @@ def get_assets( >>> from kraken.spot import Market >>> market = Market() - >>> market.get_assets(assets="DOT") - { - 'DOT': { - 'aclass': 'currency', - 'altname': 'DOT', - 'decimals': 10, - 'display_decimals': 8, - 'collateral_value': 0.9, - 'status': 'enabled' - } - } - >>> market.get_assets(assets=["MATIC", "XBT"]) + >>> market.get_assets(assets=["MATIC", "XBT"]) # same as market.get_assets(assets="MATIC,XBT"]) { 'MATIC': { 'aclass': 'currency', @@ -156,77 +169,38 @@ def get_assets( } } """ - params: dict = {} if assets is not None: - params["asset"] = self._to_str_list(assets) - if aclass is not None: - params["aclass"] = aclass - return self._request( # type: ignore[return-value] - method="GET", uri="/public/Assets", params=params, auth=False - ) + assets = self._to_str_list(assets) + return self.get_asset(asset=assets, aclass=aclass) @lru_cache() - def get_asset_pair(self: "Market", pair: str, info: Optional[str] = None) -> dict: + def get_asset_pair( + self: "Market", pair: Optional[str] = None, info: Optional[str] = None + ) -> dict: """ Get information about a single asset/currency pair. Multiple currency pairs - can be requested as follows: ``get_asset_pair(asset="BTCUSD,BTCEUR")``. + can be requested as follows: ``get_asset_pair(asset="BTCUSD,BTCEUR")``. If left + blank, all currency pairs will be returned. + + For requesting a list of asset pairs, use :func:`get_asset_pairs`. + + See :func:`get_asset_pairs` for example output. - https://docs.kraken.com/rest/#operation/getTradableAssetPairs - This function uses caching. Run ``get_asset_pair.cache_clear()`` to to clear. + This function uses caching. Run ``get_asset_pair.cache_clear()`` to clear. - :param asset: The asset of interest - :type asset: str + :param pair: The asset(s) of interest + :type pair: str, optional :param info: Filter by info, can be one of: ``info`` (all info), ``leverage`` (leverage info), ``fees`` (fee info), and ``margin`` (margin info) :type info: str, optional :return: Information about the asset pair :rtype: dict - - .. code-block:: python - :linenos: - :caption: Spot Market: Get information about a specific currency pair - - >>> from kraken.spot import Market - >>> Market().get_asset_pair(pair="XBTUSD") - { - 'XXBTZUSD': { - 'altname': 'XBTUSD', - 'wsname': 'XBT/USD', - 'aclass_base': 'currency', - 'base': 'XXBT', - 'aclass_quote': 'currency', - 'quote': 'ZUSD', - 'lot': 'unit', - 'cost_decimals': 5, - 'pair_decimals': 1, - 'lot_decimals': 8, - 'lot_multiplier': 1, - 'leverage_buy': [2, 3, 4, 5], - 'leverage_sell': [2, 3, 4, 5], - 'fees': [ - [0, 0.26], [50000, 0.24], [100000, 0.22], - [250000, 0.2], [500000, 0.18], [1000000, 0.16], - [2500000, 0.14], [5000000, 0.12], [10000000, 0.1] - ], - 'fees_maker': [ - [0, 0.16], [50000, 0.14], [100000, 0.12], - [250000, 0.1], [500000, 0.08], [1000000, 0.06], - [2500000, 0.04], [5000000, 0.02], [10000000, 0.0] - ], - 'fee_volume_currency': 'ZUSD', - 'margin_call': 80, - 'margin_stop': 40, - 'ordermin': '0.0001', - 'costmin': '0.5', - 'tick_size': '0.1', - 'status': 'online', - 'long_position_limit': 270, - 'short_position_limit': 180 - } - } """ - params: dict = {"pair": pair} + params: dict = {} + if pair is not None: + params["pair"] = pair if info is not None: params["info"] = info return self._request( # type: ignore[return-value] @@ -243,8 +217,8 @@ def get_asset_pairs( - https://docs.kraken.com/rest/#operation/getTradableAssetPairs - :param asset: Filter by asset pair(s) - :type asset: str | List[str], optional + :param pair: Filter by asset pair(s) + :type pair: str | List[str], optional :param info: Filter by info, can be one of: ``info`` (all info), ``leverage`` (leverage info), ``fees`` (fee info), and ``margin`` (margin info) :type info: str, optional @@ -294,15 +268,9 @@ def get_asset_pairs( } } """ - params: dict = {} if pair is not None: - params["pair"] = self._to_str_list(pair) - if info is not None: - params["info"] = info - - return self._request( # type: ignore[return-value] - method="GET", uri="/public/AssetPairs", params=params, auth=False - ) + pair = self._to_str_list(pair) + return self.get_asset_pair(pair=pair, info=info) def get_ticker( self: "Market", pair: Optional[Union[str, List[str]]] = None diff --git a/kraken/spot/trade/__init__.py b/kraken/spot/trade/__init__.py index 269960eb..60e43af4 100644 --- a/kraken/spot/trade/__init__.py +++ b/kraken/spot/trade/__init__.py @@ -141,7 +141,7 @@ def create_order( :type expiretm: str, optional :param close_ordertype: Conditional close order type, one of: ``limit``, ``stop-loss``, ``take-profit``, ``stop-loss-limit``, ``take-profit-limit`` - (see the referenced Kraken documentaion for more information) + (see the referenced Kraken documentaion for more information) :type close_ordertype: str, optional :param close_price: Conditional close price :type close_price: str | int | float, optional diff --git a/tests/spot/conftest.py b/tests/spot/conftest.py index c6f83391..ebe127ef 100644 --- a/tests/spot/conftest.py +++ b/tests/spot/conftest.py @@ -4,6 +4,8 @@ # Github: https://github.com/btschwertfeger # +"""Module providing fixtures used for the unit tests regarding the Kraken Spot API.""" + import os import pytest @@ -13,34 +15,55 @@ @pytest.fixture def spot_auth_user() -> User: + """ + Fixture providing an authenticated Spot user client. + """ return User(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) @pytest.fixture def spot_market() -> Market: + """ + Fixture providing an unauthenticated Spot market client. + """ return Market() @pytest.fixture def spot_auth_market() -> Market: + """ + Fixture providing an authenticated Spot market client. + """ return Market(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) @pytest.fixture def spot_trade() -> Trade: + """ + Fixture providing an unauthenticated Spot trade client. + """ return Trade() @pytest.fixture def spot_auth_trade() -> Trade: + """ + Fixture providing an authenticated Spot trade client. + """ return Trade(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) @pytest.fixture def spot_auth_funding() -> Funding: + """ + Fixture providing an authenticated Spot funding client. + """ return Funding(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) @pytest.fixture def spot_auth_staking() -> Staking: + """ + Fixture providing an authenticated Spot staking client. + """ return Staking(key=os.getenv("SPOT_API_KEY"), secret=os.getenv("SPOT_SECRET_KEY")) diff --git a/tests/spot/test_spot_market.py b/tests/spot/test_spot_market.py index 6a7158dc..34f3c3dc 100644 --- a/tests/spot/test_spot_market.py +++ b/tests/spot/test_spot_market.py @@ -32,6 +32,7 @@ def test_get_asset(spot_market: Market) -> None: """ Checks the ``get_asset`` endpoint by requesting some asset info. """ + assert is_not_error(spot_market.get_asset()) assert is_not_error(spot_market.get_asset(asset="BTC")) assert is_not_error(spot_market.get_asset(asset="BTC,EUR")) assert is_not_error(spot_market.get_asset(asset="EUR", aclass="currency")) @@ -39,7 +40,6 @@ def test_get_asset(spot_market: Market) -> None: @pytest.mark.spot @pytest.mark.spot_market -@pytest.mark.selection def test_get_assets(spot_market: Market) -> None: """ Checks the ``get_assets`` endpoint by performing multiple @@ -64,6 +64,7 @@ def test_get_asset_pair(spot_market: Market) -> None: Checks the ``get_asset_pair`` endpoint by performing multiple requests with different paramaters. """ + assert is_not_error(spot_market.get_asset_pair()) assert is_not_error(spot_market.get_asset_pair(pair="BTCUSD")) assert is_not_error(spot_market.get_asset_pair(pair="BTCUSD,ETHUSD")) assert is_not_error(spot_market.get_asset_pair(pair="ETHUSD", info="info")) From 38944e4b2905f0301343dd6968a5846fe32356e1 Mon Sep 17 00:00:00 2001 From: Benjamin Thomas Schwertfeger Date: Sat, 20 May 2023 16:46:33 +0200 Subject: [PATCH 4/4] added a ensure_string decorator --- kraken/base_api/__init__.py | 70 +++++++++++---- kraken/futures/market/__init__.py | 24 ++--- kraken/futures/trade/__init__.py | 40 ++++----- kraken/futures/user/__init__.py | 26 +++--- kraken/spot/funding/__init__.py | 10 +-- kraken/spot/market/__init__.py | 144 ++++++++---------------------- kraken/spot/staking/__init__.py | 4 +- kraken/spot/trade/__init__.py | 57 ++++++------ kraken/spot/user/__init__.py | 57 ++++++------ kraken/spot/websocket/__init__.py | 4 +- kraken/spot/ws_client/__init__.py | 63 ++++++------- tests/spot/test_spot_market.py | 25 ------ tests/spot/test_spot_trade.py | 21 ++--- 13 files changed, 244 insertions(+), 301 deletions(-) diff --git a/kraken/base_api/__init__.py b/kraken/base_api/__init__.py index c2f9bbcd..4e0a4567 100644 --- a/kraken/base_api/__init__.py +++ b/kraken/base_api/__init__.py @@ -11,7 +11,7 @@ import json import time import urllib.parse -from typing import Any, Dict, List, Optional, Type, Union +from typing import Any, Callable, Dict, List, Optional, Type, Union from urllib.parse import urljoin from uuid import uuid1 @@ -20,6 +20,59 @@ from kraken.exceptions import KrakenException +def defined(value: Any) -> bool: + """Returns ``True`` if ``value`` is not ``None``""" + return value is not None + + +def ensure_string(parameter_name: str) -> Callable: + """ + This function is intended to be used as decorator + to ensure that a specific parameter is of type string. + + .. code-block:: python + :linenos: + :caption: Example + + @ensure_string("assets") + @lru_cache() + def get_assets( + self: "Market", + assets: Optional[Union[str, List[str]]] = None, + aclass: Optional[str] = None, + ) -> dict: + # If the function was called using + # get_assets(assets=["BTC","USD","ETH"]) + # there will be no error because of the non-hashable + # parameters, because the decorator transforms the + # list into: "BTC,USD,ETH" + + :param parameter_name: The parameter name to transform into string + :type parameter_name: str + :return: The called function + :rtype: Callable + """ + + def decorator(func: Callable) -> Callable: + def wrapper(*args: Any, **kwargs: Any) -> Any: + if parameter_name in kwargs: + value: Any = kwargs[parameter_name] + if isinstance(value, str) or value is None: + pass + elif isinstance(value, list): + kwargs[parameter_name] = ",".join(value) + else: + raise ValueError( + f"{parameter_name} cannot be {type(kwargs[parameter_name])}!" + ) + + return func(*args, **kwargs) + + return wrapper + + return decorator + + class KrakenErrorHandler: """ Class that checks if the response of a request contains error messages and @@ -313,21 +366,6 @@ def return_unique_id(self: "KrakenBaseSpotAPI") -> str: """ return "".join(str(uuid1()).split("-")) - def _to_str_list(self: "KrakenBaseSpotAPI", value: Union[str, list]) -> str: - """ - Converts a list to a comme separated string - - :param value: The value to convert to e.g., ["XBT", "USD"] => "XBT,USD" - :type value: Union[str,dict] - :return: The content ov `value` as comma-separated string - :rtype: str - """ - if isinstance(value, str): - return value - if isinstance(value, list): - return ",".join(value) - raise ValueError("a must be type of str or list of strings") - def __enter__(self: "KrakenBaseSpotAPI") -> "KrakenBaseSpotAPI": return self diff --git a/kraken/futures/market/__init__.py b/kraken/futures/market/__init__.py index d589862e..7fec0c42 100644 --- a/kraken/futures/market/__init__.py +++ b/kraken/futures/market/__init__.py @@ -8,7 +8,7 @@ from functools import lru_cache from typing import List, Optional, Union -from ...base_api import KrakenBaseFuturesAPI +from ...base_api import KrakenBaseFuturesAPI, defined class Market(KrakenBaseFuturesAPI): @@ -115,9 +115,9 @@ def get_ohlc( raise ValueError(f"resolution must be in {resolutions}") params: dict = {} - if from_ is not None: + if defined(from_): params["from"] = from_ - if to is not None: + if defined(to): params["to"] = to return self._request( # type: ignore[return-value] method="GET", @@ -313,7 +313,7 @@ def get_orderbook(self: "Market", symbol: Optional[str] = None) -> dict: } """ params: dict = {} - if symbol is not None: + if defined(symbol): params["symbol"] = symbol return self._request( # type: ignore[return-value] @@ -535,9 +535,9 @@ def get_trade_history( ] """ params: dict = {} - if symbol is not None: + if defined(symbol): params["symbol"] = symbol - if lastTime is not None: + if defined(lastTime): params["lastTime"] = lastTime params.update(kwargs) return self._request( # type: ignore[return-value] @@ -646,7 +646,7 @@ def set_leverage_preference( {'result': 'success', 'serverTime': '2023-04-04T05:59:49.576Z'} """ params: dict = {"symbol": symbol} - if maxLeverage is not None: + if defined(maxLeverage): params["maxLeverage"] = maxLeverage return self._request( # type: ignore[return-value] @@ -745,15 +745,15 @@ def _get_historical_events( :type auth: bool """ params: dict = {} - if before is not None: + if defined(before): params["before"] = before - if continuation_token is not None: + if defined(continuation_token): params["continuation_token"] = continuation_token - if since is not None: + if defined(since): params["since"] = since - if sort is not None: + if defined(sort): params["sort"] = sort - if tradeable is not None: + if defined(tradeable): params["tradeable"] = tradeable params.update(kwargs) return self._request( # type: ignore[return-value] diff --git a/kraken/futures/trade/__init__.py b/kraken/futures/trade/__init__.py index dd5ced72..1b92a7ce 100644 --- a/kraken/futures/trade/__init__.py +++ b/kraken/futures/trade/__init__.py @@ -8,7 +8,7 @@ from typing import List, Optional, Tuple, Union -from ...base_api import KrakenBaseFuturesAPI +from ...base_api import KrakenBaseFuturesAPI, defined class Trade(KrakenBaseFuturesAPI): @@ -93,7 +93,7 @@ def get_fills(self: "Trade", lastFillTime: Optional[str] = None) -> dict: } """ query_params: dict = {} - if lastFillTime: + if defined(lastFillTime): query_params["lastFillTime"] = lastFillTime return self._request( # type: ignore[return-value] method="GET", @@ -248,7 +248,7 @@ def cancel_all_orders(self: "Trade", symbol: Optional[str] = None) -> dict: } """ params: dict = {} - if symbol is not None: + if defined(symbol): params["symbol"] = symbol return self._request( # type: ignore[return-value] method="POST", @@ -331,9 +331,9 @@ def cancel_order( """ params: dict = {} - if order_id is not None: + if defined(order_id): params["order_id"] = order_id - elif cliOrdId is not None: + elif defined(cliOrdId): params["cliOrdId"] = cliOrdId else: raise ValueError("Either order_id or cliOrdId must be set!") @@ -394,18 +394,18 @@ def edit_order( } """ params: dict = {} - if orderId is not None: + if defined(orderId): params["orderId"] = orderId - elif cliOrdId is not None: + elif defined(cliOrdId): params["cliOrdId"] = cliOrdId else: raise ValueError("Either orderId or cliOrdId must be set!") - if limitPrice is not None: + if defined(limitPrice): params["limitPrice"] = limitPrice - if size is not None: + if defined(size): params["size"] = size - if stopPrice is not None: + if defined(stopPrice): params["stopPrice"] = stopPrice return self._request( # type: ignore[return-value] @@ -448,9 +448,9 @@ def get_orders_status( {'result': 'success', 'serverTime': '2023-04-04T17:27:29.667Z', 'orders': []} """ params = {} - if orderIds is not None: + if defined(orderIds): params["orderIds"] = orderIds - elif cliOrdIds is not None: + elif defined(cliOrdIds): params["cliOrdIds"] = cliOrdIds return self._request( # type: ignore[return-value] @@ -647,22 +647,22 @@ def create_order( "size": size, "symbol": symbol, } - if cliOrdId is not None: + if defined(cliOrdId): params["cliOrdId"] = cliOrdId - if limitPrice is not None: + if defined(limitPrice): params["limitPrice"] = limitPrice - if reduceOnly is not None: + if defined(reduceOnly): params["reduceOnly"] = reduceOnly - if stopPrice is not None: + if defined(stopPrice): params["stopPrice"] = stopPrice - if triggerSignal is not None: - trigger_signals = ("mark", "spot", "last") + if defined(triggerSignal): + trigger_signals: tuple = ("mark", "spot", "last") if triggerSignal not in trigger_signals: raise ValueError(f"Trigger signal must be in [{trigger_signals}]!") params["triggerSignal"] = triggerSignal - if trailingStopDeviationUnit is not None: + if defined(trailingStopDeviationUnit): params["trailingStopDeviationUnit"] = trailingStopDeviationUnit - if trailingStopMaxDeviation is not None: + if defined(trailingStopMaxDeviation): params["trailingStopMaxDeviation"] = trailingStopMaxDeviation return self._request( # type: ignore[return-value] diff --git a/kraken/futures/user/__init__.py b/kraken/futures/user/__init__.py index 1ff4a61a..b8d3a15a 100644 --- a/kraken/futures/user/__init__.py +++ b/kraken/futures/user/__init__.py @@ -10,7 +10,7 @@ import requests -from ...base_api import KrakenBaseFuturesAPI +from ...base_api import KrakenBaseFuturesAPI, defined class User(KrakenBaseFuturesAPI): @@ -308,19 +308,19 @@ def get_account_log( """ params: dict = {} - if before is not None: + if defined(before): params["before"] = before - if count is not None: + if defined(count): params["count"] = count - if from_ is not None: + if defined(from_): params["from"] = from_ - if info is not None: + if defined(info): params["info"] = info - if since is not None: + if defined(since): params["since"] = since - if sort is not None: + if defined(sort): params["sort"] = sort - if to is not None: + if defined(to): params["to"] = to return self._request( # type: ignore[return-value] method="GET", @@ -389,15 +389,15 @@ def _get_historical_events( :type auth: bool """ params: dict = {} - if before is not None: + if defined(before): params["before"] = before - if continuation_token is not None: + if defined(continuation_token): params["continuation_token"] = continuation_token - if since is not None: + if defined(since): params["since"] = since - if sort is not None: + if defined(sort): params["sort"] = sort - if tradeable is not None: + if defined(tradeable): params["tradeable"] = tradeable params.update(kwargs) return self._request( # type: ignore[return-value] diff --git a/kraken/spot/funding/__init__.py b/kraken/spot/funding/__init__.py index 153b98b0..acd70080 100644 --- a/kraken/spot/funding/__init__.py +++ b/kraken/spot/funding/__init__.py @@ -8,7 +8,7 @@ from typing import List, Optional, Union -from ...base_api import KrakenBaseSpotAPI +from ...base_api import KrakenBaseSpotAPI, defined class Funding(KrakenBaseSpotAPI): @@ -197,9 +197,9 @@ def get_recent_deposits_status( ] """ params: dict = {} - if asset is not None: + if defined(asset): params["asset"] = asset - if method is not None: + if defined(method): params["method"] = method return self._request( # type: ignore[return-value] method="POST", uri="/private/DepositStatus", params=params @@ -329,9 +329,9 @@ def get_recent_withdraw_status( ] """ params: dict = {} - if asset is not None: + if defined(asset): params["asset"] = asset - if method is not None: + if defined(method): params["method"] = method return self._request( # type: ignore[return-value] method="POST", uri="/private/WithdrawStatus", params=params diff --git a/kraken/spot/market/__init__.py b/kraken/spot/market/__init__.py index 403cc738..2ff53281 100644 --- a/kraken/spot/market/__init__.py +++ b/kraken/spot/market/__init__.py @@ -9,7 +9,7 @@ from functools import lru_cache from typing import List, Optional, Union -from ...base_api import KrakenBaseSpotAPI +from ...base_api import KrakenBaseSpotAPI, defined, ensure_string class Market(KrakenBaseSpotAPI): @@ -55,37 +55,35 @@ def __enter__(self: "Market") -> "Market": super().__enter__() return self + @ensure_string("assets") @lru_cache() - def get_asset( + def get_assets( self: "Market", - asset: Optional[str] = None, + assets: Optional[Union[str, List[str]]] = None, aclass: Optional[str] = None, ) -> dict: """ - Get information about one or more assets. Multiple assets - can be requested as follows: ``get_asset(asset="BTC,EUR")``. - If left blank, all assets will be returned. - - For requesting a list of assets, use :func:`get_assets`. + Get information about one or more assets. + If ``assets`` is not specified, all assets will be returned. - https://docs.kraken.com/rest/#operation/getAssetInfo - This function uses caching. Run ``get_asset.cache_clear()`` to clear. + This function uses caching. Run ``get_assets.cache_clear()`` to clear. - :param asset: The currency pair(s) of interest - :type asset: str, optional + :param assets: Filter by asset(s) + :type assets: str | List[str], optional :param aclass: Filter by asset class :type aclass: str, optional - :return: Information about the requested asset + :return: Information about the requested assets :rtype: dict .. code-block:: python :linenos: - :caption: Spot Market: Get information about a specific asset + :caption: Spot Market: Get information about the available assets >>> from kraken.spot import Market >>> market = Market() - >>> market.get_asset(asset="DOT") + >>> market.get_assets(asset="DOT") { 'DOT': { 'aclass': 'currency', @@ -96,8 +94,7 @@ def get_asset( 'status': 'enabled' } } - >>> market.get_asset(asset="MATIC,XBT") - { + >>> market.get_assets(assets=["MATIC", "XBT"]) # same as market.get_assets(assets="MATIC,XBT"]) 'MATIC': { 'aclass': 'currency', 'altname': 'MATIC', @@ -117,106 +114,29 @@ def get_asset( } """ params: dict = {} - if asset is not None: - params["asset"] = asset - if aclass is not None: + if defined(assets): + params["asset"] = assets + if defined(aclass): params["aclass"] = aclass return self._request( # type: ignore[return-value] method="GET", uri="/public/Assets", params=params, auth=False ) - def get_assets( - self: "Market", - assets: Optional[Union[str, List[str]]] = None, - aclass: Optional[str] = None, - ) -> dict: - """ - Get information about available assets for trading, staking, deposit, - and withdraw. - - - https://docs.kraken.com/rest/#operation/getAssetInfo - - :param assets: Filter by asset(s) - :type assets: str | List[str], optional - :param aclass: Filter by asset class - :type aclass: str, optional - :return: Information about the requested assets - :rtype: dict - - .. code-block:: python - :linenos: - :caption: Spot Market: Get information about the available assets - - >>> from kraken.spot import Market - >>> market = Market() - >>> market.get_assets(assets=["MATIC", "XBT"]) # same as market.get_assets(assets="MATIC,XBT"]) - { - 'MATIC': { - 'aclass': 'currency', - 'altname': 'MATIC', - 'decimals': 10, - 'display_decimals': 5, - 'collateral_value': 0.7, - 'status': 'enabled' - }, - 'XXBT': { - 'aclass': 'currency', - 'altname': 'XBT', - 'decimals': 10, - 'display_decimals': 5, - 'collateral_value': 1.0, - 'status': 'enabled' - } - } - """ - if assets is not None: - assets = self._to_str_list(assets) - return self.get_asset(asset=assets, aclass=aclass) - + @ensure_string("pair") @lru_cache() - def get_asset_pair( - self: "Market", pair: Optional[str] = None, info: Optional[str] = None - ) -> dict: - """ - Get information about a single asset/currency pair. Multiple currency pairs - can be requested as follows: ``get_asset_pair(asset="BTCUSD,BTCEUR")``. If left - blank, all currency pairs will be returned. - - For requesting a list of asset pairs, use :func:`get_asset_pairs`. - - See :func:`get_asset_pairs` for example output. - - - https://docs.kraken.com/rest/#operation/getTradableAssetPairs - - This function uses caching. Run ``get_asset_pair.cache_clear()`` to clear. - - :param pair: The asset(s) of interest - :type pair: str, optional - :param info: Filter by info, can be one of: ``info`` (all info), ``leverage`` - (leverage info), ``fees`` (fee info), and ``margin`` (margin info) - :type info: str, optional - :return: Information about the asset pair - :rtype: dict - """ - params: dict = {} - if pair is not None: - params["pair"] = pair - if info is not None: - params["info"] = info - return self._request( # type: ignore[return-value] - method="GET", uri="/public/AssetPairs", params=params, auth=False - ) - def get_asset_pairs( self: "Market", pair: Optional[Union[str, List[str]]] = None, info: Optional[str] = None, ) -> dict: """ - Get information about the multiplle tradable asset pairs. Can be filtered by ``pair``. + Get information about a single or multiple asset/currency pair(s). + If ``pair`` is left blank, all currency pairs will be returned. - https://docs.kraken.com/rest/#operation/getTradableAssetPairs + This function uses caching. Run ``get_asset_pairs.cache_clear()`` to clear. + :param pair: Filter by asset pair(s) :type pair: str | List[str], optional :param info: Filter by info, can be one of: ``info`` (all info), ``leverage`` @@ -268,10 +188,16 @@ def get_asset_pairs( } } """ - if pair is not None: - pair = self._to_str_list(pair) - return self.get_asset_pair(pair=pair, info=info) + params: dict = {} + if defined(pair): + params["pair"] = pair + if defined(info): + params["info"] = info + return self._request( # type: ignore[return-value] + method="GET", uri="/public/AssetPairs", params=params, auth=False + ) + @ensure_string("pair") def get_ticker( self: "Market", pair: Optional[Union[str, List[str]]] = None ) -> dict: @@ -307,8 +233,8 @@ def get_ticker( } """ params: dict = {} - if pair is not None: - params["pair"] = self._to_str_list(pair) + if defined(pair): + params["pair"] = pair return self._request( # type: ignore[return-value] method="GET", uri="/public/Ticker", params=params, auth=False ) @@ -356,7 +282,7 @@ def get_ohlc( } """ params: dict = {"pair": pair, "interval": interval} - if since is not None: + if defined(since): params["since"] = since return self._request( # type: ignore[return-value] method="GET", uri="/public/OHLC", params=params, auth=False @@ -434,7 +360,7 @@ def get_recent_trades( """ params: dict = {"pair": pair} - if since is not None: + if defined(since): params["since"] = None return self._request( # type: ignore[return-value] method="GET", uri="/public/Trades", params=params, auth=False @@ -471,7 +397,7 @@ def get_recent_spreads( } """ params: dict = {"pair": pair} - if since is not None: + if defined(since): params["since"] = since return self._request( # type: ignore[return-value] method="GET", uri="/public/Spread", params=params, auth=False diff --git a/kraken/spot/staking/__init__.py b/kraken/spot/staking/__init__.py index 61b7abf7..477fa32b 100644 --- a/kraken/spot/staking/__init__.py +++ b/kraken/spot/staking/__init__.py @@ -8,7 +8,7 @@ from typing import List, Optional, Union -from ...base_api import KrakenBaseSpotAPI +from ...base_api import KrakenBaseSpotAPI, defined class Staking(KrakenBaseSpotAPI): @@ -135,7 +135,7 @@ def unstake_asset( { 'refid': 'BOG5AE5-KSCNR4-VPNPEV' } """ params: dict = {"asset": asset, "amount": amount} - if method is not None: + if defined(method): params["method"] = method return self._request( # type: ignore[return-value] diff --git a/kraken/spot/trade/__init__.py b/kraken/spot/trade/__init__.py index 60e43af4..a413407d 100644 --- a/kraken/spot/trade/__init__.py +++ b/kraken/spot/trade/__init__.py @@ -11,7 +11,7 @@ from math import floor from typing import List, Optional, Union -from ...base_api import KrakenBaseSpotAPI +from ...base_api import KrakenBaseSpotAPI, defined, ensure_string from ...spot import Market @@ -59,6 +59,7 @@ def __enter__(self: "Trade") -> "Trade": super().__enter__() return self + @ensure_string("oflags") def create_order( self: "Trade", ordertype: str, @@ -310,17 +311,17 @@ def create_order( "take-profit-limit", ) - if trigger is not None: + if defined(trigger): if ordertype not in trigger_ordertypes: raise ValueError(f"Cannot use trigger on ordertype {ordertype}!") params["trigger"] = trigger - if timeinforce is not None: + if defined(timeinforce): params["timeinforce"] = timeinforce - if expiretm is not None: + if defined(expiretm): params["expiretm"] = str(expiretm) - if price is not None: + if defined(price): params["price"] = ( price if not truncate @@ -328,31 +329,31 @@ def create_order( ) if ordertype in ("stop-loss-limit", "take-profit-limit"): - if price2 is None: + if not defined(price2): raise ValueError( f"Ordertype {ordertype} requires a secondary price (price2)!" ) params["price2"] = str(price2) elif price2 is not None: raise ValueError( - f"Ordertype {ordertype} dont allow a second price (price2)!" + f"Ordertype {ordertype} dos not allow a second price (price2)!" ) - if leverage is not None: + if defined(leverage): params["leverage"] = str(leverage) - if oflags is not None: - params["oflags"] = self._to_str_list(oflags) - if close_ordertype is not None: + if defined(oflags): + params["oflags"] = oflags + if defined(close_ordertype): params["close[ordertype]"] = close_ordertype - if close_price is not None: + if defined(close_price): params["close[price]"] = str(close_price) - if close_price2 is not None: + if defined(close_price2): params["close[price2]"] = str(close_price2) - if deadline is not None: + if defined(deadline): params["deadline"] = deadline - if userref is not None: + if defined(userref): params["userref"] = userref - if displayvol is not None: + if defined(displayvol): params["displayvol"] = str(displayvol) return self._request( # type: ignore[return-value] @@ -428,12 +429,13 @@ def create_order_batch( } """ params: dict = {"orders": orders, "pair": pair, "validate": validate} - if deadline is not None: + if defined(deadline): params["deadline"] = deadline return self._request( # type: ignore[return-value] method="POST", uri="/private/AddOrderBatch", params=params, do_json=True ) + @ensure_string("oflags") def edit_order( self: "Trade", txid: str, @@ -502,24 +504,25 @@ def edit_order( } """ params: dict = {"txid": txid, "pair": pair, "validate": validate} - if userref is not None: + if defined(userref): params["userref"] = userref - if volume is not None: + if defined(volume): params["volume"] = volume - if price is not None: + if defined(price): params["price"] = price - if price2 is not None: + if defined(price2): params["price2"] = price2 - if oflags is not None: - params["oflags"] = self._to_str_list(oflags) - if cancel_response is not None: + if defined(oflags): + params["oflags"] = oflags + if defined(cancel_response): params["cancel_response"] = cancel_response - if deadline is not None: + if defined(deadline): params["deadline"] = deadline return self._request( # type: ignore[return-value] "POST", uri="/private/EditOrder", params=params ) + @ensure_string("txid") def cancel_order(self: "Trade", txid: str) -> dict: """ Cancel a specific order by ``txid``. Instead of a transaction id @@ -547,7 +550,7 @@ def cancel_order(self: "Trade", txid: str) -> dict: return self._request( # type: ignore[return-value] method="POST", uri="/private/CancelOrder", - params={"txid": self._to_str_list(txid)}, + params={"txid": txid}, ) def cancel_all_orders(self: "Trade") -> dict: @@ -674,7 +677,7 @@ def truncate( if amount_type not in ("price", "volume"): raise ValueError("Amount type must be 'volume' or 'price'!") - pair_data: dict = self.__market.get_asset_pair(pair=pair) + pair_data: dict = self.__market.get_asset_pairs(pair=pair) data: dict = pair_data[list(pair_data)[0]] pair_decimals: int = int(data["pair_decimals"]) diff --git a/kraken/spot/user/__init__.py b/kraken/spot/user/__init__.py index 10484905..15b5e625 100644 --- a/kraken/spot/user/__init__.py +++ b/kraken/spot/user/__init__.py @@ -8,7 +8,7 @@ from decimal import Decimal from typing import List, Optional, Union -from ...base_api import KrakenBaseSpotAPI +from ...base_api import KrakenBaseSpotAPI, defined, ensure_string class User(KrakenBaseSpotAPI): @@ -201,7 +201,7 @@ def get_trade_balance(self: "User", asset: Optional[str] = "ZUSD") -> dict: } """ params: dict = {} - if asset is not None: + if defined(asset): params["asset"] = asset return self._request( # type: ignore[return-value] method="POST", uri="/private/TradeBalance", params=params @@ -266,7 +266,7 @@ def get_open_orders( } """ params: dict = {"trades": trades} - if userref is not None: + if defined(userref): params["userref"] = userref return self._request( # type: ignore[return-value] method="POST", uri="/private/OpenOrders", params=params @@ -346,19 +346,20 @@ def get_closed_orders( } """ params: dict = {"trades": trades, "closetime": closetime} - if userref is not None: + if defined(userref): params["userref"] = userref - if start is not None: + if defined(start): params["start"] = start - if end is not None: + if defined(end): params["end"] = end - if ofs is not None: + if defined(ofs): params["ofs"] = ofs return self._request( # type: ignore[return-value] method="POST", uri="/private/ClosedOrders", params=params ) + @ensure_string("txid") def get_orders_info( self: "User", txid: Union[List[str], str] = None, @@ -458,9 +459,7 @@ def get_orders_info( "trades": trades, "consolidate_taker": consolidate_taker, } - if isinstance(txid, list): - params["txid"] = self._to_str_list(txid) - if userref is not None: + if defined(userref): params["userref"] = userref return self._request( # type: ignore[return-value] method="POST", uri="/private/QueryOrders", params=params @@ -529,16 +528,17 @@ def get_trades_history( "trades": trades, "consolidate_taker": consolidate_taker, } - if start is not None: + if defined(start): params["start"] = start - if end is not None: + if defined(end): params["end"] = end - if ofs is not None: + if defined(ofs): params["ofs"] = ofs return self._request( # type: ignore[return-value] method="POST", uri="/private/TradesHistory", params=params ) + @ensure_string("txid") def get_trades_info( self: "User", txid: Union[str, List[str]], trades: Optional[bool] = False ) -> dict: @@ -586,10 +586,11 @@ def get_trades_info( uri="/private/QueryTrades", params={ "trades": trades, - "txid": self._to_str_list(txid), + "txid": txid, }, ) + @ensure_string("txid") def get_open_positions( self: "User", txid: Optional[Union[str, List[str]]] = None, @@ -642,12 +643,13 @@ def get_open_positions( } """ params: dict = {"docalcs": docalcs, "consolidation": consolidation} - if txid is not None: - params["txid"] = self._to_str_list(txid) + if defined(txid): + params["txid"] = txid return self._request( # type: ignore[return-value] method="POST", uri="/private/OpenPositions", params=params ) + @ensure_string("asset") def get_ledgers_info( self: "User", asset: Optional[Union[str, List[str]]] = "all", @@ -707,18 +709,17 @@ def get_ledgers_info( } """ params: dict = {"asset": asset, "aclass": aclass, "type": type_} - if isinstance(params["asset"], list): - params["asset"] = self._to_str_list(asset) - if start is not None: + if defined(start): params["start"] = start - if end is not None: + if defined(end): params["end"] = end - if ofs is not None: + if defined(ofs): params["ofs"] = ofs return self._request( # type: ignore[return-value] method="POST", uri="/private/Ledgers", params=params ) + @ensure_string("id_") def get_ledgers( self: "User", id_: Union[str, List[str]], trades: Optional[bool] = False ) -> dict: @@ -759,9 +760,10 @@ def get_ledgers( return self._request( # type: ignore[return-value] method="POST", uri="/private/QueryLedgers", - params={"trades": trades, "id": self._to_str_list(id_)}, + params={"trades": trades, "id": id_}, ) + @ensure_string("pair") def get_trade_volume( self: "User", pair: Optional[Union[str, List[str]]] = None, @@ -820,12 +822,13 @@ def get_trade_volume( """ params: dict = {"fee-info": fee_info} - if pair is not None: - params["pair"] = self._to_str_list(pair) + if defined(pair): + params["pair"] = pair return self._request( # type: ignore[return-value] method="POST", uri="/private/TradeVolume", params=params ) + @ensure_string("fields") def request_export_report( self: "User", report: str, @@ -877,12 +880,12 @@ def request_export_report( "report": report, "description": description, "format": format_, - "fields": self._to_str_list(fields), + "fields": fields, } params.update(kwargs) - if starttm is not None: + if defined(starttm): params["starttm"] = starttm - if endtm is not None: + if defined(endtm): params["endtm"] = endtm return self._request( # type: ignore[return-value] method="POST", uri="/private/AddExport", params=params diff --git a/kraken/spot/websocket/__init__.py b/kraken/spot/websocket/__init__.py index 9fe21d87..5ddaadb4 100644 --- a/kraken/spot/websocket/__init__.py +++ b/kraken/spot/websocket/__init__.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2023 Benjamin Thomas Schwertfeger -# Github: https://github.com/btschwertfeger +# GitHub: https://github.com/btschwertfeger # """Module that implements the kraken Spot websocket clients""" -from __future__ import annotations # to avaoid circular import for type checking +from __future__ import annotations # to avoid circular import for type checking import asyncio import json diff --git a/kraken/spot/ws_client/__init__.py b/kraken/spot/ws_client/__init__.py index f7bb930e..5f2650b0 100644 --- a/kraken/spot/ws_client/__init__.py +++ b/kraken/spot/ws_client/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2023 Benjamin Thomas Schwertfeger -# Github: https://github.com/btschwertfeger +# GitHub: https://github.com/btschwertfeger # """Module that implements the Spot Kraken Websocket client""" @@ -11,7 +11,7 @@ import logging from typing import TYPE_CHECKING, List, Optional, Union -from ...base_api import KrakenBaseSpotAPI +from ...base_api import KrakenBaseSpotAPI, defined, ensure_string if TYPE_CHECKING: # to avaoid circular import for type checking @@ -37,10 +37,10 @@ class SpotWsClientCl(KrakenBaseSpotAPI): def __init__( self: "SpotWsClientCl", - key: Optional[str] = "", - secret: Optional[str] = "", - url: Optional[str] = "", - sandbox: Optional[bool] = False, + key: str = "", + secret: str = "", + url: str = "", + sandbox: bool = False, ): super().__init__(key=key, secret=secret, url=url, sandbox=sandbox) @@ -61,6 +61,7 @@ def get_ws_token(self: "SpotWsClientCl") -> dict: "POST", "/private/GetWebSocketsToken" ) + @ensure_string("oflags") async def create_order( self: "SpotWsClientCl", ordertype: str, @@ -75,7 +76,7 @@ async def create_order( expiretm: Optional[Union[str, int]] = None, deadline: Optional[str] = None, userref: Optional[Union[str, int]] = None, - validate: Optional[bool] = False, + validate: bool = False, close_ordertype: Optional[str] = None, close_price: Optional[Union[str, int, float]] = None, close_price2: Optional[Union[str, int, float]] = None, @@ -173,38 +174,37 @@ async def create_order( "volume": str(volume), "validate": str(validate), } - if price2 is not None: + if defined(price2): payload["price2"] = str(price2) - if oflags is not None: + if defined(oflags): if isinstance(oflags, str): payload["oflags"] = oflags - elif isinstance(oflags, list): - payload["oflags"] = self._to_str_list(oflags) else: raise ValueError( - "oflags must be type List[str] or comma delimited list of order flags as str. Available flags: viqc, fcib, fciq, nompp, post" + "oflags must be a comma delimited list of order flags as str. Available flags: viqc, fcib, fciq, nompp, post" ) - if starttm is not None: + if defined(starttm): payload["starttm"] = str(starttm) - if expiretm is not None: + if defined(expiretm): payload["expiretm"] = str(expiretm) - if deadline is not None: + if defined(deadline): payload["deadline"] = str(deadline) - if userref is not None: + if defined(userref): payload["userref"] = str(userref) - if leverage is not None: + if defined(leverage): payload["leverage"] = str(leverage) - if close_ordertype is not None: + if defined(close_ordertype): payload["close[ordertype]"] = close_ordertype - if close_price is not None: + if defined(close_price): payload["close[price]"] = str(close_price) - if close_price2 is not None: + if defined(close_price2): payload["close[price2]"] = str(close_price2) - if timeinforce is not None: + if defined(timeinforce): payload["timeinforce"] = timeinforce await self._priv_conn.send_message(msg=payload, private=True) + @ensure_string("oflags") async def edit_order( self: "SpotWsClientCl", orderid: str, @@ -215,7 +215,7 @@ async def edit_order( volume: Optional[Union[str, int, float]] = None, oflags: Optional[Union[str, List[str]]] = None, newuserref: Optional[Union[str, int]] = None, - validate: Optional[bool] = False, + validate: bool = False, ) -> None: """ Edit an open order that was placed on the Spot market. @@ -270,23 +270,24 @@ async def edit_order( "orderid": orderid, "validate": str(validate), } - if reqid is not None: + if defined(reqid): payload["reqid"] = reqid - if pair is not None: + if defined(pair): payload["pair"] = pair - if price is not None: + if defined(price): payload["price"] = str(price) - if price2 is not None: + if defined(price2): payload["price2"] = str(price2) - if volume is not None: + if defined(volume): payload["volume"] = str(volume) - if oflags is not None: - payload["oflags"] = self._to_str_list(oflags) - if newuserref is not None: + if defined(oflags): + payload["oflags"] = oflags + if defined(newuserref): payload["newuserref"] = str(newuserref) await self._priv_conn.send_message(msg=payload, private=True) + @ensure_string("txid") async def cancel_order(self: "SpotWsClientCl", txid: Union[str, List[str]]) -> None: """ Cancel a specific order or a list of orders. @@ -315,7 +316,7 @@ async def cancel_order(self: "SpotWsClientCl", txid: Union[str, List[str]]) -> N if not self._priv_conn.is_auth: raise ValueError("Cannot cancel_order on public websocket client!") await self._priv_conn.send_message( - msg={"event": "cancelOrder", "txid": self._to_str_list(txid)}, private=True + msg={"event": "cancelOrder", "txid": txid}, private=True ) async def cancel_all_orders(self: "SpotWsClientCl") -> None: diff --git a/tests/spot/test_spot_market.py b/tests/spot/test_spot_market.py index 34f3c3dc..3aa01d66 100644 --- a/tests/spot/test_spot_market.py +++ b/tests/spot/test_spot_market.py @@ -26,18 +26,6 @@ def test_get_system_status(spot_market: Market) -> None: assert is_not_error(spot_market.get_system_status()) -@pytest.mark.spot -@pytest.mark.spot_market -def test_get_asset(spot_market: Market) -> None: - """ - Checks the ``get_asset`` endpoint by requesting some asset info. - """ - assert is_not_error(spot_market.get_asset()) - assert is_not_error(spot_market.get_asset(asset="BTC")) - assert is_not_error(spot_market.get_asset(asset="BTC,EUR")) - assert is_not_error(spot_market.get_asset(asset="EUR", aclass="currency")) - - @pytest.mark.spot @pytest.mark.spot_market def test_get_assets(spot_market: Market) -> None: @@ -57,19 +45,6 @@ def test_get_assets(spot_market: Market) -> None: sleep(3) -@pytest.mark.spot -@pytest.mark.spot_market -def test_get_asset_pair(spot_market: Market) -> None: - """ - Checks the ``get_asset_pair`` endpoint by performing multiple - requests with different paramaters. - """ - assert is_not_error(spot_market.get_asset_pair()) - assert is_not_error(spot_market.get_asset_pair(pair="BTCUSD")) - assert is_not_error(spot_market.get_asset_pair(pair="BTCUSD,ETHUSD")) - assert is_not_error(spot_market.get_asset_pair(pair="ETHUSD", info="info")) - - @pytest.mark.spot @pytest.mark.spot_market def test_get_asset_pairs(spot_market: Market) -> None: diff --git a/tests/spot/test_spot_trade.py b/tests/spot/test_spot_trade.py index b172b1f0..3772b2d3 100644 --- a/tests/spot/test_spot_trade.py +++ b/tests/spot/test_spot_trade.py @@ -251,7 +251,7 @@ def test_cancel_order_batch(spot_auth_trade: Trade) -> None: @pytest.mark.spot @pytest.mark.spot_trade -def test_utils_truncate_price(spot_trade: Trade) -> None: +def test_truncate_price(spot_trade: Trade) -> None: """ Checks if the truncate function returns the expected results by checking different inputs for price. @@ -285,8 +285,8 @@ def test_utils_truncate_price(spot_trade: Trade) -> None: @pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_volume(spot_trade: Trade) -> None: +@pytest.mark.spot_trade +def test_truncate_volume(spot_trade: Trade) -> None: """ Checks if the truncate function returns the expected results by checking different inputs for volume. @@ -320,8 +320,8 @@ def test_utils_truncate_volume(spot_trade: Trade) -> None: @pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_fail_price_costmin(spot_trade: Trade) -> None: +@pytest.mark.spot_trade +def test_truncate_fail_price_costmin(spot_trade: Trade) -> None: """ Checks if the truncate function fails if the price is less than the costmin. @@ -333,8 +333,8 @@ def test_utils_truncate_fail_price_costmin(spot_trade: Trade) -> None: @pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_fail_volume_ordermin(spot_trade: Trade) -> None: +@pytest.mark.spot_trade +def test_truncate_fail_volume_ordermin(spot_trade: Trade) -> None: """ Checks if the truncate function fails if the volume is less than the ordermin. @@ -346,13 +346,10 @@ def test_utils_truncate_fail_volume_ordermin(spot_trade: Trade) -> None: @pytest.mark.spot -@pytest.mark.spot_market -def test_utils_truncate_fail_invalid_amount_type(spot_trade: Trade) -> None: +@pytest.mark.spot_trade +def test_truncate_fail_invalid_amount_type(spot_trade: Trade) -> None: """ Checks if the truncate function fails when no valid ``amount_type`` was specified. - - NOTE: This test may break in the future since the lot_decimals, pair_decimals, - ordermin and costmin attributes could change. """ with pytest.raises(ValueError): spot_trade.truncate(amount=1, amount_type="invalid", pair="XBTUSD")