Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the truncate parameter to kraken.spot.Trade.create_order #95

4 changes: 2 additions & 2 deletions .github/workflows/cicd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
## a regular commit/push.
##
UploadTestPyPI:
if: success() && ${{ github.actor }} == "btschwertfeger" && github.ref == 'refs/heads/master'
if: success() && github.actor == 'btschwertfeger' && github.ref == 'refs/heads/master'
needs:
[
Test-Spot-Public,
Expand All @@ -178,7 +178,7 @@ jobs:
## Generates and uploads the coverage statistics to codecov
##
CodeCov:
if: ${{ github.actor }} == "btschwertfeger"
if: success() && github.actor == 'btschwertfeger'
needs:
[
Test-Spot-Public,
Expand Down
28 changes: 13 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,10 @@ repos:
- id: fix-byte-order-marker
- id: fix-encoding-pragma
- id: mixed-line-ending
# - id: name-tests-test
# args: ["--pytest-test-first"]
- id: requirements-txt-fixer
- id: end-of-file-fixer
- id: pretty-format-json
- id: detect-private-key
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.2.0
hooks:
- id: mypy
name: mypy
args:
[
--config-file=pyproject.toml,
--install-types,
--non-interactive,
kraken,
]
pass_filenames: false
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
Expand Down Expand Up @@ -65,6 +50,19 @@ repos:
types: [python]
exclude: ^examples/|^tests/|^setup.py$
args: ["--rcfile=.pylintrc", "-d", "R0801"] # ignore duplicate code
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.2.0
hooks:
- id: mypy
name: mypy
args:
[
--config-file=pyproject.toml,
--install-types,
--non-interactive,
kraken,
]
pass_filenames: false
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
Expand Down
8 changes: 4 additions & 4 deletions docs/links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
.. |Downloads badge| image:: https://static.pepy.tech/personalized-badge/python-kraken-sdk?period=total&units=abbreviation&left_color=grey&right_color=orange&left_text=downloads
:target: https://pepy.tech/project/python-kraken-sdk

.. |CodeQL badge| image:: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/codeql.yml/badge.svg?branch=master
:target: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/codeql.yml
.. |CodeQL badge| image:: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/codeql.yaml/badge.svg?branch=master
:target: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/codeql.yaml

.. |CI/CD badge| image:: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/cicd.yml/badge.svg?branch=master
:target: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/cicd.yml
.. |CI/CD badge| image:: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/cicd.yaml/badge.svg?branch=master
:target: https://github.com/btschwertfeger/Python-Kraken-SDK/actions/workflows/cicd.yaml

.. |codecov badge| image:: https://codecov.io/gh/btschwertfeger/Python-Kraken-SDK/branch/master/badge.svg
:target: https://app.codecov.io/gh/btschwertfeger/Python-Kraken-SDK
Expand Down
1 change: 0 additions & 1 deletion kraken/base_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import json
import time
import urllib.parse
from copy import deepcopy
from typing import Any, Dict, List, Optional, Type, Union
from urllib.parse import urljoin
from uuid import uuid1
Expand Down
6 changes: 6 additions & 0 deletions kraken/futures/market/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

"""Module that implements the Kraken Futures market client"""

from functools import lru_cache
from typing import List, Optional, Union

from ...base_api import KrakenBaseFuturesAPI
Expand Down Expand Up @@ -125,6 +126,7 @@ def get_ohlc(
auth=False,
)

@lru_cache()
def get_tick_types(self: "Market") -> List[str]:
"""
Retrieve the available tick types that can be used for example to access
Expand All @@ -145,6 +147,7 @@ def get_tick_types(self: "Market") -> List[str]:
"""
return self._request(method="GET", uri="/api/charts/v1/", auth=False) # type: ignore[return-value]

@lru_cache()
def get_tradeable_products(self: "Market", tick_type: str) -> List[str]:
"""
Retrieve a list containing the tradeable assets on the futures market.
Expand All @@ -168,6 +171,7 @@ def get_tradeable_products(self: "Market", tick_type: str) -> List[str]:
method="GET", uri=f"/api/charts/v1/{tick_type}", auth=False
)

@lru_cache()
def get_resolutions(self: "Market", tick_type: str, tradeable: str) -> List[str]:
"""
Retrieve the list of available resolutions for a specific asset.
Expand All @@ -193,6 +197,7 @@ def get_resolutions(self: "Market", tick_type: str, tradeable: str) -> List[str]
method="GET", uri=f"/api/charts/v1/{tick_type}/{tradeable}", auth=False
)

@lru_cache()
def get_fee_schedules(self: "Market") -> dict:
"""
Retrieve information about the current fees
Expand Down Expand Up @@ -301,6 +306,7 @@ def get_orderbook(self: "Market", symbol: Optional[str] = None) -> dict:
params: dict = {}
if symbol is not None:
params["symbol"] = symbol

return self._request( # type: ignore[return-value]
method="GET",
uri="/derivatives/api/v3/orderbook",
Expand Down
5 changes: 3 additions & 2 deletions kraken/spot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#!/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 provides the Spot REST clients and utility functions."""

"""Module that provides the Spot REST clients"""
# pylint: disable=unused-import
from kraken.spot.funding import Funding
from kraken.spot.market import Market
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
2 changes: 1 addition & 1 deletion kraken/spot/market/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def get_asset_pairs(
:caption: Spot Market: Get information about tradeable asset pairs

>>> from kraken.spot import Market
>>> Market().get_tradeable_asset_pair(pair="XBTUSD")
>>> Market().get_asset_pairs(pair="XBTUSD")
{
'XXBTZUSD': {
'altname': 'XBTUSD',
Expand Down
37 changes: 27 additions & 10 deletions kraken/spot/trade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import List, Optional, Union

from ...base_api import KrakenBaseSpotAPI
from ..utils import Utils


class Trade(KrakenBaseSpotAPI):
Expand Down Expand Up @@ -58,10 +59,11 @@ def create_order(
self: "Trade",
ordertype: str,
side: str,
volume: Union[str, int, float],
pair: str,
volume: Union[str, int, float],
price: Optional[Union[str, int, float]] = None,
price2: Optional[Union[str, int, float]] = None,
truncate: bool = False,
trigger: Optional[str] = None,
leverage: Optional[str] = None,
reduce_only: Optional[bool] = False,
Expand All @@ -75,7 +77,7 @@ def create_order(
close_price: Optional[Union[str, int, float]] = None,
close_price2: Optional[Union[str, int, float]] = None,
deadline: Optional[str] = None,
validate: Optional[bool] = False,
validate: bool = False,
userref: Optional[int] = None,
) -> dict:
"""
Expand All @@ -92,6 +94,8 @@ def create_order(
:type ordertype: str
:param side: ``buy`` or ``sell``
:type side: str
:param pair: The asset to trade
:type pair: str
:param volume: The volume of the position to create
:type volume: str | int | float
:param price: The limit price for ``limit`` orders and the trigger price for orders with
Expand All @@ -103,6 +107,10 @@ def create_order(
* Prefixed by # is the same as ``+`` and ``-`` but the sign is set automatically
* The percentate sign ``%`` can be used to define relative changes.
:type price2: str | int | float, optional
:param truncate: If enabled: round the ``price`` and ``volume`` to Kraken's
maximum allowed decimal places. See https://support.kraken.com/hc/en-us/articles/4521313131540
fore more information about decimals.
:type truncate: bool, optional
:param trigger: What triggers the position of ``stop-loss``, ``stop-loss-limit``, ``take-profit``, and
``take-profit-limit`` orders. Will also be used for associated conditional close orders.
Kraken will use ``last`` if nothing is specified.
Expand Down Expand Up @@ -159,7 +167,7 @@ def create_order(
... volume="0.0001"
... )
{
'txid': 'TNGMNU-XQSRA-LKCWOK',
'txid': ['TNGMNU-XQSRA-LKCWOK'],
'descr': {
'order': 'buy 4.00000000 XBTUSD @ limit 23000.0'
}
Expand All @@ -182,7 +190,7 @@ def create_order(
... oflags=["post", "fcib"]
... )
{
'txid': 'TPPI2H-CUZZ2-EQR2IE',
'txid': ['TPPI2H-CUZZ2-EQR2IE'],
'descr': {
'order': 'buy 4.0000 XBTUSD @ limit 23000.0'
}
Expand All @@ -202,7 +210,7 @@ def create_order(
... side="buy",
... )
{
'txid': 'THNUL1-8ZAS5-EEF3A8',
'txid': ['THNUL1-8ZAS5-EEF3A8'],
'descr': {
'order': 'buy 20.00000000 XBTUSD @ stop loss 22000.0'
}
Expand Down Expand Up @@ -279,21 +287,25 @@ def create_order(
}
"""
params: dict = {
"ordertype": str(ordertype),
"type": str(side),
"volume": str(volume),
"pair": str(pair),
"ordertype": ordertype,
"type": side,
"pair": pair,
"volume": volume
if not truncate
else Utils.truncate(amount=volume, amount_type="volume", pair=pair),
"stp_type": stptype,
"starttm": starttm,
"validate": validate,
"reduce_only": reduce_only,
}

trigger_ordertypes: tuple = (
"stop-loss",
"stop-loss-limit",
"take-profit-limit",
"take-profit-limit",
)

if trigger is not None:
if ordertype not in trigger_ordertypes:
raise ValueError(f"Cannot use trigger on ordertype {ordertype}!")
Expand All @@ -303,8 +315,13 @@ def create_order(
params["timeinforce"] = timeinforce
if expiretm is not None:
params["expiretm"] = str(expiretm)

if price is not None:
params["price"] = str(price)
params["price"] = (
price
if not truncate
else Utils.truncate(amount=price, amount_type="price", pair=pair)
)

if ordertype in ("stop-loss-limit", "take-profit-limit"):
if price2 is None:
Expand Down
72 changes: 72 additions & 0 deletions kraken/spot/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/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}"
Loading