Skip to content

Commit

Permalink
Add the truncate parameter to kraken.spot.Trade.create_order (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
btschwertfeger committed Mar 12, 2024
1 parent 1d88c6f commit 04382e4
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 54 deletions.
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

0 comments on commit 04382e4

Please sign in to comment.